diff --git a/.gitignore b/.gitignore index a715b98c..0594868c 100644 --- a/.gitignore +++ b/.gitignore @@ -77,4 +77,6 @@ build/ # Flutter coverage **/lcov.info -**/coverage \ No newline at end of file +**/coverage + +pubspec.lock \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index aa473bed..9f1c553d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,112 @@ # Reactter +## 8.0.0-dev.31 + +### Enhancements + +- Add Reactter devtools extension. +- Add [`Rt.addObserver`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtInterface/addObserver.html) and [`Rt.removeObserver`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtInterface/removeObserver.html) methods to manage the observers. +- Add [`RtStateObserver`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtStateObserver-class.html) class to monitor the lifecycle of state(`onStateCreated`, `onStateBound`, `onStateUnbound`, `onStateUpdated` and `onStateDisposed`). +- Add [`RtDependencyObserver`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtDependencyObserver-class.html) class to monitor the lifecycle of dependency(`onDependencyRegistered`, `onDependencyCreated`, `onDependencyMounted`, `onDependencyUnmounted`, `onDependencyDeleted`, `onDependencyUnregistered` and `onDependencyFailed`). +- Add [`Rt.initializeLogger`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtLoggerExt/initializeLogger.html) to initialize the Reactter logger. +- Add [`Rt.initializeDevTools`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtDevToolsExt/initializeDevTools.html) to initialize the devtools for observing the states and dependencies. +- Add [`debugLabel`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtState/debugLabel.html) and [`debugInfo`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtState/debugInfo.html) to states and hooks to get info for debugging. +- Add [`RtContextMixin`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtContextMixin-mixin.html) mixin to provide access to the `Rt` instance. +- Add [`RtState`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtState-class.html) abstract class to implement the base logic of the state. +- Add [`Rt.registerState`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtInterface/registerState.html) method to create a new state. +- Add [`Rt.getRefAt`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtInterface/getRefAt.html) to get the reference of dependency created. +- Add [`initHook`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtHook/initHook.html) method to `RtHook` to call when hook is created. +- Add [`cancel`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/UseAsyncState/cancel.html) method to `UseAsyncState` to cancel current method. +- Update [`batch`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtInterface/batch.html) and [`untracked`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtInterface/untracked.html) methods to support asynchronous callbacks. +- Enhance event handling and notifier logic for improved stability and performance. +- Enhance state management and error handling. +- Add dependency `mode` to [`RtComponent`](https://pub.dev/documentation/flutter_reactter/8.0.0-dev.31/flutter_reactter/RtComponent-class.html). +- Enhance dependency management to ensure to re-build when is detected changes while building. + +### Breakings + +- Remove [`Reactter`](https://pub.dev/documentation/reactter/7.3.0/reactter/Reactter.html), use [`Rt`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/Rt.html) instead. +- Remove [`ReactterStateImpl`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterStateImpl.html) and [`RtStateImpl`](https://pub.dev/documentation/reactter/7.3.0/reactter/RtStateImpl-class.html), use [`RtState`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtState-class.html) instead. +- Replace [`UseAsyncStateStatus.standby`](https://pub.dev/documentation/reactter/7.3.0/reactter/UseAsyncStateStatus.html) to [`UseAsyncStateStatus.idle`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/UseAsyncStateStatus.html). +- Rename [`DispatchEffect`](https://pub.dev/documentation/reactter/7.3.0/reactter/DispatchEffect-class.html) to [`AutoDispatchEffect`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/DispatchEffect-class.html). +- Move `asyncFunction` to the first parameter of [`UseAsyncState`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/UseAsyncState-class.html) and [`UseAsyncState.withArg`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/UseAsyncState/withArg.html). +- Remove [`Rt.isRegistered`](https://pub.dev/documentation/reactter/7.3.0/reactter/RtInterface/isRegistered.html), use [`Rt.isActive`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtInterface/isActive.html) instead. +- Remove [`Rt.getInstanceManageMode`](https://pub.dev/documentation/reactter/7.3.0/reactter/RtInterface/getInstanceManageMode.html), use [`Rt.getDependencyMode`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtInterface/getDependencyMode.html) instead. +- Remove [`InstanceManageMode`](https://pub.dev/documentation/reactter/7.3.0/reactter/InstanceManageMode.html), use [`DependencyMode`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/DependencyMode.html) instead. +- Remove [`Lifecycle.initialized`](https://pub.dev/documentation/reactter/7.3.0/reactter/Lifecycle.html), use [`Lifecycle.created`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/Lifecycle.html) instead. +- Remove [`Lifecycle.destroyed`](https://pub.dev/documentation/reactter/7.3.0/reactter/Lifecycle.html), use [`Lifecycle.deleted`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/Lifecycle.html) instead. +- Replace [`LifecycleObserver`](https://pub.dev/documentation/reactter/7.3.0/reactter/LifecycleObserver-class.html) to [`RtDependencyLifecycle`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtDependencyLifecycle-class.html). +- Remove [`LifecycleObserver.onInitialized`](https://pub.dev/documentation/reactter/7.3.0/reactter/LifecycleObserver/onInitialized.html), use [`RtDependencyLifecycle.onCreated`](https://pub.dev/documentation/reactter/8.0.0-dev.31//reactter/RtDependencyLifecycle/onCreated.html) instead. +- Remove [`ReactterInstance`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterInstance.html) and [`ReactterDependency`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterDependency.html), use [`RtDependency`](https://pub.dev/documentation/reactter/8.0.0-dev.31//reactter/RtDependency-class.html) instead. +- Remove [`ReactterHook`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterHook.html), use [`RtHook`](https://pub.dev/documentation/reactter/8.0.0-dev.31//reactter/RtHook-class.html) instead. +- Remove [`ReactterInterface`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterInterface.html), use [`RtInterface`](https://pub.dev/documentation/reactter/8.0.0-dev.31//reactter/RtInterface-class.html) instead. +- Remove [`RtState.refresh`](https://pub.dev/documentation/reactter/7.3.0/reactter/RtState/refresh.html), use [`RtState.notify`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtState/notify.html) instead. +- Remove [`UseInstance`](https://pub.dev/documentation/reactter/7.3.0/reactter/UseInstance-class.html), use [`UseDependency`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/UseDependency-class.html) instead. +- Remove [`ReactterAction`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterAction.html), use [`RtAction`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtAction-class.html) instead. +- Remove [`ReactterActionCallable`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterActionCallable.html), use [`RtActionCallable`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtActionCallable-class.html) instead. +- Remove [`MemoInterceptors`](https://pub.dev/documentation/reactter/7.3.0/reactter/MemoInterceptors.html), use [`MultiMemoInterceptor`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/MultiMemoInterceptor-class.html) instead. +- Remove [`AsyncMemoSafe`](https://pub.dev/documentation/reactter/7.3.0/reactter/AsyncMemoSafe.html), use [`MemoSafeAsyncInterceptor`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/MemoSafeAsyncInterceptor-class.html) instead. +- Remove [`TemporaryCacheMemo`](https://pub.dev/documentation/reactter/7.3.0/reactter/TemporaryCacheMemo.html), use [`MemoTemporaryCacheInterceptor`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/MemoTemporaryCacheInterceptor-class.html) instead. +- Remove [`MemoInterceptorWrapper`](https://pub.dev/documentation/reactter/7.3.0/reactter/MemoInterceptorWrapper.html), use [`MemoWrapperInterceptor`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/MemoWrapperInterceptor-class.html) instead. +- Remove [`Obj`](https://pub.dev/documentation/reactter/7.3.0/reactter/Obj-class.html). +- Remove [`init`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/RtProvider/init.html) property from `RtProvider`, use [`RtProvider.init`](https://pub.dev/documentation/flutter_reactter/8.0.0-dev.31/flutter_reactter/RtProvider/RtProvider.init.html) instead. +- Remove [`ReactterComponent`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/ReactterComponent-class.html), use [`RtComponent`](https://pub.dev/documentation/flutter_reactter/8.0.0-dev.31/flutter_reactter/RtComponent-class.html) instead. +- Remove [`ReactterConsumer`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/ReactterConsumer-class.html), use [`RtConsumer`](https://pub.dev/documentation/flutter_reactter/8.0.0-dev.31/flutter_reactter/RtConsumer-class.html) instead. +- Remove [`ReactterProvider`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/ReactterProvider-class.html), use [`RtProvider`](https://pub.dev/documentation/flutter_reactter/8.0.0-dev.31/flutter_reactter/RtProvider-class.html) instead. +- Remove [`ReactterProviders`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/ReactterProviders-class.html), use [`RtMultiProvider`](https://pub.dev/documentation/flutter_reactter/8.0.0-dev.31/flutter_reactter/RtMultiProvider-class.html) instead. +- Remove [`ReactterScope`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/ReactterScope-class.html), use [`RtScope`](https://pub.dev/documentation/flutter_reactter/8.0.0-dev.31/flutter_reactter/RtScope-class.html) instead. +- Remove [`ReactterWatcher`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/ReactterWatcher-class.html), use [`RtSignalWatcher`](https://pub.dev/documentation/flutter_reactter/8.0.0-dev.31/flutter_reactter/RtSignalWatcher-class.html) instead. +- Remove [`ReactterDependencyNotFoundException`](https://pub.dev/documentation/reactter/7.3.0/flutter_reactter/ReactterDependencyNotFoundException-class.html), use [`RtDependencyNotFoundException`](https://pub.dev/documentation/reactter/8.0.0-dev.31/flutter_reactter/RtDependencyNotFoundException-class.html) instead. +- Remove [`ReactterScopeNotFoundException`](https://pub.dev/documentation/reactter/7.3.0/flutter_reactter/ReactterScopeNotFoundException-class.html), use [`RtScopeNotFoundException`](https://pub.dev/documentation/reactter/8.0.0-dev.31/flutter_reactter/RtScopeNotFoundException-class.html) instead. + +### Fixes + +- Fix bug in [`UseEffect`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/UseEffect-class.html) causing dependencies to not be unwatched. +- Fix to show the correct dependency mode in the logger of the [`Rt.register`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtInterface/register.html) method. +- Fix nested [`Rt.batch`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/RtInterface/batch.html) method to work correctly. +- Add error handling listeners and continue to next listeners in [`Notifier`](https://pub.dev/documentation/reactter/8.0.0-dev.31/reactter/Notifier-class.html) class. +- Fix to propagate state param to `boundInstance`. +- Remove listeners correctly from single-use listeners and handle null cases. + +### Internal + +- Add `StateObserver` test. +- Rename some variables to use consistent naming convention. +- Update `UseDependency` to support id parameter in tests. +- Update annotated markdown references for clarity. +- Add Reactter devtools extension and configuration files. +- Rearrange arguments in `UseAsyncState` constructor. +- Simplify the `UseReducer` implementation. +- Add observer support for nested state updates. +- Allow communication between application and devtools extension. +- Add `IContext` interface class to represent the current context what the application is running in. +- Replace `StateBase` to `RtState` interface. +- Separate logic of `EventNotifier` to `Notifier` class. +- Restructure the framework and core classes. +- Update `RtWatcher` to extend `RtScope` and refactor code. +- Update examples to use new features. +- Improve dispose logic in `RtState` and `UseDependency`. +- Update hook registration and state attachment logic. +- Improve `null` listener handling in `Notifier` class. +- Improve dispose, register and notify logic of state. +- Support asynchronous callbacks in untracked and batch methods and add test for it. +- Simplify dependency listener callbacks and improve state management checks. +- Clean up exports, remove deprecated code, and improve event handling. +- Add tests for dependency state watching and useEffect disposal. +- Update exports to improve clarity and remove hidden types. +- Enhance autoBinding method to accept an optional bound instance; improve state binding logic. +- Enhance test coverage by adding error handling, debug label, and debug info tests for various components. +- Update dependencies and version for reactter package. +- Improve test command for better coverage. +- Update dependency references to use objects instead of hashcodes. +- Update mount and unmount logic in `ProviderElement`. +- Add constants for release, profile, debug, web modes. +- Update dependency handling and improve widget lifecycle management. +- Enhance provider and scope implementations with improved type safety and instance management. + ## 7.3.1 -## Fixes +### Fixes - **fix:** Update `RtComponentWidget` typedef to `ReactterComponent`. @@ -28,7 +132,7 @@ - Deprecate [`ReactterAction`](https://pub.dev/documentation/reactter/7.2.0/reactter/ReactterAction-class.html), use [`RtAction`](https://pub.dev/documentation/reactter/7.3.0/reactter/RtAction-class.html) instead. - Deprecate [`ReactterActionCallable`](https://pub.dev/documentation/reactter/7.2.0/reactter/ReactterActionCallable-class.html), use [`RtActionCallable`](https://pub.dev/documentation/reactter/7.3.0/reactter/RtActionCallable-class.html) instead. - **refactor:** Update memo interceptors to use more descriptive names. - - Deprecate [`MemoInterceptors`](https://pub.dev/documentation/reactter/7.2.0/reactter/MemoInterceptors-class.html), use [`MemoMultiInterceptor`](https://pub.dev/documentation/reactter/7.3.0/reactter/MemoMultiInterceptor-class.html) instead. + - Deprecate [`MemoInterceptors`](https://pub.dev/documentation/reactter/7.2.0/reactter/MemoInterceptors-class.html), use [`MultiMemoInterceptor`](https://pub.dev/documentation/reactter/7.3.0/reactter/MultiMemoInterceptor-class.html) instead. - Deprecate [`AsyncMemoSafe`](https://pub.dev/documentation/reactter/7.2.0/reactter/AsyncMemoSafe-class.html) , use [`MemoSafeAsyncInterceptor`](https://pub.dev/documentation/reactter/7.3.0/reactter/MemoSafeAsyncInterceptor-class.html) instead. - Deprecate [`TemporaryCacheMemo`](https://pub.dev/documentation/reactter/7.2.0/reactter/TemporaryCacheMemo-class.html), use [`MemoTemporaryCacheInterceptor`](https://pub.dev/documentation/reactter/7.3.0/reactter/MemoTemporaryCacheInterceptor-class.html) instead. - Deprecate [`MemoInterceptorWrapper`](https://pub.dev/documentation/reactter/7.2.0/reactter/MemoInterceptorWrapper-class.html), use [`MemoWrapperInterceptor`](https://pub.dev/documentation/reactter/7.3.0/reactter/MemoWrapperInterceptor-class.html) instead. @@ -338,7 +442,7 @@ If `ReactterProvider.contextOf` doesn't have a type defined, use `ReactterScope` - **refactor(framework):** Rename `_RegisterHook` to `HookRegister`. - **test:** Add `ReactterStateListExtension` test and other adjustments. - **refactor(widgets):** Make `ReactterConsumer.builder` required. -- **fix(framework):** Add instance attached validation before `UseCompute` is disponsed. +- **fix(framework):** Add instance attached validation before `UseCompute` is disposed. - **refactor(extensions):** Use `UseCompute` type on `when` method. - **test:** Add `UseCompute` test and other adjustments. - **build(example):** Change folder structure. diff --git a/README.md b/README.md index 9e3d6c78..263d8373 100644 --- a/README.md +++ b/README.md @@ -12,1793 +12,43 @@ ____ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/2devs-team/reactter/dart.yml?branch=master)](https://github.com/2devs-team/reactter/actions) [![Codecov](https://img.shields.io/codecov/c/github/2devs-team/reactter?logo=codecov)](https://app.codecov.io/gh/2devs-team/reactter) -**A light, powerful and quick Reactive State Management, Dependency Injection and Event Handler.** - -> Now documentation on the official web site: -> +A lightweight, powerful, and reactive **State Management**, **Dependency Injection** and **Event Handler** for Dart/Flutter ## Features -- ⚡️ Engineered for **Speed**. -- ⚖️ Super **Lightweight**([🥇 See benchmarks](https://github.com/CarLeonDev/state_managements#memory-size)). -- 📏 **Reduce Boilerplate Code** significantly([🥇 See benchmarks](https://github.com/CarLeonDev/state_managements#lines-number)). -- ✏️ Improve **Code Readability**. -- 💧 **Flexible** and **Adaptable** to any architecture. -- ☢️ **Reactive States** using [Signal](#signal) and Hooks. -- ♻️ **Reusable States and Logic** with [Custom hooks](#custom-hooks). -- 🎮 Fully **[Rendering Control](#rendering-control)**. -- 🧪 Fully **Testable**, 100% code coverage. -- 🪄 **Zero Configuration** and **No Code Generation** necessary. -- 💙 **Compatible with Dart and Flutter**, supports the latest version of Dart. - -Let's see a small and simple example: - -```dart -// Create a reactive state using `Signal` -final count = Signal(0); - -void main() { - // Change the `value` in any time(e.g., each 1 second). - Timer.periodic( - Duration(seconds: 1), - (timer) => count.value++, - ); - - // Put on listen `didUpdate` event, whitout use `Stream` - Rt.on( - count, - Lifecycle.didUpdate, - (inst, state) => print('Count: $count'), - ); - - // And you can use in flutter, e.g: - runApp( - MaterialApp( - home: Scaffold( - body: RtSignalWatcher( - // Just use it, and puts it in listening mode - // for further rendering automatically. - builder: (context, child) => Text("Count: $count"), - ), - ), - ), - ); -} -``` - -Clean and easy! - -See more examples [here](https://zapp.run/pub/flutter_reactter)! - -## Contents - -- [Features](#features) -- [Contents](#contents) -- [Quickstart](#quickstart) -- [About Reactter](#about-reactter) -- [State management](#state-management) - - [Signal](#signal) - - [UseState](#usestate) - - [UseAsyncState](#useasyncstate) - - [UseReducer](#usereducer) - - [UseCompute](#usecompute) -- [Dependency injection](#dependency-injection) - - [Builder](#builder) - - [Factory](#factory) - - [Singleton](#singleton) - - [Shortcuts to manage instances](#shortcuts-to-manage-instances) - - [UseDependency](#usedependency) -- [Event handler](#event-handler) - - [Lifecycles](#lifecycles) - - [Shortcuts to manage events](#shortcuts-to-manage-events) - - [UseEffect](#useeffect) -- [Rendering control](#rendering-control) - - [RtProvider](#rtprovider) - - [RtMultiProvider](#rtmultiprovider) - - [RtComponent](#rtcomponent) - - [RtConsumer](#rtconsumer) - - [RtSelector](#rtselector) - - [RtSignalWatcher](#rtsignalwatcher) - - [BuildContext.use](#buildcontextuse) - - [BuildContext.watch](#buildcontextwatch) - - [BuildContext.select](#buildcontextselect) -- [Custom hooks](#custom-hooks) -- [Lazy state](#lazy-state) -- [Batch](#batch) -- [Untracked](#untracked) -- [Generic arguments](#generic-arguments) -- [Memo](#memo) -- [Difference between Signal and UseState](#difference-between-signal-and-usestate) -- [Resources](#resources) -- [Contribute](#contribute) -- [Authors](#authors) - -## Quickstart - -Before anything, you need to be aware that Reactter is distributed on two packages, with slightly different usage. - -The package of Reactter that you will want to install depends on the type of project you are working on. - -Select one of the following options to know how to install it: - -
- - Dart only  - - Reactter - - - -Add the package on your project. - -- Using command: - - ```shell - dart pub add reactter - ``` - -- Or put directly into `pubspec.yaml` file: - - ```yaml - dependencies: - reactter: #add version here - ``` - - and run `dart pub get`. - -Now in your Dart code, you can use: - -```dart -import 'package:reactter/reactter.dart'; -``` - -
- -

- -

- - Flutter  - - Flutter Reactter - - - -Add the package on your project. - -- Using command: - - ```shell - flutter pub add flutter_reactter - ``` - -- Or put directly into `pubspec.yaml` file: - - ```yaml - dependencies: - flutter_reactter: #add version here - ``` - - and run `flutter pub get`. - -Now in your Dart code, you can use: - -```dart -import 'package:flutter_reactter/flutter_reactter.dart'; -``` - -
- -And it is recommended to use -[![Reactter Lint](https://img.shields.io/pub/v/reactter_lint?color=1d7fac&labelColor=29b6f6&label=reactter_lint&logo=dart)](https://pub.dev/packages/reactter_lint) -which will help to encourage good coding practices and prevent frequent problems using the Reactter convensions. - -If you use Visual Studio Code, it is a good idea to use [Reactter Snippets](https://marketplace.visualstudio.com/items?itemName=CarLeonDev.reacttersnippets) for improving productivity. - -## About Reactter - -Reactter is a light and powerful solution for Dart and Flutter. It is composed of three main concepts that can be used together to create maintainable and scalable applications, which are: - -- [State management](#state-management) -- [Dependency injection](#dependency-injection) -- [Event handler](#event-handler) - -Moreover, Reactter offers an extensive collection of widgets and extensions, granting advanced [rendering control](#rendering-control) through the `flutter_reactter` package. - -## State management - -In Reactter, state is understood as any object that extends [`RtState`](https://pub.dev/documentation/reactter/latest/reactter/RtState-class.html), endowing it with capabilities such as the ability to store one or more values and to broadcast notifications of its changes. - -Reactter offers the following several state managers: - -- [Signal](#signal) -- [UseState](#usestate) -- [UseAsyncState](#useasyncstate) -- [UseReducer](#usereducer) -- [UseCompute](#usecompute) - -> **NOTE:** -> The hooks (also known as [`RtHook`](https://pub.dev/documentation/reactter/latest/reactter/RtHook-class.html)) are named with the prefix `Use` according to convention. - -> **RECOMMENDED:** -> See also [difference between Signal and UseState](#difference-between-signal-and-usestate) and about [custom hooks](#custom-hooks). - -### Signal - -[`Signal`](https://pub.dev/documentation/reactter/latest/reactter/Signal-class.html) is an object (that extends [`RtState`](https://pub.dev/documentation/reactter/latest/reactter/RtState-class.html)) which has a `value` and notifies about its changes. - -It can be initialized using the constructor class `Signal(T initialValue)`: - -```dart -final intSignal = Signal(0); -final strSignal = Signal("initial value"); -final userSignal = Signal(User()); -``` - -`Signal` has a `value` property that allows to read and write its state: - -```dart -intSignal.value = 10; -print("Current state: ${intSignal.value}"); -``` - -or also can use the callable function: - -```dart -intSignal(10); -print("Current state: ${intSignal()}"); -``` - -or simply use `.toString()` implicit to get its `value` as String: - -```dart -print("Current state: $intSignal"); -``` - -> **NOTE:** -> `Signal` notifies that its `value` has changed when the previous `value` is different from the current `value`. -> If its `value` is an `Object`, it does not detect internal changes, only when `value` is setted to another `Object`. - -Use `update` method to notify changes after run a set of instructions: - -```dart -userSignal.update((user) { - user.firstname = "Firstname"; - user.lastname = "Lastname"; -}); -``` - -Use `refresh` method to force to notify changes. - -```dart -userSignal.refresh(); -``` - -When `value` has changed, the `Signal` will emit the following events(learn about it [here](#lifecycle-and-event-management)): - -- `Lifecycle.willUpdate` event is triggered before the `value` change or `update`, `refresh` methods have been invoked. -- `Lifecycle.didUpdate` event is triggered after the `value` change or `update`, `refresh` methods have been invoked. - -> **NOTE:** -> When you do any arithmetic operation between two `Signal`s, it returns an `Obj`, for example: `signal(1) + Signal(2)` returns `Obj(3)`. -> An [`Obj`](https://pub.dev/documentation/reactter/latest/reactter/Obj-class.html) is like a `Signal` without reactive functionality, but you can convert it to `Signal` using `.toSignal`. - -> **NOTE:** -> In flutter, using [`RtSignalWatcher`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtSignalWatcher-class.html), is a way to keep the widgets automatically updates, accessing the value of signal reactively. - -### UseState - -[`UseState`](https://pub.dev/documentation/reactter/latest/reactter/UseState-class.html) is a hook([`RtHook`](https://pub.dev/documentation/reactter/latest/reactter/RtHook-class.html)) that allows to declare state variables and manipulate its `value`, which in turn notifies about its changes. - -```dart -UseState(T initialValue) -``` - -`UseState` accepts a property: - -- `initialValue`: is a unique value of any type that you use to initialize the state. - -It can be declared inside a class, like this: - -```dart -class CounterController { - final count = UseState(0); -} -``` - -> **NOTE:** -> if your variable hook is `late` use `Rt.lazyState`. Learn about it [here](#lazy-state). - -`UseState` has a `value` property that allows to read and write its state: - -```dart -class CounterController { - final count = UseState(0); - - CounterController() { - print("Prev state: ${count.value}"); - count.value = 10; - print("Current state: ${count.value}"); - } -} -``` - -> **NOTE:** -> `UseState` notifies that its `value` has changed when the previous `value` is different from the current `value`. -> If its `value` is an `Object`, it does not detect internal changes, only when `value` is setted to another `Object`. - -Use `update` method to notify changes after run a set of instructions: - -```dart -userState.update((user) { - user.firstname = "Firstname"; - user.lastname = "Lastname"; -}); -``` - -Use `refresh` method to force to notify changes. - -```dart -userState.refresh(); -``` - -When `value` has changed, the `UseState` will emitted the following events(learn about it [here](#lifecycle-and-event-management)): - -- `Lifecycle.willUpdate` event is triggered before the `value` change or `update`, `refresh` methods have been invoked. -- `Lifecycle.didUpdate` event is triggered after the `value` change or `update`, `refresh` methods have been invoked. - -### UseAsyncState - -[`UseAsyncState`](https://pub.dev/documentation/reactter/latest/reactter/UseAsyncState-class.html) is a hook ([`RtHook`](https://pub.dev/documentation/reactter/latest/reactter/RtHook-class.html)) with the same feature as [`UseState`](#usestate) but its value will be lazily resolved by a function(`asyncFunction`). - -```dart -UseAsyncState( - T initialValue, - Future asyncFunction(), -); -``` - -`UseAsyncState` accepts these properties: - -- `initialValue`: is a unique value of any type that you use to initialize the state. -- `asyncFunction`: is a function that will be called by the `resolved` method and sets the value of the state. - -Use `UseAsyncState.withArg` to pass a argument to the `asyncFunction`. - -```dart -UseAsyncState.withArg( - T initialValue, - Future asyncFunction(A) , -) -``` - -> **NOTE:** -> if your variable hook is `late` use `Rt.lazyState`. Learn about it [here](#lazy-state). - -This is a translate example: - -```dart -class TranslateController { - final translateState = UseAsyncStates.withArg( - null, - (ArgsX3 args) async { - final text = args.arg; - final from = args.arg2; - final to = args.arg3; - // this is fake code, which simulates a request to API - return await api.translate(text, from, to); - } - ); - - TranslateController() { - translateState.resolve( - Args3('Hello world', 'EN','ES'), - ).then((_) { - print("'Hello world' translated to Spanish: '${translateState.value}'"); - }); - } -} -``` - -> **RECOMMENDED:** -> If you wish to optimize the state resolution, the best option is to use the memoization technique. Reactter provides this using `Memo`(Learn about it [here](#memo)), e.g: -> -> ```dart -> [...] -> final translateState = UseAsyncState.withArg>( -> null, -> /// `Memo` stores the value resolved in cache, -> /// and retrieving that same value from the cache the next time -> /// it's needed instead of resolving it again. -> Memo.inline( -> (ArgsX3 args) async { -> final text = args.arg; -> final from = args.arg2; -> final to = args.arg3; -> // this is fake code, which simulates a request to API -> return await api.translate(text, from, to); -> }, -> AsyncMemoSafe(), // avoid to save in cache when throw a error -> ), -> ); -> [...] -> ``` - -> **RECOMMENDED:** -> In the above example uses `Args`([generic arguments](#generic-arguments)), but using [Record](https://dart.dev/language/records#record-types) instead is recommended if your project supports it. - -Use the `when` method to return a computed value depending on it's state: - -```dart -final computedValue = asyncState.when( - standby: (value) => "🔵 Standby: $value", - loading: (value) => "⏳ Loading...", - done: (value) => "✅ Resolved: $value", - error: (error) => "❌ Error: $error", -); -``` - -When `value` has changed, the `UseAsyncState` will emit the following events (learn about it [here](#lifecycle-and-event-management)): - -- `Lifecycle.willUpdate` event is triggered before the `value` change or `update`, `refresh` methods have been invoked. -- `Lifecycle.didUpdate` event is triggered after the `value` change or `update`, `refresh` methods have been invoked. - -### UseReducer - -[`UseReducer`](https://pub.dev/documentation/reactter/latest/reactter/UseReducer-class.html) is a hook([`RtHook`](https://pub.dev/documentation/reactter/latest/reactter/RtHook-class.html)) that manages state using reducer method. An alternative to [`UseState`](#usestate). - -> **RECOMMENDED:** -> `UseReducer` is usually preferable over `UseState` when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. - - ```dart - UseReducer( - T reducer(T state, RtAction action), - T initialState, - ); - ``` - -`UseReducer` accepts two properties: - -- `reducer`: is a method contains your custom state logic that calculates the new state using current state, and actions. -- `initialState`: is a unique value of any type that you use to initialize the state. - -`UseReducer` exposes a `dispatch` method that allows you to invoke the `reducer` method sending a `RtAction`. - -The current state can be accessed through the `value` property. - -Here's the counter example using `UseReducer`: - - ```dart -class Store { - final int count; - - Store({this.count = 0}); -} - -Store reducer(Store state, RtAction action) { - switch (action.type) { - case 'INCREMENT': - return Store(count: state.count + (action.payload ?? 1)); - case 'DECREMENT': - return Store(count: state.count + (action.payload ?? 1)); - default: - throw UnimplementedError(); - } -} - -class CounterController { - final useCounter = UseReducer(reducer, Store(count: 0)); - - CounterController() { - print("count: ${useCounter.value.count}"); // count: 0; - useCounter.dispatch(RtAction(type: 'INCREMENT', payload: 2)); - print("count: ${useCounter.value.count}"); // count: 2; - useCounter.dispatch(RtAction(type: 'DECREMENT')); - print("count: ${useCounter.value.count}"); // count: 1; - } -} - ``` - -The actions can be created as a callable class, extending from [`RtActionCallable`](https://pub.dev/documentation/reactter/latest/reactter/RtActionCallable-class.html) and used as follows: - -```dart -class IncrementAction extends RtActionCallable { - IncrementAction([int quantity = 1]) : super( - type: 'INCREEMNT', payload: quantity - ); - - @override - Store call(Store state) => Store(count: state.count + payload); -} - -class DecrementAction extends RtActionCallable { - DecrementAction([int quantity = 1]) : super( - type: 'DECREMENT', payload: quantity - ); - - @override - Store call(Store state) => Store(count: state.count - payload); -} - -Store reducer(Store state, RtAction action) { - if (action is RtActionCallable) return action(state); - - return UnimplementedError(); -} - -class CounterController { - final useCounter = UseReducer(reducer , Store(count: 0)); - - CounterController() { - print("count: ${useCounter.value.count}"); // count: 0; - useCounter.dispatch(IncrementAction(2)); - print("count: ${useCounter.value.count}"); // count: 2; - useCounter.dispatch(DecrementAction()); - print("count: ${useCounter.value.count}"); // count: 1; - } -} -``` - -When `value` has changed, the `UseReducer` will emit the following events (learn about it [here](#lifecycle-and-event-management)): - -- `Lifecycle.willUpdate` event is triggered before the `value` change or `update`, `refresh` methods have been invoked. -- `Lifecycle.didUpdate` event is triggered after the `value` change or `update`, `refresh` methods have been invoked. - -### UseCompute - -[`UseCompute`](https://pub.dev/documentation/reactter/latest/reactter/UseCompute-class.html) is a hook([`RtHook`](https://pub.dev/documentation/reactter/latest/reactter/RtHook-class.html)) that keeps listening for state `dependencies` changes, to return a computed value(`T`) from a defined method(`computeValue`). - -```dart -UseCompute( - T computeValue(), - List dependencies, -) -``` - -`UseCompute` accepts two arguments: - -- `computeValue`: is a method is called whenever there is a change in any of the `dependencies`, and it is responsible for calculating and setting the computed value. -- `dependencies`: is a list of states that `UseCompute` keeps an active watch on, listening for any changes that may occur for calling the `computeValue` function. - -so, here is an example: - -```dart -class MyController { - final stateA = UseState(1); - final stateB = UseState(7); - - late final computeState = Rt.lazyState( - () => UseCompute( - // The `clamp` is a method that returns this num clamped - // to be in the range lowerLimit-upperLimit(e.g., 10-15). - () => addAB().clamp(10, 15), - [stateA, stateB], - ), - ); - - int addAB() => stateA.value + stateB.value; - void printResult() => print("${addAB()} -> ${computeState.value}"); - - MyController() { - printResult(); // 8 -> 10 - stateA.value += 1; // Will not notify change - printResult(); // 9 -> 10 - stateB.value += 2; // Will notify change - printResult(); // 11 -> 11 - stateA.value += 6; // Will notify change - printResult(); // 17 -> 15 - stateB.value -= 1; // Will not notify change - printResult(); // 16 -> 15 - stateA.value -= 8; // Will notify change - printResult(); // 8 -> 10 - } -} -``` - -`UseCompute` has a `value` property which represents the computed value. - -> **NOTE:** -> `UseCompute` notifies that its `value` has changed when the previous `value` is different from the current `value`. - -When `value` has changed, the `UseState` will emit the following events (learn about it [here](#lifecycle-and-event-management)): - -- `Lifecycle.willUpdate` event is triggered before the `value` change or `update`, `refresh` methods have been invoked. -- `Lifecycle.didUpdate` event is triggered after the `value` change or `update`, `refresh` methods have been invoked. - -> **NOTE:** -> `UseCompute` is read-only, meaning that its value cannot be changed, except by invoking the `computeValue` method. - -> **RECOMENDED:** -> `UseCompute` does not cache the computed value, meaning it recalculates the value when its depenencies has changes, potentially impacting performance, especially if the computation is expensive. In these cases, you should consider using `Memo`(learn about it [here](#memo)) in the following manner: - -```dart - late final myUseComputeMemo = Rt.lazyState((){ - final addAB = Memo( - (Args2 args) => args.arg1 + args.arg2, - ); - - return UseCompute( - () => addAB( - Args2(stateA.value, stateB.value), - ), - [stateA, stateB], - ), - }, this); -``` - -## Dependency injection - -With Reactter, you can create, delete and access the desired object from a single location, and you can do it from anywhere in the code, thanks to reactter's dependency injection system. - -Dependency injection offers several benefits. It promotes the principle of inversion of control, where the control over object creation and management is delegated to Reactter. This improves code modularity, reusability, and testability. It also simplifies the code by removing the responsibility of creating dependencies from individual classes, making them more focused on their core functionality. - -Reactter has three ways to manage an instance, which are: - -- [Builder](#builder) -- [Factory](#factory) -- [Singleton](#singleton) - -Reactter offers the following several instance managers: - -- [Shorcuts to manage instances](#shortcuts-to-manage-instances) -- [UseDependency](#usedependency) - -by `flutter_reactter`: - -- [RtProvider](#rtprovider) -- [RtProviders](#rtproviders) -- [RtComponent](#rtcomponent) -- [BuildContext.use](#buildcontextuse) - -### Builder - -Builder is a ways to manage an instance, which registers a builder function and creates the instance, unless it has already done so. - -In builder mode, when the dependency tree no longer needs it, it is completely deleted, including deregistration (deleting the builder function). - -Reactter identifies the builder mode as [`DependencyMode.builder`](https://pub.dev/documentation/reactter/latest/DependencyMode/DependencyMode.builder.html) and it's using for default. - -> **NOTE:** -> **Builder** uses less RAM than [Factory](#factory) and [Singleton](#singleton), but it consumes more CPU than the other modes. - -### Factory - -Factory is a ways to manage an instance, which registers a builder function only once and creates the instance if not already done. - -In factory mode, when the dependency tree no longer needs it, the instance is deleted and the builder function is kept in the register. - -Reactter identifies the factory mode as [`DependencyMode.factory`](https://pub.dev/documentation/reactter/latest/DependencyMode/DependencyMode.factory.html) and to active it, set it in the `mode` argument of `Rt.register` and `Rt.create`, or use `Rt.lazyFactory`, `Rt.factory`. - -> **NOTE:** -> **Factory** uses more RAM than [Builder](#builder) but not more than [Singleton](#singleton), and consumes more CPU than [Singleton](#singleton) but not more than [Builder](#builder). - -### Singleton - -Singleton is a ways to manage an instance, which registers a builder function and creates the instance only once. - -The singleton mode preserves the instance and its states, even if the dependency tree stops using it. - -Reactter identifies the singleton mode as [`DependencyMode.singleton`](https://pub.dev/documentation/reactter/latest/DependencyMode/DependencyMode.singleton.html) and to active it, set it in the `mode` argument of `Rt.register` and `Rt.create`, or use `Rt.lazySingleton`, `Rt.singleton`. - -> **NOTE:** -> Use `Rt.destroy` if you want to force destroy the instance and its register. - -> **NOTE:** -> **Singleton** consumes less CPU than [Builder](#builder) and [Factory](#factory), but uses more RAM than the other modes. - -### Shortcuts to manage instances - -Reactter offers several convenient shortcuts for managing instances: - -- [`Rt.register`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/register.html): Registers a builder function, for creating a new instance using `[Rt|UseDependency].[get|create|builder|factory|singleton]`. -- [`Rt.lazyBuilder`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/lazyBuilder.html): Registers a builder function, for creating a new instance as [Builder](#builder) mode using `[Rt|UseDependency].[get|create|builder]`. -- [`Rt.lazyFactory`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/lazyFactory.html): Registers a builder function, for creating a new instance as [Factory](#factory) mode using `[Rt|UseDependency].[get|create|factory]`. -- [`Rt.lazySingleton`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/lazySingleton.html): Registers a builder function, for creating a new instance as [Singleton](#singleton) mode using `[Rt|UseDependency].[get|create|singleton]`. -- [`Rt.create`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/create.html): Registers, creates and returns the instance directly. -- [`Rt.builder`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/builder.html): Registers, creates and returns the instance as [Builder](#builder) directly. -- [`Rt.factory`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/factory.html): Registers, creates and returns the instance as [Factory](#factory) directly. -- [`Rt.singleton`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/singleton.html): Registers, creates and returns the instance as [Singleton](#singleton) directly. -- [`Rt.get`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/get.html): Returns a previously created instance or creates a new instance from the builder function registered by `[Rt|UseDependency].[register|lazyBuilder|lazyFactory|lazySingleton]`. -- [`Rt.delete`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/delete.html): Deletes the instance but keeps the builder function. -- [`Rt.unregister`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/factory.html): Removes the builder function, preventing the creation of the instance. -- [`Rt.destroy`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/destroy.html): Destroys the instance and the builder function. -- [`Rt.find`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/find.html): Gets the instance. -- [`Rt.isRegistered`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/isRegistered.html): Checks if an instance is registered in Reactter. -- [`Rt.getDependencyMode`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/getDependencyMode.html): Returns the `DependencyMode` of the instance. - -In each of the events methods shown above (except `Rt.isRegister` and `Rt.getDependencyMode`), it provides the `id` argument for managing the instances of the same type by a unique identity. - -> **NOTE:** -> The scope of the registered instances is global. -> This indicates that using the [shortcuts to manage instance](#shortcuts-to-manage-events) or [`UseDependency`](#usedependency) will allow you to access them from anywhere in the project. - -### UseDependency - -[`UseDependency`](https://pub.dev/documentation/reactter/latest/reactter/UseDependency-class.html) is a hook([`RtHook`](https://pub.dev/documentation/reactter/latest/reactter/RtHook-class.html)) that allows to manage an instance. - -```dart -UseDependency([String? id]); -``` - -The default constructor uses `Rt.find` to get the instance of the `T` type with or without `id` that is available. - -> **NOTE:** -> The instance that you need to get, must be created by [`Dependency injection`](#dependency-injection) before. - -Use `instance` getter to get the instance. - -Here is an example using `UseIntance`: - -```dart -class MyController { - final useAuthController = UseDependency(); - // final useOtherControllerWithId = UseDependency("UniqueId"); - - AuthController? authController = useAuthController.instance; - - MyController() { - UseEffect(() { - authController = useAuthController.instance; - }, [useAuthController], - ); - } -} -``` - -> **NOTE:** -> In the example above uses [`UseEffect`](#useeffect) hook, to wait for the `instance` to become available. - -`UseDependency` provides some constructors and factories for managing an instance, which are: - -- [`UseDependency.register`](https://pub.dev/documentation/reactter/latest/reactter/UseDependency/register.html): Registers a builder function, for creating a new instance using `[Rt|UseDependency].[get|create|builder|factory|singleton]`. -- [`UseDependency.lazyBuilder`](https://pub.dev/documentation/reactter/latest/reactter/UseDependency/lazyBuilder.html): Registers a builder function, for creating a new instance as [Builder](#builder) mode using `[Rt|UseDependency].[get|create|builder]`. -- [`UseDependency.lazyFactory`](https://pub.dev/documentation/reactter/latest/reactter/UseDependency/lazyFactory.html): Registers a builder function, for creating a new instance as [Factory](#factory) mode using `[Rt|UseDependency].[get|create|factory]`. -- [`UseDependency.lazySingleton`](https://pub.dev/documentation/reactter/latest/reactter/UseDependency/lazySingleton.html): Registers a builder function, for creating a new instance as [Singleton](#singleton) mode using `[Rt|UseDependency].[get|create|singleton]`. -- [`UseDependency.create`](https://pub.dev/documentation/reactter/latest/reactter/UseDependency/create.html): Registers, creates and returns the instance directly. -- [`UseDependency.builder`](https://pub.dev/documentation/reactter/latest/reactter/UseDependency/builder.html): Registers, creates and returns the instance as [Builder](#builder) directly. -- [`UseDependency.factory`](https://pub.dev/documentation/reactter/latest/reactter/UseDependency/factory.html): Registers, creates and returns the instance as [Factory](#factory) directly. -- [`UseDependency.singleton`](https://pub.dev/documentation/reactter/latest/reactter/UseDependency/singleton.html): Registers, creates and returns the instance as [Singleton](#singleton) directly. -- [`UseDependency.get`](https://pub.dev/documentation/reactter/latest/reactter/UseDependency/get.html): Returns a previously created instance or creates a new instance from the builder function registered by `[Rt|UseDependency].[register|lazyBuilder|lazyFactory|lazySingleton]`. - -In each of the contructors or factories above shown, it provides the `id` property for managing the instances of the same type by a unique identity. - -> **NOTE:** -> The scope of the registered instances is global. -> This indicates that using the [shortcuts to manage instance](#shortcuts-to-manage-events) or [`UseDependency`](#usedependency) will allow you to access them from anywhere in the project. - -## Event handler - -In Reactter, event handler plays a pivotal role in facilitating seamless communication and coordination between various components within the application. -The event handler system is designed to ensure efficient handling of states and instances, fostering a cohesive ecosystem where different parts of the application can interact harmoniously. - -One of the key aspects of event handler in Reactter is the introduction of [lifecycles](#lifecycles) linked to events. -These lifecycles define the different stages through which a state or instance passes, offering a structured flow and effective handling of changes. - -Additionally, Reactter offers the following event managers: - -- [Shortcuts to manage events](#shortcuts-to-manage-instances) -- [UseEffect](#useeffect) - -by `flutter_reactter`: - -- [RtConsumer](#rtconsumer) -- [RtSelector](#rtselector) -- [RtSignalWatcher](#rtsignalwatcher) -- [BuildContext.watch](#buildcontextwatch) -- [BuildContext.select](#buildcontextselect) - -### Lifecycles - -In Reactter, both the states ([`RtState`](#state-management)) and the instances (managed by the [`dependency injection`](#dependency-injection)) contain different stages, also known as [`Lifecycle`](https://pub.dev/documentation/reactter/latest/reactter/Lifecycle.html). -This lifecycles linked events, which are: - -- `Lifecycle.registered`: is triggered when the dependency has been registered. -- `Lifecycle.created`: is triggered when the dependency instance has been created. -- `Lifecycle.willMount` (exclusive of `flutter_reactter`): is triggered when the dependency is going to be mounted in the widget tree. -- `Lifecycle.didMount` (exclusive of `flutter_reactter`): is triggered after the dependency has been successfully mounted in the widget tree. -- `Lifecycle.willUpdate`: is triggered anytime the dependency's state is about to be updated. The event parameter is a `RtState`. -- `Lifecycle.didUpdate`: is triggered anytime the dependency's state has been updated. The event parameter is a `RtState`. -- `Lifecycle.willUnmount`(exclusive of `flutter_reactter`): is triggered when the dependency is about to be unmounted from the widget tree. -- `Lifecycle.didUnmount`(exclusive of `flutter_reactter`): is triggered when the dependency has been successfully unmounted from the widget tree. -- `Lifecycle.deleted`: is triggered when the dependency instance has been deleted. -- `Lifecycle.unregistered`: is triggered when the dependency is no longer registered. - -You can extend your instances with [`LifecycleObserver`](https://pub.dev/documentation/reactter/latest/reactter/LifecycleObserver-class.html) mixin for observing and reacting to the various lifecycle events. e.g: - -```dart -class MyController with LifecycleObserver { - final state = UseState('initial'); - - @override - void onInitialized() { - print("MyController has been initialized"); - } - - @override - void onDidUpdate(RtState? state) { - print("$state has been changed"); - } -} - -final myController = Rt.create(() => MyController()); -// MyController has been initialized -myController.state.value = "value changed"; -// state has been changed -``` - -### Shortcuts to manage events - -Reactter offers several convenient shortcuts for managing events: - -- [`Rt.on`](https://pub.dev/documentation/reactter/latest/reactter/RtEventHandler/on.html): turns on the listen event. When the `event` of `instance` is emitted, the `callback` is called: - - ```dart - Rt.on(Object instance, Enum event, callback(T inst, P params)); - ``` - -- [`Rt.one`](https://pub.dev/documentation/reactter/latest/reactter/RtEventHandler/one.html): turns on the listen event for only once. When the `event` of `instance` is emitted, the `callback` is called and then removed. - - ```dart - Rt.one(Object instance, Enum event, callback(T inst, P param)); - ``` - -- [`Rt.off`](https://pub.dev/documentation/reactter/latest/reactter/RtEventHandler/off.html): removes the `callback` from `event` of `instance`. - - ```dart - Rt.off(Object instance, Enum event, callback(T instance, P param)); - ``` - -- [`Rt.offAll`](https://pub.dev/documentation/reactter/latest/reactter/RtEventHandler/offAll.html): removes all events of `instance`. - - ```dart - Rt.offAll(Object instance); - ``` - - > **IMPORTANT**: - > Don't use it, if you're not sure. Because it will remove all events, even those events that Reactter needs to work properly. Instead, use `Rt.off` to remove the specific events. - -- [`Rt.emit`](https://pub.dev/documentation/reactter/latest/reactter/RtEventHandler/emit.html): triggers an `event` of `instance` with or without the `param` given. - - ```dart - Rt.emit(Object instance, Enum event, [dynamic param]); - ``` - -In each of the methods it receives as first parameter an `instance` that can be directly the instance object or use `RtInstance` instead: - -```dart -void onDidUpdate(inst, state) => print("Instance: $inst, state: $state"); - -final myController = Rt.get(); -// or using `RtDependency` -final myController = RtDependency(); - -Rt.on(myController, Lifecycle.didUpdate, onDidUpdate); -Rt.emit(myController, Lifecycle.didUpdate, 'test param'); -``` - -> **RECOMMENDED:** -> Use the instance object directly on event methods for optimal performance. - -> **NOTE:** -> The `RtDependency` helps to find the instance for event, if the instance not exists, put it on wait. It's a good option if you're not sure that the instance has been created yet. - -### UseEffect - -[`UseEffect`](https://pub.dev/documentation/reactter/latest/reactter/UseEffect-class.html) is a hook([`RtHook`](https://pub.dev/documentation/reactter/latest/reactter/RtHook-class.html)) that allows to manage side-effect. - -```dart -UseEffect( - Function() callback, - List dependencies, -) -``` - -The side-effect logic into the `callback` function is executed when the `dependencies` argument changes or the `instance` trigger `Lifecycle.didMount` event. - -If the `callback` returns a function, then `UseEffect` considers this as an effect `cleanup`. -The `cleanup` callback is executed, before `callback` is called or `instance` trigger `Lifecycle.willUnmount` event: - -Let's see an example with a counter that increments every second: - -```dart -class MyController { - final count = UseState(0); - - MyController() { - UseEffect((){ - // Execute by count state changed or 'Lifecycle.didMount' event - print("Count: ${count.value}"); - Future.delayed(const Duration(seconds: 1), () => count.value += 1); - - return () { - // Cleanup - Execute before count state changed or 'Lifecycle.willUnmount' event - print("Cleanup executed"); - }; - }, [count]); - } -} -``` - -Use `UseEffect.runOnInit` to execute the callback effect on initialization. - -```dart -UseEffect.runOnInit( - () => print("Excute immediately and by hook changes"), - [someState], -); -``` - -## Rendering control - -Rendering control provides the capability to observe specific instances or states, triggering re-renders of the widget tree as required. This methodology ensures a unified and responsive user interface, facilitating efficient updates based on changes in the application's state. - -In this context, the [`flutter_reactter`](https://pub.dev/packages/flutter_reactter) package provides the following purpose-built widgets and certain `BuildContext` extension for rendering control: - -- [RtProvider](#rtprovider) -- [RtProviders](#rtproviders) -- [RtComponent](#rtcomponent) -- [RtConsumer](#rtconsumer) -- [RtSignalWatcher](#rtsignalwatcher) -- [RtSelector](#rtselector) -- [BuildContext.use](#buildcontextuse) -- [BuildContext.watch](#buildcontextwatch) -- [BuildContext.select](#buildcontextselect) - -### RtProvider - -[`RtProvider`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtProvider-class.html) is a Widget (exclusive of `flutter_reactter`) that hydrates from an instance of `T` type to the Widget tree. - -```dart -RtProvider( - T instanceBuilder(), { - String? id, - DependencyMode type = DependencyMode.builder, - Widget? child, - required Widget builder(BuilderContext context, T instance, Widget? child), -}) -``` - -`RtProvider` accepts these properties: - -- `instanceBuilder`: to define a method for the creation of a new instance of `T` type. - - > **RECOMMENDED:** - > Don't use Object with constructor parameters to prevent conflicts. - -- `id`: to uniquely identify the instance. -- `mode`: to determine the instance manage mode([Builder](#builder), [Factory](#factory) or [Singleton](#singleton)). -- `child`: to pass a `Widget` through the `builder` method that it will be built only once. -- `builder`: to define a method that contains the builder logic of the widget that will be embedded in the widget tree. This method exposes the `instance`(`T`) created, a new `context`(`BuildContext`) and a `child`(`Widget`) defined in the `child` property. - -Here is an example: - -```dart -RtProvider( - () => CounterController(), - child: const Text('This widget is rendered once'), - builder: (context, counterController, child) { - // `context.watch` listens any CounterController changes for rebuild this widget tree. - context.watch(); - - // Change the `value` each 1 second. - Future.delayed(Duration(seconds: 1), (_) => counterController.count.value++); - - return Column( - children: [ - child!, // The child widget has already been built in `child` property. - Text("count: ${counterController.count.value}"), - ], - ); - }, -) -``` - -Use [`RtProvider.init`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtProvider/RtProvider.init.html) to initialize the dependency instance before that it's mounted. - -Use [`RtProvider.lazy`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtProvider/RtProvider.lazy.html) to enable lazy-loading of the instance, ensuring it is only instantiated when necessary. While this feature enhances performance by deferring instantiation until required, it's important to note that it may result in the loss of lifecycle tracing. - -> **NOTE:** -> `RtProvider` is "scoped". So, the `builder` method will be rebuild when the instance or any `RtState` specified in [`BuildContext.watch`](#buildcontextwatch) or [`BuildContext.select`](#buildcontextselect) changes. - -### RtMultiProvider - -[`RtMultiProvider`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtMultiProvider-class.html) is a Widget (exclusive of `flutter_reactter`) that allows to use multiple [`RtProvider`](#rtprovider) in a nested way. - -```dart -RtMultiProvider( - [ - RtProvider( - () => MyController(), - ), - RtProvider( - () => ConfigController(), - id: 'App', - ), - RtProvider( - () => ConfigController(), - id: 'Dashboard' - ), - ], - builder: (context, child) { - final myController = context.use(); - final appConfigController = context.use('App'); - final dashboardConfigController = context.use('Dashboard'); - ... - }, -) -``` - -> **RECOMMENDED:** -> Don't use Object with constructor parameters to prevent conflicts. - -> **NOTE:** -> `RtProvider` is "scoped". So, the `builder` method will be rebuild when the instance or any `RtState` specified in [`BuildContext.watch`](#buildcontextwatch) or [`BuildContext.select`](#buildcontextselect) changes. - -### RtComponent - -[`RtComponent`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtComponent-class.html) is a abstract `StatelessWidget` (exclusive of `flutter_reactter`) that provides [`RtProvider`](#rtprovider) features, whose instance of `T` type is exposed trough `render` method. - -```dart -class CounterComponent extends RtComponent { - const CounterComponent({Key? key}) : super(key: key); - - @override - get builder => () => CounterController(); - - @override - void listenStates(counterController) => [counterController.count]; - - @override - Widget render(context, counterController) { - return Text("Count: ${counterController.count.value}"); - } -} -``` - -Use `builder` getter to define the instance builder function. - -> **RECOMMENDED:** -> Don't use Object with constructor parameters to prevent conflicts. - -> **NOTE:** -> If you don't use `builder` getter, the instance will not be created. Instead, an attempt will be made to locate it within the closest ancestor where it was initially created. - -Use the `id` getter to identify the instance of `T`: - -Use the `listenStates` getter to define the states that will rebuild the tree of the widget defined in the `render` method whenever it changes. - -Use the `listenAll` getter as `true` to listen to all the instance changes to rebuild the Widget tree defined in the `render` method. - -### RtConsumer - -[`RtConsumer`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtConsumer-class.html) is a Widget (exclusive of `flutter_reactter`) that allows to access the instance of `T` type from `RtProvider`'s nearest ancestor and can listen all or specified states to rebuild the Widget when these changes occur: - -```dart -RtConsumer({ - String? id, - bool listenAll = false, - List listenStates(T instance)?, - Widget? child, - required Widget builder(BuildContext context, T instance, Widget? child), -}); -``` - -`RtConsumer` accepts these properties: - -- `id`: to uniquely identify the instance. -- `listenAll`: to listen to all events emitted by the instance or its states(`RtState`). -- `listenStates`: to listen to states(`RtState`) defined in it. -- `child`: to pass a `Widget` through the `builder` method that it will be built only once. -- `builder`: to define a method that contains the builder logic of the widget that will be embedded in the widget tree. This method exposes the `instance`(`T`) created, a new `context`(`BuildContext`) and a `child`(`Widget`) defined in the `child` property. - -Here is an example: - -```dart -class ExampleWidget extends StatelessWidget { - ... - Widget build(context) { - return RtConsumer( - listenStates: (inst) => [inst.stateA, inst.stateB], - child: const Text('This widget is rendered once'), - builder: (context, myController, child) { - // This is built when stateA or stateB has changed. - return Column( - children: [ - Text("My instance: $d"), - Text("StateA: ${d.stateA.value}"), - Text("StateB: ${d.stateB.value}"), - child!, // The child widget has already been built in `child` property. - ], - ); - } - ); - } -} -``` - -> **NOTE:** -> `ReactteConsumer` is "scoped". So, the `builder` method will be rebuild when the instance or any `RtState` specified get change. - -> **NOTE:** -> Use [`RtSelector`](#rtselector) for more specific conditional state when you want the widget tree to be re-rendered. - -### RtSelector - -[`RtSelector`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtSelector-class.html) is a Widget (exclusive of `flutter_reactter`) that allows to control the rebuilding of widget tree by selecting the states([`RtState`](https://pub.dev/documentation/reactter/latest/reactter/RtState-class.html)) and a computed value. - -```dart -RtSelector( - V selector( - T inst, - RtState $(RtState state), - ), - String? id, - Widget? child, - Widget builder( - BuildContext context, - T inst, - V value, - Widget? child - ), -) -``` - -`RtSelector` accepts four properties: - -- `selector`: to define a method that contains the computed value logic and determined when to be rebuilding the widget tree which defined in `build` property. It returns a value of `V` type and exposes the following arguments: - - `inst`: the found instance of `T` type and by `id` if specified it. - - `$`: a method that allows to wrap to the state(`RtState`) to put it in listening. -- `id`: to uniquely identify the instance. -- `child`: to pass a `Widget` through the `builder` method that it will be built only once. -- `builder`: to define a method that contains the builder logic of the widget that will be embedded in the widget tree. It exposes the following arguments: - - `context`: a new `BuilContext`. - - `inst`: the found instance of `T` type and by `id` if specified it. - - `value`: the computed value of `V` type. It is computed by `selector` method. - - `child`: a `Widget` defined in the `child` property. - -`RtSelector` determines if the widget tree of `builder` needs to be rebuild again by comparing the previous and new result of `selector`. -This evaluation only occurs if one of the selected states(`RtState`) gets updated, or by the instance if the `selector` does not have any selected states(`RtState`). e.g.: - -```dart -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - - @override - Widget? build(BuildContext context) { - return RtProvider( - () => MyController(), - builder: (context, inst, child) { - return OtherWidget(); - } - ); - } -} - -class OtherWidget extends StatelessWidget { - const OtherWidget({Key? key}) : super(key: key); - - @override - Widget? build(BuildContext context) { - return RtSelector( - selector: (inst, $) => $(inst.stateA).value % $(inst.stateB).value, - builder: (context, inst, value, child) { - // This is rebuilt every time that the result of selector is different to previous result. - return Text("${inst.stateA.value} mod ${inst.stateB.value}: ${value}"); - }, - ); - } -} -``` - -`RtSelector` typing can be ommited, but the app must be wrapper by `RtScope`. e.g.: - -```dart -[...] -RtScope( - child: MyApp(), -) -[...] - -class OtherWidget extends StatelessWidget { - const OtherWidget({Key? key}) : super(key: key); - - @override - Widget? build(BuildContext context) { - final myController = context.use(); - - return RtSelector( - selector: (_, $) => $(myController.stateA).value % $(myController.stateB).value, - builder: (context, _, value, child) { - // This is rebuilt every time that the result of selector is different to previous result. - return Text("${myController.stateA.value} mod ${myController.stateB.value}: ${value}"); - }, - ); -} -``` - -### RtSignalWatcher - -[`RtSignalWatcher`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtSignalWatcher-class.html) is a Widget (exclusive of `flutter_reactter`) that allows to listen all `Signal`s contained in `builder` property and rebuilt the Widget when it changes: - -```dart -RtSignalWatcher({ - Widget? child, - required Widget builder(BuildContext context, Widget? child), -}) -``` - -`RtSignalWatcher` accepts two properties: - -- `child`: to pass a `Widget` through the `builder` method that it will be built only once. -- `builder`: to define a method that contains the builder logic of the widget that will be embedded in the widget tree. It exposes the following arguments: - - `context`: a new `BuilContext`. - - `child`: a `Widget` defined in the `child` property. - -```dart -final count = Signal(0); -final flag = Signal(false); - -void increase() => count.value += 1; -void toggle() => flag(!flag.value); - -class App extends StatelessWidget { - ... - Widget build(context) { - return RtSignalWatcher( - // This widget is rendered once only and passed through the `builder` method. - child: Row( - children: const [ - ElevatedButton( - onPressed: increase, - child: Text("Increase"), - ), - ElevatedButton( - onPressed: toggle, - child: Text("Toogle"), - ), - ], - ), - builder: (context, child) { - // Rebuilds the Widget tree returned when `count` or `flag` are updated. - return Column( - children: [ - Text("Count: $count"), - Text("Flag is: $flag"), - child!, // Takes the Widget from the `child` property in each rebuild. - ], - ); - }, - ); - } -} -``` - -### BuildContext.use - -[`BuildContext.use`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtBuildContextExtension/use.html) is an extension method of the `BuildContext`, that allows to access to instance of `T` type from the closest ancestor [`RtProvider`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtProvider-class.html). - -```dart -T context.use([String? id]) -``` - -Here is an example: - -```dart -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - - @override - Widget? build(BuildContext context) { - return RtProvider( - () => MyController(), - builder: (context, inst, child) { - return OtherWidget(); - } - ); - } -} - -class OtherWidget extends StatelessWidget { - const OtherWidget({Key? key}) : super(key: key); - - @override - Widget? build(BuildContext context) { - final myController = context.use(); - - return Text("value: ${myController.stateA.value}"); - } -} -``` - -Use the first argument for obtaining the instance by `id`. e.g.: - -```dart - final myControllerById = context.use('uniqueId'); -``` - -Use the nullable type to safely get the instance, avoiding exceptions if the instance is not found, and get `null` instead. e.g.: - -```dart - final myController = context.use(); -``` - ->**NOTE:** -> If `T` is non-nullable and the instance is not found, it will throw `ProviderNullException`. - -### BuildContext.watch - -[`BuildContext.watch`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtBuildContextExtension/watch.html) is an extension method of the `BuildContext`, that allows to access to instance of `T` type from the closest ancestor [`RtProvider`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtProvider-class.html), and listen to the instance or [`RtState`](https://pub.dev/documentation/reactter/latest/reactter/RtState-class.html) list for rebuilding the widget tree in the scope of `BuildContext`. - -```dart -T context.watch( - List listenStates(T inst)?, -) -``` - -Here is an example, that shows how to listen an instance and react for rebuild: - -```dart -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - - @override - Widget? build(BuildContext context) { - return RtProvider( - () => MyController(), - builder: (context, inst, child) { - return OtherWidget(); - } - ); - } -} - -class OtherWidget extends StatelessWidget { - const OtherWidget({Key? key}) : super(key: key); - - @override - Widget? build(BuildContext context) { - final myController = context.watch(); - // This is rebuilt every time any states in the instance are updated - return Text("value: ${myController.stateA.value}"); - } -} -``` - -Use the first argument(`listenStates`) to specify the states that are to be listen on for rebuild. e.g.: - -```dart -[...] - @override - Widget? build(BuildContext context) { - final myController = context.watch( - (inst) => [inst.stateA, inst.stateB], - ); - // This is rebuilt every time any defined states are updated - return Text("value: ${myController.stateA.value}"); - } -[...] -``` - -Use [`BuildContext.watchId`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtBuildContextExtension/watchId.html) for obtaining the instance of `T` type by `id`, and listens the instance or [`RtState`](https://pub.dev/documentation/reactter/latest/reactter/RtState-class.html) list for rebuilding the widget tree in the scope of `BuildContext`. - -```dart -T context.watchId( - String id, - List listenStates(T inst)?, -) -``` - -It is used as follows: - -```dart -// for listening the instance -final myControllerById = context.watchId('uniqueId'); -// for listening the states -final myControllerById = context.watchId( - 'uniqueId', - (inst) => [inst.stateA, inst.stateB], -); -``` - -### BuildContext.select - -[`BuildContext.select`](https://pub.dev/documentation/flutter_reactter/latest/flutter_reactter/RtBuildContextExtension/select.html) is an extension method of the `BuildContext`, that allows to control the rebuilding of widget tree by selecting the states([`RtState`](https://pub.dev/documentation/reactter/latest/reactter/RtState-class.html)) and a computed value. - -```dart -V context.select( - V selector( - T inst, - RtState $(RtState state), - ), - [String? id], -) -``` - -`BuildContext.select` accepts two argtuments: - -- `selector`: to define a method that computed value logic and determined when to be rebuilding the widget tree of the `BuildContext`. It returns a value of `V` type and exposes the following arguments: - - `inst`: the found instance of `T` type and by `id` if specified it. - - `$`: a method that allows to wrap to the state(`RtState`) to put it in listening. -- `id`: to uniquely identify the instance. - -`BuildContext.select` determines if the widget tree in scope of `BuildContext` needs to be rebuild again by comparing the previous and new result of `selector`. -This evaluation only occurs if one of the selected states(`RtState`) gets updated, or by the instance if the `selector` does not have any selected states(`RtState`). e.g.: - -```dart -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - - @override - Widget? build(BuildContext context) { - return RtProvider( - () => MyController(), - builder: (context, inst, child) { - return OtherWidget(); - } - ); - } -} - -class OtherWidget extends StatelessWidget { - const OtherWidget({Key? key}) : super(key: key); - - @override - Widget? build(BuildContext context) { - final value = context.select( - (inst, $) => $(inst.stateA).value % $(inst.stateB).value, - ); - // This is rebuilt every time that the result of selector is different to previous result. - return Text("stateA mod stateB: ${value}"); - } -} -``` - -`BuildContext.select` typing can be ommited, but the app must be wrapper by `RtScope`. e.g.: - -```dart -[...] -RtScope( - child: MyApp(), -) -[...] - -class OtherWidget extends StatelessWidget { - const OtherWidget({Key? key}) : super(key: key); - - @override - Widget? build(BuildContext context) { - final myController = context.use(); - final value = context.select( - (_, $) => $(myController.stateA).value % $(myController.stateB).value, - ); - // This is rebuilt every time that the result of selector is different to previous result. - return Text("stateA mod stateB: ${value}"); - } -} -``` - -## Custom hooks - -Custom hooks are classes that extend [`RtHook`](https://pub.dev/documentation/reactter/latest/reactter/RtHook-class.html) that follow a special naming convention with the `use` prefix and can contain state logic, effects or any other custom code. - -There are several advantages to using Custom Hooks: - -- **Reusability**: to use the same hook again and again, without the need to write it twice. -- **Clean Code**: extracting part of code into a hook will provide a cleaner codebase. -- **Maintainability**: easier to maintain. if you need to change the logic of the hook, you only need to change it once. - -Here's the counter example: - -```dart -class UseCount extends RtHook { - final $ = RtHook.$register; - - int _count = 0; - int get value => _count; - - UseCount(int initial) : _count = initial; - - void increment() => update(() => _count += 1); - void decrement() => update(() => _count -= 1); -} -``` - -> **IMPORTANT:** -> To create a `RtHook`, you need to register it by adding the following line: -> `final $ = RtHook.$register;` - -> **NOTE:** -> `RtHook` provides an `update` method which notifies about its changes. - -You can then call that custom hook from anywhere in the code and get access to its shared logic: - -```dart -class MyController { - final count = UseCount(0); - - MyController() { - Timer.periodic(Duration(seconds: 1), (_) => count.increment()); - - // Print count value every second - Rt.on( - count, - Lifecycle.didUpdate, - (_, __) => print("Count: ${count.value}", - ); - } -} -``` - -## Lazy state - -A lazy state is a `RtState`([`Signal`](#signal) or [`RtHook`](https://pub.dev/documentation/reactter/latest/reactter/RtHook-class.html)) that is loaded lazily using `Rt.lazyState`. - -```dart -T Rt.lazyState(T stateFn(), Object instance); -``` - -`Rt.lazyState` is generally used in states declared with the `late` keyword. -> In dart, `late` keyword is used to declare a variable or field that will be initialized at a later time. It is used to declare a non-nullable variable that is not initialized at the time of declaration. - -For example, when the a state declared in a class requires some variable or methods immediately: - -```dart -class MyController { - final String initialValue = 'test'; - dynamic resolveValue() async => [...]; - - /// late final state = UseAsyncState( - /// initialValue, - /// resolveValue - /// ); <- to use `Rt.lazyState` is required, like: - - late final state = Rt.lazyState( - () => UseAsyncState(initialValue, resolveValue), - this, - ); - - ... -} -``` - -> **IMPORTANT**: -> A state(`RtState`) declared with the `late` keyword and not using `Rt.lazyState` is outside the context of the instance where it was declared, and therefore the instance does not notice about its changes. - -## Batch - -```dart -T Rt.batch(T Function() callback) -``` - -The [`batch`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/batch.html) function allows you to combine multiple state changes to be grouped together, ensuring that any associated side effects are only triggered once, improving performance and reducing unnecessary re-renders. e.g.: - -```dart -final stateA = UseState(0); -final stateB = UseState(0); -final computed = UseCompute( - () => stateA.value + stateB.value, - [stateA, stateB], -); - -final batchReturned = Rt.batch(() { - stateA.value = 1; - stateB.value = 2; - - print(computed.value); // 0 -> because the batch operation is not completed yet. - - return stateA.value + stateB.value; -}); - -print(batchReturned); // 3 -print(computed.value); // 3 -> because the batch operation is completed. -``` - -Batches can be nested and updates will be flushed when the outermost batch call completes. e.g.: - -```dart -final stateA = UseState(0); -final stateB = UseState(0); -final computed = UseCompute( - () => stateA.value + stateB.value, - [stateA, stateB], -); - -Rt.batch(() { - stateA.value = 1; - print(computed.value); // 0; - - Rt.batch(() { - stateB.value = 2; - print(computed.value); // 0; - }); - - print(computed.value); // 0; -}); - -print(computed.value); // 3; -``` - -## Untracked - -```dart -T Rt.untracked(T Function() callback) -``` - -The [`untracked`](https://pub.dev/documentation/reactter/latest/reactter/RtInterface/untracked.html) function helps you to execute the given `callback` function without tracking any state changes. This means that any state changes that occur inside the `callback` function will not trigger any side effects. e.g.: - -```dart -final state = UseState(0); -final computed = UseCompute(() => state.value + 1, [state]); - -Rt.untracked(() { - state.value = 2; - - print(computed.value); // 1 -> because the state change is not tracked -}); - -print(computed.value); // 1 -> because the state change is not tracked -``` - -## Generic arguments - -Generic arguments are objects of the `Args` class that represent the arguments of the specified types. -It is used to define the arguments that are passed through a `Function` and allows to type the `Function` appropriately. - -> **RECOMMENDED**: -> If your project supports [`Record`](https://dart.dev/language/records#record-types), it is recommended to use it instead of the generic arguments. - -Reactter provides these generic arguments classes: - -- [`Args`](https://pub.dev/documentation/reactter/latest/reactter/Args-class.html): represents one or more arguments of `A` type. -- [`Args1`](https://pub.dev/documentation/reactter/latest/reactter/Args1-class.html) : represents a argument of `A` type. -- [`Args2`](https://pub.dev/documentation/reactter/latest/reactter/Args2-class.html): represents two arguments of `A`, `A2` type consecutively. -- [`Args3`](https://pub.dev/documentation/reactter/latest/reactter/Args3-class.html): represents three arguments of `A`, `A2`, `A3` type consecutively. -- [`ArgsX2`](https://pub.dev/documentation/reactter/latest/reactter/ArgsX2.html): represents two arguments of `A` type. -- [`ArgsX3`](https://pub.dev/documentation/reactter/latest/reactter/ArgsX3.html): represents three arguments of `A` type. - -In each of the methods it provides these methods and properties: - -- `arguments`: gets the list of arguments. -- `toList()`: gets the list of arguments `T` type. -- `arg1`: gets the first argument. -- `arg2`(`Args2`, `Args3`, `ArgsX2`, `ArgsX3` only): gets the second argument. -- `arg3`(`Args3`, `ArgsX3` only): gets the third argument. - -> **NOTE:** -> If you need a generic argument class with more arguments, then create a new class following pattern: -> -> ```dart -> class Args+n extends Args+(n-1) { -> final A+n arg+n; -> -> const Args+n(A arg1, (...), A+(n-1) arg+(n-1), this.arg+n) : super(arg1, (...), arg+(n-1)); -> -> @override -> List get arguments => [...super.arguments, arg+n]; -> } -> -> typedef ArgX+n = Args+n; -> ``` -> -> e.g. 4 arguments: -> -> ```dart -> class Args4 extends Args3 { -> final A4 arg4; -> -> const Args4(A arg1, A2 arg2, A3 arg3, this.arg4) : super(arg1, arg2, arg3); -> -> @override -> List get arguments => [...super.arguments, arg4]; -> } -> -> typedef ArgX4 = Args4; -> ``` - -> **NOTE:** -> Use `ary` Function extention to convert any `Function` with positional arguments to `Function` with generic argument, e.g.: -> -> ```dart -> int addNum(int num1, int num2) => num1 + num2; -> // convert to `int Function(Args2(int, int))` -> final addNumAry = myFunction.ary; -> addNumAry(Arg2(1, 1)); -> // or call directly -> addNum.ary(ArgX2(2, 2)); -> ``` - -## Memo - -[`Memo`](https://pub.dev/documentation/reactter/latest/reactter/Memo-class.html) is a class callable with memoization logic which it stores computation results in cache, and retrieve that same information from the cache the next time it's needed instead of computing it again. - -> **NOTE:** -> Memoization is a powerful trick that can help speed up our code, especially when dealing with repetitive and heavy computing functions. - -```dart -Memo( - T computeValue(A arg), [ - MemoInterceptor? interceptor, -]); -``` - -`Memo` accepts these properties: - -- `computeValue`: represents a method that takes an argument of type `A` and returns a value of `T` type. This is the core function that will be memoized. -- `interceptor`: receives a [`MemoInterceptor`](https://pub.dev/documentation/reactter/latest/reactter/MemoInterceptor-class.html) that allows you to intercept the memoization function calls and modify the memoization process. - Reactter providers some interceptors: - - [`MemoMultiInterceptor`](https://pub.dev/documentation/reactter/latest/reactter/MemoMultiInterceptor-class.html): allows multiple memoization interceptors to be used together. - - [`MemoWrapperInterceptor`](https://pub.dev/documentation/reactter/latest/reactter/MemoWrapperInterceptor-class.html): a wrapper for a memoized function that allows you to define callbacks for initialization, successful completion, error handling, and finishing. - - [`MemoSafeAsyncInterceptor`](https://pub.dev/documentation/reactter/latest/reactter/MemoSafeAsyncInterceptor-class.html): prevents saving in cache if the `Future` calculation function throws an error during execution. - - [`MemoTemporaryCacheInterceptor`](https://pub.dev/documentation/reactter/latest/reactter/MemoTemporaryCacheInterceptor-class.html): removes memoized values from the cache after a specified duration. - -Here an factorial example using `Memo`: - -```dart -late final factorialMemo = Memo(calculateFactorial); - -/// A factorial(n!) represents the multiplication of all numbers between 1 and n. -/// So if you were to have 3!, for example, you'd compute 3 x 2 x 1 (which = 6). -BigInt calculateFactorial(int number) { - if (number == 0) return BigInt.one; - return BigInt.from(number) * factorialMemo(number - 1); -} - -void main() { - // Returns the result of multiplication of 1 to 50. - final f50 = factorialMemo(50); - // Returns the result immediately from cache - // because it was resolved in the previous line. - final f10 = factorialMemo(10); - // Returns the result of the multiplication of 51 to 100 - // and 50! which is obtained from the cache(as computed previously by f50). - final f100 = factorialMemo(100); - - print( - 'Results:\n' - '\t10!: $f10\n' - '\t50!: $f50\n' - '\t100!: $f100\n' - ); -} -``` - -> **NOTE**: -> The `computeValue` of `Memo` accepts one argument only. If you want to add more arguments, you can supply it using the `Record`(`if your proyect support`) or `generic arguments`(learn more [here](#generic-arguments)). - -> **NOTE:** -> Use [`Memo.inline`](https://pub.dev/documentation/reactter/latest/reactter/Memo/inline.html) in case there is a typing conflict, e.g. with the `UseAsynState` and `UseCompute` hooks which a `Function` type is required. - -`Memo` provides the following methods that will help you manipulate the cache as you wish: - -- `T? get(A arg)`: returns the cached value by `arg`. -- `T? remove(A arg)`: removes the cached value by `arg`. -- `clear`: removes all cached data. - -## Difference between Signal and UseState - -Both `UseState` and `Signal` represent a state (`RtState`). However, it possesses distinct features that set them apart. - -`UseState` is a `RtHook`, giving it the unique ability to be extended and enriched with new capabilities, which sets it apart from `Signal`. - -In the case of `UseState`, it necessitates the use of the `value` attribute whenever state is read or modified. On the other hand, `Signal` streamlines this process, eliminating the need for explicit `value` handling, thus enhancing code clarity and ease of understanding. - -In the context of Flutter, when implementing `UseState`, it is necessary to expose the parent class containing the state to the widget tree via a `RtProvider` or `RtComponent`, and subsequently access it through `BuildContext`. Conversely, with `Signal`, which is inherently reactive, you can conveniently employ `RtSignalWatcher`. - -It's important to note that while `Signal` offers distinct advantages, particularly for managing global states and enhancing code readability, it can introduce potential antipatterns and may complicate the debugging process. Nevertheless, these concerns are actively being addressed and improved in upcoming versions of the package. - -Ultimately, the choice between `UseState` and `Signal` lies in your hands. They can coexist seamlessly, and you have the flexibility to transition from `UseState` to `Signal`, or vice versa, as your project's requirements evolve. +- ⚡️ Engineered for **speed**. +- 🪶 Super **lightweight**. +- 👓 **Simple syntax**, easy to learn. +- ✂️ **Reduce boilerplate code** significantly. +- 👁️ Improve **code readability**. +- 🚀 **Granular reactivity** using [state](https://2devs-team.github.io/reactter/core_concepts/state_management/#state) and [hooks](https://2devs-team.github.io/reactter/core_concepts/hooks). +- 🧩 **Highly reusable** states and logic via [custom hooks](https://2devs-team.github.io/reactter/core_concepts/hooks/#custom-hook) and [dependency injection](https://2devs-team.github.io/reactter/core_concepts/dependency_injection/). +- 🎮 Total [**rendering control**](https://2devs-team.github.io/reactter/core_concepts/rendering_control). +- ✅ **Highly testable** with 100% code coverage. +- 🐞 **Fully debuggable** using the [Reactter DevTools extension](https://2devs-team.github.io/reactter/devtools_extension). +- 💧 **Not opinionated**. Use it with any architecture or pattern. +- 🪄 **Zero dependencies**, **zero configuration** and **no code generation**. +- 💙 **Compatible with Dart and Flutter**, supporting the latest Dart version. + +_To start using Reactter, check out the full documentation on [The Official Documentation](https://2devs-team.github.io/reactter)._ ## Resources - [Website Official](https://2devs-team.github.io/reactter) - [Github](https://github.com/2devs-team/reactter) - [Examples](https://github.com/2devs-team/reactter/tree/master/packages/flutter_reactter/example) -- [Examples in zapp](https://zapp.run/pub/flutter_reactter) +- [Examples in Zapp](https://zapp.run/pub/flutter_reactter) - [Reactter documentation](https://pub.dev/documentation/reactter/latest) - [Flutter Reactter documentation](https://pub.dev/documentation/flutter_reactter/latest) - [Reactter Lint](https://pub.dev/packages/reactter_lint) - [Reactter Snippets](https://marketplace.visualstudio.com/items?itemName=CarLeonDev.reacttersnippets) +## DevTools + +![Reactter DevTools](https://raw.githubusercontent.com/2devs-team/reactter_assets/refs/heads/main/devtools.png) + +You can debug your app using the **[Reactter DevTools extension](https://2devs-team.github.io/reactter/devtools_extension)**. + ## Contribute If you want to contribute don't hesitate to create an [issue](https://github.com/2devs-team/reactter/issues/new) or [pull-request](https://github.com/2devs-team/reactter/pulls) in [Reactter repository](https://github.com/2devs-team/reactter). @@ -1814,11 +64,11 @@ You can: - Add a new widget. - Add examples. - Translate documentation. -- Write articles or make videos teaching how to use [Reactter](https://github.com/2devs-team/reactter). Any idean is welcome! -## Authors +## Contributors -- **[Carlos León](https://twitter.com/CarLeonDev)** - -- **[Leo Castellanos](https://twitter.com/leoocast10)** - + + + diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index cdb23e21..00000000 --- a/ROADMAP.md +++ /dev/null @@ -1,29 +0,0 @@ - -# Roadmap - -We want to keeping adding features for `Reactter`, those are some we have in mind order by priority: - -- Improve performance and do benchmark. - -# Contribute - -If you want to contribute don't hesitate to create an [issue](https://github.com/2devs-team/reactter/issues/new) or [pull-request](https://github.com/2devs-team/reactter/pulls) in **[Reactter repository](https://github.com/2devs-team/reactter).** - -You can: - -- Provide new features. -- Report bugs. -- Report situations difficult to implement. -- Report an unclear error. -- Report unclear documentation. -- Add a new custom hook. -- Add a new widget. -- Add examples. -- Write articles or make videos teaching how to use **[Reactter](https://github.com/2devs-team/reactter)**. - -Any idea is welcome! - -# Authors - -- **[Leo Castellanos](https://twitter.com/leoocast10)** - -- **[Carlos León](_blank)** - diff --git a/packages/flutter_reactter/devtools_options.yaml b/packages/flutter_reactter/devtools_options.yaml new file mode 100644 index 00000000..995fadae --- /dev/null +++ b/packages/flutter_reactter/devtools_options.yaml @@ -0,0 +1,4 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: + - reactter: true \ No newline at end of file diff --git a/packages/flutter_reactter/doc/screenshots/devtools.png b/packages/flutter_reactter/doc/screenshots/devtools.png new file mode 120000 index 00000000..9907c222 --- /dev/null +++ b/packages/flutter_reactter/doc/screenshots/devtools.png @@ -0,0 +1 @@ +/Users/leon/projects/reactter/screenshots/devtools.png \ No newline at end of file diff --git a/packages/flutter_reactter/doc/screenshots/example_app.png b/packages/flutter_reactter/doc/screenshots/example_app.png new file mode 120000 index 00000000..3a018387 --- /dev/null +++ b/packages/flutter_reactter/doc/screenshots/example_app.png @@ -0,0 +1 @@ +/Users/leon/projects/reactter/screenshots/example_app.png \ No newline at end of file diff --git a/packages/flutter_reactter/example/README.md b/packages/flutter_reactter/example/README.md index 91ce5452..4c88b336 100644 --- a/packages/flutter_reactter/example/README.md +++ b/packages/flutter_reactter/example/README.md @@ -13,20 +13,17 @@ flutter create . flutter run ``` -for [Github search example](#github-search), add http permission: - -- [Android](https://docs.flutter.dev/development/data-and-backend/networking#android) -- [IOS](https://guides.codepath.com/ios/Internet-Permissions) +for [Github search example](#github-search), add http permission(This is the documented [here](https://docs.flutter.dev/data-and-backend/networking)) ## Counter Increase and decrease the counter. -> Learn how to use reactive state using signal. +> Learn how to use reactive state using UseState. [![Counter example](https://raw.githubusercontent.com/2devs-team/reactter_assets/main/examples/counter_example.gif)](https://github.com/2devs-team/reactter/tree/master/packages/flutter_reactter/example/lib/counter) -Implements: `ReactterWatcher`, `Signal`. +Implements: `RtWatcher`, `UseState`. ## Calculator @@ -36,7 +33,7 @@ Performs simple arithmetic operations on numbers [![Counter example](https://raw.githubusercontent.com/2devs-team/reactter_assets/main/examples/calculator_example.png)](https://github.com/2devs-team/reactter/tree/master/packages/flutter_reactter/example/lib/calculator) -Implements: `BuildContext.use`, `ReactterConsumer`, `ReactterProvider`, `ReactterSelector`, `Signal`. +Implements: `BuildContext.use`, `RtProvider`, `RtSelector`, `RtWatcher`, `UseState`. ## Shopping cart @@ -46,7 +43,7 @@ Add, remove product to cart and checkout. [![Shopping cart example](https://raw.githubusercontent.com/2devs-team/reactter_assets/main/examples/cart_example.gif)](https://github.com/2devs-team/reactter/tree/master/packages/flutter_reactter/example/lib/shopping_cart) -Implements: `ReactterComponent`, `ReactterConsumer`, `ReactterProvider`, `ReactterProviders`, `ReactterSelector`, `UseDependencycy`, `UseState`. +Implements: `ReactterComponent`, `RtConsumer`, `RtProvider`, `RtProviders`, `RtSelector`, `UseDependencycy`, `UseState`. ## Tree widget @@ -56,7 +53,7 @@ Add, remove and hide child widget with counter. [![Tree widget example](https://raw.githubusercontent.com/2devs-team/reactter_assets/main/examples/tree_example.gif)](https://github.com/2devs-team/reactter/tree/master/packages/flutter_reactter/example/lib/tree) -Implements: `BuilContext.use`, `BuilContext.watchId`, `Reactter.lazyState`, `ReactterComponent`, `ReactterProvider`, `UseEffect`, `UseState`. +Implements: `BuilContext.use`, `BuilContext.watchId`, `Reactter.lazyState`, `ReactterComponent`, `RtProvider`, `UseEffect`, `UseState`. ## Github search @@ -66,7 +63,7 @@ Search user or repository and show info about it. [![Github search example](https://raw.githubusercontent.com/2devs-team/reactter_assets/main/examples/api_example.png)](https://github.com/2devs-team/reactter/tree/master/packages/flutter_reactter/example/lib/api) -Implements: `Memo`, `ReactterConsumer`, `ReactterProvider`, `UseAsyncState`. +Implements: `Memo`, `RtConsumer`, `RtProvider`, `UseAsyncState`. ## To-Do List @@ -76,7 +73,7 @@ Add and remove to-do, mark and unmark to-do as done and filter to-do list. [![To-Do List example](https://raw.githubusercontent.com/2devs-team/reactter_assets/main/examples/todos_example.png)](https://github.com/2devs-team/reactter/tree/master/packages/flutter_reactter/example/lib/todo) -Implements: `Reactter.lazyState`, `ReactterActionCallable`, `ReactterCompontent`, `ReactterConsumer`, `ReactterProvider`, `ReactterSelector`, `UseCompute`, `UseReducer`. +Implements: `Reactter.lazyState`, `ReactterActionCallable`, `ReactterCompontent`, `RtConsumer`, `RtProvider`, `RtSelector`, `UseCompute`, `UseReducer`. ## Animate widget @@ -86,4 +83,4 @@ Change size, shape and color using animations. [![Animate widget example](https://raw.githubusercontent.com/2devs-team/reactter_assets/main/examples/animation_example.gif)](https://github.com/2devs-team/reactter/tree/master/packages/flutter_reactter/example/lib/animation) -Implements: `Reactter.lazyState`, `ReactterConsumer`, `ReactterHook`, `ReactterProvider`, `ReactterSelector`, `UseCompute`, `UseEffect`, `UseState`. +Implements: `Reactter.lazyState`, `RtConsumer`, `ReactterHook`, `RtProvider`, `RtSelector`, `UseCompute`, `UseEffect`, `UseState`. diff --git a/packages/flutter_reactter/example/devtools_options.yaml b/packages/flutter_reactter/example/devtools_options.yaml new file mode 100644 index 00000000..995fadae --- /dev/null +++ b/packages/flutter_reactter/example/devtools_options.yaml @@ -0,0 +1,4 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: + - reactter: true \ No newline at end of file diff --git a/packages/flutter_reactter/example/lib/examples_page.dart b/packages/flutter_reactter/example/lib/examples.dart similarity index 79% rename from packages/flutter_reactter/example/lib/examples_page.dart rename to packages/flutter_reactter/example/lib/examples.dart index fa58dc64..9ff31a88 100644 --- a/packages/flutter_reactter/example/lib/examples_page.dart +++ b/packages/flutter_reactter/example/lib/examples.dart @@ -3,10 +3,10 @@ import 'package:examples/custom_list.dart'; import 'examples/1_counter/counter_page.dart'; import 'examples/2_calculator/calculator_page.dart'; -import 'examples/4_shopping_cart/shopping_cart_page.dart'; -import 'examples/3_tree/tree_page.dart'; -import 'examples/5_api/api_page.dart'; -import 'examples/6_todo/todo_page.dart'; +import 'examples/3_shopping_cart/shopping_cart_page.dart'; +import 'examples/6_tree/tree_page.dart'; +import 'examples/4_github_search/github_search_page.dart'; +import 'examples/5_todo/todo_page.dart'; import 'examples/7_animation/animation_page.dart'; final examples = [ @@ -16,7 +16,7 @@ final examples = [ "Increase and decrease the counter", [ "RtWatcher", - "Signal", + "UseState", ], () => const CounterPage(), ), @@ -26,34 +26,16 @@ final examples = [ "Performs simple arithmetic operations on numbers", [ "BuilContext.use", - "Rt.batch", - "RtConsumer", "RtProvider", "RtSelector", "RtWatcher", - "Signal", - ], - () => const CalculatorPage(), - ), - ExampleItem( - "/tree", - "3. Tree widget", - "Add, remove and hide child widget with counter.", - [ - "BuilContext.use", - "BuilContext.watchId", - "Rt.lazyState", - "RtComponent", - "RtProvider", - "UseCompute", - "UseEffect", "UseState", ], - () => const TreePage(), + () => const CalculatorPage(), ), ExampleItem( "/shopping-cart", - "4. Shopping Cart", + "3. Shopping Cart", "Add, remove product to cart and checkout", [ "Rt.lazyState", @@ -62,14 +44,15 @@ final examples = [ "RtProvider", "RtMultiProvider", "RtSelector", + "RtWatcher", "UseDependency", "UseState", ], () => const ShoppingCartPage(), ), ExampleItem( - "/api", - "5. Github Search", + "/github-search", + "4. Github Search", "Search user or repository and show info about it.", [ "Memo", @@ -77,12 +60,13 @@ final examples = [ "RtConsumer", "RtProvider", "UseAsyncState", + "UseDependency", ], - () => const ApiPage(), + () => const GithubSearchPage(), ), ExampleItem( "/todo", - "6. To-Do List", + "5. To-Do List", "Add and remove to-do, mark and unmark to-do as done and filter to-do list", [ "Rt.lazyState", @@ -95,6 +79,26 @@ final examples = [ ], () => const TodoPage(), ), + ExampleItem( + "/tree", + "6. Tree widget", + "Add, remove and hide child widget with counter.", + [ + "Rt.batch", + "Rt.createSate", + "Rt.lazyState", + "RtContextMixin", + "RtComponent", + "RtConsumer", + "RtProvider", + "RtState", + "RtWatcher", + "UseCompute", + "UseEffect", + "UseState", + ], + () => const TreePage(), + ), ExampleItem( "/animation", "7. Animate widget", @@ -112,24 +116,6 @@ final examples = [ ), ]; -class ExamplesPage extends StatelessWidget { - const ExamplesPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Title( - title: 'Reactter | Exmaples', - color: Theme.of(context).primaryColor, - child: Scaffold( - appBar: AppBar( - title: const Text("Examples"), - ), - body: CustomList(items: examples), - ), - ); - } -} - /// A ListItem that contains data to display a message. class ExampleItem implements ListItem { final String routeName; diff --git a/packages/flutter_reactter/example/lib/examples/1_counter/counter_page.dart b/packages/flutter_reactter/example/lib/examples/1_counter/counter_page.dart index 7b0693c9..e901d346 100644 --- a/packages/flutter_reactter/example/lib/examples/1_counter/counter_page.dart +++ b/packages/flutter_reactter/example/lib/examples/1_counter/counter_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_reactter/flutter_reactter.dart'; -final count = UseState(0); +final uCount = UseState(0, debugLabel: "uCount"); class CounterPage extends StatelessWidget { const CounterPage({Key? key}) : super(key: key); @@ -20,7 +20,7 @@ class CounterPage extends StatelessWidget { runSpacing: 8, children: [ ElevatedButton( - onPressed: () => count.value--, + onPressed: () => uCount.value--, style: ElevatedButton.styleFrom( shape: const CircleBorder(), backgroundColor: Colors.red, @@ -39,7 +39,7 @@ class CounterPage extends StatelessWidget { child: FittedBox( child: RtWatcher((context, watch) { return Text( - "${watch(count).value}", + "${watch(uCount).value}", style: Theme.of(context).textTheme.displaySmall, textAlign: TextAlign.center, ); @@ -47,7 +47,7 @@ class CounterPage extends StatelessWidget { ), ), ElevatedButton( - onPressed: () => count.value++, + onPressed: () => uCount.value++, style: ElevatedButton.styleFrom( shape: const CircleBorder(), backgroundColor: Colors.green, diff --git a/packages/flutter_reactter/example/lib/examples/2_calculator/calculator_page.dart b/packages/flutter_reactter/example/lib/examples/2_calculator/calculator_page.dart index d3afca04..52da67c1 100644 --- a/packages/flutter_reactter/example/lib/examples/2_calculator/calculator_page.dart +++ b/packages/flutter_reactter/example/lib/examples/2_calculator/calculator_page.dart @@ -8,6 +8,8 @@ import 'package:examples/examples/2_calculator/widgets/calculator_action_button. import 'package:examples/examples/2_calculator/widgets/calculator_number_button.dart'; import 'package:examples/examples/2_calculator/widgets/calculator_result.dart'; +const kCalculatorSize = Size(230, 315); + class CalculatorPage extends StatelessWidget { const CalculatorPage({Key? key}) : super(key: key); @@ -20,50 +22,87 @@ class CalculatorPage extends StatelessWidget { appBar: AppBar( title: const Text('Calculator'), ), - body: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - CalculatorResult(), - Row( - children: [ - CalculatorActionButton(action: ActionCalculator.clear), - CalculatorActionButton(action: ActionCalculator.sign), - CalculatorActionButton(action: ActionCalculator.porcentage), - CalculatorActionButton(action: ActionCalculator.divide), - ], - ), - Row( - children: [ - CalculatorNumberButton(number: 7), - CalculatorNumberButton(number: 8), - CalculatorNumberButton(number: 9), - CalculatorActionButton(action: ActionCalculator.multiply), - ], - ), - Row( - children: [ - CalculatorNumberButton(number: 4), - CalculatorNumberButton(number: 5), - CalculatorNumberButton(number: 6), - CalculatorActionButton(action: ActionCalculator.subtract), - ], - ), - Row( - children: [ - CalculatorNumberButton(number: 1), - CalculatorNumberButton(number: 2), - CalculatorNumberButton(number: 3), - CalculatorActionButton(action: ActionCalculator.add), - ], + body: Center( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: kCalculatorSize.width, + maxHeight: kCalculatorSize.height, ), - Row( - children: [ - CalculatorNumberButton(number: 0), - CalculatorActionButton(action: ActionCalculator.point), - CalculatorActionButton(action: ActionCalculator.equal), - ], + child: AspectRatio( + aspectRatio: kCalculatorSize.width / kCalculatorSize.height, + child: FittedBox( + fit: BoxFit.fitWidth, + child: Container( + color: Colors.grey.shade900, + width: kCalculatorSize.width, + height: kCalculatorSize.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + CalculatorResult(), + Row( + children: [ + CalculatorActionButton( + action: ActionCalculator.clear, + ), + CalculatorActionButton( + action: ActionCalculator.sign, + ), + CalculatorActionButton( + action: ActionCalculator.porcentage, + ), + CalculatorActionButton( + action: ActionCalculator.divide, + ), + ], + ), + Row( + children: [ + CalculatorNumberButton(number: 7), + CalculatorNumberButton(number: 8), + CalculatorNumberButton(number: 9), + CalculatorActionButton( + action: ActionCalculator.multiply, + ), + ], + ), + Row( + children: [ + CalculatorNumberButton(number: 4), + CalculatorNumberButton(number: 5), + CalculatorNumberButton(number: 6), + CalculatorActionButton( + action: ActionCalculator.subtract, + ), + ], + ), + Row( + children: [ + CalculatorNumberButton(number: 1), + CalculatorNumberButton(number: 2), + CalculatorNumberButton(number: 3), + CalculatorActionButton( + action: ActionCalculator.add, + ), + ], + ), + Row( + children: [ + CalculatorNumberButton(number: 0, flex: 2), + CalculatorActionButton( + action: ActionCalculator.point, + ), + CalculatorActionButton( + action: ActionCalculator.equal, + ), + ], + ), + ], + ), + ), + ), ), - ], + ), ), ); }, diff --git a/packages/flutter_reactter/example/lib/examples/2_calculator/controllers/calculator_controller.dart b/packages/flutter_reactter/example/lib/examples/2_calculator/controllers/calculator_controller.dart index 8f47c627..abf7d1a3 100644 --- a/packages/flutter_reactter/example/lib/examples/2_calculator/controllers/calculator_controller.dart +++ b/packages/flutter_reactter/example/lib/examples/2_calculator/controllers/calculator_controller.dart @@ -21,76 +21,92 @@ final mathOperationMethods = { }; class CalculatorController { - final result = Signal("0"); - final mathOperation = Signal(null); + final uResult = UseState("0", debugLabel: "uResult"); + final uMathOperation = UseState( + null, + debugLabel: "uMathOperation", + ); ActionCalculator? _lastAction; double? _numberMemorized; - late final _actionMethods = { - ActionCalculator.number: _insertNumber, - ActionCalculator.add: _resolveMathOperation, - ActionCalculator.subtract: _resolveMathOperation, - ActionCalculator.multiply: _resolveMathOperation, - ActionCalculator.divide: _resolveMathOperation, - ActionCalculator.equal: _equal, - ActionCalculator.sign: _changeSign, - ActionCalculator.porcentage: _calculatePercentage, - ActionCalculator.point: _addPoint, - ActionCalculator.clear: _clear, - }; - void executeAction(ActionCalculator action, [int? value]) { - final actionMethod = _actionMethods[action]; - - () { - if (actionMethod is Function(int) && value != null) { - return actionMethod(value); - } - - if (actionMethod is Function(ActionCalculator)) { - return actionMethod(action); - } - - actionMethod?.call(); - }(); + switch (action) { + case ActionCalculator.number: + if (value != null) _insertNumber(value); + break; + case ActionCalculator.add: + case ActionCalculator.subtract: + case ActionCalculator.multiply: + case ActionCalculator.divide: + _resolveMathOperation(action); + break; + case ActionCalculator.equal: + _equal(); + break; + case ActionCalculator.sign: + _changeSign(); + break; + case ActionCalculator.porcentage: + _calculatePercentage(); + break; + case ActionCalculator.point: + _addPoint(); + break; + case ActionCalculator.clear: + _clear(); + break; + } _lastAction = action; } void _insertNumber(int value) { - if (_lastAction != ActionCalculator.number) _resetResult(); + if (![ + ActionCalculator.number, + ActionCalculator.point, + ActionCalculator.sign, + ].contains(_lastAction)) { + _resetResult(); + } + _concatValue(value); } void _resolveMathOperation(ActionCalculator action) { - mathOperation.value = action; + uMathOperation.value = action; _resolve(); - _numberMemorized = double.tryParse(result.value); + _numberMemorized = double.tryParse(uResult.value); } void _equal() { - final resultNumber = double.tryParse(result.value); + final resultNumber = double.tryParse(uResult.value); + _resolve(); - if (_lastAction != ActionCalculator.equal) _numberMemorized = resultNumber; + + if (_lastAction != ActionCalculator.equal) { + _numberMemorized = resultNumber; + } } void _changeSign() { - final resultNumber = double.tryParse(result.value) ?? 0; - result(_formatValue(resultNumber * -1)); + final resultNumber = double.tryParse(uResult.value) ?? 0; + uResult.value = _formatValue(resultNumber * -1); } void _calculatePercentage() { - final resultNumber = double.tryParse(result.value) ?? 0; - result(_formatValue(resultNumber / 100)); + final resultNumber = double.tryParse(uResult.value) ?? 0; + uResult.value = _formatValue(resultNumber / 100); } void _addPoint() { - if (!result.value.contains(".")) result("$result."); + if (uResult.value.contains(".")) return; + + uResult.value = "${uResult.value}."; } void _clear() { - mathOperation.value = null; + uMathOperation.value = null; _numberMemorized = null; _resetResult(); } @@ -98,21 +114,23 @@ class CalculatorController { void _resolve() { if (_numberMemorized == null) return; - final resultNumber = double.tryParse(result.value) ?? 0; - final mathOperationMethod = mathOperationMethods[mathOperation.value]; + final resultNumber = double.tryParse(uResult.value) ?? 0; + final mathOperationMethod = mathOperationMethods[uMathOperation.value]; final resultOfOperation = mathOperationMethod?.call( _numberMemorized ?? resultNumber, resultNumber, ); - result(_formatValue(resultOfOperation ?? 0)); + uResult.value = _formatValue(resultOfOperation ?? 0); } - void _resetResult() => result(_formatValue(0.0)); + void _resetResult() { + uResult.value = _formatValue(0.0); + } void _concatValue(int? value) { - final numConcatenated = double.tryParse("$result$value") ?? 0; - result(_formatValue(numConcatenated)); + final numConcatenated = double.tryParse("${uResult.value}$value") ?? 0; + uResult.value = _formatValue(numConcatenated); } String _formatValue(double value) { diff --git a/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/button.dart b/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/button.dart index c807f4e3..63a7c398 100644 --- a/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/button.dart +++ b/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/button.dart @@ -42,18 +42,23 @@ class Button extends StatelessWidget { onPressed: onPressed ?? () {}, style: ElevatedButton.styleFrom( backgroundColor: color ?? Colors.grey.shade700, + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), side: BorderSide( width: isSelected ? 4 : 1, color: Theme.of(context).scaffoldBackgroundColor, ), shape: const ContinuousRectangleBorder(side: BorderSide.none), ), - child: Text( - label, - style: Theme.of(context).textTheme.headlineSmall?.copyWith( - color: Colors.white, - fontSize: isSmall ? 16 : null, - ), + child: FittedBox( + child: Text( + label, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + color: Colors.white, + fontSize: isSmall ? 16 : null, + ), + ), ), ), ); diff --git a/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/calculator_action_button.dart b/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/calculator_action_button.dart index b025646b..53db474c 100644 --- a/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/calculator_action_button.dart +++ b/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/calculator_action_button.dart @@ -1,9 +1,8 @@ +import 'package:examples/examples/2_calculator/controllers/calculator_controller.dart'; +import 'package:examples/examples/2_calculator/widgets/button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/2_calculator/widgets/button.dart'; -import 'package:examples/examples/2_calculator/controllers/calculator_controller.dart'; - final actionLabel = { ActionCalculator.add: '+', ActionCalculator.clear: 'C', @@ -12,7 +11,7 @@ final actionLabel = { ActionCalculator.multiply: '×', ActionCalculator.point: '.', ActionCalculator.porcentage: '%', - ActionCalculator.sign: '+/–', + ActionCalculator.sign: 'ᐩ⁄-', ActionCalculator.subtract: '–', }; @@ -29,14 +28,13 @@ class CalculatorActionButton extends StatelessWidget { final calculatorController = context.use(); final isMathOperation = mathOperationMethods.keys.contains(action); final label = actionLabel[action] ?? 'N/A'; - void onPressed() => calculatorController.executeAction(action); if (isMathOperation) { return Expanded( child: RtSelector( - selector: (inst, $) { - return $(calculatorController.mathOperation).value == action; + selector: (inst, watch) { + return watch(calculatorController.uMathOperation).value == action; }, builder: (_, __, isSelected, ___) { return Button.primary( diff --git a/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/calculator_number_button.dart b/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/calculator_number_button.dart index eb9f020f..93bb656f 100644 --- a/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/calculator_number_button.dart +++ b/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/calculator_number_button.dart @@ -1,21 +1,21 @@ +import 'package:examples/examples/2_calculator/controllers/calculator_controller.dart'; +import 'package:examples/examples/2_calculator/widgets/button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/2_calculator/widgets/button.dart'; -import 'package:examples/examples/2_calculator/controllers/calculator_controller.dart'; - class CalculatorNumberButton extends StatelessWidget { - const CalculatorNumberButton({Key? key, required this.number}) + const CalculatorNumberButton({Key? key, required this.number, this.flex = 1}) : super(key: key); final int number; + final int flex; @override Widget build(BuildContext context) { final calculatorController = context.use(); return Expanded( - flex: number == 0 ? 2 : 1, + flex: flex, child: Button.tertiary( label: "$number", onPressed: () => calculatorController.executeAction( diff --git a/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/calculator_result.dart b/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/calculator_result.dart index d2fb4849..a4726e54 100644 --- a/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/calculator_result.dart +++ b/packages/flutter_reactter/example/lib/examples/2_calculator/widgets/calculator_result.dart @@ -1,27 +1,34 @@ +import 'package:examples/examples/2_calculator/controllers/calculator_controller.dart'; import 'package:flutter/material.dart'; import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/2_calculator/controllers/calculator_controller.dart'; - class CalculatorResult extends StatelessWidget { const CalculatorResult({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - final calculatorController = context.use(); - return Container( - padding: const EdgeInsets.all(10.0), alignment: Alignment.bottomRight, - child: RtWatcher((context, watch) { - return Text( - "${watch(calculatorController.result)}", - style: const TextStyle( - fontSize: 48, - fontWeight: FontWeight.w200, - ), - ); - }), + height: 75, + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: FittedBox( + fit: BoxFit.fitWidth, + child: RtWatcher((context, watch) { + final calculatorController = context.use(); + final result = watch(calculatorController.uResult).value; + + return Text( + result, + style: TextStyle( + color: Colors.grey.shade100, + fontSize: 48, + fontWeight: FontWeight.w200, + ), + ); + }), + ), ); } } diff --git a/packages/flutter_reactter/example/lib/examples/3_shopping_cart/controllers/cart_controller.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/controllers/cart_controller.dart new file mode 100644 index 00000000..8f41ba2c --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/controllers/cart_controller.dart @@ -0,0 +1,119 @@ +import 'package:examples/examples/3_shopping_cart/models/cart_item.dart'; +import 'package:examples/examples/3_shopping_cart/repositories/cart_repository.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +class CartController { + final _recentlyUpdatedCartItems = {}; + + final uCartRepository = UseDependency.create( + CartRepository.new, + debugLabel: 'uCartRepository', + ); + + late final uAsyncCart = Rt.lazyState(() { + assert(uCartRepository.instance != null); + + return UseAsyncState( + uCartRepository.instance!.getCart, + null, + debugLabel: 'uAsyncCart', + ); + }, this); + + late final uCartItems = Rt.lazyState(() { + return UseCompute( + () => uAsyncCart.value?.items.toList() ?? [], + [uAsyncCart.uValue], + debugLabel: 'uCartItems', + ); + }, this); + + late final uTotal = Rt.lazyState(() { + return UseCompute( + () => uAsyncCart.value?.total ?? 0, + [uAsyncCart.uValue], + debugLabel: 'uTotal', + ); + }, this); + + CartController() { + _loadCartItems(); + } + + Future incrementCartItem(CartItem cartItem) async { + final newCartItem = cartItem.copyWith(quantity: cartItem.quantity + 1); + await _updateCartItem(newCartItem); + } + + Future decrementCartItem(CartItem cartItem) async { + final newCartItem = cartItem.copyWith(quantity: cartItem.quantity - 1); + await _updateCartItem(newCartItem); + } + + Future removeCartItem(CartItem cartItem) async { + await _updateCartItem(cartItem.copyWith(quantity: 0)); + } + + Future checkout() async { + await uCartRepository.instance?.checkout(); + await _loadCartItems(); + } + + Future _loadCartItems() async { + uAsyncCart.cancel(); + await uAsyncCart.resolve(); + } + + bool _updateCartItemsState(CartItem cartItem) { + final index = uCartItems.value.indexOf(cartItem); + final product = cartItem.product; + + if (cartItem.quantity > product.stock) { + return false; + } + + uCartItems.value.remove(cartItem); + + if (cartItem.quantity > 0) { + final indexToInsert = index < 0 ? uCartItems.value.length : index; + uCartItems.value.insert(indexToInsert, cartItem); + } + + uCartItems.notify(); + + return true; + } + + Future _updateCartItem(CartItem cartItem) async { + uAsyncCart.cancel(); + + final isStateUpdated = _updateCartItemsState(cartItem); + + if (!isStateUpdated) return null; + + final isUpdating = _recentlyUpdatedCartItems.remove(cartItem); + _recentlyUpdatedCartItems.add(cartItem); + + if (isUpdating) return cartItem; + + final cartItemUpdated = await uCartRepository.instance?.updateCartItem( + cartItem, + ); + + final mostRecentCartItem = _recentlyUpdatedCartItems.lookup(cartItem); + final isStillUpdating = mostRecentCartItem != null && + cartItem.quantity != mostRecentCartItem.quantity; + + _recentlyUpdatedCartItems.remove(mostRecentCartItem); + + if (isStillUpdating) { + return await _updateCartItem(mostRecentCartItem); + } + + if (_recentlyUpdatedCartItems.isEmpty) { + await _loadCartItems(); + } + + return cartItemUpdated; + } +} diff --git a/packages/flutter_reactter/example/lib/examples/3_shopping_cart/controllers/product_controller.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/controllers/product_controller.dart new file mode 100644 index 00000000..7177f79d --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/controllers/product_controller.dart @@ -0,0 +1,40 @@ +import 'package:examples/examples/3_shopping_cart/controllers/cart_controller.dart'; +import 'package:examples/examples/3_shopping_cart/models/cart_item.dart'; +import 'package:examples/examples/3_shopping_cart/models/product.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +class ProductController { + final Product product; + + final uCartController = UseDependency.create(CartController.new); + + late final uQuantity = Rt.lazyState(() { + assert(uCartController.instance != null); + + return UseCompute( + () { + final cartItem = uCartController.instance!.uCartItems.value.firstWhere( + (item) => item.product == product, + orElse: () => CartItem(product, 0), + ); + + return cartItem.quantity; + }, + [uCartController.instance!.uCartItems], + ); + }, this); + + ProductController(this.product); + + Future addToCart() async { + await uCartController.instance?.incrementCartItem( + CartItem(product, uQuantity.value), + ); + } + + Future removeFromCart() async { + await uCartController.instance?.decrementCartItem( + CartItem(product, uQuantity.value), + ); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/3_shopping_cart/controllers/products_controller.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/controllers/products_controller.dart new file mode 100644 index 00000000..e3910cc1 --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/controllers/products_controller.dart @@ -0,0 +1,24 @@ +import 'package:examples/examples/3_shopping_cart/models/product.dart'; +import 'package:examples/examples/3_shopping_cart/repositories/product_repository.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +class ProductsController { + final uProductRepository = UseDependency.create(ProductRepository.new); + + late final uAsyncProducts = Rt.lazyState( + () { + assert(uProductRepository.instance != null); + + return UseAsyncState>( + uProductRepository.instance!.getAllProducts, + [], + debugLabel: 'uAsyncProducts', + ); + }, + this, + ); + + ProductsController() { + uAsyncProducts.resolve(); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/3_shopping_cart/data/data_source.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/data/data_source.dart new file mode 100644 index 00000000..220b748e --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/data/data_source.dart @@ -0,0 +1,118 @@ +import 'dart:math'; + +import 'package:examples/examples/3_shopping_cart/models/cart.dart'; +import 'package:examples/examples/3_shopping_cart/models/cart_item.dart'; +import 'package:examples/examples/3_shopping_cart/models/product.dart'; +import 'package:examples/examples/3_shopping_cart/utils/lorem_gen.dart'; + +const delay = Duration(milliseconds: 500); + +abstract class IDataSource { + Future> fetchAllProducts(); + Future fetchCart(); + Future fetchCartItemByProduct(Product product); + Future putCartItem(CartItem cartItem); + Future deleteCartItem(CartItem cartItem); + Future checkout(); +} + +class DataSource implements IDataSource { + static final _random = Random(); + + static final dataSource = DataSource._(); + + Cart _cart = Cart(); + final _products = _generateProducts(); + + DataSource._(); + + factory DataSource() { + return dataSource; + } + + @override + Future> fetchAllProducts() async { + await Future.delayed(delay); + return _products; + } + + @override + Future fetchCart() async { + await Future.delayed(delay); + return _cart; + } + + @override + Future fetchCartItemByProduct(Product product) async { + await Future.delayed(delay); + return _cart.items.lookup(CartItem(product, 0)); + } + + @override + Future putCartItem(CartItem cartItem) async { + await Future.delayed(delay); + + final newItems = { + ..._cart.items.map((item) => item == cartItem ? cartItem : item), + cartItem, + }; + final cartItemPrev = _cart.items.lookup(cartItem); + final cartItemPrevTotal = cartItemPrev != null + ? cartItemPrev.product.price * cartItemPrev.quantity + : 0; + final cartItemTotal = cartItem.product.price * cartItem.quantity; + + _cart = _cart.copyWith( + items: newItems, + total: _cart.total - cartItemPrevTotal + cartItemTotal, + ); + + return cartItem; + } + + @override + Future deleteCartItem(CartItem cartItem) async { + await Future.delayed(delay); + + final cartItemPrev = _cart.items.lookup(cartItem); + final cartItemPrevTotal = cartItemPrev != null + ? cartItemPrev.product.price * cartItemPrev.quantity + : 0; + + _cart.items.remove(cartItem); + _cart = _cart.copyWith( + items: _cart.items, + total: _cart.total - cartItemPrevTotal, + ); + } + + @override + Future checkout() async { + await Future.delayed(delay); + + for (final item in _cart.items) { + final product = item.product; + final newStock = product.stock - item.quantity; + final index = _products.indexOf(product); + + _products.remove(product); + _products.insert(index, product.copyWith(stock: newStock)); + } + + _cart = Cart(); + } + + static List _generateProducts() { + return List.generate( + 26, + (index) { + return Product( + id: index, + name: generateLoremText(_random.nextInt(8) + 3), + price: (_random.nextInt(3000) + 100) + _random.nextDouble(), + stock: _random.nextInt(20), + ); + }, + ); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/3_shopping_cart/models/cart.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/models/cart.dart new file mode 100644 index 00000000..c7fc150d --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/models/cart.dart @@ -0,0 +1,20 @@ +import 'package:examples/examples/3_shopping_cart/models/cart_item.dart'; + +class Cart { + final Set items; + final double total; + + Cart({ + this.items = const {}, + this.total = 0.0, + }); + + Cart copyWith({ + Set? items, + double? total, + }) => + Cart( + items: items ?? this.items, + total: total ?? this.total, + ); +} diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/models/cart_item.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/models/cart_item.dart similarity index 52% rename from packages/flutter_reactter/example/lib/examples/4_shopping_cart/models/cart_item.dart rename to packages/flutter_reactter/example/lib/examples/3_shopping_cart/models/cart_item.dart index db3f4476..019fecbf 100644 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/models/cart_item.dart +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/models/cart_item.dart @@ -1,4 +1,4 @@ -import 'package:examples/examples/4_shopping_cart/models/product.dart'; +import 'package:examples/examples/3_shopping_cart/models/product.dart'; class CartItem { final Product product; @@ -6,6 +6,13 @@ class CartItem { CartItem(this.product, this.quantity); + @override + bool operator ==(Object other) => + identical(this, other) || other is CartItem && product == other.product; + + @override + int get hashCode => Object.hashAll([product]); + CartItem copyWith({ Product? product, int? quantity, diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/models/product.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/models/product.dart similarity index 82% rename from packages/flutter_reactter/example/lib/examples/4_shopping_cart/models/product.dart rename to packages/flutter_reactter/example/lib/examples/3_shopping_cart/models/product.dart index c6a21f3f..5759e261 100644 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/models/product.dart +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/models/product.dart @@ -1,25 +1,26 @@ class Product { + final int id; final String name; - final String image; final double price; final int stock; Product({ + required this.id, required this.name, - required this.image, required this.price, required this.stock, }); Product copyWith({ + int? id, String? name, String? image, double? price, int? stock, }) => Product( + id: id ?? this.id, name: name ?? this.name, - image: image ?? this.image, price: price ?? this.price, stock: stock ?? this.stock, ); diff --git a/packages/flutter_reactter/example/lib/examples/3_shopping_cart/repositories/cart_repository.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/repositories/cart_repository.dart new file mode 100644 index 00000000..7016c18e --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/repositories/cart_repository.dart @@ -0,0 +1,37 @@ +import 'package:examples/examples/3_shopping_cart/data/data_source.dart'; +import 'package:examples/examples/3_shopping_cart/models/cart.dart'; +import 'package:examples/examples/3_shopping_cart/models/cart_item.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +abstract class ICartRepository { + Future getCart(); + Future updateCartItem(CartItem cartItem); + Future checkout(); +} + +class CartRepository implements ICartRepository { + final _uDataSource = UseDependency.create(DataSource.new); + + DataSource get _dataSource { + return _uDataSource.instance ?? (throw Exception('DataSource not found')); + } + + @override + Future getCart() async => _dataSource.fetchCart(); + + @override + Future updateCartItem(CartItem cartItem) async { + if (cartItem.quantity <= 0) { + await _dataSource.deleteCartItem(cartItem); + + return null; + } + + return await _dataSource.putCartItem(cartItem); + } + + @override + Future checkout() async { + await _dataSource.checkout(); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/3_shopping_cart/repositories/product_repository.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/repositories/product_repository.dart new file mode 100644 index 00000000..9cb7831b --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/repositories/product_repository.dart @@ -0,0 +1,20 @@ +import 'package:examples/examples/3_shopping_cart/data/data_source.dart'; +import 'package:examples/examples/3_shopping_cart/models/product.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +abstract class IProductRepository { + Future> getAllProducts(); +} + +class ProductRepository implements IProductRepository { + final _uDataSource = UseDependency.create(DataSource.new); + + DataSource get _dataSource { + return _uDataSource.instance ?? (throw Exception('DataSource not found')); + } + + @override + Future> getAllProducts() async { + return _dataSource.fetchAllProducts(); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/shopping_cart_page.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/shopping_cart_page.dart similarity index 73% rename from packages/flutter_reactter/example/lib/examples/4_shopping_cart/shopping_cart_page.dart rename to packages/flutter_reactter/example/lib/examples/3_shopping_cart/shopping_cart_page.dart index 1d7b7786..9a419834 100644 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/shopping_cart_page.dart +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/shopping_cart_page.dart @@ -1,10 +1,9 @@ +import 'package:examples/examples/3_shopping_cart/controllers/cart_controller.dart'; +import 'package:examples/examples/3_shopping_cart/controllers/products_controller.dart'; +import 'package:examples/examples/3_shopping_cart/views/products_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/4_shopping_cart/controllers/cart_controller.dart'; -import 'package:examples/examples/4_shopping_cart/controllers/products_controller.dart'; -import 'package:examples/examples/4_shopping_cart/views/products_view.dart'; - class ShoppingCartPage extends StatelessWidget { const ShoppingCartPage({Key? key}) : super(key: key); diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/utils/format_currency.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/utils/format_currency.dart similarity index 100% rename from packages/flutter_reactter/example/lib/examples/4_shopping_cart/utils/format_currency.dart rename to packages/flutter_reactter/example/lib/examples/3_shopping_cart/utils/format_currency.dart diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/utils/lorem_gen.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/utils/lorem_gen.dart similarity index 100% rename from packages/flutter_reactter/example/lib/examples/4_shopping_cart/utils/lorem_gen.dart rename to packages/flutter_reactter/example/lib/examples/3_shopping_cart/utils/lorem_gen.dart diff --git a/packages/flutter_reactter/example/lib/examples/3_shopping_cart/views/cart_view.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/views/cart_view.dart new file mode 100644 index 00000000..0b37617a --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/views/cart_view.dart @@ -0,0 +1,127 @@ +// ignore_for_file: avoid_renaming_method_parameters + +import 'package:examples/examples/3_shopping_cart/controllers/cart_controller.dart'; +import 'package:examples/examples/3_shopping_cart/utils/format_currency.dart'; +import 'package:examples/examples/3_shopping_cart/widgets/cart_item_card.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +class CartView extends RtComponent { + const CartView({ + Key? key, + this.onCheckout, + }) : super(key: key); + + final VoidCallback? onCheckout; + + @override + get builder => CartController.new; + + @override + Widget render(BuildContext context, CartController cartController) { + return Scaffold( + appBar: AppBar( + title: const Text('Shopping Cart'), + ), + body: RtSelector( + selector: (inst, watch) => watch(inst.uCartItems).value.length, + builder: (_, __, itemCount, ____) { + if (itemCount == 0) { + return Center( + child: Text( + "Your cart is empty!", + style: Theme.of(context).textTheme.titleMedium, + ), + ); + } + + return ListView.builder( + itemCount: itemCount, + cacheExtent: 70, + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 4, + ), + itemBuilder: (context, index) { + return RtSelector( + selector: (inst, watch) { + try { + return watch(inst.uCartItems) + .value + .elementAt(index) + .quantity; + } catch (e) { + return 0; + } + }, + builder: (_, cartController, __, ___) { + final item = cartController.uCartItems.value[index]; + + return SizedBox( + height: 80, + child: CartItemCard( + key: ObjectKey(item), + cartItem: item, + onIncrement: cartController.incrementCartItem, + onDecrement: cartController.decrementCartItem, + onRemove: cartController.removeCartItem, + ), + ); + }, + ); + }, + ); + }, + ), + bottomNavigationBar: RtWatcher( + (context, watch) { + final total = watch(cartController.uTotal).value; + final isLoading = watch(cartController.uAsyncCart.uIsLoading).value; + + return Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: isLoading || total == 0 + ? null + : () { + cartController.checkout(); + onCheckout?.call(); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Builder(builder: (context) { + return Row( + children: [ + Text( + "Checkout", + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith(color: Colors.white), + ), + const Spacer(), + if (isLoading) + const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(), + ) + else + Text( + formatCurrency(total), + style: Theme.of(context) + .textTheme + .titleLarge! + .copyWith(color: Colors.grey.shade200), + ), + ], + ); + }), + ), + ), + ); + }, + ), + ); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/3_shopping_cart/views/products_view.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/views/products_view.dart new file mode 100644 index 00000000..fd2d200b --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/views/products_view.dart @@ -0,0 +1,83 @@ +// ignore_for_file: avoid_renaming_method_parameters + +import 'package:examples/examples/3_shopping_cart/controllers/products_controller.dart'; +import 'package:examples/examples/3_shopping_cart/views/cart_view.dart'; +import 'package:examples/examples/3_shopping_cart/widgets/cart_action.dart'; +import 'package:examples/examples/3_shopping_cart/widgets/product_card.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +class ProductsView extends RtComponent { + const ProductsView({Key? key}) : super(key: key); + + @override + get builder => ProductsController.new; + + @override + Widget render(BuildContext context, ProductsController productsController) { + return Scaffold( + appBar: AppBar( + title: const Text('Shopping Cart'), + actions: [ + RtConsumer( + builder: (_, __, ___) { + return CartAction( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => CartView( + onCheckout: productsController.uAsyncProducts.resolve, + ), + ), + ); + }, + ); + }, + ), + ], + ), + body: RtConsumer( + listenStates: (inst) => [inst.uAsyncProducts], + builder: (_, __, ___) { + return productsController.uAsyncProducts.when( + idle: (_) { + return const Center( + child: CircularProgressIndicator(), + ); + }, + loading: (_) { + return const Center( + child: CircularProgressIndicator(), + ); + }, + done: (products) { + return LayoutBuilder( + builder: (context, constraints) { + final crossAxisCount = (constraints.maxWidth / 140).floor(); + + return GridView.count( + padding: const EdgeInsets.all(8), + crossAxisCount: crossAxisCount.clamp(1, crossAxisCount), + childAspectRatio: 9 / 16, + children: [ + for (final product in products) + ProductCard( + key: ObjectKey(product), + product: product, + ), + ], + ); + }, + ); + }, + error: (error) { + return Center( + child: Text(error.toString()), + ); + }, + ) as Widget; + }, + ), + ); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/cart_action.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/cart_action.dart similarity index 86% rename from packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/cart_action.dart rename to packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/cart_action.dart index 3e5a58a7..e53210c9 100644 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/cart_action.dart +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/cart_action.dart @@ -1,8 +1,7 @@ +import 'package:examples/examples/3_shopping_cart/controllers/cart_controller.dart'; import 'package:flutter/material.dart'; import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/4_shopping_cart/controllers/cart_controller.dart'; - class CartAction extends StatelessWidget { const CartAction({ Key? key, @@ -28,7 +27,9 @@ class CartAction extends StatelessWidget { backgroundColor: Colors.amber, radius: 8, child: RtSelector( - selector: (inst, $) => $(inst.uCartItems).value.length, + selector: (inst, watch) { + return watch(inst.uCartItems).value.length; + }, builder: (_, cartController, count, __) { return Text( "$count", diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/cart_item_card.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/cart_item_card.dart similarity index 76% rename from packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/cart_item_card.dart rename to packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/cart_item_card.dart index d29dd957..3d5c8843 100644 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/cart_item_card.dart +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/cart_item_card.dart @@ -1,23 +1,24 @@ import 'package:flutter/material.dart'; -import 'package:examples/examples/4_shopping_cart/models/cart_item.dart'; -import 'package:examples/examples/4_shopping_cart/models/product.dart'; -import 'package:examples/examples/4_shopping_cart/utils/format_currency.dart'; -import 'package:examples/examples/4_shopping_cart/widgets/custom_icon_button.dart'; -import 'package:examples/examples/4_shopping_cart/widgets/quantity.dart'; +import 'package:examples/examples/3_shopping_cart/models/cart_item.dart'; +import 'package:examples/examples/3_shopping_cart/models/product.dart'; +import 'package:examples/examples/3_shopping_cart/utils/format_currency.dart'; +import 'package:examples/examples/3_shopping_cart/widgets/custom_icon_button.dart'; +import 'package:examples/examples/3_shopping_cart/widgets/product_cover.dart'; +import 'package:examples/examples/3_shopping_cart/widgets/quantity.dart'; class CartItemCard extends StatelessWidget { final CartItem cartItem; - final void Function(Product product)? onAdd; - final void Function(Product product)? onRemove; - final void Function(Product product)? onDelete; + final void Function(CartItem cartItem)? onIncrement; + final void Function(CartItem cartItem)? onDecrement; + final void Function(CartItem cartItem)? onRemove; const CartItemCard({ Key? key, required this.cartItem, - this.onAdd, + this.onIncrement, + this.onDecrement, this.onRemove, - this.onDelete, }) : super(key: key); Product get product => cartItem.product; @@ -33,7 +34,7 @@ class CartItemCard extends StatelessWidget { children: [ AspectRatio( aspectRatio: 1, - child: Image.network(product.image), + child: ProductCover(index: product.id), ), Expanded( child: Padding( @@ -65,14 +66,16 @@ class CartItemCard extends StatelessWidget { Quantity( quantity: quantity, maxQuantity: product.stock, - onRemove: () => onRemove?.call(product), - onAdd: () => onAdd?.call(product), + onDecrement: quantity > 1 + ? () => onDecrement?.call(cartItem) + : null, + onIncrement: () => onIncrement?.call(cartItem), ), const SizedBox(width: 8), CustomIconButton( icon: Icons.delete, color: Colors.red, - onPressed: () => onDelete?.call(product), + onPressed: () => onRemove?.call(cartItem), ), ], ), diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/custom_icon_button.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/custom_icon_button.dart similarity index 100% rename from packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/custom_icon_button.dart rename to packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/custom_icon_button.dart diff --git a/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/product_buttons.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/product_buttons.dart new file mode 100644 index 00000000..02d192c4 --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/product_buttons.dart @@ -0,0 +1,40 @@ +import 'package:examples/examples/3_shopping_cart/controllers/product_controller.dart'; +import 'package:examples/examples/3_shopping_cart/models/product.dart'; +import 'package:examples/examples/3_shopping_cart/widgets/custom_icon_button.dart'; +import 'package:examples/examples/3_shopping_cart/widgets/quantity.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +class ProductButtons extends StatelessWidget { + const ProductButtons({ + Key? key, + required this.product, + }) : super(key: key); + + final Product product; + + @override + Widget build(BuildContext context) { + return RtWatcher((context, watch) { + final productController = context.use("${product.id}"); + final quantity = watch(productController.uQuantity).value; + + if (quantity == 0) { + return CustomIconButton( + icon: Icons.add, + color: Colors.green, + onPressed: quantity < product.stock + ? () => productController.addToCart() + : null, + ); + } + + return Quantity( + quantity: quantity, + maxQuantity: product.stock, + onDecrement: () => productController.removeFromCart(), + onIncrement: () => productController.addToCart(), + ); + }); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/product_card.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/product_card.dart new file mode 100644 index 00000000..707fd45d --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/product_card.dart @@ -0,0 +1,103 @@ +import 'package:examples/examples/3_shopping_cart/controllers/product_controller.dart'; +import 'package:examples/examples/3_shopping_cart/models/product.dart'; +import 'package:examples/examples/3_shopping_cart/utils/format_currency.dart'; +import 'package:examples/examples/3_shopping_cart/widgets/product_buttons.dart'; +import 'package:examples/examples/3_shopping_cart/widgets/product_cover.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +class ProductCard extends StatelessWidget { + final Product product; + + const ProductCard({ + Key? key, + required this.product, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return RtProvider( + () => ProductController(product), + id: "${product.id}", + builder: (context, controller, child) { + return Card( + clipBehavior: Clip.hardEdge, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: 1, + child: Container( + color: Colors.black45, + child: Stack( + children: [ + SizedBox.expand( + child: ProductCover(index: product.id), + ), + Positioned( + top: 4, + right: 4, + child: ProductButtons( + product: product, + ), + ) + ], + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + formatCurrency(product.price), + style: Theme.of(context).textTheme.titleMedium, + ), + Text( + product.name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const Spacer(), + _buildStock(context), + ], + ), + ), + ), + ], + ), + ); + }, + ); + } + + Widget _buildStock(BuildContext context) { + if (product.stock == 0) { + return Text( + "Sold out", + style: Theme.of(context).textTheme.labelSmall, + ); + } + + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + 'In stock: ', + style: Theme.of(context).textTheme.labelSmall, + ), + Text( + "${product.stock}", + style: Theme.of(context).textTheme.labelSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + ], + ); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/product_cover.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/product_cover.dart new file mode 100644 index 00000000..eb25b23a --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/product_cover.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +const kCoverColors = [...Colors.primaries, ...Colors.accents]; + +class ProductCover extends StatelessWidget { + final int index; + + const ProductCover({ + Key? key, + required this.index, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + color: kCoverColors[index % kCoverColors.length], + child: FittedBox( + child: Text( + String.fromCharCode(index + 65), + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Colors.white, + ), + ), + ), + ); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/quantity.dart b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/quantity.dart similarity index 52% rename from packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/quantity.dart rename to packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/quantity.dart index 890bd55c..1155c7af 100644 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/quantity.dart +++ b/packages/flutter_reactter/example/lib/examples/3_shopping_cart/widgets/quantity.dart @@ -1,50 +1,46 @@ +import 'package:examples/examples/3_shopping_cart/widgets/custom_icon_button.dart'; import 'package:flutter/material.dart'; -import 'package:examples/examples/4_shopping_cart/widgets/custom_icon_button.dart'; - class Quantity extends StatelessWidget { const Quantity({ Key? key, required this.quantity, required this.maxQuantity, - this.onAdd, - this.onRemove, + this.onIncrement, + this.onDecrement, }) : super(key: key); final int quantity; final int maxQuantity; - final VoidCallback? onAdd; - final VoidCallback? onRemove; + final VoidCallback? onIncrement; + final VoidCallback? onDecrement; @override Widget build(BuildContext context) { return Container( - decoration: const ShapeDecoration( - color: Colors.black38, - shape: StadiumBorder(), + decoration: ShapeDecoration( + color: Theme.of(context).highlightColor, + shape: const StadiumBorder(), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ CustomIconButton( - icon: quantity == 1 ? Icons.delete : Icons.remove, + icon: quantity == 1 && onDecrement != null + ? Icons.delete + : Icons.remove, color: Colors.red, - onPressed: quantity > 0 ? onRemove : null, + onPressed: quantity > 0 ? onDecrement : null, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 8), - child: Text( - "$quantity", - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(color: Colors.white), - ), + child: Text("$quantity", + style: Theme.of(context).textTheme.titleMedium), ), CustomIconButton( icon: Icons.add, color: Colors.green, - onPressed: quantity < maxQuantity ? onAdd : null, + onPressed: quantity < maxQuantity ? onIncrement : null, ), ], ), diff --git a/packages/flutter_reactter/example/lib/examples/3_tree/tree_node.dart b/packages/flutter_reactter/example/lib/examples/3_tree/tree_node.dart deleted file mode 100644 index 9a96658d..00000000 --- a/packages/flutter_reactter/example/lib/examples/3_tree/tree_node.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter_reactter/flutter_reactter.dart'; - -class TreeNode { - final TreeNode? parent; - - final uIsExpanded = UseState(false); - final uChildren = UseState([]); - final uCount = UseState(0); - final uChildrenTotal = UseState(0); - late final uTotal = Rt.lazyState( - () => UseCompute( - () => uCount.value + uChildrenTotal.value, - [uCount, uChildrenTotal], - ), - this, - ); - - String get path => "${parent?.path ?? ''} > $hashCode"; - - TreeNode([this.parent]) { - if (parent != null) { - UseEffect( - parent!._calculateChildrenTotal, - [uTotal], - ); - } - - UseEffect(() { - uIsExpanded.value = true; - _calculateChildrenTotal(); - }, [uChildren]); - } - - void increase() => uCount.value++; - - void decrease() => uCount.value--; - - void toggleExpansion() => uIsExpanded.value = !uIsExpanded.value; - - void addChild() { - uChildren.update( - () => uChildren.value.add(TreeNode(this)), - ); - } - - void removeChild(TreeNode child) { - uChildren.update( - () => uChildren.value.remove(child), - ); - } - - void removeFromParent() => parent?.removeChild(this); - - void _calculateChildrenTotal() { - uChildrenTotal.value = uChildren.value.fold( - 0, - (acc, child) => acc + child.uTotal.value, - ); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/3_tree/tree_page.dart b/packages/flutter_reactter/example/lib/examples/3_tree/tree_page.dart deleted file mode 100644 index 384b932c..00000000 --- a/packages/flutter_reactter/example/lib/examples/3_tree/tree_page.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_reactter/flutter_reactter.dart'; - -import 'package:examples/examples/3_tree/tree_node.dart'; -import 'package:examples/examples/3_tree/widgets/tree_item.dart'; - -class TreePage extends StatelessWidget { - const TreePage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return RtProvider( - TreeNode.new, - builder: (context, treeContext, _) { - return Scaffold( - appBar: AppBar( - title: const Text("Tree widget"), - ), - body: SingleChildScrollView( - child: TreeItem(treeNode: treeContext), - ), - ); - }, - ); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/3_tree/widgets/tree_item.dart b/packages/flutter_reactter/example/lib/examples/3_tree/widgets/tree_item.dart deleted file mode 100644 index 01ad4ffa..00000000 --- a/packages/flutter_reactter/example/lib/examples/3_tree/widgets/tree_item.dart +++ /dev/null @@ -1,252 +0,0 @@ -// ignore_for_file: avoid_renaming_method_parameters - -import 'dart:async'; -import 'dart:math'; -import 'package:flutter/material.dart'; -import 'package:flutter_reactter/flutter_reactter.dart'; - -import 'package:examples/examples/3_tree/tree_node.dart'; -import 'package:examples/examples/3_tree/widgets/custom_icon_button.dart'; -import 'package:examples/examples/3_tree/widgets/tree_items.dart'; - -class TreeItem extends RtComponent { - const TreeItem({ - Key? key, - required this.treeNode, - this.isTreeNodeLast = false, - }) : super(key: key); - - final TreeNode treeNode; - final bool isTreeNodeLast; - - @override - String get id => "${treeNode.hashCode}"; - - @override - get builder => () => treeNode; - - @override - Widget render(context, treeContext) { - return InkWell( - onTap: () => _openDialog(context), - child: Stack( - children: [ - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(width: 8), - _buildExpandButton(), - CustomIconButton( - icon: const Icon(Icons.add_circle), - onPressed: treeContext.addChild, - ), - if (treeContext.parent != null) - CustomIconButton( - icon: Transform.rotate( - angle: -pi / 4, - child: const Icon(Icons.add_circle), - ), - onPressed: treeContext.removeFromParent, - ), - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: Text( - id, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodySmall, - ), - ), - ), - const SizedBox(width: 8), - _buildCountLabel(), - CustomIconButton( - icon: const Icon(Icons.indeterminate_check_box_rounded), - onPressed: treeContext.decrease, - ), - CustomIconButton( - icon: const Icon(Icons.add_box), - onPressed: treeContext.increase, - ), - ], - ), - _buildTreeItems(), - ], - ), - _buildTreeLine(treeContext), - ], - ), - ); - } - - Future _openDialog(BuildContext context) { - final treeNode = context.use(id); - - return showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text("About item[id='$id']"), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text("path:"), - const SizedBox(width: 8), - Expanded( - child: Text( - treeNode.path, - style: Theme.of(context).textTheme.bodySmall, - maxLines: 3, - ), - ), - ], - ), - Row( - children: [ - const Text("children:"), - const SizedBox(width: 8), - Text( - "${treeNode.uChildren.value.length}", - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - Row( - children: [ - const Text("count:"), - const SizedBox(width: 8), - Text( - "${treeNode.uCount.value}", - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - Row( - children: [ - const Text("childrenTotal:"), - const SizedBox(width: 8), - Text( - "${treeNode.uChildrenTotal.value}", - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - Row( - children: [ - const Text("total(count + childrenTotal):"), - const SizedBox(width: 8), - Text( - "${treeNode.uTotal.value}", - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - ], - ), - ); - }, - ); - } - - Widget _buildExpandButton() { - return Builder( - builder: (context) { - final treeNode = context.watchId( - id, - (inst) => [inst.uIsExpanded, inst.uChildren], - ); - - if (treeNode.uChildren.value.isEmpty) { - return const SizedBox(width: 36); - } - - return CustomIconButton( - icon: Transform.rotate( - angle: treeNode.uIsExpanded.value ? 0 : -pi / 2, - child: const Icon(Icons.expand_circle_down_rounded), - ), - onPressed: treeNode.toggleExpansion, - ); - }, - ); - } - - Widget _buildCountLabel() { - return Builder( - builder: (context) { - final treeNode = context.watchId( - id, - (inst) => [inst.uCount, inst.uTotal], - ); - - return Text( - "${treeNode.uCount.value} (${treeNode.uTotal.value})", - ); - }, - ); - } - - Widget _buildTreeItems() { - return Builder( - builder: (context) { - final treeNode = context.watchId( - id, - (inst) => [ - inst.uIsExpanded, - inst.uChildren, - ], - ); - - return TreeItems( - children: treeNode.uChildren.value, - expanded: treeNode.uIsExpanded.value, - ); - }, - ); - } - - Widget _buildTreeLine(TreeNode treeContext) { - return Positioned( - top: 0, - bottom: 0, - child: Align( - alignment: Alignment.topLeft, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: treeContext.parent == null || isTreeNodeLast ? 19 : null, - child: const VerticalDivider( - width: 2, - thickness: 2, - ), - ), - Builder( - builder: (context) { - final treeNode = context.watchId( - id, - (inst) => [inst.uChildren], - ); - - return SizedBox( - width: treeNode.uChildren.value.isEmpty ? 42 : 6, - height: 36, - child: const Divider( - height: 2, - thickness: 2, - ), - ); - }, - ), - ], - ), - ), - ); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/3_tree/widgets/tree_items.dart b/packages/flutter_reactter/example/lib/examples/3_tree/widgets/tree_items.dart deleted file mode 100644 index c5087751..00000000 --- a/packages/flutter_reactter/example/lib/examples/3_tree/widgets/tree_items.dart +++ /dev/null @@ -1,131 +0,0 @@ -// ignore_for_file: avoid_renaming_method_parameters - -import 'package:flutter/material.dart'; - -import 'package:examples/examples/3_tree/tree_node.dart'; -import 'package:examples/examples/3_tree/widgets/tree_item.dart'; - -class TreeItems extends StatefulWidget { - const TreeItems({ - super.key, - required this.children, - required this.expanded, - }); - - final List children; - final bool expanded; - - @override - State createState() => _TreeItemsState(); -} - -class _TreeItemsState extends State with TickerProviderStateMixin { - late AnimationController _rotationController; - - bool _hide = false; - - @override - void initState() { - super.initState(); - - _rotationController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - ) - ..drive(CurveTween(curve: Curves.easeOut)) - ..addStatusListener((status) { - if (status == AnimationStatus.dismissed) { - return setState(() { - _hide = true; - }); - } - - if (_hide) { - setState(() { - _hide = false; - }); - } - }); - - if (widget.expanded) { - _rotationController.forward(); - } - } - - @override - void didUpdateWidget(TreeItems oldWidget) { - super.didUpdateWidget(oldWidget); - - if (widget.expanded) { - _rotationController.forward(); - } else { - _rotationController.reverse(); - } - } - - @override - Widget build(BuildContext context) { - // Remove instance build - if (_hide) return const SizedBox(); - - final treeItems = _generateTreeItems(); - - return SizeTransition( - sizeFactor: _rotationController, - axisAlignment: -1, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(width: 24), - Expanded( - child: Column( - children: treeItems, - ), - ), - ], - ), - ); - } - - void toggleExpansion() { - if (widget.expanded) { - _rotationController.forward(); - } else { - _rotationController.reverse(); - } - - setState(() {}); - } - - @override - void dispose() { - _rotationController.dispose(); - super.dispose(); - } - - List _generateTreeItems() { - final treeNodes = widget.children; - final treeNodeLast = treeNodes.isNotEmpty ? treeNodes.last : null; - - return [ - for (final treeNode in treeNodes) - // Fix StackOverflowError when building too many TreeItem - if (treeNodes.indexOf(treeNode) % 2 == 0) - LayoutBuilder( - builder: (_, __) { - return TreeItem( - key: ObjectKey(treeNode), - treeNode: treeNode, - isTreeNodeLast: treeNode == treeNodeLast, - ); - }, - ) - else - TreeItem( - key: ObjectKey(treeNode), - treeNode: treeNode, - isTreeNodeLast: treeNode == treeNodeLast, - ), - ]; - } -} diff --git a/packages/flutter_reactter/example/lib/examples/4_github_search/controllers/github_search_controller.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/controllers/github_search_controller.dart new file mode 100644 index 00000000..595c1a76 --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/4_github_search/controllers/github_search_controller.dart @@ -0,0 +1,44 @@ +import 'package:flutter_reactter/flutter_reactter.dart'; + +import 'package:examples/examples/4_github_search/repositories/github_repository.dart'; + +class GithubSearchController { + String _query = ''; + String get query => _query; + + final uGithubRepository = UseDependency.create(GithubRepository.new); + + GithubRepository get githubRepository => + uGithubRepository.instance ?? + (throw Exception('GithubRepository not found')); + + late final uEntity = Rt.lazyState(() { + final getEntityMemo = Memo.inline, String>( + _getEntity, + const MultiMemoInterceptor([ + MemoSafeAsyncInterceptor(), + MemoTemporaryCacheInterceptor(Duration(seconds: 30)), + ]), + ); + + return UseAsyncState.withArg(getEntityMemo, null); + }, this); + + Future _getEntity(String query) async { + final queryPath = query.split("/"); + + if (queryPath.length > 1) { + final owner = queryPath.first; + final repo = queryPath.last; + + return await githubRepository.getRepository(owner, repo); + } + + return await githubRepository.getUser(query); + } + + void onSearch(String query) { + _query = query; + uEntity.resolve(query); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/4_github_search/controllers/search_input_controller.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/controllers/search_input_controller.dart new file mode 100644 index 00000000..f0270573 --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/4_github_search/controllers/search_input_controller.dart @@ -0,0 +1,29 @@ +import 'package:flutter/widgets.dart'; + +class SearchInputController { + final void Function(String query) onSearch; + + SearchInputController({required this.onSearch}); + + final searchKey = GlobalKey>(); + final focusNode = FocusNode(); + + String? validator(String? value) { + if ((value?.isEmpty ?? true)) { + return "Can't be empty"; + } + + return null; + } + + void onFieldSubmitted(String query) { + if (!(searchKey.currentState?.validate() ?? false)) return; + + onSearch.call(query); + } + + void onButtonPressed() { + final query = searchKey.currentState?.value ?? ''; + onFieldSubmitted(query); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/4_github_search/github_search_page.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/github_search_page.dart new file mode 100644 index 00000000..bbf42015 --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/4_github_search/github_search_page.dart @@ -0,0 +1,43 @@ +// ignore_for_file: undefined_hidden_name + +import 'package:examples/examples/4_github_search/widgets/info_card.dart'; +import 'package:flutter/material.dart' hide SearchBar; +import 'package:flutter_reactter/flutter_reactter.dart'; + +import 'package:examples/examples/4_github_search/controllers/github_search_controller.dart'; +import 'package:examples/examples/4_github_search/widgets/search_bar.dart'; + +class GithubSearchPage extends StatelessWidget { + const GithubSearchPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return RtProvider( + GithubSearchController.new, + builder: (context, githubSearchController, child) { + return Scaffold( + appBar: AppBar( + title: const Text("Github search"), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(40), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: SearchBar( + onSearch: githubSearchController.onSearch, + ), + ), + ), + ), + body: const SingleChildScrollView( + padding: EdgeInsets.all(8), + child: Center( + child: FittedBox( + child: InfoCard(), + ), + ), + ), + ); + }, + ); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/5_api/models/repository.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/models/repository.dart similarity index 92% rename from packages/flutter_reactter/example/lib/examples/5_api/models/repository.dart rename to packages/flutter_reactter/example/lib/examples/4_github_search/models/repository.dart index 41a3c4bf..e2273663 100644 --- a/packages/flutter_reactter/example/lib/examples/5_api/models/repository.dart +++ b/packages/flutter_reactter/example/lib/examples/4_github_search/models/repository.dart @@ -1,4 +1,4 @@ -import 'package:examples/examples/5_api/models/user.dart'; +import 'package:examples/examples/4_github_search/models/user.dart'; class Repository { final int id; diff --git a/packages/flutter_reactter/example/lib/examples/5_api/models/user.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/models/user.dart similarity index 100% rename from packages/flutter_reactter/example/lib/examples/5_api/models/user.dart rename to packages/flutter_reactter/example/lib/examples/4_github_search/models/user.dart diff --git a/packages/flutter_reactter/example/lib/examples/4_github_search/providers/github_provider.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/providers/github_provider.dart new file mode 100644 index 00000000..30049de6 --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/4_github_search/providers/github_provider.dart @@ -0,0 +1,39 @@ +import 'package:http/http.dart' as http; +import 'package:http/http.dart'; + +class GithubAPI { + static const String _apiBaseUrl = 'api.github.com'; + + Uri user(String username) { + return _buildUri(endpoint: '/users/$username'); + } + + Uri repository(String owner, String repo) { + return _buildUri(endpoint: '/repos/$owner/$repo'); + } + + Uri _buildUri({ + required String endpoint, + Map Function()? parametersBuilder, + }) { + return Uri( + scheme: "https", + host: _apiBaseUrl, + path: endpoint, + queryParameters: parametersBuilder?.call(), + ); + } +} + +class GithubProvider { + final GithubAPI _api = GithubAPI(); + final Client _client = http.Client(); + + Future getUser(String username) { + return _client.get(_api.user(username)); + } + + Future getRepository(String owner, String repo) { + return _client.get(_api.repository(owner, repo)); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/4_github_search/repositories/github_repository.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/repositories/github_repository.dart new file mode 100644 index 00000000..2cde65cb --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/4_github_search/repositories/github_repository.dart @@ -0,0 +1,30 @@ +import 'dart:convert'; + +import 'package:examples/examples/4_github_search/providers/github_provider.dart'; +import 'package:examples/examples/4_github_search/utils/http_response.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +import 'package:examples/examples/4_github_search/models/repository.dart'; +import 'package:examples/examples/4_github_search/models/user.dart'; + +class GithubRepository { + final uGithubProvider = UseDependency.create(GithubProvider.new); + + GithubProvider get _githubProvider { + return uGithubProvider.instance ?? (throw Exception('ApiProvider not found')); + } + + Future getUser(String query) async { + return getResponse( + request: _githubProvider.getUser(query), + onSuccess: (response) => User.fromJson(jsonDecode(response.body)), + ); + } + + Future getRepository(String owner, String repo) async { + return getResponse( + request: _githubProvider.getRepository(owner, repo), + onSuccess: (response) => Repository.fromJson(jsonDecode(response.body)), + ); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/4_github_search/utils/http_response.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/utils/http_response.dart new file mode 100644 index 00000000..4d16483d --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/4_github_search/utils/http_response.dart @@ -0,0 +1,20 @@ +import 'package:http/http.dart'; + +typedef NotFoundException = Exception; + +Future getResponse({ + required Future request, + required T Function(Response) onSuccess, +}) { + return request.then((response) { + if (response.statusCode == 200) { + return onSuccess(response); + } + + if (response.statusCode == 404) { + throw NotFoundException('Not found'); + } + + throw Exception('Failed to load data'); + }); +} diff --git a/packages/flutter_reactter/example/lib/examples/5_api/widgets/badget.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/widgets/badget.dart similarity index 100% rename from packages/flutter_reactter/example/lib/examples/5_api/widgets/badget.dart rename to packages/flutter_reactter/example/lib/examples/4_github_search/widgets/badget.dart diff --git a/packages/flutter_reactter/example/lib/examples/4_github_search/widgets/info_card.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/widgets/info_card.dart new file mode 100644 index 00000000..93147581 --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/4_github_search/widgets/info_card.dart @@ -0,0 +1,60 @@ +import 'package:examples/examples/4_github_search/controllers/github_search_controller.dart'; +import 'package:examples/examples/4_github_search/models/repository.dart'; +import 'package:examples/examples/4_github_search/models/user.dart'; +import 'package:examples/examples/4_github_search/utils/http_response.dart'; +import 'package:examples/examples/4_github_search/widgets/repository_info.dart'; +import 'package:examples/examples/4_github_search/widgets/user_info.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +class InfoCard extends StatelessWidget { + const InfoCard({super.key}); + + @override + Widget build(BuildContext context) { + return Card( + child: Container( + alignment: Alignment.center, + padding: const EdgeInsets.all(16), + width: 400, + child: RtConsumer( + listenStates: (inst) => [inst.uEntity], + builder: (_, githubSearchController, ___) { + return githubSearchController.uEntity.when( + idle: (_) { + return const Text( + 'Search a user or repository(like "flutter/flutter")', + ); + }, + loading: (_) { + return const Center( + child: CircularProgressIndicator(), + ); + }, + done: (entity) { + if (entity is User) { + return UserInfo(user: entity); + } + + if (entity is Repository) { + return RepositoryInfo(repository: entity); + } + + return const Text("Not found"); + }, + error: (error) { + if (error is NotFoundException) { + return Text( + 'Not found "${githubSearchController.query}"', + ); + } + + return Text(error.toString()); + }, + ) as Widget; + }, + ), + ), + ); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/5_api/widgets/repository_item.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/widgets/repository_info.dart similarity index 94% rename from packages/flutter_reactter/example/lib/examples/5_api/widgets/repository_item.dart rename to packages/flutter_reactter/example/lib/examples/4_github_search/widgets/repository_info.dart index d83e4d73..c0777170 100644 --- a/packages/flutter_reactter/example/lib/examples/5_api/widgets/repository_item.dart +++ b/packages/flutter_reactter/example/lib/examples/4_github_search/widgets/repository_info.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import 'badget.dart'; -import 'package:examples/examples/5_api/models/repository.dart'; +import 'package:examples/examples/4_github_search/models/repository.dart'; -class RepositoryItem extends StatelessWidget { +class RepositoryInfo extends StatelessWidget { final Repository repository; - const RepositoryItem({Key? key, required this.repository}) : super(key: key); + const RepositoryInfo({Key? key, required this.repository}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/packages/flutter_reactter/example/lib/examples/4_github_search/widgets/search_bar.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/widgets/search_bar.dart new file mode 100644 index 00000000..4909f00c --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/4_github_search/widgets/search_bar.dart @@ -0,0 +1,57 @@ +import 'package:examples/examples/4_github_search/controllers/search_input_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +class SearchBar extends StatelessWidget { + final void Function(String query) onSearch; + + const SearchBar({ + super.key, + required this.onSearch, + }); + + @override + Widget build(BuildContext context) { + return RtProvider( + () => SearchInputController(onSearch: onSearch), + builder: (context, searchInputController, child) { + return TextFormField( + key: searchInputController.searchKey, + maxLength: 150, + autofocus: true, + scrollPadding: EdgeInsets.zero, + textCapitalization: TextCapitalization.sentences, + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: searchInputController.validator, + focusNode: searchInputController.focusNode, + onFieldSubmitted: searchInputController.onFieldSubmitted, + decoration: InputDecoration( + isDense: true, + filled: true, + fillColor: Theme.of(context).colorScheme.surface, + hintText: 'Type a username or repository (like "flutter/flutter")', + border: const OutlineInputBorder(), + counter: const SizedBox(), + prefixIcon: const Icon(Icons.search), + suffixIcon: Padding( + padding: const EdgeInsets.all(4), + child: OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + foregroundColor: Theme.of(context).colorScheme.inversePrimary, + side: BorderSide( + strokeAlign: BorderSide.strokeAlignOutside, + width: 0.5, + color: Theme.of(context).colorScheme.primary, + ), + ), + onPressed: searchInputController.onButtonPressed, + child: const Text('Search'), + ), + ), + ), + ); + }, + ); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/5_api/widgets/user_item.dart b/packages/flutter_reactter/example/lib/examples/4_github_search/widgets/user_info.dart similarity index 93% rename from packages/flutter_reactter/example/lib/examples/5_api/widgets/user_item.dart rename to packages/flutter_reactter/example/lib/examples/4_github_search/widgets/user_info.dart index 7fe28cb9..9a4e3735 100644 --- a/packages/flutter_reactter/example/lib/examples/5_api/widgets/user_item.dart +++ b/packages/flutter_reactter/example/lib/examples/4_github_search/widgets/user_info.dart @@ -2,13 +2,13 @@ import 'package:intl/intl.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:examples/examples/5_api/models/user.dart'; -import 'package:examples/examples/5_api/widgets/badget.dart'; +import 'package:examples/examples/4_github_search/models/user.dart'; +import 'package:examples/examples/4_github_search/widgets/badget.dart'; -class UserItem extends StatelessWidget { +class UserInfo extends StatelessWidget { final User user; - const UserItem({Key? key, required this.user}) : super(key: key); + const UserInfo({Key? key, required this.user}) : super(key: key); @override Widget build(BuildContext context) { diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/controllers/cart_controller.dart b/packages/flutter_reactter/example/lib/examples/4_shopping_cart/controllers/cart_controller.dart deleted file mode 100644 index 6553d9c7..00000000 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/controllers/cart_controller.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:examples/examples/4_shopping_cart/models/product.dart'; -import 'package:flutter_reactter/flutter_reactter.dart'; - -import 'package:examples/examples/4_shopping_cart/models/cart.dart'; -import 'package:examples/examples/4_shopping_cart/models/cart_item.dart'; -import 'package:examples/examples/4_shopping_cart/repositories/store_repository.dart'; - -class CartController { - final uStoreRepository = UseDependency.create(StoreRepository.new); - final uCartItems = UseState>([]); - final uCartItemsCount = UseState(0); - final uTotal = UseState(0.0); - - int getProductQuantity(Product product) { - return uCartItems.value - .firstWhere( - (item) => item.product == product, - orElse: () => CartItem(product, 0), - ) - .quantity; - } - - void addProduct(Product product) { - final cart = uStoreRepository.instance?.addProductToCart(product); - if (cart != null) _setState(cart); - } - - void removeProduct(Product product) { - final cart = uStoreRepository.instance?.removeProductFromCart(product); - if (cart != null) _setState(cart); - } - - void deleteProduct(Product product) { - final cart = uStoreRepository.instance?.deleteProductFromCart(product); - if (cart != null) _setState(cart); - } - - void checkout() { - uStoreRepository.instance?.checkout(uCartItems.value); - _setState(Cart()); - } - - void _setState(Cart cart) { - uCartItems.value = cart.items; - uCartItemsCount.value = cart.itemsCount; - uTotal.value = cart.total; - } -} diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/controllers/products_controller.dart b/packages/flutter_reactter/example/lib/examples/4_shopping_cart/controllers/products_controller.dart deleted file mode 100644 index 5be53310..00000000 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/controllers/products_controller.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter_reactter/flutter_reactter.dart'; - -import 'package:examples/examples/4_shopping_cart/repositories/store_repository.dart'; - -class ProductsController { - final uStoreRepository = UseDependency.create(StoreRepository.new); - late final uProducts = Rt.lazyState( - () => UseState(uStoreRepository.instance?.getProducts() ?? []), - this, - ); -} diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/models/cart.dart b/packages/flutter_reactter/example/lib/examples/4_shopping_cart/models/cart.dart deleted file mode 100644 index e848c5f0..00000000 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/models/cart.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:examples/examples/4_shopping_cart/models/cart_item.dart'; - -class Cart { - final List items; - final int itemsCount; - final double total; - - Cart({ - this.items = const [], - this.itemsCount = 0, - this.total = 0.0, - }); -} diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/repositories/store_repository.dart b/packages/flutter_reactter/example/lib/examples/4_shopping_cart/repositories/store_repository.dart deleted file mode 100644 index 2dcd3c35..00000000 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/repositories/store_repository.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'dart:math'; - -import 'package:examples/examples/4_shopping_cart/models/cart.dart'; -import 'package:examples/examples/4_shopping_cart/models/cart_item.dart'; -import 'package:flutter/material.dart'; - -import 'package:examples/examples/4_shopping_cart/models/product.dart'; -import 'package:examples/examples/4_shopping_cart/utils/lorem_gen.dart'; - -class StoreRepository { - static final _random = Random(); - final _products = _generateProducts(); - final _cartItems = {}; - int _cartItemsCount = 0; - double _total = 0.0; - - List getProducts() => _products; - - Cart addProductToCart(Product product, [int quantity = 1]) { - final item = _cartItems[product] ?? CartItem(product, 0); - - _cartItems[product] = item.copyWith(quantity: item.quantity + quantity); - _cartItemsCount += quantity; - _total += product.price * quantity; - - return Cart( - items: _cartItems.values.toList(), - itemsCount: _cartItemsCount, - total: _total, - ); - } - - Cart removeProductFromCart(Product product, [int quantity = 1]) { - final item = _cartItems[product] ?? CartItem(product, 0); - - if (item.quantity <= quantity) { - _cartItems.remove(product); - } else { - _cartItems[product] = item.copyWith(quantity: item.quantity - quantity); - } - - _cartItemsCount -= quantity; - _total -= product.price * quantity; - - return Cart( - items: _cartItems.values.toList(), - itemsCount: _cartItemsCount, - total: _total, - ); - } - - Cart deleteProductFromCart(Product product) { - final item = _cartItems[product] ?? CartItem(product, 0); - - _cartItems.remove(product); - _cartItemsCount -= item.quantity; - _total -= item.product.price * item.quantity; - - return Cart( - items: _cartItems.values.toList(), - itemsCount: _cartItemsCount, - total: _total, - ); - } - - void checkout(List items) { - final productsChanged = _products.map((product) { - final item = items.firstWhere( - (element) => element.product == product, - orElse: () => CartItem(product, 0), - ); - final quantity = item.quantity; - - return product.copyWith(stock: product.stock - quantity); - }).toList(); - - _products.clear(); - _products.addAll(productsChanged); - _cartItems.clear(); - _cartItemsCount = 0; - _total = 0.0; - } - - static List _generateProducts() { - final colors = [...Colors.primaries, ...Colors.accents]; - - return List.generate( - 26, - (index) { - final letter = String.fromCharCode(index + 65); - final iColor = _random.nextInt(colors.length); - final color = colors[iColor][100]!.value.toRadixString(16).substring(2); - final color2 = - colors[iColor][700]!.value.toRadixString(16).substring(2); - - return Product( - name: generateLoremText(_random.nextInt(8) + 3), - image: 'https://placehold.co/200/$color/$color2/png?text=$letter', - price: (_random.nextInt(3000) + 100) + _random.nextDouble(), - stock: _random.nextInt(20), - ); - }, - ); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/views/cart_view.dart b/packages/flutter_reactter/example/lib/examples/4_shopping_cart/views/cart_view.dart deleted file mode 100644 index 7b2ad7b8..00000000 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/views/cart_view.dart +++ /dev/null @@ -1,97 +0,0 @@ -// ignore_for_file: avoid_renaming_method_parameters - -import 'package:flutter/material.dart'; -import 'package:flutter_reactter/flutter_reactter.dart'; - -import 'package:examples/examples/4_shopping_cart/widgets/cart_bottom.dart'; -import 'package:examples/examples/4_shopping_cart/widgets/cart_item_card.dart'; -import 'package:examples/examples/4_shopping_cart/controllers/cart_controller.dart'; - -class CartView extends RtComponent { - const CartView({ - Key? key, - this.onCheckout, - }) : super(key: key); - - final VoidCallback? onCheckout; - - @override - get builder => CartController.new; - - @override - Widget render(BuildContext context, CartController cartController) { - return Scaffold( - appBar: AppBar( - title: const Text('Shopping Cart'), - ), - body: RtSelector( - selector: (inst, $) => $(inst.uCartItems).value.length, - builder: (_, __, itemCount, ____) { - if (itemCount == 0) { - return Center( - child: Text( - "Your cart is empty!", - style: Theme.of(context).textTheme.titleMedium, - ), - ); - } - - return ListView.builder( - itemCount: itemCount, - cacheExtent: 70, - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 4, - ), - itemBuilder: (context, index) { - return RtSelector( - selector: (inst, $) { - try { - return $(inst.uCartItems).value[index].quantity; - } catch (e) { - return 0; - } - }, - builder: (_, cartController, __, ___) { - final item = cartController.uCartItems.value[index]; - final product = item.product; - - return SizedBox( - height: 80, - child: CartItemCard( - key: ObjectKey(product), - cartItem: item, - onAdd: cartController.addProduct, - onRemove: cartController.removeProduct, - onDelete: cartController.deleteProduct, - ), - ); - }, - ); - }, - ); - }, - ), - bottomNavigationBar: RtConsumer( - listenStates: (inst) => [inst.uCartItems], - builder: (_, cartController, __) { - final items = cartController.uCartItems.value; - final itemsCount = cartController.uCartItemsCount.value; - final total = cartController.uTotal.value; - - return CartBottom( - productsCount: items.length, - itemsCount: itemsCount, - total: total, - onCheckout: items.isNotEmpty - ? () { - cartController.checkout(); - onCheckout?.call(); - } - : null, - ); - }, - ), - ); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/views/products_view.dart b/packages/flutter_reactter/example/lib/examples/4_shopping_cart/views/products_view.dart deleted file mode 100644 index 9875252d..00000000 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/views/products_view.dart +++ /dev/null @@ -1,67 +0,0 @@ -// ignore_for_file: avoid_renaming_method_parameters - -import 'package:flutter/material.dart'; -import 'package:flutter_reactter/flutter_reactter.dart'; - -import 'package:examples/examples/4_shopping_cart/controllers/products_controller.dart'; -import 'package:examples/examples/4_shopping_cart/views/cart_view.dart'; -import 'package:examples/examples/4_shopping_cart/widgets/cart_action.dart'; -import 'package:examples/examples/4_shopping_cart/widgets/product_card.dart'; - -class ProductsView extends RtComponent { - const ProductsView({Key? key}) : super(key: key); - - @override - get builder => ProductsController.new; - - @override - Widget render(BuildContext context, ProductsController productsController) { - return Scaffold( - appBar: AppBar( - title: const Text('Shopping Cart'), - actions: [ - RtConsumer( - builder: (_, __, ___) { - return CartAction( - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => CartView( - onCheckout: productsController.uProducts.refresh, - ), - ), - ); - }, - ); - }, - ), - ], - ), - body: LayoutBuilder( - builder: (context, constraints) { - final crossAxisCount = (constraints.maxWidth / 140).floor(); - - return RtConsumer( - listenStates: (inst) => [inst.uProducts], - builder: (_, __, ___) { - final products = productsController.uProducts.value; - - return GridView.count( - padding: const EdgeInsets.all(8), - crossAxisCount: crossAxisCount.clamp(1, crossAxisCount), - childAspectRatio: 9 / 16, - children: [ - for (final product in products) - ProductCard( - key: ObjectKey(product), - product: product, - ) - ], - ); - }, - ); - }, - ), - ); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/cart_bottom.dart b/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/cart_bottom.dart deleted file mode 100644 index f971b158..00000000 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/cart_bottom.dart +++ /dev/null @@ -1,88 +0,0 @@ -// ignore_for_file: avoid_renaming_method_parameters - -import 'package:flutter/material.dart'; - -import 'package:examples/examples/4_shopping_cart/utils/format_currency.dart'; - -class CartBottom extends StatelessWidget { - final int productsCount; - final int itemsCount; - final double total; - final void Function()? onCheckout; - - const CartBottom({ - Key? key, - this.productsCount = 0, - this.itemsCount = 0, - this.total = 0, - required this.onCheckout, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return BottomSheet( - onClosing: () {}, - elevation: 16, - enableDrag: false, - builder: (context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Expanded( - child: Wrap( - children: [ - Text( - "$itemsCount " - "${itemsCount == 1 ? 'item' : 'items'} ", - style: Theme.of(context).textTheme.titleSmall, - ), - Text( - "of $productsCount " - "${productsCount == 1 ? 'product' : 'products'}", - style: Theme.of(context).textTheme.titleSmall, - ), - ], - ), - ), - Row( - children: [ - Text( - "Total: ", - style: Theme.of(context).textTheme.titleMedium, - ), - Text( - formatCurrency(total), - style: Theme.of(context).textTheme.titleLarge, - ), - ], - ), - ], - ), - ), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: onCheckout, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Text( - "Checkout", - style: Theme.of(context) - .textTheme - .titleLarge! - .copyWith(color: Colors.white), - ), - ), - ), - ), - ], - ); - }, - ); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/product_buttons.dart b/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/product_buttons.dart deleted file mode 100644 index 767ee9ab..00000000 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/product_buttons.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_reactter/flutter_reactter.dart'; - -import 'package:examples/examples/4_shopping_cart/controllers/cart_controller.dart'; -import 'package:examples/examples/4_shopping_cart/models/product.dart'; -import 'package:examples/examples/4_shopping_cart/widgets/custom_icon_button.dart'; -import 'package:examples/examples/4_shopping_cart/widgets/quantity.dart'; - -class ProductButtons extends StatelessWidget { - const ProductButtons({ - Key? key, - required this.product, - }) : super(key: key); - - final Product product; - - @override - Widget build(BuildContext context) { - final cartController = context.use(); - final quantity = context.select( - (inst, $) => inst.getProductQuantity(product), - ); - - if (quantity == 0) { - return CustomIconButton( - icon: Icons.add, - color: Colors.green, - onPressed: quantity < product.stock - ? () => cartController.addProduct(product) - : null, - ); - } - - return Quantity( - quantity: quantity, - maxQuantity: product.stock, - onRemove: () => cartController.removeProduct(product), - onAdd: () => cartController.addProduct(product), - ); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/product_card.dart b/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/product_card.dart deleted file mode 100644 index 1d1e09d0..00000000 --- a/packages/flutter_reactter/example/lib/examples/4_shopping_cart/widgets/product_card.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:examples/examples/4_shopping_cart/models/product.dart'; -import 'package:examples/examples/4_shopping_cart/utils/format_currency.dart'; -import 'package:examples/examples/4_shopping_cart/widgets/product_buttons.dart'; - -class ProductCard extends StatelessWidget { - final Product product; - - const ProductCard({ - Key? key, - required this.product, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Card( - clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AspectRatio( - aspectRatio: 1, - child: Container( - color: Colors.black45, - child: Stack( - children: [ - SizedBox.expand( - child: Image.network( - product.image, - fit: BoxFit.contain, - ), - ), - Positioned( - top: 4, - right: 4, - child: ProductButtons( - product: product, - ), - ) - ], - ), - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - formatCurrency(product.price), - style: Theme.of(context).textTheme.titleMedium, - ), - Text( - product.name, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const Spacer(), - _buildStock(context), - ], - ), - ), - ), - ], - ), - ); - } - - Widget _buildStock(BuildContext context) { - if (product.stock == 0) { - return Text( - "Sold out", - style: Theme.of(context).textTheme.labelSmall, - ); - } - - return Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - 'In stock: ', - style: Theme.of(context).textTheme.labelSmall, - ), - Text( - "${product.stock}", - style: Theme.of(context).textTheme.labelSmall?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - ], - ); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/5_api/api_page.dart b/packages/flutter_reactter/example/lib/examples/5_api/api_page.dart deleted file mode 100644 index 3dcb1746..00000000 --- a/packages/flutter_reactter/example/lib/examples/5_api/api_page.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'package:flutter/material.dart' hide SearchBar; -import 'package:flutter_reactter/flutter_reactter.dart'; - -import 'package:examples/examples/5_api/controllers/api_controller.dart'; -import 'package:examples/examples/5_api/models/repository.dart'; -import 'package:examples/examples/5_api/models/user.dart'; -import 'package:examples/examples/5_api/services/api_service.dart'; -import 'package:examples/examples/5_api/widgets/repository_item.dart'; -import 'package:examples/examples/5_api/widgets/search_bar.dart'; -import 'package:examples/examples/5_api/widgets/user_item.dart'; - -class ApiPage extends StatelessWidget { - const ApiPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return RtProvider( - ApiController.new, - builder: (context, apiController, child) { - return Scaffold( - appBar: AppBar( - title: const Text("Github search"), - bottom: PreferredSize( - preferredSize: const Size.fromHeight(56), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8) - .copyWith(bottom: 2), - child: SearchBar( - onSearch: apiController.search, - ), - ), - ), - ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(8), - child: Center( - child: RtConsumer( - listenStates: (inst) => [inst.uEntity], - builder: (_, __, ___) { - return FittedBox( - child: SizedBox( - width: 400, - child: Card( - margin: EdgeInsets.zero, - child: Container( - alignment: Alignment.center, - padding: const EdgeInsets.all(16), - child: apiController.uEntity.when( - standby: (_) => const Text( - 'Search a user or repository(like "flutter/flutter")', - ), - loading: (_) => const Center( - child: CircularProgressIndicator(), - ), - done: (entity) { - if (entity is User) { - return UserItem(user: entity); - } - - if (entity is Repository) { - return RepositoryItem(repository: entity); - } - - return const Text("Not found"); - }, - error: (error) { - if (error is NotFoundException) { - return Text( - 'Not found "${apiController.query}"', - ); - } - - return Text(error.toString()); - }, - ), - ), - ), - ), - ); - }, - ), - ), - ), - ); - }, - ); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/5_api/controllers/api_controller.dart b/packages/flutter_reactter/example/lib/examples/5_api/controllers/api_controller.dart deleted file mode 100644 index ec97f212..00000000 --- a/packages/flutter_reactter/example/lib/examples/5_api/controllers/api_controller.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter_reactter/flutter_reactter.dart'; - -import 'package:examples/examples/5_api/services/api_service.dart'; - -class ApiController { - final apiService = ApiService(); - - String _query = ''; - String get query => _query; - - late final uEntity = Rt.lazyState( - () => UseAsyncState.withArg( - null, - Memo.inline, String>( - getEntity, - const MemoMultiInterceptor([ - MemoSafeAsyncInterceptor(), - MemoTemporaryCacheInterceptor(Duration(seconds: 30)), - ]), - ), - ), - this, - ); - - Future getEntity(String query) async { - final queryPath = query.split("/"); - - if (queryPath.length > 1) { - return await apiService.getRepository(queryPath[0], queryPath[1]); - } - - return await apiService.getUser(query); - } - - void search(String query) { - _query = query; - uEntity.resolve(query); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/5_api/services/api_service.dart b/packages/flutter_reactter/example/lib/examples/5_api/services/api_service.dart deleted file mode 100644 index f65c9ee2..00000000 --- a/packages/flutter_reactter/example/lib/examples/5_api/services/api_service.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'dart:convert'; - -import 'package:http/http.dart' as http; - -import 'package:examples/examples/5_api/models/repository.dart'; -import 'package:examples/examples/5_api/models/user.dart'; - -typedef NotFoundException = Exception; - -class ApiService { - Future getUser(String query) async { - final response = - await http.get(Uri.parse('https://api.github.com/users/$query')); - - if (response.statusCode == 200) { - return User.fromJson(jsonDecode(response.body)); - } - - if (response.statusCode == 404) { - throw NotFoundException('User not found'); - } - - throw Exception('Failed to load user'); - } - - Future getRepository(String owner, String repo) async { - final response = - await http.get(Uri.parse('https://api.github.com/repos/$owner/$repo')); - - if (response.statusCode == 200) { - return Repository.fromJson(jsonDecode(response.body)); - } - - if (response.statusCode == 404) { - throw NotFoundException('Repository not found'); - } - - throw Exception('Failed to load repository'); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/5_api/widgets/search_bar.dart b/packages/flutter_reactter/example/lib/examples/5_api/widgets/search_bar.dart deleted file mode 100644 index a5f0d310..00000000 --- a/packages/flutter_reactter/example/lib/examples/5_api/widgets/search_bar.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:flutter/material.dart'; - -class SearchBar extends StatelessWidget { - const SearchBar({ - super.key, - this.onSearch, - }); - - final void Function(String query)? onSearch; - - @override - Widget build(BuildContext context) { - final searchKey = GlobalKey>(); - final focusNode = FocusNode(); - - void search() { - final isValid = searchKey.currentState?.validate() ?? false; - - if (!isValid) return; - - final query = searchKey.currentState?.value ?? ''; - - onSearch?.call(query); - } - - String? validator(String? value) { - if (value == null || value.isEmpty) { - return "Can't be empty"; - } - - return null; - } - - return TextFormField( - key: searchKey, - maxLength: 150, - autofocus: true, - scrollPadding: EdgeInsets.zero, - textCapitalization: TextCapitalization.sentences, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: validator, - focusNode: focusNode, - onFieldSubmitted: (_) { - focusNode.requestFocus(); - search(); - }, - decoration: InputDecoration( - isDense: true, - filled: true, - hintText: 'Type a username or repository (like "flutter/flutter")', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(30), - borderSide: const BorderSide( - strokeAlign: BorderSide.strokeAlignOutside, - ), - ), - counter: const SizedBox(), - prefixIcon: const Icon(Icons.search), - suffixIcon: Padding( - padding: const EdgeInsets.symmetric( - vertical: 2, - horizontal: 4, - ).copyWith(left: 0), - child: OutlinedButton( - style: OutlinedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.primary, - foregroundColor: Theme.of(context).colorScheme.inversePrimary, - side: BorderSide( - strokeAlign: BorderSide.strokeAlignOutside, - width: 0.5, - color: Theme.of(context).colorScheme.primary, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(30.0), - ), - ), - child: const Text('Search'), - onPressed: () => search(), - ), - ), - ), - ); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/actions/add_todo_action.dart b/packages/flutter_reactter/example/lib/examples/5_todo/actions/add_todo_action.dart similarity index 76% rename from packages/flutter_reactter/example/lib/examples/6_todo/actions/add_todo_action.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/actions/add_todo_action.dart index 38c35179..14fb5369 100644 --- a/packages/flutter_reactter/example/lib/examples/6_todo/actions/add_todo_action.dart +++ b/packages/flutter_reactter/example/lib/examples/5_todo/actions/add_todo_action.dart @@ -1,7 +1,7 @@ import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/6_todo/models/todo.dart'; -import 'package:examples/examples/6_todo/stores/todo_store.dart'; +import 'package:examples/examples/5_todo/models/todo.dart'; +import 'package:examples/examples/5_todo/stores/todo_store.dart'; class AddTodoAction extends RtActionCallable { final Todo todo; diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/actions/clear_completed_action.dart b/packages/flutter_reactter/example/lib/examples/5_todo/actions/clear_completed_action.dart similarity index 87% rename from packages/flutter_reactter/example/lib/examples/6_todo/actions/clear_completed_action.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/actions/clear_completed_action.dart index ead48afa..1e3341e4 100644 --- a/packages/flutter_reactter/example/lib/examples/6_todo/actions/clear_completed_action.dart +++ b/packages/flutter_reactter/example/lib/examples/5_todo/actions/clear_completed_action.dart @@ -1,6 +1,6 @@ import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/6_todo/stores/todo_store.dart'; +import 'package:examples/examples/5_todo/stores/todo_store.dart'; class ClearCompletedAction extends RtActionCallable { const ClearCompletedAction() diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/actions/filter_action.dart b/packages/flutter_reactter/example/lib/examples/5_todo/actions/filter_action.dart similarity index 86% rename from packages/flutter_reactter/example/lib/examples/6_todo/actions/filter_action.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/actions/filter_action.dart index 0bd7f987..ef8f7ff4 100644 --- a/packages/flutter_reactter/example/lib/examples/6_todo/actions/filter_action.dart +++ b/packages/flutter_reactter/example/lib/examples/5_todo/actions/filter_action.dart @@ -1,6 +1,6 @@ import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/6_todo/stores/todo_store.dart'; +import 'package:examples/examples/5_todo/stores/todo_store.dart'; class FilterAction extends RtActionCallable { final TodoListType todoListType; diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/actions/remove_todo_action.dart b/packages/flutter_reactter/example/lib/examples/5_todo/actions/remove_todo_action.dart similarity index 81% rename from packages/flutter_reactter/example/lib/examples/6_todo/actions/remove_todo_action.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/actions/remove_todo_action.dart index 56c12682..1cb0a940 100644 --- a/packages/flutter_reactter/example/lib/examples/6_todo/actions/remove_todo_action.dart +++ b/packages/flutter_reactter/example/lib/examples/5_todo/actions/remove_todo_action.dart @@ -1,7 +1,7 @@ import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/6_todo/models/todo.dart'; -import 'package:examples/examples/6_todo/stores/todo_store.dart'; +import 'package:examples/examples/5_todo/models/todo.dart'; +import 'package:examples/examples/5_todo/stores/todo_store.dart'; class RemoveTodoAction extends RtActionCallable { final Todo todo; diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/actions/toggle_todo_action.dart b/packages/flutter_reactter/example/lib/examples/5_todo/actions/toggle_todo_action.dart similarity index 83% rename from packages/flutter_reactter/example/lib/examples/6_todo/actions/toggle_todo_action.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/actions/toggle_todo_action.dart index 993dd24b..42f691d5 100644 --- a/packages/flutter_reactter/example/lib/examples/6_todo/actions/toggle_todo_action.dart +++ b/packages/flutter_reactter/example/lib/examples/5_todo/actions/toggle_todo_action.dart @@ -1,7 +1,7 @@ import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/6_todo/models/todo.dart'; -import 'package:examples/examples/6_todo/stores/todo_store.dart'; +import 'package:examples/examples/5_todo/models/todo.dart'; +import 'package:examples/examples/5_todo/stores/todo_store.dart'; class ToggleTodoAction extends RtActionCallable { final Todo todo; diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/controllers/todo_controller.dart b/packages/flutter_reactter/example/lib/examples/5_todo/controllers/todo_controller.dart similarity index 65% rename from packages/flutter_reactter/example/lib/examples/6_todo/controllers/todo_controller.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/controllers/todo_controller.dart index 555f3d02..e7591eb7 100644 --- a/packages/flutter_reactter/example/lib/examples/6_todo/controllers/todo_controller.dart +++ b/packages/flutter_reactter/example/lib/examples/5_todo/controllers/todo_controller.dart @@ -1,12 +1,12 @@ import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/6_todo/actions/add_todo_action.dart'; -import 'package:examples/examples/6_todo/actions/clear_completed_action.dart'; -import 'package:examples/examples/6_todo/actions/filter_action.dart'; -import 'package:examples/examples/6_todo/actions/remove_todo_action.dart'; -import 'package:examples/examples/6_todo/actions/toggle_todo_action.dart'; -import 'package:examples/examples/6_todo/models/todo.dart'; -import 'package:examples/examples/6_todo/stores/todo_store.dart'; +import 'package:examples/examples/5_todo/actions/add_todo_action.dart'; +import 'package:examples/examples/5_todo/actions/clear_completed_action.dart'; +import 'package:examples/examples/5_todo/actions/filter_action.dart'; +import 'package:examples/examples/5_todo/actions/remove_todo_action.dart'; +import 'package:examples/examples/5_todo/actions/toggle_todo_action.dart'; +import 'package:examples/examples/5_todo/models/todo.dart'; +import 'package:examples/examples/5_todo/stores/todo_store.dart'; TodoStore _reducer(TodoStore state, RtAction action) { return action is RtActionCallable ? action(state) : UnimplementedError(); @@ -25,11 +25,12 @@ class TodoController { List getTodosBy(TodoListType todoListType) { if (todoListType == TodoListType.all) return uReduce.value.todoList; - final isDone = todoListType == TodoListType.done; + final isShowTodoDone = todoListType == TodoListType.done; - return uReduce.value.todoList - .where((todo) => todo.isDone == isDone) - .toList(); + return [ + for (final todo in uReduce.value.todoList) + if (todo.isDone == isShowTodoDone) todo + ]; } void filterBy(TodoListType? todoListType) { diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/models/todo.dart b/packages/flutter_reactter/example/lib/examples/5_todo/models/todo.dart similarity index 73% rename from packages/flutter_reactter/example/lib/examples/6_todo/models/todo.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/models/todo.dart index 066db5d7..91f95270 100644 --- a/packages/flutter_reactter/example/lib/examples/6_todo/models/todo.dart +++ b/packages/flutter_reactter/example/lib/examples/5_todo/models/todo.dart @@ -15,4 +15,11 @@ class Todo { title: title ?? this.title, isDone: isDone ?? this.isDone, ); + + Map toJson() { + return { + 'title': title, + 'isDone': isDone, + }; + } } diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/stores/todo_store.dart b/packages/flutter_reactter/example/lib/examples/5_todo/stores/todo_store.dart similarity index 68% rename from packages/flutter_reactter/example/lib/examples/6_todo/stores/todo_store.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/stores/todo_store.dart index 52e4d276..8cb13845 100644 --- a/packages/flutter_reactter/example/lib/examples/6_todo/stores/todo_store.dart +++ b/packages/flutter_reactter/example/lib/examples/5_todo/stores/todo_store.dart @@ -1,4 +1,4 @@ -import 'package:examples/examples/6_todo/models/todo.dart'; +import 'package:examples/examples/5_todo/models/todo.dart'; enum TodoListType { all, done, todo } @@ -23,4 +23,12 @@ class TodoStore { filteredBy: filteredBy ?? this.filteredBy, doneCount: doneCount ?? this.doneCount, ); + + Map toJson() { + return { + 'todoList': todoList.map((e) => e.toJson()).toList(), + 'filteredBy': filteredBy.toString(), + 'doneCount': doneCount, + }; + } } diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/todo_page.dart b/packages/flutter_reactter/example/lib/examples/5_todo/todo_page.dart similarity index 51% rename from packages/flutter_reactter/example/lib/examples/6_todo/todo_page.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/todo_page.dart index f9f869ab..3e719ef1 100644 --- a/packages/flutter_reactter/example/lib/examples/6_todo/todo_page.dart +++ b/packages/flutter_reactter/example/lib/examples/5_todo/todo_page.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/6_todo/controllers/todo_controller.dart'; -import 'package:examples/examples/6_todo/widgets/input_bar.dart'; -import 'package:examples/examples/6_todo/widgets/todo_filter.dart'; -import 'package:examples/examples/6_todo/widgets/todo_list.dart'; +import 'package:examples/examples/5_todo/controllers/todo_controller.dart'; +import 'package:examples/examples/5_todo/widgets/input_bar.dart'; +import 'package:examples/examples/5_todo/widgets/todo_filter.dart'; +import 'package:examples/examples/5_todo/widgets/todo_list.dart'; class TodoPage extends StatelessWidget { const TodoPage({Key? key}) : super(key: key); @@ -14,13 +14,11 @@ class TodoPage extends StatelessWidget { return RtProvider( TodoController.new, builder: (context, todoController, _) { - final size = MediaQuery.of(context).size; - return Scaffold( appBar: AppBar( title: const Text("To-Do List"), bottom: PreferredSize( - preferredSize: Size.fromHeight(size.width >= 480 ? 82 : 110), + preferredSize: const Size.fromHeight(40), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -30,7 +28,10 @@ class TodoPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ InputBar(onAdd: todoController.addTodo), - const TodoFilter(), + // const Padding( + // padding: EdgeInsets.symmetric(vertical: 8.0), + // child: TodoFilter(), + // ), ], ), ), @@ -38,7 +39,22 @@ class TodoPage extends StatelessWidget { ), ), ), - body: const TodoList(), + body: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + TodoFilter(), + SizedBox(height: 4), + Expanded( + child: Card( + child: TodoList(), + ), + ), + ], + ), + ), ); }, ); diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/widgets/input_bar.dart b/packages/flutter_reactter/example/lib/examples/5_todo/widgets/input_bar.dart similarity index 94% rename from packages/flutter_reactter/example/lib/examples/6_todo/widgets/input_bar.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/widgets/input_bar.dart index 04d07a00..a624eb3b 100644 --- a/packages/flutter_reactter/example/lib/examples/6_todo/widgets/input_bar.dart +++ b/packages/flutter_reactter/example/lib/examples/5_todo/widgets/input_bar.dart @@ -51,6 +51,7 @@ class InputBar extends StatelessWidget { decoration: InputDecoration( isDense: true, filled: true, + fillColor: Theme.of(context).colorScheme.surface, hintText: 'What needs to be done?', border: const OutlineInputBorder( borderSide: BorderSide( @@ -60,10 +61,7 @@ class InputBar extends StatelessWidget { ), counter: const SizedBox(), suffixIcon: Padding( - padding: const EdgeInsets.symmetric( - vertical: 2, - horizontal: 4, - ).copyWith(left: 0), + padding: const EdgeInsets.all(4), child: OutlinedButton( style: OutlinedButton.styleFrom( tapTargetSize: MaterialTapTargetSize.shrinkWrap, diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/widgets/radio_with_label.dart b/packages/flutter_reactter/example/lib/examples/5_todo/widgets/radio_with_label.dart similarity index 100% rename from packages/flutter_reactter/example/lib/examples/6_todo/widgets/radio_with_label.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/widgets/radio_with_label.dart diff --git a/packages/flutter_reactter/example/lib/examples/5_todo/widgets/todo_filter.dart b/packages/flutter_reactter/example/lib/examples/5_todo/widgets/todo_filter.dart new file mode 100644 index 00000000..1af399cd --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/5_todo/widgets/todo_filter.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +import 'package:examples/examples/5_todo/controllers/todo_controller.dart'; +import 'package:examples/examples/5_todo/stores/todo_store.dart'; +import 'package:examples/examples/5_todo/widgets/radio_with_label.dart'; + +class TodoFilter extends StatelessWidget { + const TodoFilter({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RtSelector( + selector: (inst, watch) => watch(inst.uReduce).value.filteredBy, + builder: (_, __, filteredBy, ___) { + return Row( + mainAxisSize: MainAxisSize.max, + children: [ + const Text('Filter by:'), + const SizedBox(width: 4), + RtSelector( + selector: (inst, watch) { + return watch(inst.uReduce).value.todoList.length; + }, + builder: (_, todoController, allCount, __) { + return RadioWithLabel( + label: 'All($allCount)', + value: TodoListType.all, + groupValue: filteredBy, + onChanged: todoController.filterBy, + ); + }, + ), + RtSelector( + selector: (inst, watch) { + final uReduce = watch(inst.uReduce).value; + final allCount = uReduce.todoList.length; + final doneCount = uReduce.doneCount; + + return allCount - doneCount; + }, + builder: (_, todoController, activeCount, __) { + return RadioWithLabel( + label: 'Active($activeCount)', + value: TodoListType.todo, + groupValue: filteredBy, + onChanged: todoController.filterBy, + ); + }, + ), + RtSelector( + selector: (inst, watch) { + return watch(inst.uReduce).value.doneCount; + }, + builder: (_, todoController, doneCount, __) { + return Row( + children: [ + RadioWithLabel( + label: 'Completed($doneCount)', + value: TodoListType.done, + groupValue: filteredBy, + onChanged: todoController.filterBy, + ), + const SizedBox(width: 4), + IconButton( + tooltip: 'Clear completed', + color: Colors.red.shade400, + padding: EdgeInsets.zero, + constraints: + const BoxConstraints.tightFor(width: 24), + splashRadius: 18, + iconSize: 24, + icon: const Icon(Icons.delete), + onPressed: doneCount > 0 + ? todoController.clearCompleted + : null, + ), + ], + ); + }, + ), + ], + ); + }, + ), + ], + ), + ); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/widgets/todo_item.dart b/packages/flutter_reactter/example/lib/examples/5_todo/widgets/todo_item.dart similarity index 85% rename from packages/flutter_reactter/example/lib/examples/6_todo/widgets/todo_item.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/widgets/todo_item.dart index 7e002684..7640464d 100644 --- a/packages/flutter_reactter/example/lib/examples/6_todo/widgets/todo_item.dart +++ b/packages/flutter_reactter/example/lib/examples/5_todo/widgets/todo_item.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:examples/examples/6_todo/models/todo.dart'; +import 'package:examples/examples/5_todo/models/todo.dart'; class TodoItem extends StatelessWidget { final Todo todo; @@ -20,6 +20,7 @@ class TodoItem extends StatelessWidget { value: todo.isDone, onChanged: (_) => onTap?.call(), dense: true, + visualDensity: VisualDensity.compact, controlAffinity: ListTileControlAffinity.leading, title: Text( todo.title, @@ -33,7 +34,7 @@ class TodoItem extends StatelessWidget { onPressed: onRemove, color: Colors.red.shade400, padding: EdgeInsets.zero, - constraints: const BoxConstraints.tightFor(width: 42), + constraints: const BoxConstraints.tightFor(width: 24), splashRadius: 18, iconSize: 24, icon: const Icon(Icons.close_outlined), diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/widgets/todo_list.dart b/packages/flutter_reactter/example/lib/examples/5_todo/widgets/todo_list.dart similarity index 85% rename from packages/flutter_reactter/example/lib/examples/6_todo/widgets/todo_list.dart rename to packages/flutter_reactter/example/lib/examples/5_todo/widgets/todo_list.dart index ca7cdd7a..dfefff21 100644 --- a/packages/flutter_reactter/example/lib/examples/6_todo/widgets/todo_list.dart +++ b/packages/flutter_reactter/example/lib/examples/5_todo/widgets/todo_list.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_reactter/flutter_reactter.dart'; -import 'package:examples/examples/6_todo/controllers/todo_controller.dart'; -import 'package:examples/examples/6_todo/widgets/todo_item.dart'; +import 'package:examples/examples/5_todo/controllers/todo_controller.dart'; +import 'package:examples/examples/5_todo/widgets/todo_item.dart'; class TodoList extends RtComponent { const TodoList({super.key}); diff --git a/packages/flutter_reactter/example/lib/examples/6_todo/widgets/todo_filter.dart b/packages/flutter_reactter/example/lib/examples/6_todo/widgets/todo_filter.dart deleted file mode 100644 index fcafa092..00000000 --- a/packages/flutter_reactter/example/lib/examples/6_todo/widgets/todo_filter.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_reactter/flutter_reactter.dart'; - -import 'package:examples/examples/6_todo/controllers/todo_controller.dart'; -import 'package:examples/examples/6_todo/stores/todo_store.dart'; -import 'package:examples/examples/6_todo/widgets/radio_with_label.dart'; - -class TodoFilter extends StatelessWidget { - const TodoFilter({super.key}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(bottom: 4), - child: Wrap( - alignment: WrapAlignment.end, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - FittedBox( - child: SizedBox( - height: 30, - child: RtSelector( - selector: (inst, $) => $(inst.uReduce).value.filteredBy, - builder: (_, __, filteredBy, ___) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Filter by:'), - RtSelector( - selector: (inst, $) { - return $(inst.uReduce).value.todoList.length; - }, - builder: (_, todoController, allCount, __) { - return RadioWithLabel( - label: 'All($allCount)', - value: TodoListType.all, - groupValue: filteredBy, - onChanged: todoController.filterBy, - ); - }, - ), - RtSelector( - selector: (inst, $) { - final uReduce = $(inst.uReduce).value; - final allCount = uReduce.todoList.length; - final doneCount = uReduce.doneCount; - - return allCount - doneCount; - }, - builder: (_, todoController, activeCount, __) { - return RadioWithLabel( - label: 'Active($activeCount)', - value: TodoListType.todo, - groupValue: filteredBy, - onChanged: todoController.filterBy, - ); - }, - ), - RtSelector( - selector: (inst, $) { - return $(inst.uReduce).value.doneCount; - }, - builder: (_, todoController, doneCount, __) { - return RadioWithLabel( - label: 'Completed($doneCount)', - value: TodoListType.done, - groupValue: filteredBy, - onChanged: todoController.filterBy, - ); - }, - ), - ], - ); - }, - ), - ), - ), - const SizedBox(width: 8), - RtSelector( - selector: (inst, $) { - return $(inst.uReduce).value.doneCount > 0; - }, - builder: (_, todoController, hasCount, __) { - return OutlinedButton( - style: OutlinedButton.styleFrom( - foregroundColor: Colors.red, - ), - onPressed: hasCount ? todoController.clearCompleted : null, - child: const Text('Clear completed'), - ); - }, - ), - ], - ), - ); - } -} diff --git a/packages/flutter_reactter/example/lib/examples/6_tree/controllers/tree_controller.dart b/packages/flutter_reactter/example/lib/examples/6_tree/controllers/tree_controller.dart new file mode 100644 index 00000000..2243fb5b --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/6_tree/controllers/tree_controller.dart @@ -0,0 +1,11 @@ +import 'package:examples/examples/6_tree/states/tree_list.dart'; +import 'package:examples/examples/6_tree/states/tree_node.dart'; + +class TreeController { + final root = TreeNode(); + final treeList = TreeList(); + + TreeController() { + treeList.add(root); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/6_tree/states/tree_list.dart b/packages/flutter_reactter/example/lib/examples/6_tree/states/tree_list.dart new file mode 100644 index 00000000..2a82c60c --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/6_tree/states/tree_list.dart @@ -0,0 +1,64 @@ +import 'dart:collection'; + +import 'package:examples/examples/6_tree/states/tree_node.dart'; +import 'package:flutter_reactter/reactter.dart'; + +class TreeList extends LinkedList with RtState { + TreeList._(); + + factory TreeList() { + return Rt.registerState(() => TreeList._()); + } + + @override + void add(TreeNode entry) { + update(() { + super.add(entry); + + entry.bind(this); + }); + } + + @override + void addFirst(TreeNode entry) { + update(() { + super.addFirst(entry); + + entry.bind(this); + }); + } + + @override + void addAll(Iterable entries) { + update(() { + super.addAll(entries); + + for (final entry in entries) { + entry.bind(this); + } + }); + } + + @override + bool remove(TreeNode entry) { + Rt.destroy(id: entry.id); + + final res = super.remove(entry); + + if (res) notify(); + + return res; + } + + @override + void dispose() { + clear(); + super.dispose(); + } + + @override + void clear() { + super.clear(); + if (!isDisposed) notify(); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/6_tree/states/tree_node.dart b/packages/flutter_reactter/example/lib/examples/6_tree/states/tree_node.dart new file mode 100644 index 00000000..8e5ce83c --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/6_tree/states/tree_node.dart @@ -0,0 +1,203 @@ +/// I challange you to do the same thing with another solution. +/// You will see that Reactter is faster, simple and easy to use. + +import 'dart:collection'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +class TreeNode extends LinkedListEntry with RtState { + /// A unique identifier for each instance of [TreeNode]. + static int _lastId = 0; + static String _getId() => (_lastId++).toString(); + + final String id = _getId(); + final TreeNode? parent; + + late final String path = parent != null ? "${parent!.path} > $id" : id; + late final int depth = parent != null ? parent!.depth + 1 : 0; + + @override + String? get debugLabel => id; + + @override + Map get debugInfo => { + 'id': id, + 'path': path, + 'depth': depth, + 'isExpanded': uIsExpanded.value, + 'count': uCount.value, + 'descendantsTotal': uDescendantsTotal.value, + 'total': uTotal.value, + 'parent': parent?.id, + 'children': uChildren.value, + }; + + final uChildren = UseState({}, debugLabel: 'uChildren'); + final uIsExpanded = UseState(true, debugLabel: 'uIsExpanded'); + final uCount = UseState(0, debugLabel: 'uCount'); + final uDescendantsTotal = UseState(0, debugLabel: 'uDescendantsTotal'); + late final uTotal = Rt.lazyState( + () => UseCompute( + () => uCount.value + uDescendantsTotal.value, + [uCount, uDescendantsTotal], + debugLabel: 'uTotal', + ), + this, + ); + + TreeNode._(this.parent) { + UseEffect( + _onIsExpandedChanged, + [uIsExpanded], + debugLabel: 'onIsExpandedChanged', + ); + + UseEffect( + _onChildrenChanged, + [uChildren], + debugLabel: 'onChildrenChanged', + ); + + if (parent != null) { + UseEffect( + _onTotalChanged, + [uTotal], + debugLabel: 'onTotalChanged', + ); + } + + /// Bind the instance to the parent or list + /// This way, the instance is automatically disposed including it's hooks + /// when the parent or list is destroyed. + if (parent != null) { + bind(parent!); + } else if (list != null) { + bind(list!); + } + } + + /// Create a new instance of [TreeNode] and register as a singleton. + /// This way, the instance can be accessed from anywhere in the application + /// and keep it in memory until it is destroyed. + factory TreeNode([TreeNode? parent]) { + return Rt.singleton( + () => Rt.registerState(() { + return TreeNode._(parent); + }), + id: _lastId.toString(), + )!; + } + + @override + void dispose() { + super.dispose(); + + /// It's need to destroy the instance because it's a singleton + /// and it's not automatically destroyed when it's removed from the parent. + Rt.destroy(id: id); + } + + @override + void unlink() { + if (list == null) return; + + final rList = list; + super.unlink(); + + if (rList is RtState) (rList as RtState).notify(); + } + + @override + void insertAfter(TreeNode entry) { + if (list == null) return; + + super.insertAfter(entry); + + if (list is RtState) (list as RtState).notify(); + } + + @override + void insertBefore(TreeNode entry) { + if (list == null) return; + + super.insertBefore(entry); + + if (list is RtState) (list as RtState).notify(); + } + + void addChild() { + Rt.batch(() { + final node = TreeNode(this); + + uChildren.value.add(node); + uChildren.notify(); + + uIsExpanded.value = true; + uIsExpanded.notify(); + }); + } + + bool remove() { + Rt.batch(() { + for (final node in uChildren.value.toList()) { + node.remove(); + } + + unlink(); + + parent?.uChildren.value.remove(this); + parent?.uChildren.notify(); + + if (!isDisposed) dispose(); + }); + + return list == null; + } + + void toggleExpansion() => uIsExpanded.value = !uIsExpanded.value; + + void increase() => uCount.value++; + + void decrease() => uCount.value--; + + void _onIsExpandedChanged() { + Rt.batch(() { + if (uIsExpanded.value) return _showChildren(); + + _hideChildren(); + }); + } + + void _onChildrenChanged() => _calculateDescendantsTotal(); + + void _onTotalChanged() => parent?._calculateDescendantsTotal(); + + void _calculateDescendantsTotal() { + uDescendantsTotal.value = uChildren.value.fold( + 0, + (acc, child) => acc + child.uTotal.value, + ); + } + + void _showChildren() { + Rt.batch(() { + for (final node in uChildren.value) { + if (node.list != null) continue; + + insertAfter(node); + + if (node.uIsExpanded.value) node._showChildren(); + } + }); + } + + void _hideChildren() { + Rt.batch(() { + for (final node in uChildren.value) { + if (node.list == null) continue; + + node._hideChildren(); + node.unlink(); + } + }); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/6_tree/tree_page.dart b/packages/flutter_reactter/example/lib/examples/6_tree/tree_page.dart new file mode 100644 index 00000000..1c39be2d --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/6_tree/tree_page.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +import 'package:examples/examples/6_tree/controllers/tree_controller.dart'; +import 'package:examples/examples/6_tree/widgets/tree_item.dart'; + +class Test extends ChangeNotifier { + final int value; + + Test(this.value); +} + +class TreePage extends StatelessWidget { + const TreePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final listKey = GlobalKey(); + return RtProvider( + TreeController.new, + builder: (context, treeController, _) { + return Scaffold( + appBar: AppBar( + title: const Text("Tree widget"), + ), + body: CustomScrollView( + slivers: [ + RtSelector( + selector: (treeController, watch) { + return watch(treeController.treeList).length; + }, + builder: (context, treeController, length, _) { + return SliverList( + key: listKey, + delegate: SliverChildBuilderDelegate( + (context, index) { + final treeNode = + treeController.treeList.elementAt(index); + + return TreeItem( + key: ObjectKey(treeNode), + treeNode: treeNode, + ); + }, + childCount: treeController.treeList.length, + ), + ); + }, + ), + ], + ), + ); + }, + ); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/3_tree/widgets/custom_icon_button.dart b/packages/flutter_reactter/example/lib/examples/6_tree/widgets/custom_icon_button.dart similarity index 92% rename from packages/flutter_reactter/example/lib/examples/3_tree/widgets/custom_icon_button.dart rename to packages/flutter_reactter/example/lib/examples/6_tree/widgets/custom_icon_button.dart index 637acd7a..c1bf995e 100644 --- a/packages/flutter_reactter/example/lib/examples/3_tree/widgets/custom_icon_button.dart +++ b/packages/flutter_reactter/example/lib/examples/6_tree/widgets/custom_icon_button.dart @@ -13,7 +13,7 @@ class CustomIconButton extends StatelessWidget { @override Widget build(BuildContext context) { return IconButton( - padding: const EdgeInsets.all(6), + padding: const EdgeInsets.all(2), splashRadius: 18, iconSize: 24, constraints: const BoxConstraints.tightForFinite(), diff --git a/packages/flutter_reactter/example/lib/examples/6_tree/widgets/tree_item.dart b/packages/flutter_reactter/example/lib/examples/6_tree/widgets/tree_item.dart new file mode 100644 index 00000000..e77d7fad --- /dev/null +++ b/packages/flutter_reactter/example/lib/examples/6_tree/widgets/tree_item.dart @@ -0,0 +1,302 @@ +// ignore_for_file: avoid_renaming_method_parameters + +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +import 'package:examples/examples/6_tree/states/tree_node.dart'; +import 'package:examples/examples/6_tree/widgets/custom_icon_button.dart'; + +class TreeItem extends RtComponent { + final TreeNode treeNode; + + @override + get builder => () => treeNode; + + @override + String? get id => treeNode.id; + + String get idStr => id != null ? 'ID: $id' : "root"; + + const TreeItem({ + Key? key, + required this.treeNode, + }) : super(key: key); + + @override + Widget render(context, treeNode) { + return SizedBox( + height: 36, + child: InkWell( + onTap: () => _openDialog(context), + child: Stack( + clipBehavior: Clip.none, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(width: 22.0 * treeNode.depth), + _buildNode(), + const Expanded( + child: Divider( + height: 2, + thickness: 2, + ), + ), + _buildCount(), + ], + ), + ], + ), + ), + _buildTreeLine(), + ], + ), + ), + ); + } + + Future _openDialog(BuildContext context) { + return showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text("About the node($idStr)"), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text("Path:"), + const SizedBox(width: 8), + Expanded( + child: Text( + treeNode.path, + style: Theme.of(context).textTheme.bodySmall, + maxLines: 3, + ), + ), + ], + ), + Row( + children: [ + const Text("Children:"), + const SizedBox(width: 8), + Text( + "${treeNode.uChildren.value.length}", + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + Row( + children: [ + const Text("Count:"), + const SizedBox(width: 8), + Text( + "${treeNode.uCount.value}", + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + Row( + children: [ + const Text("Descendants Total:"), + const SizedBox(width: 8), + Text( + "${treeNode.uDescendantsTotal.value}", + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + Row( + children: [ + const Text("Total (Count + Descendants Total):"), + const SizedBox(width: 8), + Text( + "${treeNode.uTotal.value}", + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ], + ), + ); + }, + ); + } + + Widget _buildNode() { + return RtWatcher((context, watch) { + final children = watch(treeNode.uChildren).value; + final isExpanded = watch(treeNode.uIsExpanded).value; + + return Container( + margin: EdgeInsets.only( + left: children.isEmpty ? 28 : 0, + ), + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).dividerColor, + width: 2, + ), + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + ), + child: Row( + children: [ + if (children.isNotEmpty) + CustomIconButton( + icon: Transform.rotate( + angle: isExpanded ? 0 : -pi / 2, + child: const Icon(Icons.expand_circle_down_rounded), + ), + onPressed: treeNode.toggleExpansion, + ), + CustomIconButton( + icon: const Icon(Icons.add_circle), + onPressed: treeNode.addChild, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 2).copyWith( + right: treeNode.parent == null ? 8 : 0, + ), + child: Text( + idStr, + style: Theme.of(context).textTheme.bodySmall, + ), + ), + if (treeNode.parent != null) + CustomIconButton( + icon: const Icon(Icons.cancel), + onPressed: treeNode.remove, + ), + ], + ), + ); + }); + } + + Widget _buildCount() { + return Builder( + builder: (context) { + return Container( + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).dividerColor, + width: 2, + ), + ), + ), + child: Row( + children: [ + CustomIconButton( + icon: const Icon(Icons.indeterminate_check_box_rounded), + onPressed: treeNode.decrease, + ), + RtWatcher((context, watch) { + return Text( + "${watch(treeNode.uCount).value} (${watch(treeNode.uTotal).value})", + style: Theme.of(context).textTheme.bodySmall, + ); + }), + CustomIconButton( + icon: const Icon(Icons.add_box), + onPressed: treeNode.increase, + ), + ], + ), + ); + }, + ); + } + + Widget _buildTreeLine() { + if (treeNode.parent == null) { + return const SizedBox(); + } + + return Positioned( + top: -2, + left: -6, + bottom: 2, + child: Align( + alignment: Alignment.topLeft, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ..._buildParentLines(treeNode, []), + RtWatcher((context, watch) { + return SizedBox( + height: treeNode.parent != null && + watch(treeNode.parent!.uChildren).value.first == + treeNode + ? 21 + : null, + child: const VerticalDivider( + width: 2, + thickness: 2, + ), + ); + }), + Column( + mainAxisSize: MainAxisSize.max, + children: [ + const SizedBox(height: 19), + RtWatcher((context, watch) { + return SizedBox( + width: watch(treeNode.uChildren).value.isEmpty ? 32 : 4, + child: const Divider( + height: 2, + thickness: 2, + ), + ); + }), + ], + ), + ], + ), + ), + ); + } + + List _buildParentLines(TreeNode node, List parentLines) { + if (node.parent == null) { + return parentLines; + } + + parentLines.insert( + 0, + RtWatcher((context, watch) { + final parentFromParent = node.parent?.parent; + final hasParentParent = parentFromParent != null; + final isParentFirst = hasParentParent && + watch(parentFromParent.uChildren).value.first == node.parent; + + if (isParentFirst) { + return const SizedBox(width: 22); + } + + return Container( + width: 22, + alignment: Alignment.centerLeft, + child: const VerticalDivider( + width: 2, + thickness: 2, + ), + ); + }), + ); + + return _buildParentLines(node.parent!, parentLines); + } +} diff --git a/packages/flutter_reactter/example/lib/examples/7_animation/animation_page.dart b/packages/flutter_reactter/example/lib/examples/7_animation/animation_page.dart index 365a54a1..f3ad33d3 100644 --- a/packages/flutter_reactter/example/lib/examples/7_animation/animation_page.dart +++ b/packages/flutter_reactter/example/lib/examples/7_animation/animation_page.dart @@ -24,9 +24,9 @@ class AnimationPage extends StatelessWidget { direction: Axis.horizontal, children: [ RtSelector( - selector: (inst, $) { + selector: (inst, watch) { return inst.checkIfPlaying( - $(inst.uBorderRadiusAnimation.uControl).value, + watch(inst.uBorderRadiusAnimation.uControl).value, ); }, child: RtConsumer( @@ -51,9 +51,9 @@ class AnimationPage extends StatelessWidget { }, ), RtSelector( - selector: (inst, $) { + selector: (inst, watch) { return inst.checkIfPlaying( - $(inst.uSizeAnimation.uControl).value, + watch(inst.uSizeAnimation.uControl).value, ); }, child: RtConsumer( @@ -77,9 +77,9 @@ class AnimationPage extends StatelessWidget { }, ), RtSelector( - selector: (inst, $) { + selector: (inst, watch) { return inst.checkIfPlaying( - $(inst.uColorAnimation.uControl).value, + watch(inst.uColorAnimation.uControl).value, ); }, child: RtConsumer( @@ -101,11 +101,11 @@ class AnimationPage extends StatelessWidget { }, ), RtSelector( - selector: (inst, $) { + selector: (inst, watch) { return [ - $(inst.uSizeAnimation.uControl).value, - $(inst.uBorderRadiusAnimation.uControl).value, - $(inst.uColorAnimation.uControl).value, + watch(inst.uSizeAnimation.uControl).value, + watch(inst.uBorderRadiusAnimation.uControl).value, + watch(inst.uColorAnimation.uControl).value, ].every(inst.checkIfPlaying); }, child: RtConsumer( diff --git a/packages/flutter_reactter/example/lib/examples/7_animation/controllers/animation_controller.dart b/packages/flutter_reactter/example/lib/examples/7_animation/controllers/animation_controller.dart index e69109e8..1785b777 100644 --- a/packages/flutter_reactter/example/lib/examples/7_animation/controllers/animation_controller.dart +++ b/packages/flutter_reactter/example/lib/examples/7_animation/controllers/animation_controller.dart @@ -11,6 +11,7 @@ class AnimationController { control: AnimationControl.mirror, curve: Curves.easeOut, ), + debugLabel: 'uSizeAnimation', ); final uBorderRadiusAnimation = UseAnimation( @@ -23,6 +24,7 @@ class AnimationController { control: AnimationControl.mirror, curve: Curves.easeOut, ), + debugLabel: 'uBorderRadiusAnimation', ); final uColorAnimation = UseAnimation( @@ -63,6 +65,7 @@ class AnimationController { control: AnimationControl.mirror, curve: Curves.easeInOut, ), + debugLabel: 'uColorAnimation', ); void resumeAllAnimation() { diff --git a/packages/flutter_reactter/example/lib/examples/7_animation/hooks/use_animation.dart b/packages/flutter_reactter/example/lib/examples/7_animation/hooks/use_animation.dart index 4672e6e0..0427a581 100644 --- a/packages/flutter_reactter/example/lib/examples/7_animation/hooks/use_animation.dart +++ b/packages/flutter_reactter/example/lib/examples/7_animation/hooks/use_animation.dart @@ -4,81 +4,26 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_reactter/flutter_reactter.dart'; -/// Defines different control options for playing an animation. Each option -/// represents a specific behavior for the animation playback: -enum AnimationControl { - /// Plays the animation from the current position to the end. - play, - - /// Plays the animation from the current position reverse to the start. - playReverse, - - /// Reset the position of the animation to `0.0` and starts playing - /// to the end. - playFromStart, - - /// Reset the position of the animation to `1.0` and starts playing - /// reverse to the start. - playReverseFromEnd, - - /// Endlessly plays the animation from the start to the end. - loop, - - /// Endlessly plays the animation from the start to the end, then - /// it plays reverse to the start, then forward again and so on. - mirror, - - /// Stops the animation at the current position. - pause, - - /// Stops and resets animation. - stop, -} - -/// The `AnimationOptions` class represents the options for an animation, -/// including the tween, duration, control, curve, start position, -/// frames per second, delay, and animation status listener. -class AnimationOptions { - final Animatable tween; - final Duration duration; - final AnimationControl control; - final Curve curve; - final double startPosition; - final int? fps; - final Duration delay; - final AnimationStatusListener? animationStatusListener; - - const AnimationOptions({ - required this.tween, - this.control = AnimationControl.play, - this.duration = const Duration(seconds: 1), - this.curve = Curves.linear, - this.startPosition = 0.0, - this.fps, - this.delay = Duration.zero, - this.animationStatusListener, - }); -} - -class UseAnimation extends RtHook implements TickerProvider { +class UseAnimation extends RtHook + with AutoDispatchEffect + implements TickerProvider { @override - @protected final $ = RtHook.$register; late final uTween = Rt.lazyState( - () => UseState(options.tween), + () => UseState(options.tween, debugLabel: 'uTween'), this, ); late final uControl = Rt.lazyState( - () => UseState(options.control), + () => UseState(options.control, debugLabel: 'uControl'), this, ); late final uDuration = Rt.lazyState( - () => UseState(options.duration), + () => UseState(options.duration, debugLabel: 'uDuration'), this, ); late final uCurve = Rt.lazyState( - () => UseState(options.curve), + () => UseState(options.curve, debugLabel: 'uCurve'), this, ); @@ -93,15 +38,31 @@ class UseAnimation extends RtHook implements TickerProvider { T get value => _animation.value; + @override + final String? debugLabel; + + @override + Map get debugInfo => { + 'value': value, + 'duration': uDuration.value, + 'control': uControl.value, + 'curve': uCurve.value, + }; + final AnimationOptions options; - UseAnimation(this.options) { + UseAnimation(this.options, {this.debugLabel}); + + @override + void initHook() { controller.addStatusListener(_onAnimationStatus); _buildAnimation(); UseEffect(_addFrameLimitingUpdater, []); UseEffect(_rebuild, [uTween, uControl, uCurve]); UseEffect(() => controller.duration = uDuration.value, [uDuration]); + + super.initHook(); } @override @@ -249,6 +210,62 @@ class UseAnimation extends RtHook implements TickerProvider { } } +/// Defines different control options for playing an animation. Each option +/// represents a specific behavior for the animation playback: +enum AnimationControl { + /// Plays the animation from the current position to the end. + play, + + /// Plays the animation from the current position reverse to the start. + playReverse, + + /// Reset the position of the animation to `0.0` and starts playing + /// to the end. + playFromStart, + + /// Reset the position of the animation to `1.0` and starts playing + /// reverse to the start. + playReverseFromEnd, + + /// Endlessly plays the animation from the start to the end. + loop, + + /// Endlessly plays the animation from the start to the end, then + /// it plays reverse to the start, then forward again and so on. + mirror, + + /// Stops the animation at the current position. + pause, + + /// Stops and resets animation. + stop, +} + +/// The `AnimationOptions` class represents the options for an animation, +/// including the tween, duration, control, curve, start position, +/// frames per second, delay, and animation status listener. +class AnimationOptions { + final Animatable tween; + final Duration duration; + final AnimationControl control; + final Curve curve; + final double startPosition; + final int? fps; + final Duration delay; + final AnimationStatusListener? animationStatusListener; + + const AnimationOptions({ + required this.tween, + this.control = AnimationControl.play, + this.duration = const Duration(seconds: 1), + this.curve = Curves.linear, + this.startPosition = 0.0, + this.fps, + this.delay = Duration.zero, + this.animationStatusListener, + }); +} + /// Provides additional methods for playing, reversing, looping, and mirroring animations. class _UseAnimationController extends AnimationController { bool _skipStopMirror = false; diff --git a/packages/flutter_reactter/example/lib/main.dart b/packages/flutter_reactter/example/lib/main.dart index 96194c5f..3f256007 100644 --- a/packages/flutter_reactter/example/lib/main.dart +++ b/packages/flutter_reactter/example/lib/main.dart @@ -1,6 +1,101 @@ -import 'package:examples/app.dart'; +// This file contains the main entry point of the example app. +// It also contains a small example using a global state for the theme mode. +// The main app widget contains a list of examples that can be navigated to. +// Each example has its own page that demonstrates the use of the Reactter package. +// For viewing the examples online, you can visit https://zapp.run/pub/flutter_reactter + +import 'package:examples/custom_list.dart'; +import 'package:examples/title_oute_observer.dart'; +import 'package:examples/examples.dart'; +import 'package:examples/page_404.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; + +// Global state for theme mode +final uThemeMode = UseState(ThemeMode.dark, debugLabel: 'uThemeMode'); + +class IndexPage extends StatelessWidget { + const IndexPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Title( + title: 'Reactter | Examples', + color: Theme.of(context).primaryColor, + child: Scaffold( + appBar: AppBar( + title: const Text("Examples"), + actions: [ + IconButton( + tooltip: "Change theme mode", + icon: RtWatcher((context, watch) { + // Get and watch the global state + // This will rebuild the icon button when the global state changes + // and update the icon to reflect the current theme mode + final themeMode = watch(uThemeMode).value; + + return Icon( + // Use the value of the global state to determine the icon + themeMode == ThemeMode.dark + ? Icons.light_mode + : Icons.dark_mode, + ); + }), + onPressed: () { + // Change the value of the global state to toggle the theme mode + uThemeMode.value = uThemeMode.value == ThemeMode.dark + ? ThemeMode.light + : ThemeMode.dark; + }, + ), + ], + ), + body: CustomList(items: examples), + ), + ); + } +} + +const indexRoute = '/'; +final indexPageKey = GlobalKey(); +final routes = { + indexRoute: (context) => IndexPage(key: indexPageKey), + for (final e in examples) e.routeName: (context) => e.renderPage(), +}; + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return RtWatcher((context, watch) { + // Get and watch the global state + // This will rebuild the app when the global state changes + final themeMode = watch(uThemeMode).value; + + return MaterialApp( + debugShowCheckedModeBanner: false, + // Use the value of the global state + themeMode: themeMode, + theme: ThemeData.light().copyWith( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + darkTheme: ThemeData.dark().copyWith( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + initialRoute: indexRoute, + routes: routes, + navigatorObservers: [TitleRouteObserver()], + onUnknownRoute: (RouteSettings settings) { + return MaterialPageRoute( + builder: (_) => const Page404(), + ); + }, + ); + }); + } +} -Future main() async { +void main() { runApp(const MyApp()); } diff --git a/packages/flutter_reactter/example/lib/app.dart b/packages/flutter_reactter/example/lib/title_oute_observer.dart similarity index 59% rename from packages/flutter_reactter/example/lib/app.dart rename to packages/flutter_reactter/example/lib/title_oute_observer.dart index 317bca00..dfefcf6c 100644 --- a/packages/flutter_reactter/example/lib/app.dart +++ b/packages/flutter_reactter/example/lib/title_oute_observer.dart @@ -1,36 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:examples/examples_page.dart'; -import 'package:examples/page_404.dart'; - -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: ThemeData.light().copyWith( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - darkTheme: ThemeData.dark().copyWith( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - initialRoute: '/', - routes: { - '/': (context) => const ExamplesPage(), - for (final e in examples) e.routeName: (context) => e.renderPage(), - }, - onUnknownRoute: (RouteSettings settings) { - return MaterialPageRoute( - builder: (_) => const Page404(), - ); - }, - navigatorObservers: [TitleRouteObserver()], - ); - } -} +import 'package:examples/examples.dart'; class TitleRouteObserver extends RouteObserver { static Map titleRoute = { diff --git a/packages/flutter_reactter/example/pubspec.lock b/packages/flutter_reactter/example/pubspec.lock deleted file mode 100644 index a944f7ba..00000000 --- a/packages/flutter_reactter/example/pubspec.lock +++ /dev/null @@ -1,517 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: "405666cd3cf0ee0a48d21ec67e65406aad2c726d9fa58840d3375e7bdcd32a07" - url: "https://pub.dev" - source: hosted - version: "60.0.0" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "1952250bd005bacb895a01bf1b4dc00e3ba1c526cf47dca54dfe24979c65f5b3" - url: "https://pub.dev" - source: hosted - version: "5.12.0" - analyzer_plugin: - dependency: transitive - description: - name: analyzer_plugin - sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d - url: "https://pub.dev" - source: hosted - version: "0.11.2" - args: - dependency: transitive - description: - name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 - url: "https://pub.dev" - source: hosted - version: "2.4.2" - async: - dependency: transitive - description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - characters: - dependency: transitive - description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff - url: "https://pub.dev" - source: hosted - version: "2.0.3" - ci: - dependency: transitive - description: - name: ci - sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" - url: "https://pub.dev" - source: hosted - version: "0.1.0" - cli_util: - dependency: transitive - description: - name: cli_util - sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 - url: "https://pub.dev" - source: hosted - version: "0.4.0" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" - source: hosted - version: "1.1.1" - collection: - dependency: transitive - description: - name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a - url: "https://pub.dev" - source: hosted - version: "1.18.0" - convert: - dependency: transitive - description: - name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" - url: "https://pub.dev" - source: hosted - version: "3.1.1" - crypto: - dependency: transitive - description: - name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab - url: "https://pub.dev" - source: hosted - version: "3.0.3" - custom_lint: - dependency: transitive - description: - name: custom_lint - sha256: b09a939c3bb062fdf072aa4c8eb5447231338854c4fefb23e4c9f7cef41cb3ff - url: "https://pub.dev" - source: hosted - version: "0.5.0" - custom_lint_builder: - dependency: transitive - description: - name: custom_lint_builder - sha256: f1430048ccddcd82cbf7722fce7701530fd535b82e91f45b0c81ed07814143bf - url: "https://pub.dev" - source: hosted - version: "0.5.0" - custom_lint_core: - dependency: transitive - description: - name: custom_lint_core - sha256: a39e98cba1d96076e2e5c96aad582a776909c78b4d7480d0efdcc3791b225c1b - url: "https://pub.dev" - source: hosted - version: "0.5.0" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - file: - dependency: transitive - description: - name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" - url: "https://pub.dev" - source: hosted - version: "6.1.4" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 - url: "https://pub.dev" - source: hosted - version: "2.0.3" - flutter_reactter: - dependency: "direct main" - description: - name: flutter_reactter - sha256: "82eaa9fd97d3520656bcdc07ff701d57a382b8e8620e1d585fb9fa40103c9183" - url: "https://pub.dev" - source: hosted - version: "7.3.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - freezed_annotation: - dependency: transitive - description: - name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d - url: "https://pub.dev" - source: hosted - version: "2.4.1" - glob: - dependency: transitive - description: - name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - hotreloader: - dependency: transitive - description: - name: hotreloader - sha256: "728c0613556c1d153f7e7f4a367cffacc3f5a677d7f6497a1c2b35add4e6dacf" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - http: - dependency: "direct main" - description: - name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" - url: "https://pub.dev" - source: hosted - version: "0.13.6" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - intl: - dependency: "direct main" - description: - name: intl - sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" - url: "https://pub.dev" - source: hosted - version: "0.17.0" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 - url: "https://pub.dev" - source: hosted - version: "4.8.1" - lints: - dependency: transitive - description: - name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" - url: "https://pub.dev" - source: hosted - version: "2.0.1" - logging: - dependency: transitive - description: - name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" - url: "https://pub.dev" - source: hosted - version: "0.12.16" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" - url: "https://pub.dev" - source: hosted - version: "0.8.0" - meta: - dependency: transitive - description: - name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 - url: "https://pub.dev" - source: hosted - version: "1.11.0" - package_config: - dependency: transitive - description: - name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - path: - dependency: transitive - description: - name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" - url: "https://pub.dev" - source: hosted - version: "1.8.3" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d - url: "https://pub.dev" - source: hosted - version: "2.1.6" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 - url: "https://pub.dev" - source: hosted - version: "1.2.3" - reactter: - dependency: transitive - description: - name: reactter - sha256: ad773608411600531cbc3513ae05a4ce91610ccb93701f24e79d26999b663f04 - url: "https://pub.dev" - source: hosted - version: "7.3.0" - reactter_lint: - dependency: "direct dev" - description: - name: reactter_lint - sha256: "14e1d927b7e4f9e079a480582d1f34d9b0fd54d121e6f64c29d034e35c745f39" - url: "https://pub.dev" - source: hosted - version: "0.1.0" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" - url: "https://pub.dev" - source: hosted - version: "0.27.7" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" - url: "https://pub.dev" - source: hosted - version: "1.11.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" - url: "https://pub.dev" - source: hosted - version: "0.6.0" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" - url_launcher: - dependency: "direct main" - description: - name: url_launcher - sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 - url: "https://pub.dev" - source: hosted - version: "6.1.11" - url_launcher_android: - dependency: transitive - description: - name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" - url: "https://pub.dev" - source: hosted - version: "6.2.0" - url_launcher_ios: - dependency: transitive - description: - name: url_launcher_ios - sha256: "4ac97281cf60e2e8c5cc703b2b28528f9b50c8f7cebc71df6bdf0845f647268a" - url: "https://pub.dev" - source: hosted - version: "6.2.0" - url_launcher_linux: - dependency: transitive - description: - name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - url_launcher_macos: - dependency: transitive - description: - name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 - url: "https://pub.dev" - source: hosted - version: "3.1.0" - url_launcher_platform_interface: - dependency: transitive - description: - name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - url_launcher_web: - dependency: transitive - description: - name: url_launcher_web - sha256: ba140138558fcc3eead51a1c42e92a9fb074a1b1149ed3c73e66035b2ccd94f2 - url: "https://pub.dev" - source: hosted - version: "2.0.19" - url_launcher_windows: - dependency: transitive - description: - name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" - url: "https://pub.dev" - source: hosted - version: "3.1.0" - uuid: - dependency: transitive - description: - name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" - url: "https://pub.dev" - source: hosted - version: "3.0.7" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 - url: "https://pub.dev" - source: hosted - version: "11.10.0" - watcher: - dependency: transitive - description: - name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" - url: "https://pub.dev" - source: hosted - version: "1.0.2" - yaml: - dependency: transitive - description: - name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" - url: "https://pub.dev" - source: hosted - version: "3.1.2" -sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.7.0" diff --git a/packages/flutter_reactter/example/pubspec.yaml b/packages/flutter_reactter/example/pubspec.yaml index d2cd002e..4dab4dcb 100644 --- a/packages/flutter_reactter/example/pubspec.yaml +++ b/packages/flutter_reactter/example/pubspec.yaml @@ -1,10 +1,10 @@ name: examples description: Reactter examples -version: 2.3.0+1 +version: 3.0.0+2 publish_to: "none" environment: - sdk: ">=2.19.0 <4.0.0" + sdk: ">=2.19.2 <4.0.0" dependencies: flutter: @@ -12,11 +12,14 @@ dependencies: http: ^0.13.6 url_launcher: ^6.1.2 intl: ^0.17.0 - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev + # flutter_reactter: + # path: ../../flutter_reactter dev_dependencies: - flutter_lints: ^2.0.2 - reactter_lint: ^0.1.0 + custom_lint: ^0.5.0 + flutter_lints: ^2.0.3 + reactter_lint: ^1.0.0 flutter: uses-material-design: true diff --git a/packages/flutter_reactter/example/test/widget_test.dart b/packages/flutter_reactter/example/test/widget_test.dart new file mode 100644 index 00000000..d1ac2333 --- /dev/null +++ b/packages/flutter_reactter/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// // This is a basic Flutter widget test. +// // +// // To perform an interaction with a widget in your test, use the WidgetTester +// // utility in the flutter_test package. For example, you can send tap and scroll +// // gestures. You can also use WidgetTester to find child widgets in the widget +// // tree, read text, and verify that the values of widget properties are correct. + +// import 'package:flutter/material.dart'; +// import 'package:flutter_test/flutter_test.dart'; + +// import 'package:example/main.dart'; + +// void main() { +// testWidgets('Counter increments smoke test', (WidgetTester tester) async { +// // Build our app and trigger a frame. +// await tester.pumpWidget(const MyApp()); + +// // Verify that our counter starts at 0. +// expect(find.text('0'), findsOneWidget); +// expect(find.text('1'), findsNothing); + +// // Tap the '+' icon and trigger a frame. +// await tester.tap(find.byIcon(Icons.add)); +// await tester.pump(); + +// // Verify that our counter has incremented. +// expect(find.text('0'), findsNothing); +// expect(find.text('1'), findsOneWidget); +// }); +// } diff --git a/packages/flutter_reactter/lib/flutter_reactter.dart b/packages/flutter_reactter/lib/flutter_reactter.dart index a8921473..75c4c757 100644 --- a/packages/flutter_reactter/lib/flutter_reactter.dart +++ b/packages/flutter_reactter/lib/flutter_reactter.dart @@ -1,7 +1,6 @@ library flutter_reactter; -export 'src/framework.dart' - show ReactterDependencyNotFoundException, RtDependencyNotFoundException; +export 'src/framework.dart' show RtDependencyNotFoundException; export 'src/extensions.dart'; export 'src/types.dart'; export 'src/widgets.dart' hide ProviderWrapper; diff --git a/packages/flutter_reactter/lib/reactter.dart b/packages/flutter_reactter/lib/reactter.dart index 90f847af..27832dcc 100644 --- a/packages/flutter_reactter/lib/reactter.dart +++ b/packages/flutter_reactter/lib/reactter.dart @@ -1,6 +1 @@ -export 'package:reactter/reactter.dart' hide Rt; -import 'package:flutter/foundation.dart'; -import 'package:reactter/reactter.dart' as r show Rt; - -// ignore: non_constant_identifier_names -final Rt = r.Rt..isLogEnable = kDebugMode; +export 'package:reactter/reactter.dart'; diff --git a/packages/flutter_reactter/lib/src/extensions/build_context_extension.dart b/packages/flutter_reactter/lib/src/extensions/build_context_extension.dart index d0438318..cfad86f0 100644 --- a/packages/flutter_reactter/lib/src/extensions/build_context_extension.dart +++ b/packages/flutter_reactter/lib/src/extensions/build_context_extension.dart @@ -8,7 +8,7 @@ extension ReactterBuildContextExtension on BuildContext { /// This evaluation only occurs if one of the selected [RtState]s gets updated, /// or by the dependency if the [selector] does not have any selected [RtState]s. /// - /// The [selector] callback has a two arguments, the first one is + /// The [selector] callback has two parameters, the first one is /// the dependency of [T] type which is obtained from the closest ancestor /// of [RtProvider] and the second one is a [Select] function which /// allows to wrapper any [RtState]s to listen. diff --git a/packages/flutter_reactter/lib/src/framework/dependency.dart b/packages/flutter_reactter/lib/src/framework/dependency.dart index 89b39ab2..24fd0a76 100644 --- a/packages/flutter_reactter/lib/src/framework/dependency.dart +++ b/packages/flutter_reactter/lib/src/framework/dependency.dart @@ -3,10 +3,45 @@ part of '../framework.dart'; abstract class Dependency { T? _instance; final Set _states; + bool _isDirty = false; + bool get isDirty => _isDirty; + CallbackEvent? _listener; - Dependency(this._instance, this._states); + Dependency(this._instance, this._states) { + listen(markDirty); + } + + MasterDependency toMaster(); + + void commitToMaster(MasterDependency masterDependency); + + @mustCallSuper + void markDirty() { + _isDirty = true; + unlisten(); + } + + void listen(covariant void Function() callback); - MasterDependency makeMaster(); + @mustCallSuper + void unlisten() { + _listener = null; + } + + @mustCallSuper + void dispose() { + unlisten(); + } + + CallbackEvent _buildListenerCallback(void Function() callback) { + unlisten(); + + return _listener = (instanceOrState, _) { + callback(); + markDirty(); + unlisten(); + }; + } } class MasterDependency extends Dependency { @@ -19,58 +54,150 @@ class MasterDependency extends Dependency { }) : _selects = selects.toSet(), super(instance, states.toSet()); - void putDependency(Dependency dependency) { - if (dependency is InstanceDependency) { - _instance = dependency._instance as T; - return; + void putDependency(Dependency dependency) { + dependency.commitToMaster(this); + } + + @override + void dispose() { + unlisten(); + + for (final select in _selects) { + select.unlisten(); + } + + super.dispose(); + } + + @override + void listen(void Function() callback) { + final eventListener = _buildListenerCallback(callback); + + if (_instance != null) { + Rt.on(_instance, Lifecycle.didUpdate, eventListener); + } + + for (final state in _states) { + Rt.on(state, Lifecycle.didUpdate, eventListener); + } + + for (final select in _selects) { + select.listen(callback); } + } + + @override + void unlisten() { + if (_listener == null) return; - if (dependency is StatesDependency) { - _states.addAll(dependency._states); - return; + if (_instance != null) { + Rt.off(_instance, Lifecycle.didUpdate, _listener!); } - if (dependency is SelectDependency) { - _instance = dependency._instance as T; - _selects.add(dependency); + for (final state in _states) { + Rt.off(state, Lifecycle.didUpdate, _listener!); } + + super.unlisten(); } // coverage:ignore-start @override - MasterDependency makeMaster() { - throw UnimplementedError(); + void commitToMaster(MasterDependency masterDependency) { + assert(true, 'This method should not be called'); } // coverage:ignore-end + + // coverage:ignore-start + @override + MasterDependency toMaster() => this; + // coverage:ignore-end } class InstanceDependency extends Dependency { InstanceDependency(T instance) : super(instance, const {}); @override - MasterDependency makeMaster() => MasterDependency( - instance: _instance, - ); + MasterDependency toMaster() { + final masterDependency = MasterDependency( + instance: _instance, + ); + + if (isDirty) masterDependency.markDirty(); + + return masterDependency; + } + + @override + void commitToMaster(MasterDependency masterDependency) { + if (_instance != null) masterDependency._instance = _instance; + } + + @override + void listen(void Function() callback) { + final eventListener = _buildListenerCallback(callback); + + Rt.on(_instance, Lifecycle.didUpdate, eventListener); + } + + @override + void unlisten() { + if (_listener == null) return; + + Rt.off(_instance, Lifecycle.didUpdate, _listener!); + super.unlisten(); + } } class StatesDependency extends Dependency { StatesDependency(Set states) : super(null, states); @override - MasterDependency makeMaster() => MasterDependency( - states: _states, - ); + MasterDependency toMaster() { + final masterDependency = MasterDependency( + states: _states, + ); + + if (isDirty) masterDependency.markDirty(); + + return masterDependency; + } + + @override + void commitToMaster(MasterDependency masterDependency) { + masterDependency._states.addAll(_states); + } + + @override + void listen(void Function() callback) { + final eventListener = _buildListenerCallback(callback); + + for (final state in _states) { + Rt.on(state, Lifecycle.didUpdate, eventListener); + } + } + + @override + void unlisten() { + if (_listener == null) return; + + for (final state in _states) { + Rt.off(state, Lifecycle.didUpdate, _listener!); + } + + super.unlisten(); + } } class SelectDependency extends Dependency { - final T _instanceSelect; + final T instanceSelect; final Selector computeValue; late final dynamic value; SelectDependency({ required T instance, required this.computeValue, - }) : _instanceSelect = instance, + }) : instanceSelect = instance, super(null, {}) { value = resolve(true); if (_states.isEmpty) _instance = instance; @@ -78,7 +205,7 @@ class SelectDependency extends Dependency { dynamic resolve([bool isWatchState = false]) { return computeValue( - _instanceSelect, + instanceSelect, isWatchState ? watchState : skipWatchState, ); } @@ -88,10 +215,58 @@ class SelectDependency extends Dependency { return state; } - static S skipWatchState(S s) => s; + S skipWatchState(S s) => s; + + @override + MasterDependency toMaster() { + return MasterDependency(selects: {this}); + } + + @override + void commitToMaster(MasterDependency masterDependency) { + if (_instance != null) masterDependency._instance = _instance; + + masterDependency._selects.add(this); + } + + @override + CallbackEvent _buildListenerCallback(void Function() callback) { + unlisten(); + + return _listener = (instanceOrState, _) { + if (value == resolve()) return; + + callback(); + markDirty(); + unlisten(); + }; + } @override - MasterDependency makeMaster() => MasterDependency( - selects: {this}, - ); + void listen(void Function() callback) { + final eventListener = _buildListenerCallback(callback); + + if (_instance != null) { + Rt.on(_instance, Lifecycle.didUpdate, eventListener); + } + + for (final state in _states) { + Rt.on(state, Lifecycle.didUpdate, eventListener); + } + } + + @override + void unlisten() { + if (_listener == null) return; + + if (_instance != null) { + Rt.off(_instance, Lifecycle.didUpdate, _listener!); + } + + for (final state in _states) { + Rt.off(state, Lifecycle.didUpdate, _listener!); + } + + super.unlisten(); + } } diff --git a/packages/flutter_reactter/lib/src/framework/nested.dart b/packages/flutter_reactter/lib/src/framework/nested.dart index 8bac3a63..fba07dff 100644 --- a/packages/flutter_reactter/lib/src/framework/nested.dart +++ b/packages/flutter_reactter/lib/src/framework/nested.dart @@ -16,7 +16,6 @@ class NestedWidget extends StatelessWidget { @override NestedElement createElement() => NestedElement(this); - // coverage:ignore-start @override Widget build(BuildContext context) => throw StateError('handled internally'); @@ -48,31 +47,32 @@ class NestedElement extends StatelessElement { } } - WrapperWidget? _wrappedChild; - WrapperWidget? get wrappedChild => _wrappedChild; - set wrappedChild(WrapperWidget? value) { - if (_wrappedChild != value) { - _wrappedChild = value; + WrapperWidget? _wrappedWidget; + WrapperWidget? get wrappedWidget => _wrappedWidget; + set wrappedWidget(WrapperWidget? value) { + if (_wrappedWidget != value) { + _wrappedWidget?.dispose(); + _wrappedWidget = value; markNeedsBuild(); } } @override void mount(Element? parent, dynamic newSlot) { - widget.owner.nodes.add(this); - _wrappedChild = widget.wrappedWidget; + widget.owner.addNode(this); + _wrappedWidget = widget.wrappedWidget; _injectedChild = widget.injectedChild; super.mount(parent, newSlot); } @override void unmount() { - widget.owner.nodes.remove(this); + widget.owner.removeNode(this); super.unmount(); } @override Widget build() { - return wrappedChild!; + return wrappedWidget!; } } diff --git a/packages/flutter_reactter/lib/src/framework/provider_base.dart b/packages/flutter_reactter/lib/src/framework/provider_base.dart index e7dfc719..22947171 100644 --- a/packages/flutter_reactter/lib/src/framework/provider_base.dart +++ b/packages/flutter_reactter/lib/src/framework/provider_base.dart @@ -4,7 +4,7 @@ part of '../framework.dart'; @internal abstract class ProviderBase extends Widget { - /// {@template provider_base.id} + /// {@template flutter_reactter.provider_base.id} /// It's used to identify the dependency of [T] type /// that is provided by the provider. /// @@ -16,7 +16,7 @@ abstract class ProviderBase extends Widget { /// {@endtemplate} final String? id; - /// {@template provider_base.mode} + /// {@template flutter_reactter.provider_base.mode} /// It's used to specify the type of dependency creation for the provided object. /// /// It is of mode [DependencyMode], which is an enum with three possible values: @@ -24,27 +24,27 @@ abstract class ProviderBase extends Widget { /// {@endtemplate} final DependencyMode mode; - /// {@template provider_base.instanceBuilder} + /// {@template flutter_reactter.provider_base.instanceBuilder} /// Create a [T] instance. /// {@endtemplate} @protected final InstanceBuilder instanceBuilder; - /// {@template provider_base.init} + /// {@template flutter_reactter.provider_base.init} /// Immediately create the dependency defined /// on firts parameter([instanceBuilder]). /// {@endtemplate} @protected final bool init; - /// {@template provider_base.isLazy} + /// {@template flutter_reactter.provider_base.isLazy} /// Lazily create the dependency defined /// on firts parameter([instanceBuilder]). /// {@endtemplate} @protected final bool isLazy; - /// {@template provider_base.builder} + /// {@template flutter_reactter.provider_base.builder} /// Method which has the render logic /// /// Exposes [BuilderContext], instance of the dependency and [child] widget as arguments. @@ -53,7 +53,7 @@ abstract class ProviderBase extends Widget { @protected final InstanceChildBuilder? builder; - /// {@template provider_base.lazyBuilder} + /// {@template flutter_reactter.provider_base.lazyBuilder} /// Method which has the render logic /// /// Exposes [BuilderContext] and [child] widget as arguments. @@ -62,7 +62,7 @@ abstract class ProviderBase extends Widget { @protected final ChildBuilder? lazyBuilder; - /// {@template provider_base.init} + /// {@template flutter_reactter.provider_base.init} /// The child widget that will be wrapped by the provider. /// The child widget can be accessed within the `builder` method of the provider. /// {@endtemplate} diff --git a/packages/flutter_reactter/lib/src/framework/provider_impl.dart b/packages/flutter_reactter/lib/src/framework/provider_impl.dart index ccde0b31..0113458c 100644 --- a/packages/flutter_reactter/lib/src/framework/provider_impl.dart +++ b/packages/flutter_reactter/lib/src/framework/provider_impl.dart @@ -3,12 +3,23 @@ part of '../framework.dart'; @internal -abstract class ProviderRef {} +abstract class ProviderRef { + @protected + void registerInstance(); + @protected + T? findInstance(); + @protected + T? getInstance(); + @protected + T? createInstance(); + @protected + void disposeInstance(); +} @internal class ProvideImpl extends ProviderBase implements InheritedWidget { - final ProviderRef ref; + final ProviderRef ref; const ProvideImpl( InstanceBuilder instanceBuilder, { @@ -37,22 +48,21 @@ class ProvideImpl extends ProviderBase Widget get child { assert( super.child != null || - (!super.isLazy && super.builder != null || - super.isLazy && super.lazyBuilder != null), + (!isLazy && builder != null || isLazy && lazyBuilder != null), ); - if (super.isLazy && super.lazyBuilder != null) { + if (isLazy && lazyBuilder != null) { return Builder( builder: (context) { - return super.lazyBuilder!(context, super.child); + return lazyBuilder!(context, super.child); }, ); } - if (super.builder != null) { + if (builder != null) { return Builder( builder: (context) { - return super.builder!(context, context.use(id), super.child); + return builder!(context, context.use(id), super.child); }, ); } @@ -60,8 +70,10 @@ class ProvideImpl extends ProviderBase return super.child!; } + // coverage:ignore-start @override bool updateShouldNotify(covariant InheritedWidget oldWidget) => false; + // coverage:ignore-end @override ProviderElement createElement() => ProviderElement(widget: this); @@ -99,8 +111,8 @@ class ProvideImpl extends ProviderBase context.dependOnInheritedElement( providerInheritedElement!, aspect: listenStates == null - ? InstanceDependency(instance) - : StatesDependency(listenStates(instance).toSet()), + ? InstanceDependency(instance) + : StatesDependency(listenStates(instance).toSet()), ); return instance; @@ -137,18 +149,16 @@ class ProvideImpl extends ProviderBase } } -/// [ProviderElement] is a class that manages the lifecycle of the [RtDependency] and -/// provides the [RtDependency] to its descendants +/// [ProviderElement] is a class that manages the lifecycle of the [RtDependencyRef] and +/// provides the [RtDependencyRef] to its descendants @internal class ProviderElement extends InheritedElement with ScopeElementMixin { - Widget? prevChild; - HashMap>? _inheritedElementsWithId; - bool _isLazyInstanceObtained = false; + static final Map _instanceMountCount = {}; - bool get isRoot { - return Rt.getHashCodeRefAt(0, widget.id) == widget.ref.hashCode; - } + Widget? _prevChild; + HashMap>? _inheritedElementsWithId; + bool _isLazyInstanceObtained = false; @override ProvideImpl get widget { @@ -157,14 +167,14 @@ class ProviderElement extends InheritedElement T? get instance { if (!_isLazyInstanceObtained && widget.isLazy) { - final instance = Rt.get(widget.id, widget.ref); + final instance = widget.ref.getInstance(); _isLazyInstanceObtained = instance != null; return instance; } - return Rt.find(widget.id); + return widget.ref.findInstance(); } /// Creates an element that uses the given widget as its configuration. @@ -172,85 +182,95 @@ class ProviderElement extends InheritedElement required ProvideImpl widget, String? id, }) : super(widget) { - if (widget.init && !widget.isLazy) { - // TODO: Remove this when the `init` property is removed - Rt.create( - widget.instanceBuilder, - id: widget.id, - mode: widget.mode, - ref: widget.ref, - ); + if (widget.init && !widget.isLazy) return; - return; - } - - Rt.register( - widget.instanceBuilder, - id: widget.id, - mode: widget.mode, - ); + widget.ref.registerInstance(); } @override void mount(Element? parent, Object? newSlot) { + final dependency = RtDependencyRef(widget.id); + var count = _instanceMountCount.putIfAbsent(dependency, () => 0); + _instanceMountCount[dependency] = ++count; + final shouldNotifyMount = count == 1; + if (!widget.init && !widget.isLazy) { - Rt.get(widget.id, widget.ref); + widget.ref.getInstance(); } _updateInheritedElementWithId(parent); - if (isRoot) { - Rt.emit(instance!, Lifecycle.willMount); + if (shouldNotifyMount) { + Rt.emit(dependency, Lifecycle.willMount); } super.mount(parent, newSlot); - if (isRoot) { - Rt.emit(instance!, Lifecycle.didMount); + if (shouldNotifyMount) { + Rt.emit(dependency, Lifecycle.didMount); } } + @override + void update(covariant InheritedWidget newWidget) { + final ref = widget.ref; + + if (newWidget is ProvideImpl) { + newWidget.ref.createInstance(); + } + + super.update(newWidget); + + if (ref is RtProvider) ref.disposeInstance(); + } + @override Widget build() { - if (hasDependenciesDirty) { + if (hasDirtyDependencies) { notifyClients(widget); - if (prevChild != null) return prevChild!; + if (_prevChild != null) return _prevChild!; } - return prevChild = super.build(); + return _prevChild = super.build(); } @override void unmount() { - final id = widget.id; final ref = widget.ref; - final isRoot = this.isRoot; - final instance = this.instance; + final dependency = RtDependencyRef(widget.id); + final count = (_instanceMountCount[dependency] ?? 0) - 1; + final shouldNotifyUnmount = count < 1; + + if (shouldNotifyUnmount) { + _instanceMountCount.remove(dependency); + } else { + _instanceMountCount[dependency] = count; + } try { - if (isRoot) { - Rt.emit(instance!, Lifecycle.willUnmount); + if (shouldNotifyUnmount) { + Rt.emit(dependency, Lifecycle.willUnmount); } return super.unmount(); } finally { - if (isRoot) { - Rt.emit(instance!, Lifecycle.didUnmount); + if (shouldNotifyUnmount) { + Rt.emit(dependency, Lifecycle.didUnmount); } - Rt.delete(id, ref); + if (ref is RtProvider) ref.disposeInstance(); _inheritedElementsWithId = null; - prevChild = null; + _prevChild = null; } } - /// Gets [ProviderElement] that it has the [RtDependency]'s id. + /// Gets [ProviderElement] that it has the [RtDependencyRef]'s id. ProviderElement? getInheritedElementOfExactId( String id, ) => - _inheritedElementsWithId?[RtDependency(id)]; + _inheritedElementsWithId?[RtDependencyRef(id)]; /// updates [inheritedElementsWithId] /// with all ancestor [ProviderElement] with id @@ -262,14 +282,15 @@ class ProviderElement extends InheritedElement as ProviderElement?; if (ancestorInheritedElement?._inheritedElementsWithId != null) { - _inheritedElementsWithId = HashMap>.of( + _inheritedElementsWithId = + HashMap>.of( ancestorInheritedElement!._inheritedElementsWithId!, ); } else { - _inheritedElementsWithId = HashMap>(); + _inheritedElementsWithId = HashMap>(); } - _inheritedElementsWithId![RtDependency(widget.id)] = this; + _inheritedElementsWithId![RtDependencyRef(widget.id)] = this; } } @@ -343,10 +364,3 @@ https://stackoverflow.com/questions/tagged/flutter '''; } } - -/// {@macro flutter_reactter.provider_not_found_exception} -@Deprecated( - 'Use `RtDependencyNotFoundException` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterDependencyNotFoundException = RtDependencyNotFoundException; diff --git a/packages/flutter_reactter/lib/src/framework/scope_element_mixin.dart b/packages/flutter_reactter/lib/src/framework/scope_element_mixin.dart index 32d7fce3..317a05bd 100644 --- a/packages/flutter_reactter/lib/src/framework/scope_element_mixin.dart +++ b/packages/flutter_reactter/lib/src/framework/scope_element_mixin.dart @@ -4,14 +4,14 @@ part of '../framework.dart'; /// and notify when should be updated its dependencies. @internal mixin ScopeElementMixin on InheritedElement { + final _dependents = HashMap(); + final _dependentsFlushReady = {}; bool _isFlushDependentsScheduled = false; bool _updatedShouldNotify = false; - final _dependenciesDirty = HashSet(); - final _dependents = HashMap(); - final _instancesAndStatesDependencies = HashMap>(); - final _dependentsFlushReady = {}; + bool _isMarkNeedsBuild = false; - bool get hasDependenciesDirty => _dependenciesDirty.isNotEmpty; + bool get hasDirtyDependencies => + _isMarkNeedsBuild || _dependents.values.any((dep) => dep.isDirty); @override void update(InheritedWidget newWidget) { @@ -22,226 +22,178 @@ mixin ScopeElementMixin on InheritedElement { @override void updated(InheritedWidget oldWidget) { - super.updated(oldWidget); - if (_updatedShouldNotify) { + // If the widget tree is updated, we need to reset the state + // to avoid memory leaks. + _resetState(); notifyClients(oldWidget); + return; } + + super.updated(oldWidget); // coverage:ignore-line } @override void unmount() { - _dependents.clear(); - _dependenciesDirty.clear(); - _removeListeners(); + _resetState(); return super.unmount(); } + /// Returns the value of the dependency of the dependent. + /// If a MasterDepndency is stored in this scope, it will be returned. + /// Otherwise, it will return the value of the dependency of the dependent in + /// the parent scope. @override Object? getDependencies(Element dependent) { return _dependents[dependent] ?? super.getDependencies(dependent); } + /// Sets the value of the dependency of the dependent. + /// If a MasterDepndency is stored in this scope, it will be set. + /// Otherwise, it will set the value of the dependency of the dependent in + /// the parent scope. + @override + void setDependencies(Element dependent, Object? value) { + if (value is MasterDependency) _dependents[dependent] = value; + + super.setDependencies(dependent, value); + } + @override void updateDependencies(Element dependent, Object? aspect) { - // coverage:ignore-start - if (aspect is! Dependency) { + // If the aspect is not a Dependency or if the widget tree is marked as + // needing build, we can skip the update of the dependencies. + if (aspect is! Dependency || _isMarkNeedsBuild) { return super.updateDependencies(dependent, aspect); } - var masterDependency = getDependencies(dependent); + var dependency = getDependencies(dependent); - if (masterDependency != null && masterDependency is! MasterDependency) { - return super.updateDependencies(dependent, aspect); + // If no MasterDependency is stored, we can skip the update of the dependencies. + if (dependency != null && dependency is! MasterDependency) { + return super + .updateDependencies(dependent, aspect); // coverage:ignore-line } - // coverage:ignore-end - _scheduleflushDependents(); + assert(dependency is MasterDependency?); + MasterDependency? masterDependency = dependency as MasterDependency?; + + // Flush the dependent element and dispose of the dependency if it exists. masterDependency = _flushDependent(dependent, masterDependency); + // If the dependency is a MasterDependency, add the aspect to it. if (masterDependency is MasterDependency) { masterDependency.putDependency(aspect); } - masterDependency ??= aspect.makeMaster(); - _addListener(masterDependency as MasterDependency); - - return setDependencies(dependent, masterDependency); - } + // If the dependency is not a MasterDependency, create a new MasterDependency. + masterDependency ??= aspect.toMaster(); - @override - void setDependencies(Element dependent, Object? value) { - if (value is MasterDependency) { - _dependents[dependent] = value; + if (masterDependency.isDirty) { + markNeedsBuild(); // coverage:ignore-line + } else { + masterDependency.listen(markNeedsBuild); } - super.setDependencies(dependent, value); + return setDependencies(dependent, masterDependency); } @override void notifyDependent(InheritedWidget oldWidget, Element dependent) { - // select can never be used inside `didChangeDependencies`, so if the - // dependent is already marked as needed build, there is no point - // in executing the selectors. + // if the dependent is already marked as needed build, there is no point + // in executing the evaluations about the dirty dependencies, because + // the dependent will be rebuild anyway. if (dependent.dirty) return; - final dependency = getDependencies(dependent); - final dependencies = { - dependency, - if (dependency is MasterDependency) ...dependency._selects, - }; - - if (dependencies.any(_dependenciesDirty.contains)) { - dependent.didChangeDependencies(); - _removeDependencies(dependent); - _dependenciesDirty.removeAll(dependencies); - } - } - - void _addListener(Dependency dependency) { - if (dependency._instance != null) { - _addInstanceListener(dependency._instance!, dependency); - } - - if (dependency._states.isNotEmpty) { - _addStatesListener(dependency._states, dependency); - } - - if (dependency is MasterDependency && dependency._selects.isNotEmpty) { - for (final dependency in dependency._selects) { - _addListener(dependency); - } - } - } - - void _addInstanceListener( - Object instance, - Dependency dependency, - ) { - if (!_instancesAndStatesDependencies.containsKey(instance)) { - Rt.on(instance, Lifecycle.didUpdate, _markNeedsNotifyDependents); - } + if (!_isMarkNeedsBuild) return super.notifyDependent(oldWidget, dependent); - _instancesAndStatesDependencies[instance] ??= {}; - _instancesAndStatesDependencies[instance]?.add(dependency); - } - - void _addStatesListener( - Set states, - Dependency dependency, - ) { - for (final state in states) { - if (!_instancesAndStatesDependencies.containsKey(state)) { - Rt.on(state, Lifecycle.didUpdate, _markNeedsNotifyDependents); - } - - _instancesAndStatesDependencies[state] ??= {}; - _instancesAndStatesDependencies[state]?.add(dependency); - } - } - - void _markNeedsNotifyDependents(Object? instanceOrState, _) { - assert(instanceOrState != null); - - final dependencies = _instancesAndStatesDependencies[instanceOrState]; - - if (dependencies?.isEmpty ?? true) return; - - final dependenciesDirty = [ - for (final dependency in dependencies!) - if (dependency is! SelectDependency) - dependency - else if (dependency.value != dependency.resolve()) - dependency - ]; + final dependency = getDependencies(dependent); - if (dependenciesDirty.isEmpty) return; + if (dependency is! MasterDependency) return; - _dependenciesDirty.addAll(dependenciesDirty); - dependencies.removeAll(dependenciesDirty); + final isDirty = + dependency.isDirty || dependency._selects.any((dep) => dep.isDirty); - if (dependencies.isEmpty) { - _clearInstanceOrStateDependencies(instanceOrState); - } + if (!isDirty) return; - markNeedsBuild(); + _removeDependencies(dependent); + _isMarkNeedsBuild = false; + dependent.didChangeDependencies(); } + /// Schedules a callback to flush dependents after the current frame. void _scheduleflushDependents() { if (_isFlushDependentsScheduled) return; _isFlushDependentsScheduled = true; - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + WidgetsBinding.instance.addPostFrameCallback((_) { _isFlushDependentsScheduled = false; _dependentsFlushReady.clear(); }); } - Object? _flushDependent(Element dependent, Object? dependency) { + /// Flushes the dependent element and disposes of the dependency if it exists. + /// + /// This method schedules the flushing of dependents and ensures that the + /// dependent element is marked as ready for flushing. If the dependent element + /// is already marked as ready, it returns the existing dependency. + /// + /// If the dependency is of type [MasterDependency], it is disposed of. + /// + /// Returns `null` after disposing of the dependency. + MasterDependency? _flushDependent( + Element dependent, + MasterDependency? dependency, + ) { + _scheduleflushDependents(); + if (_dependentsFlushReady.contains(dependent)) return dependency; _dependentsFlushReady.add(dependent); - if (dependency is! MasterDependency) return dependency; - - _clearDependency(dependency); + if (dependency is MasterDependency) dependency.dispose(); return null; } - void _removeDependencies(Element dependent) { - setDependencies(dependent, null); - _dependents.remove(dependent); - } + @override + void markNeedsBuild() { + if (_isMarkNeedsBuild) return; + + _isMarkNeedsBuild = true; + try { + super.markNeedsBuild(); + } catch (error) { + if (error is! AssertionError) rethrow; - void _removeListeners() { - for (final instancesOrStates in _instancesAndStatesDependencies.keys) { - Rt.off( - instancesOrStates, - Lifecycle.didUpdate, - _markNeedsNotifyDependents, + WidgetsBinding.instance.addPostFrameCallback( + (_) => super.markNeedsBuild(), ); } - - _instancesAndStatesDependencies.clear(); } - void _clearDependency(Dependency dependency) { - if (dependency._instance != null) { - final dependencies = - _instancesAndStatesDependencies[dependency._instance]; - dependencies?.remove(dependency); - - if (dependencies?.isEmpty ?? false) { - _clearInstanceOrStateDependencies(dependency._instance); - } - } + void _removeDependencies(Element dependent) { + setDependencies(dependent, null); + _dependents.remove(dependent)?.dispose(); + } - for (final state in dependency._states) { - final dependenciesOfState = _instancesAndStatesDependencies[state]; - dependenciesOfState?.remove(dependency); + void _resetState() { + _disposeDependencies(); + _dependentsFlushReady.clear(); + _isFlushDependentsScheduled = false; + _updatedShouldNotify = false; + _isMarkNeedsBuild = false; + } - if (dependenciesOfState?.isEmpty ?? false) { - _clearInstanceOrStateDependencies(state); - } - } + void _disposeDependencies() { + final dependencies = _dependents.values; - if (dependency is MasterDependency) { - for (final select in dependency._selects) { - _clearDependency(select); - } + for (final dependency in dependencies) { + dependency.dispose(); } - } - void _clearInstanceOrStateDependencies(Object? instanceOrState) { - Rt.off( - instanceOrState, - Lifecycle.didUpdate, - _markNeedsNotifyDependents, - ); - - _instancesAndStatesDependencies.remove(instanceOrState); + _dependents.clear(); } } diff --git a/packages/flutter_reactter/lib/src/framework/wrapper.dart b/packages/flutter_reactter/lib/src/framework/wrapper.dart index 6dd8bb74..96b63b5a 100644 --- a/packages/flutter_reactter/lib/src/framework/wrapper.dart +++ b/packages/flutter_reactter/lib/src/framework/wrapper.dart @@ -5,6 +5,8 @@ part of '../framework.dart'; abstract class WrapperWidget implements Widget { @override WrapperElementMixin createElement(); + + void dispose(); } /// Mixin to [WrapperWidget]'s Element @@ -35,4 +37,12 @@ mixin WrapperElementMixin on Element { return false; }); } + + void addNode(NestedElement node) { + nodes.add(node); + } + + void removeNode(NestedElement node) { + nodes.remove(node); + } } diff --git a/packages/flutter_reactter/lib/src/widgets/rt_component.dart b/packages/flutter_reactter/lib/src/widgets/rt_component.dart index d0687c55..23be6aab 100644 --- a/packages/flutter_reactter/lib/src/widgets/rt_component.dart +++ b/packages/flutter_reactter/lib/src/widgets/rt_component.dart @@ -79,6 +79,9 @@ abstract class RtComponent extends StatelessWidget { /// An indentify of the [T] dependency. String? get id => null; + /// How to handle the [T] dependency. + DependencyMode get mode => DependencyMode.builder; + /// How to builder the [T] dependency. InstanceBuilder? get builder => null; @@ -112,6 +115,7 @@ abstract class RtComponent extends StatelessWidget { return RtProvider( builder!, id: id, + mode: mode, builder: (context, instance, _) => render(context, _getInstance(context)), ); } @@ -139,10 +143,3 @@ abstract class RtComponent extends StatelessWidget { ); } } - -/// {@macro flutter_reactter.rt_component} -@Deprecated( - 'Use `RtComponent` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterComponent = RtComponent; diff --git a/packages/flutter_reactter/lib/src/widgets/rt_consumer.dart b/packages/flutter_reactter/lib/src/widgets/rt_consumer.dart index f70883b2..91d10662 100644 --- a/packages/flutter_reactter/lib/src/widgets/rt_consumer.dart +++ b/packages/flutter_reactter/lib/src/widgets/rt_consumer.dart @@ -105,10 +105,3 @@ class RtConsumer extends StatelessWidget { ); } } - -/// {@macro flutter_reactter.rt_consumer} -@Deprecated( - 'Use `RtConsumer` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterConsumer = RtConsumer; diff --git a/packages/flutter_reactter/lib/src/widgets/rt_provider.dart b/packages/flutter_reactter/lib/src/widgets/rt_provider.dart index 2cf68b7a..4e736796 100644 --- a/packages/flutter_reactter/lib/src/widgets/rt_provider.dart +++ b/packages/flutter_reactter/lib/src/widgets/rt_provider.dart @@ -124,18 +124,13 @@ part of '../widgets.dart'; /// * [RtMultiProvider], a widget that allows to use multiple [RtProvider]. /// {@endtemplate} class RtProvider extends ProviderBase - implements ProviderWrapper, ProviderRef { + implements ProviderWrapper, ProviderRef { /// Creates an instance of [T] dependency and provides it to tree widget. const RtProvider( InstanceBuilder instanceBuilder, { Key? key, String? id, DependencyMode mode = DependencyMode.builder, - @Deprecated( - 'This feature not working anymore, use `RtProvider.init` instead. ' - 'It was deprecated after v7.2.0.', - ) - bool init = false, Widget? child, InstanceChildBuilder? builder, }) : super( @@ -143,7 +138,6 @@ class RtProvider extends ProviderBase key: key, id: id, mode: mode, - init: init, child: child, builder: builder, ); @@ -164,12 +158,7 @@ class RtProvider extends ProviderBase child: child, builder: builder, ) { - Rt.create( - instanceBuilder, - id: id, - mode: mode, - ref: this, - ); + createInstance(); } /// Creates a lazy instance of [T] dependency and provides it to tree widget. @@ -190,6 +179,7 @@ class RtProvider extends ProviderBase lazyBuilder: builder, ); + @protected Widget buildWithChild(Widget? child) { if (id != null) { return ProvideImpl( @@ -222,6 +212,41 @@ class RtProvider extends ProviderBase ); } + @protected + @override + void registerInstance() { + Rt.register(instanceBuilder, id: id, mode: mode); + } + + @override + T? findInstance() { + return Rt.find(id); + } + + @protected + @override + T? getInstance() { + return Rt.get(id, this); + } + + @protected + @override + T? createInstance() { + return Rt.create(instanceBuilder, id: id, mode: mode, ref: this); + } + + @protected + @override + void disposeInstance() { + Rt.delete(id, this); + } + + @protected + @override + void dispose() { + disposeInstance(); + } + @override RtProviderElement createElement() => RtProviderElement(this); @@ -244,14 +269,10 @@ class RtProvider extends ProviderBase class RtProviderElement extends ComponentElement with WrapperElementMixin> { bool get isRoot { - return Rt.getHashCodeRefAt(0, widget.id) == _widget.hashCode; + return Rt.getRefAt(0, widget.id) == widget; } - final RtProvider _widget; - - RtProviderElement(RtProvider widget) - : _widget = widget, - super(widget); + RtProviderElement(Widget widget) : super(widget); @override Widget build() { @@ -280,10 +301,3 @@ class RtProviderElement extends ComponentElement ); } } - -/// {@macro flutter_reactter.rt_provider} -@Deprecated( - 'Use `RtProvider` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterProvider = RtProvider; diff --git a/packages/flutter_reactter/lib/src/widgets/rt_providers.dart b/packages/flutter_reactter/lib/src/widgets/rt_providers.dart index 586dd76b..24309c04 100644 --- a/packages/flutter_reactter/lib/src/widgets/rt_providers.dart +++ b/packages/flutter_reactter/lib/src/widgets/rt_providers.dart @@ -89,6 +89,11 @@ class RtMultiProvider extends StatelessWidget implements ProviderWrapper { RtMultiProviderElement createElement() { return RtMultiProviderElement(this); } + + // coverage:ignore-start + @override + void dispose() {} + // coverage:ignore-end } class RtMultiProviderElement extends StatelessElement @@ -112,31 +117,33 @@ class RtMultiProviderElement extends StatelessElement ); } - if (nestedHook != null) { - /// We manually update [NestedElement] instead of letter widgets do their thing - /// because an item N may be constant but N+1 not. So, if we used widgets - /// then N+1 wouldn't rebuild because N didn't change - for (final node in nodes) { - node - ..wrappedChild = nestedHook!.wrappedWidget - ..injectedChild = nestedHook.injectedChild; - - final next = nestedHook.injectedChild; - if (next is NestedWidget) { - nestedHook = next; - } else { - break; - } + if (nestedHook == null) return nextNode; + + /// We manually update [NestedElement] instead of letter widgets do their thing + /// because an item N may be constant but N+1 not. So, if we used widgets + /// then N+1 wouldn't rebuild because N didn't change + for (final node in nodes.toList(growable: false)) { + node + ..wrappedWidget = nestedHook!.wrappedWidget + ..injectedChild = nestedHook.injectedChild; + + final next = nestedHook.injectedChild; + if (next is NestedWidget) { + nestedHook = next; + } else { + break; } } return nextNode; } -} -/// {@macro flutter_reactter.rt_multi_provider} -@Deprecated( - 'Use `RtMultiProvider` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterProviders = RtMultiProvider; + @override + void removeNode(NestedElement node) { + super.removeNode(node); + + if (node.wrappedWidget is RtProvider) { + (node.wrappedWidget as RtProvider).disposeInstance(); + } + } +} diff --git a/packages/flutter_reactter/lib/src/widgets/rt_scope.dart b/packages/flutter_reactter/lib/src/widgets/rt_scope.dart index 85bc7dfb..9d01f2a4 100644 --- a/packages/flutter_reactter/lib/src/widgets/rt_scope.dart +++ b/packages/flutter_reactter/lib/src/widgets/rt_scope.dart @@ -10,8 +10,10 @@ class RtScope extends InheritedWidget { required Widget child, }) : super(key: key, child: child); + // coverage:ignore-start @override bool updateShouldNotify(covariant InheritedWidget oldWidget) => false; + // coverage:ignore-end @override RtScopeElement createElement() => RtScopeElement(this); @@ -57,7 +59,7 @@ class RtScopeElement extends InheritedElement with ScopeElementMixin { @override Widget build() { - if (hasDependenciesDirty) { + if (hasDirtyDependencies) { notifyClients(widget); if (prevChild != null) return prevChild!; @@ -91,16 +93,3 @@ https://stackoverflow.com/questions/tagged/flutter '''; } } - -/// {@macro flutter_reactter.rt_scope} -@Deprecated( - 'Use `RtScope` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterScope = RtScope; - -@Deprecated( - 'Use `RtScopeNotFoundException` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterScopeNotFoundException = RtScopeNotFoundException; diff --git a/packages/flutter_reactter/lib/src/widgets/rt_selector.dart b/packages/flutter_reactter/lib/src/widgets/rt_selector.dart index 3fd725ef..b1c64277 100644 --- a/packages/flutter_reactter/lib/src/widgets/rt_selector.dart +++ b/packages/flutter_reactter/lib/src/widgets/rt_selector.dart @@ -10,7 +10,7 @@ part of '../widgets.dart'; /// This evaluation only occurs if one of the selected [RtState]s gets updated, /// or by the dependency if the [selector] does not have any selected [RtState]s. /// -/// The [selector] property has a two arguments, the first one is the instance +/// The [selector] property has two parameters, the first one is the instance /// of [T] dependency which is obtained from the closest ancestor [RtProvider]. /// and the second one is a [Select] function which allows to wrapper any /// [RtState]s to listen, and returns the value in each build. e.g: @@ -136,7 +136,7 @@ class RtSelector extends StatelessWidget { /// This evaluation only occurs if one of the selected [RtState]s gets updated, /// or by the dependency if the [selector] does not have any selected [RtState]s. /// - /// The [selector] callback has a two arguments, the first one is + /// The [selector] callback has two parameters, the first one is /// the instance of [T] dependency which is obtained from the closest ancestor /// of [RtProvider] and the second one is a [Select] function which /// allows to wrapper any [RtState]s to listen. @@ -163,7 +163,7 @@ class RtSelector extends StatelessWidget { ? inheritedElement.instance : null; - final dependency = SelectDependency( + final dependency = SelectDependency( instance: instance as T, computeValue: selector as dynamic, ); @@ -176,10 +176,3 @@ class RtSelector extends StatelessWidget { return dependency.value; } } - -/// {@macro flutter_reactter.rt_selector} -@Deprecated( - 'Use `RtSelector` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterSelector = RtSelector; diff --git a/packages/flutter_reactter/lib/src/widgets/rt_signal_watcher.dart b/packages/flutter_reactter/lib/src/widgets/rt_signal_watcher.dart index 45a7fb64..b6c51d3f 100644 --- a/packages/flutter_reactter/lib/src/widgets/rt_signal_watcher.dart +++ b/packages/flutter_reactter/lib/src/widgets/rt_signal_watcher.dart @@ -152,10 +152,3 @@ class _RtSignalWatcherState extends State { _states.clear(); } } - -/// {@macro flutter_reactter.rt_signal_watcher} -@Deprecated( - 'Use `RtSignalWatcher` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterWatcher = RtSignalWatcher; diff --git a/packages/flutter_reactter/lib/src/widgets/rt_watcher.dart b/packages/flutter_reactter/lib/src/widgets/rt_watcher.dart index b2de8e25..45c1f627 100644 --- a/packages/flutter_reactter/lib/src/widgets/rt_watcher.dart +++ b/packages/flutter_reactter/lib/src/widgets/rt_watcher.dart @@ -60,7 +60,15 @@ part of '../widgets.dart'; /// ); /// ``` /// {@endtemplate} -class RtWatcher extends StatefulWidget { +class RtWatcher extends StatelessWidget { + /// Provides a widget , which render one time. + /// + /// It's expose on [builder] method as second parameter. + final Widget? child; + + /// {@macro flutter_reactter.watch_child_builder} + final WatchChildBuilder builder; + /// {@macro flutter_reactter.rt_watcher} RtWatcher( WatchBuilder builder, { @@ -75,76 +83,21 @@ class RtWatcher extends StatefulWidget { required this.builder, }) : super(key: key); - /// Provides a widget , which render one time. - /// - /// It's expose on [builder] method as second parameter. - final Widget? child; - - /// {@macro flutter_reactter.watch_child_builder} - final WatchChildBuilder builder; - - @override - State createState() => _RtWatcherState(); -} - -class _RtWatcherState extends State { - final Set _states = {}; - - static _RtWatcherState? _currentState; - @override Widget build(BuildContext context) { - final prevState = _currentState; - - _currentState = this; - - final widgetBuit = widget.builder(context, _watchState, widget.child); - - _currentState = prevState; - - return widgetBuit; + return RtScope( + child: Builder( + builder: (context) => builder( + context, + (T state) => _watchState(context, state), + child, + ), + ), + ); } - @override - void dispose() { - _clearStates(); - super.dispose(); - } - - T _watchState(T state) { - if (_currentState != this || _states.contains(state)) { - return state; - } - - _states.add(state); - Rt.on(state, Lifecycle.didUpdate, _onStateDidUpdate); - + T _watchState(BuildContext context, T state) { + context.watch((_) => [state]); return state; } - - void _onStateDidUpdate(_, __) { - _clearStates(); - _rebuild(); - } - - void _rebuild() async { - if (!mounted) return; - - // coverage:ignore-start - if (SchedulerBinding.instance.schedulerPhase != SchedulerPhase.idle) { - await SchedulerBinding.instance.endOfFrame; - } - // coverage:ignore-end - - if (!mounted) return; - - (context as Element).markNeedsBuild(); - } - - void _clearStates() { - for (final state in _states) { - Rt.off(state, Lifecycle.didUpdate, _onStateDidUpdate); - } - _states.clear(); - } } diff --git a/packages/flutter_reactter/pubspec.lock b/packages/flutter_reactter/pubspec.lock deleted file mode 100644 index e924cd73..00000000 --- a/packages/flutter_reactter/pubspec.lock +++ /dev/null @@ -1,263 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - async: - dependency: transitive - description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - characters: - dependency: transitive - description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.dev" - source: hosted - version: "1.3.0" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" - source: hosted - version: "1.1.1" - collection: - dependency: transitive - description: - name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a - url: "https://pub.dev" - source: hosted - version: "1.18.0" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - file: - dependency: transitive - description: - name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_driver: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c - url: "https://pub.dev" - source: hosted - version: "2.0.1" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - fuchsia_remote_debug_protocol: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" - url: "https://pub.dev" - source: hosted - version: "10.0.0" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 - url: "https://pub.dev" - source: hosted - version: "2.0.1" - lints: - dependency: transitive - description: - name: lints - sha256: "5cfd6509652ff5e7fe149b6df4859e687fca9048437857cb2e65c8d780f396e3" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" - url: "https://pub.dev" - source: hosted - version: "0.8.0" - meta: - dependency: "direct main" - description: - name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 - url: "https://pub.dev" - source: hosted - version: "1.11.0" - path: - dependency: transitive - description: - name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.dev" - source: hosted - version: "1.9.0" - platform: - dependency: transitive - description: - name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" - url: "https://pub.dev" - source: hosted - version: "3.1.4" - process: - dependency: transitive - description: - name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" - url: "https://pub.dev" - source: hosted - version: "5.0.2" - reactter: - dependency: "direct main" - description: - name: reactter - sha256: ad773608411600531cbc3513ae05a4ce91610ccb93701f24e79d26999b663f04 - url: "https://pub.dev" - source: hosted - version: "7.3.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" - url: "https://pub.dev" - source: hosted - version: "1.11.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - sync_http: - dependency: transitive - description: - name: sync_http - sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" - url: "https://pub.dev" - source: hosted - version: "0.3.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" - url: "https://pub.dev" - source: hosted - version: "0.6.1" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 - url: "https://pub.dev" - source: hosted - version: "13.0.0" - webdriver: - dependency: transitive - description: - name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" - url: "https://pub.dev" - source: hosted - version: "3.0.3" -sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=1.17.0" diff --git a/packages/flutter_reactter/pubspec.yaml b/packages/flutter_reactter/pubspec.yaml index 4a5ab150..b6f1355a 100644 --- a/packages/flutter_reactter/pubspec.yaml +++ b/packages/flutter_reactter/pubspec.yaml @@ -4,21 +4,39 @@ homepage: https://2devs-team.github.io/reactter/ documentation: https://2devs-team.github.io/reactter/ repository: https://github.com/2devs-team/reactter/tree/master/packages/flutter_reactter license: MIT License -version: 7.3.1 +version: 8.0.0-dev.31 + +topics: + - reactive + - state + - dependency + - event + +screenshots: + - description: "Reactter DevTools Extension" + path: doc/screenshots/devtools.png + - description: "Reactter Example App" + path: doc/screenshots/example_app.png environment: - sdk: ">=2.14.0 <4.0.0" + sdk: ">=2.19.2 <4.0.0" flutter: ">=1.17.0" dependencies: flutter: sdk: flutter meta: ^1.7.0 - reactter: ^7.3.0 + reactter: ^8.0.0-dev.31 + # reactter: + # path: ../reactter dev_dependencies: - flutter_driver: - sdk: flutter - flutter_lints: ^2.0.1 flutter_test: sdk: flutter + flutter_lints: ^2.0.3 + +scripts: + test: "fvm flutter test --coverage" + analyze: "fvm dart analyze ." + public-dry-run: "fvm dart pub publish --dry-run" + public: "fvm dart pub publish" \ No newline at end of file diff --git a/packages/flutter_reactter/test/extensions/build_context_extension_test.dart b/packages/flutter_reactter/test/extensions/build_context_extension_test.dart index f78fca79..d6043aca 100644 --- a/packages/flutter_reactter/test/extensions/build_context_extension_test.dart +++ b/packages/flutter_reactter/test/extensions/build_context_extension_test.dart @@ -147,6 +147,67 @@ void main() { expect(find.text("stateInt: 2"), findsOneWidget); }); + testWidgets("should watch dependency's states while rebuilding", + (tester) async { + late TestController instanceObtained; + late TestController instanceObtainedWithId; + + await tester.pumpWidget( + TestBuilder( + child: RtMultiProviderBuilder( + builder: (context, _) { + instanceObtained = context.watch( + (inst) => [inst.stateInt], + ); + instanceObtainedWithId = context.watch( + (inst) => [inst.stateInt], + 'uniqueId', + ); + + return Column( + children: [ + Text("stateString: ${instanceObtained.stateString.value}"), + Text("stateInt: ${instanceObtained.stateInt.value}"), + Text( + "stateStringWithId: ${instanceObtainedWithId.stateString.value}", + ), + Text( + "stateIntWithId: ${instanceObtainedWithId.stateInt.value}", + ), + Builder(builder: (context) { + if (instanceObtained.stateInt.value == 1) { + instanceObtained.stateInt.value += 1; + } + + return const SizedBox(); + }) + ], + ); + }, + ), + ), + ); + + await tester.pumpAndSettle(); + + expectLater(instanceObtained, isInstanceOf()); + expectLater(instanceObtainedWithId, isInstanceOf()); + + expect(find.text("stateString: initial"), findsOneWidget); + expect(find.text("stateInt: 0"), findsOneWidget); + expect(find.text("stateStringWithId: from uniqueId"), findsOneWidget); + expect(find.text("stateIntWithId: 0"), findsOneWidget); + + instanceObtained.stateInt.value += 1; + instanceObtainedWithId.stateInt.value += 1; + await tester.pumpAndSettle(); + + expect(find.text("stateString: initial"), findsOneWidget); + expect(find.text("stateInt: 2"), findsOneWidget); + expect(find.text("stateStringWithId: from uniqueId"), findsOneWidget); + expect(find.text("stateIntWithId: 1"), findsOneWidget); + }); + testWidgets( "should watch multiple dependency's states, using different context.watch", (tester) async { diff --git a/packages/flutter_reactter/test/shareds/test_controller.dart b/packages/flutter_reactter/test/shareds/test_controller.dart index 33510454..dc520a36 100644 --- a/packages/flutter_reactter/test/shareds/test_controller.dart +++ b/packages/flutter_reactter/test/shareds/test_controller.dart @@ -16,7 +16,7 @@ Future _resolveStateAsync([bool throwError = false]) async { return "resolved"; } -class TestController extends LifecycleObserver { +class TestController with RtDependencyLifecycle { final signalString = Signal("initial"); final signalInt = Signal(0); final stateBool = UseState(false); @@ -26,5 +26,5 @@ class TestController extends LifecycleObserver { final stateList = UseState([]); final stateMap = UseState({}); final stateClass = UseState(null); - final stateAsync = UseAsyncState("initial", _resolveStateAsync); + final stateAsync = UseAsyncState(_resolveStateAsync, "initial"); } diff --git a/packages/flutter_reactter/test/widgets/rt_selector_test.dart b/packages/flutter_reactter/test/widgets/rt_selector_test.dart index cd126a45..7444e9af 100644 --- a/packages/flutter_reactter/test/widgets/rt_selector_test.dart +++ b/packages/flutter_reactter/test/widgets/rt_selector_test.dart @@ -16,6 +16,7 @@ void main() { child: RtSelector( selector: (_, $) { return $(signalList) + .value .fold(0, (prev, elem) => prev + elem); }, builder: (context, inst, total, child) => Text("total: $total"), @@ -28,7 +29,7 @@ void main() { expect(find.text("total: 0"), findsOneWidget); - signalList.addAll([1, 1, 2]); + signalList.update((value) => value.addAll([1, 1, 2])); await tester.pumpAndSettle(); expect(find.text("total: 4"), findsOneWidget); @@ -38,7 +39,7 @@ void main() { expect(find.text("total: 4"), findsOneWidget); - signalList.refresh(); + signalList.notify(); await tester.pumpAndSettle(); expect(find.text("total: 5"), findsOneWidget); diff --git a/packages/reactter/devtools_options.yaml b/packages/reactter/devtools_options.yaml new file mode 100644 index 00000000..995fadae --- /dev/null +++ b/packages/reactter/devtools_options.yaml @@ -0,0 +1,4 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: + - reactter: true \ No newline at end of file diff --git a/packages/reactter/doc/screenshots/devtools.png b/packages/reactter/doc/screenshots/devtools.png new file mode 120000 index 00000000..9907c222 --- /dev/null +++ b/packages/reactter/doc/screenshots/devtools.png @@ -0,0 +1 @@ +/Users/leon/projects/reactter/screenshots/devtools.png \ No newline at end of file diff --git a/packages/reactter/doc/screenshots/example_app.png b/packages/reactter/doc/screenshots/example_app.png new file mode 120000 index 00000000..3a018387 --- /dev/null +++ b/packages/reactter/doc/screenshots/example_app.png @@ -0,0 +1 @@ +/Users/leon/projects/reactter/screenshots/example_app.png \ No newline at end of file diff --git a/packages/reactter/extension/devtools/.pubignore b/packages/reactter/extension/devtools/.pubignore new file mode 100644 index 00000000..684ae5d8 --- /dev/null +++ b/packages/reactter/extension/devtools/.pubignore @@ -0,0 +1 @@ +!build \ No newline at end of file diff --git a/packages/reactter/extension/devtools/config.yaml b/packages/reactter/extension/devtools/config.yaml new file mode 100644 index 00000000..6bb38e83 --- /dev/null +++ b/packages/reactter/extension/devtools/config.yaml @@ -0,0 +1,5 @@ +name: reactter +version: 1.0.0 +issueTracker: https://github.com/2devs-team/reactter/issues +materialIconCodePoint: "0xf0537" +requiresConnection: false diff --git a/packages/reactter/lib/reactter.dart b/packages/reactter/lib/reactter.dart index 9e0b46f1..e342cc8a 100644 --- a/packages/reactter/lib/reactter.dart +++ b/packages/reactter/lib/reactter.dart @@ -1,9 +1,12 @@ library reactter; -export 'src/framework/framework.dart'; +export 'src/args.dart'; +export 'src/devtools.dart' + hide RtDevTools, NodeKind, RtDevToolsInitializeAssertionError; +export 'src/framework.dart'; export 'src/hooks/hooks.dart' hide UseAsyncStateBase; export 'src/memo/memo.dart'; -export 'src/obj/obj.dart'; export 'src/signal/signal.dart'; -export 'src/args.dart'; +export 'src/logger.dart' + hide RtLogger, RtLoggerInitializeAssertionError, prettyFormat; export 'src/types.dart'; diff --git a/packages/reactter/lib/src/args.dart b/packages/reactter/lib/src/args.dart index 1618ae6b..28aa1c4d 100644 --- a/packages/reactter/lib/src/args.dart +++ b/packages/reactter/lib/src/args.dart @@ -1,12 +1,12 @@ import 'dart:async'; -/// {@template args} +/// {@template reactter.args} /// A class that represents an argument /// {@endtemplate} class Args { final List arguments; - /// {@macro args} + /// {@macro reactter.args} const Args([this.arguments = const []]); /// Returns the first element of the `arguments` list. @@ -25,14 +25,14 @@ class Args { other is Args && this.hashCode == other.hashCode; } -/// {@template args1} +/// {@template reactter.args1} /// A class that represents an argument /// {@endtemplate} class Args1 extends Args { @override final A arg1; - /// {@macro args1} + /// {@macro reactter.args1} const Args1(this.arg1) : super(); /// Returns a list containing all arguments. @@ -43,13 +43,13 @@ class Args1 extends Args { List get arguments => [arg1]; } -/// {@template args2} +/// {@template reactter.args2} /// A class that represents a set of two arguments. /// {@endtemplate} class Args2 extends Args1 { final A2 arg2; - /// {@macro args2} + /// {@macro reactter.args2} const Args2(A arg1, this.arg2) : super(arg1); /// Returns a list containing all arguments. @@ -60,13 +60,13 @@ class Args2 extends Args1 { List get arguments => [...super.arguments, arg2]; } -/// {@template args3} +/// {@template reactter.args3} /// A class that represents a set of three arguments. /// {@endtemplate} class Args3 extends Args2 { final A3 arg3; - /// {@macro args3} + /// {@macro reactter.args3} const Args3(A arg1, A2 arg2, this.arg3) : super(arg1, arg2); /// Returns a list containing all arguments. diff --git a/packages/reactter/lib/src/core/binding_zone.dart b/packages/reactter/lib/src/core/binding_zone.dart index 618343be..25935a79 100644 --- a/packages/reactter/lib/src/core/binding_zone.dart +++ b/packages/reactter/lib/src/core/binding_zone.dart @@ -1,9 +1,9 @@ -part of 'core.dart'; +part of '../internals.dart'; /// Represents an environment that Reactter uses for managing and /// attaching instances to a collection of states. @internal -class BindingZone { +class BindingZone { /// It's used to keep track of the current [BindingZone]. static BindingZone? _currentZone; @@ -11,46 +11,72 @@ class BindingZone { /// creating a new instance. final _parentZone = _currentZone; - /// It's used to store a collection of [StateBase]. - final states = {}; + /// It's used to store a collection of [IState]. + final _states = {}; /// Returns the current [BindingZone]. static BindingZone? get currentZone => _currentZone; + /// The verification status indicates whether the object is verified or not. + /// + /// Note: The [_isVerified] variable is set to `true` within a finally block, + /// ensuring it is always updated. + bool _isVerified = false; + get isVerified { + try { + return _isVerified; + } finally { + _isVerified = true; + } + } + BindingZone() { /// This is done to keep track of the current [BindingZone] instance. _currentZone = this; } - /// It's used to bind the instance to the stored states([StateBase]). - static void autoBinding(Object? Function() getInstance) { - final zone = BindingZone(); - zone.bindInstanceToStates(getInstance()); + /// {@template reactter.binding_zone.auto_binding} + /// Automatically binds the instance to the stored states([IState]). + /// If the instance is null, it adds the stored states to the [BindingZone] parent. + /// {@endtemplate} + static T autoBinding( + T Function() getInstance, [ + Object? boundInstance, + ]) { + final zone = BindingZone(); + final instance = getInstance(); + + final instanceToBind = boundInstance ?? instance; + + zone._bindInstanceToStates(instanceToBind); + + return instance; } /// Stores the state given from parameter. - static void recollectState(StateBase state) { - _currentZone?.states.add(state); + static void recollectState(IState state) { + _currentZone?._states.add(state); } - /// Attaches the instance to the stored states([StateBase]), and if the instance is null, + /// Attaches the instance to the stored states([IState]), and if the instance is null, /// it adds the stored states to the [BindingZone] parent. - void bindInstanceToStates(T instance) { + E _bindInstanceToStates(E instance) { try { if (instance == null) { - _parentZone?.states.addAll(states); - return; + _parentZone?._states.addAll(_states); + return instance; } - for (final state in states) { - if (state is State && state.instanceBinded != null) { - state._validateInstanceBinded(); - } else { + for (final state in _states.toList(growable: false)) { + if (state != instance && state.boundInstance == null) { state.bind(instance); } } + + return instance; } finally { _dispose(); + if (instance is IState) instance._register(); } } diff --git a/packages/reactter/lib/src/core/core.dart b/packages/reactter/lib/src/core/core.dart deleted file mode 100644 index fc8a4076..00000000 --- a/packages/reactter/lib/src/core/core.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'dart:async'; -import 'dart:collection'; -import 'package:meta/meta.dart'; -import 'package:reactter/reactter.dart'; - -part 'event_handler.dart'; -part 'event_notifier.dart'; -part 'hook_register.dart'; -part 'hook.dart'; -part 'dependency_mode.dart'; -part 'dependency_injection.dart'; -part 'dependency_register.dart'; -part 'dependency_ref.dart'; -part 'lifecycle_observer.dart'; -part 'lifecycle.dart'; -part 'logger.dart'; -part 'state_base.dart'; -part 'state_management.dart'; -part 'state.dart'; -part 'binding_zone.dart'; diff --git a/packages/reactter/lib/src/core/dependency_injection.dart b/packages/reactter/lib/src/core/dependency_injection.dart index a38d47be..ab4cf543 100644 --- a/packages/reactter/lib/src/core/dependency_injection.dart +++ b/packages/reactter/lib/src/core/dependency_injection.dart @@ -1,4 +1,4 @@ -part of 'core.dart'; +part of '../internals.dart'; /// A mixin-class that adds dependency injection features /// to classes that use it. @@ -13,9 +13,10 @@ part of 'core.dart'; /// emits lifecycle events when dependencies are registered, unregistered, /// initialized, and destroyed. @internal -abstract class DependencyInjection { - Logger get logger; - EventHandler get eventHandler; +abstract class DependencyInjection implements IContext { + @override + @internal + DependencyInjection get dependencyInjection => this; /// It stores the dependencies registered. final _dependencyRegisters = HashSet(); @@ -23,7 +24,7 @@ abstract class DependencyInjection { /// It stores the dependencies and its registers for quick access. final _instances = HashMap(); - /// {@template register} + /// {@template reactter.register} /// Register a [builder] function of the [T] dependency with/without [id]. /// {@endtemplate} /// @@ -38,9 +39,11 @@ abstract class DependencyInjection { var dependencyRegister = _getDependencyRegister(id); if (dependencyRegister != null) { - logger.log( - 'The "$dependencyRegister" builder already registered as `$mode`.', + _notifyDependencyFailed( + dependencyRegister, + DependencyFail.alreadyRegistered, ); + return false; } @@ -53,13 +56,10 @@ abstract class DependencyInjection { _dependencyRegisters.add(dependencyRegister); eventHandler.emit(dependencyRegister, Lifecycle.registered); - logger.log( - 'The "$dependencyRegister" builder has been registered as `$mode`.', - ); return true; } - /// {@template lazy_builder} + /// {@template reactter.lazy_builder} /// Register a [builder] function of the [T] dependency with/without [id] /// as [DependencyMode.builder]. /// {@endtemplate} @@ -89,7 +89,7 @@ abstract class DependencyInjection { ); } - /// {@template lazy_factory} + /// {@template reactter.lazy_factory} /// Register a [builder] function of the [T] dependency with/without [id] /// as [DependencyMode.factory]. /// {@endtemplate} @@ -119,7 +119,7 @@ abstract class DependencyInjection { ); } - /// {@template lazy_singleton} + /// {@template reactter.lazy_singleton} /// Register a [builder] function of the [T] dependency with/without [id] /// as [DependencyMode.singleton]. /// {@endtemplate} @@ -149,7 +149,7 @@ abstract class DependencyInjection { ); } - /// {@template create} + /// {@template reactter.create} /// Register a [builder] function of the [T] dependency with/without [id] /// and creates the instance if it isn't already registered, /// else gets its instance only. @@ -157,7 +157,7 @@ abstract class DependencyInjection { /// /// Use [mode] parameter for defining how to manage the dependency. /// - /// {@template create_conditions} + /// {@template reactter.create_conditions} /// Under the following conditions: /// /// - if not found and hasn't registered it, registers, creates and returns it. @@ -176,7 +176,7 @@ abstract class DependencyInjection { return _getOrCreateIfNotExtist(id, ref)?.instance; } - /// {@template builder} + /// {@template reactter.builder} /// Register a [builder] function of the [T] dependency with/without [id] /// as [DependencyMode.builder] /// and creates the instance if it isn't already registered, @@ -212,7 +212,7 @@ abstract class DependencyInjection { ); } - /// {@template factory} + /// {@template reactter.factory} /// Register a [builder] function of the [T] dependency with/without [id] /// as [DependencyMode.factory] /// and creates the instance if it isn't already registered, @@ -247,7 +247,7 @@ abstract class DependencyInjection { ); } - /// {@template singleton} + /// {@template reactter.singleton} /// Register a [builder] function of the [T] dependency with/without [id] /// as [DependencyMode.singleton] /// and creates the instance if it isn't already registered, @@ -282,11 +282,11 @@ abstract class DependencyInjection { ); } - /// {@template get} + /// {@template reactter.get} /// Creates and/or gets the instance of the [T] dependency with/without [id]. /// {@endtemplate} /// - /// {@template get_conditions} + /// {@template reactter.get_conditions} /// Under the following conditions: /// /// - if found it, returns it. @@ -314,13 +314,16 @@ abstract class DependencyInjection { if (dependencyRegister?.instance == null) { final dependencyRef = DependencyRef(id); - logger.log('The "$dependencyRef" dependency already deleted.'); + _notifyDependencyFailed( + dependencyRef, + DependencyFail.alreadyDeleted, + ); return false; } if (ref != null) { - dependencyRegister!.refs.remove(ref.hashCode); + dependencyRegister!.refs.remove(ref); } if (dependencyRegister!.refs.isNotEmpty) { @@ -333,15 +336,17 @@ abstract class DependencyInjection { return true; case DependencyMode.factory: _removeInstance(dependencyRegister); - logger.log( - 'The "$dependencyRegister" builder has been retained ' - 'because it\'s `${DependencyMode.factory}`.', + + _notifyDependencyFailed( + dependencyRegister, + DependencyFail.builderRetained, ); + return true; case DependencyMode.singleton: - logger.log( - 'The "$dependencyRegister" dependency has been retained ' - 'because it\'s `${DependencyMode.singleton}`.', + _notifyDependencyFailed( + dependencyRegister, + DependencyFail.dependencyRetained, ); } @@ -353,27 +358,22 @@ abstract class DependencyInjection { /// Returns `true` when dependency has been unregistered. bool unregister([String? id]) { final dependencyRegister = _getDependencyRegister(id); - final typeLabel = - dependencyRegister?.mode.label ?? DependencyMode.builder.label; if (dependencyRegister == null) { final dependencyRef = DependencyRef(id); - logger.log('The "$dependencyRef" $typeLabel already deregistered.'); + _notifyDependencyFailed( + dependencyRef, + DependencyFail.alreadyUnregistered, + ); return false; } if (dependencyRegister._instance != null) { - final idParam = id != null ? "id: '$id, '" : ''; - - logger.log( - 'The "$T" builder couldn\'t deregister ' - 'because the "$dependencyRegister" dependency is active.\n' - 'You should delete the instance before with:\n' - '`Rt.delete<$T>(${id ?? ''});` or \n' - '`Rt.destroy<$T>($idParam, onlyInstance: true);`\n', - level: LogLevel.warning, + _notifyDependencyFailed( + dependencyRegister, + DependencyFail.cannotUnregisterActiveInstance, ); return false; @@ -383,7 +383,6 @@ abstract class DependencyInjection { eventHandler.emit(dependencyRegister, Lifecycle.unregistered); eventHandler.offAll(dependencyRegister); - logger.log('The "$dependencyRegister" $typeLabel has been deregistered.'); return true; } @@ -403,7 +402,10 @@ abstract class DependencyInjection { if (dependencyRegister?.instance == null && onlyInstance) { final dependencyRef = DependencyRef(id); - logger.log('The "$dependencyRef" instance already deleted.'); + _notifyDependencyFailed( + dependencyRef, + DependencyFail.alreadyDeleted, + ); return false; } @@ -418,7 +420,7 @@ abstract class DependencyInjection { return unregister(id); } - /// {@template find} + /// {@template reactter.find} /// Gets the instance of the [T] dependency with/without [id]. /// {@endtemplate} /// @@ -432,15 +434,6 @@ abstract class DependencyInjection { return _getDependencyRegister(id)?.instance != null; } - /// Checks if an instance is registered in Reactter - // coverage:ignore-start - @Deprecated( - 'Use `isActive` instead.' - 'This feature was deprecated after v7.2.0.', - ) - bool isRegistered(Object? instance) => isActive(instance); - // coverage:ignore-end - /// Checks if the [instance] is active in Reactter. bool isActive(Object? instance) { return _instances[instance] != null; @@ -452,23 +445,14 @@ abstract class DependencyInjection { return _dependencyRegisters.lookup(dependencyRef) != null; } - // coverage:ignore-start - @Deprecated( - 'Use `getDependencyMode` instead. ' - 'This feature was deprecated after v7.1.0.', - ) - InstanceManageMode? getInstanceManageMode(Object? instance) => - getDependencyMode(instance); - // coverage:ignore-end - /// Returns [DependencyMode] of instance parameter. DependencyMode? getDependencyMode(Object? instance) { return _instances[instance]?.mode; } - /// Returns the hashCode reference at a specified index for a given type and + /// Returns the reference at a specified index for a given type and /// optional ID. - int? getHashCodeRefAt(int index, [String? id]) { + Object? getRefAt(int index, [String? id]) { final refs = _getDependencyRegister(id)?.refs; if (refs == null || refs.length < index + 1) return null; @@ -486,36 +470,35 @@ abstract class DependencyInjection { if (instanceRegister == null) { final dependencyRef = DependencyRef(id); - final idParam = id != null ? ", id: '$id'" : ''; - - logger.log( - 'The "$dependencyRef" builder is not registered.\n' - 'You should register the instance build with: \n' - '`Rt.register<$T>(() => $T()$idParam);` or \n' - '`Rt.create<$T>(() => $T()$idParam);`.', - level: LogLevel.warning, + + _notifyDependencyFailed( + dependencyRef, + DependencyFail.missingInstanceBuilder, ); - return _getDependencyRegisterByRef(dependencyRef); + return getDependencyRegisterByRef(dependencyRef); + } + + if (ref != null) { + instanceRegister.refs.add(ref); } if (instanceRegister.instance != null) { - logger.log('The "$instanceRegister" instance already created.'); + _notifyDependencyFailed( + instanceRegister, + DependencyFail.alreadyCreated, + ); return instanceRegister; } BindingZone.autoBinding(() => _createInstance(instanceRegister)); - if (ref != null) { - instanceRegister.refs.add(ref.hashCode); - } - - // ignore: deprecated_member_use_from_same_package - eventHandler.emit(instanceRegister, Lifecycle.initialized); - eventHandler.emit(instanceRegister, Lifecycle.created); - - logger.log('The "$instanceRegister" instance has been created.'); + eventHandler.emit( + instanceRegister, + Lifecycle.created, + instanceRegister.instance, + ); return instanceRegister; } @@ -533,27 +516,22 @@ abstract class DependencyInjection { /// Removes an instance of a generic type from a [DependencyRegister]. void _removeInstance(DependencyRegister dependencyRegister) { - final log = 'The "$dependencyRegister" instance has been deleted.'; final instance = dependencyRegister.instance; + dependencyRegister._instance = null; _instances.remove(instance); + eventHandler.emit(instance, Lifecycle.deleted, instance); + eventHandler.emit(dependencyRegister, Lifecycle.deleted, instance); - // ignore: deprecated_member_use_from_same_package - eventHandler.emit(instance, Lifecycle.destroyed); - eventHandler.emit(instance, Lifecycle.deleted); - - // ignore: deprecated_member_use_from_same_package - eventHandler.emit(dependencyRegister, Lifecycle.destroyed); - eventHandler.emit(dependencyRegister, Lifecycle.deleted); - logger.log(log); - - if (instance is StateBase) instance.dispose(); + if (instance is IState && !instance.isDisposed) { + instance.dispose(); + } } /// Returns the [DependencyRef] associated with the given instance. /// If the instance is null or not found, returns null. - DependencyRef? _getDependencyRef(Object? instance) { + DependencyRef? getDependencyRef(Object? instance) { return _instances[instance] as DependencyRef?; } @@ -566,9 +544,22 @@ abstract class DependencyInjection { } /// Returns the [DependencyRegister] of [T] type with a given [dependencyRef]. - DependencyRegister? _getDependencyRegisterByRef( - DependencyRef? dependencyRef, + DependencyRegister? getDependencyRegisterByRef( + DependencyRef? dependencyRef, ) { - return _dependencyRegisters.lookup(dependencyRef) as DependencyRegister?; + if (dependencyRef == null) return null; + + return _dependencyRegisters.lookup(dependencyRef as dynamic) + as DependencyRegister?; + } + + void _notifyDependencyFailed( + DependencyRef dependencyRef, + DependencyFail fail, + ) { + for (final observer + in IDependencyObserver._observers.toList(growable: false)) { + observer.onDependencyFailed(dependencyRef, fail); + } } } diff --git a/packages/reactter/lib/src/core/dependency_mode.dart b/packages/reactter/lib/src/core/dependency_mode.dart index 557dd025..c0f48d5a 100644 --- a/packages/reactter/lib/src/core/dependency_mode.dart +++ b/packages/reactter/lib/src/core/dependency_mode.dart @@ -1,14 +1,10 @@ -part of 'core.dart'; - -@Deprecated( - 'Use `DependencyMode` instead. ' - 'This feature was deprecated after v7.1.0.', -) -typedef InstanceManageMode = DependencyMode; +part of '../internals.dart'; +/// {@template reactter.dependency_mode} /// Represents different ways for managing instances. +/// {@endtemplate} enum DependencyMode { - /// {@template dependency_mode.builder} + /// {@template reactter.dependency_mode.builder} /// It's a ways to manage a dependency, which registers a builder function /// and creates the instance, unless it has already done so. /// @@ -20,7 +16,7 @@ enum DependencyMode { /// {@endtemplate} builder, - /// {@template dependency_mode.factory} + /// {@template reactter.dependency_mode.factory} /// It's a ways to manage a dependency, which registers /// a builder function only once and creates the instance if not already done. /// @@ -32,7 +28,7 @@ enum DependencyMode { /// {@endtemplate} factory, - /// {@template dependency_mode.singleton} + /// {@template reactter.dependency_mode.singleton} /// It's a ways to manage a dependency, which registers a builder function /// and creates the instance only once. /// diff --git a/packages/reactter/lib/src/core/dependency_ref.dart b/packages/reactter/lib/src/core/dependency_ref.dart index ca42273b..13416812 100644 --- a/packages/reactter/lib/src/core/dependency_ref.dart +++ b/packages/reactter/lib/src/core/dependency_ref.dart @@ -1,4 +1,4 @@ -part of 'core.dart'; +part of '../internals.dart'; /// A generic class that represents the reference to a dependency in Reactter's context. /// Provides an optional [id] parameter for identifying the dependency. @@ -8,20 +8,14 @@ part of 'core.dart'; class DependencyRef { final String? id; - const DependencyRef([this.id]); - - @override - String toString() { - final type = T.toString().replaceAll(RegExp(r'\?'), ''); - final id = this.id != null ? "[id='${this.id}']" : ""; + Type get type => getType(); - return '$type$id'; - } + const DependencyRef([this.id]); - int _getTypeHashCode() => TT.hashCode; + Type getType() => TT; @override - int get hashCode => Object.hash(_getTypeHashCode(), id.hashCode); + int get hashCode => Object.hash(type, id); @override bool operator ==(Object other) { diff --git a/packages/reactter/lib/src/core/dependency_register.dart b/packages/reactter/lib/src/core/dependency_register.dart index 128a4c14..27ad6bc3 100644 --- a/packages/reactter/lib/src/core/dependency_register.dart +++ b/packages/reactter/lib/src/core/dependency_register.dart @@ -1,4 +1,4 @@ -part of 'core.dart'; +part of '../internals.dart'; /// A class that represents a dependency register in Reactter. /// @@ -17,7 +17,7 @@ class DependencyRegister extends DependencyRef { final DependencyMode mode; /// Stores the refs where the instance was created. - final refs = HashSet(); + final LinkedHashSet refs = LinkedHashSet(); T? _instance; T? get instance => _instance; @@ -28,16 +28,8 @@ class DependencyRegister extends DependencyRef { this.mode = DependencyMode.builder, }) : super(id); + /// Creates an instance of the dependency. T? builder() { - _instance = _builder(); - - return _instance; - } - - @override - String toString() { - final hashCode = instance != null ? "(${instance.hashCode})" : ""; - - return '${super.toString()}$hashCode'; + return _instance = _builder(); } } diff --git a/packages/reactter/lib/src/core/event_handler.dart b/packages/reactter/lib/src/core/event_handler.dart index fd288fb0..70f1b1fd 100644 --- a/packages/reactter/lib/src/core/event_handler.dart +++ b/packages/reactter/lib/src/core/event_handler.dart @@ -1,16 +1,14 @@ -part of 'core.dart'; +part of '../internals.dart'; /// A abstract-class that adds event handler features to classes that use it. /// /// It contains methods for adding, removing, and triggering events, /// as well as storing event callbacks. @internal -abstract class EventHandler { +abstract class EventHandler implements IContext { + @override @internal - DependencyInjection get dependencyInjection; - - @internal - Logger get logger; + EventHandler get eventHandler => this; final _notifiers = HashSet(); @@ -71,11 +69,14 @@ abstract class EventHandler { if (notifier?._dependencyRef == null) { notifier?.notifyListeners(param); notifierPartner?.notifyListeners(param); - return; + } else { + notifierPartner?.notifyListeners(param); + notifier?.notifyListeners(param); } - notifierPartner?.notifyListeners(param); - notifier?.notifyListeners(param); + if (eventName is Lifecycle) { + _notifyToObservers(instance, eventName, param); + } } /// Removes all instance's events @@ -83,18 +84,29 @@ abstract class EventHandler { final notifiers = _notifiers.where((notifier) { if (!generic && notifier._dependencyRef != null) return false; - return notifier == instance; + return instance != null && notifier.isInstanceOrDependencyRef(instance); }); - for (final notifier in {...notifiers}) { + final boundInstance = instance is RtState ? instance.boundInstance : null; + + for (final notifier in notifiers.toList(growable: false)) { + if (boundInstance != null && + notifier.isInstanceOrDependencyRef(boundInstance)) { + continue; + } + notifier.dispose(); _notifiers.remove(notifier); + stateManagement._deferredEvents.remove(notifier); } } /// Checks if an object has any listeners. bool _hasListeners(Object? instance) { - return _notifiers.any((notifier) => notifier == instance); + return instance != null && + _notifiers.any( + (notifier) => notifier.isInstanceOrDependencyRef(instance), + ); } /// Retrieves the [EventNotifier] for the given [instance] and [eventName]. @@ -105,7 +117,6 @@ abstract class EventHandler { instance, eventName, dependencyInjection, - logger, _offEventNotifier, ); } @@ -120,8 +131,8 @@ abstract class EventHandler { /// If the [EventNotifier] does not exist in the lookup table, it creates a new one. EventNotifier? _getEventNotifierPartner(Object? instance, Enum eventName) { final instancePartner = instance is DependencyRef - ? dependencyInjection._getDependencyRegisterByRef(instance)?.instance - : dependencyInjection._getDependencyRef(instance); + ? dependencyInjection.getDependencyRegisterByRef(instance)?.instance + : dependencyInjection.getDependencyRef(instance); if (instancePartner == null) return null; @@ -140,17 +151,58 @@ abstract class EventHandler { void _resolveLifecycleEvent( Object? instance, Lifecycle lifecycle, [ - StateBase? state, + dynamic param, ]) { - if (instance is LifecycleObserver) { - return _executeLifecycleObserver(instance, lifecycle, state); - } + final instanceObj = instance is DependencyRef + ? dependencyInjection.getDependencyRegisterByRef(instance)?.instance + : instance; - if (instance is! DependencyRef) return; - - final instanceObj = - dependencyInjection._getDependencyRegisterByRef(instance)?.instance; + if (instanceObj is DependencyLifecycle) { + _processLifecycleCallbacks(instanceObj, lifecycle, param); + } + } - return _resolveLifecycleEvent(instanceObj, lifecycle, state); + void _notifyToObservers( + Object? instanceOrDependencyRef, + Lifecycle lifecycle, + dynamic param, + ) { + final dependencyRef = instanceOrDependencyRef is DependencyRef + ? instanceOrDependencyRef + : dependencyInjection.getDependencyRef(instanceOrDependencyRef); + + final instance = param is Object + ? param + : dependencyInjection + .getDependencyRegisterByRef(dependencyRef) + ?.instance; + + if (dependencyRef == null) return; + + for (final observer + in IDependencyObserver._observers.toList(growable: false)) { + switch (lifecycle) { + case Lifecycle.registered: + observer.onDependencyRegistered(dependencyRef); + break; + case Lifecycle.created: + observer.onDependencyCreated(dependencyRef, instance); + break; + case Lifecycle.didMount: + observer.onDependencyMounted(dependencyRef, instance); + break; + case Lifecycle.didUnmount: + observer.onDependencyUnmounted(dependencyRef, instance); + break; + case Lifecycle.deleted: + observer.onDependencyDeleted(dependencyRef, instance); + break; + case Lifecycle.unregistered: + observer.onDependencyUnregistered(dependencyRef); + break; + default: + break; + } + } } } diff --git a/packages/reactter/lib/src/core/event_notifier.dart b/packages/reactter/lib/src/core/event_notifier.dart index 67de77fc..46e403b9 100644 --- a/packages/reactter/lib/src/core/event_notifier.dart +++ b/packages/reactter/lib/src/core/event_notifier.dart @@ -1,4 +1,4 @@ -part of 'core.dart'; +part of '../internals.dart'; /// A class that represents an event notifier reference for the [EventHandler]. class EventNotifierRef { @@ -23,19 +23,19 @@ class EventNotifierRef { @override bool operator ==(Object other) { - if (other is EventNotifierRef) { - if (other.event != event) { - return false; - } + if (other is! EventNotifierRef) { + return false; + } - if (_dependencyRef != null && other._dependencyRef != null) { - return _dependencyRef == other._dependencyRef; - } + if (other.event != event) { + return false; + } - return _instanceObj == other._instanceObj; + if (_dependencyRef != null && other._dependencyRef != null) { + return _dependencyRef == other._dependencyRef; } - return false; + return _instanceObj == other._instanceObj; } } @@ -53,44 +53,26 @@ class EventNotifierRef { /// behavior when notifying listeners. /// @internal -class EventNotifier extends EventNotifierRef { - int _count = 0; - // The _listeners is intentionally set to a fixed-length _GrowableList instead - // of const []. - // - // The const [] creates an instance of _ImmutableList which would be - // different from fixed-length _GrowableList used elsewhere in this class. - // keeping runtime type the same during the lifetime of this class lets the - // compiler to infer concrete type for this property, and thus improves - // performance. - static final List _emptyListeners = - List.filled(0, null); - List _listeners = _emptyListeners; - final _listenersSingleUse = {}; - - int _notificationCallStackDepth = 0; - int _reentrantlyRemovedListeners = 0; - bool _debugDisposed = false; - +class EventNotifier extends EventNotifierRef with Notifier { final DependencyInjection dependencyInjection; - final Logger logger; + @override + String get target => "$instanceObj about $event"; final void Function(EventNotifier notifier) onNotifyComplete; + DependencyRef? get dependencyRef => + _dependencyRef ?? dependencyInjection.getDependencyRef(_instanceObj); + + Object? get instanceObj => + _instanceObj ?? + dependencyInjection.getDependencyRegisterByRef(_dependencyRef)?.instance; + EventNotifier( Object? instanceOrObj, Enum event, this.dependencyInjection, - this.logger, this.onNotifyComplete, ) : super(instanceOrObj, event); - DependencyRef? get instanceRef => - _dependencyRef ?? dependencyInjection._getDependencyRef(_instanceObj); - - Object? get instanceObj => - _instanceObj ?? - dependencyInjection._getDependencyRegisterByRef(_dependencyRef)?.instance; - @override int get hashCode => Object.hash( _dependencyRef.hashCode, @@ -100,233 +82,15 @@ class EventNotifier extends EventNotifierRef { @override bool operator ==(Object other) { - if (other is EventNotifierRef) { - return super == other; - } - - if (other is DependencyRef) { - return instanceRef == other; - } - - final instanceRefSelf = instanceRef; - - if (instanceRefSelf != null) { - return instanceRefSelf == dependencyInjection._getDependencyRef(other); - } - - return instanceObj == other; - } - - /// Copied from Flutter - /// Used by subclasses to assert that the [EventNotifier] has not yet been - /// disposed. - /// - /// {@tool snippet} - /// The [debugAssertNotDisposed] function should only be called inside of an - /// assert, as in this example. - /// - /// ```dart - /// class MyNotifier with EventNotifier { - /// void doUpdate() { - /// assert(ReacterNotifier.debugAssertNotDisposed(this)); - /// // ... - /// } - /// } - /// ``` - /// {@end-tool} - // This is static and not an instance method because too many people try to - // implement EventNotifier instead of extending it (and so it is too breaking - // to add a method, especially for debug). - // coverage:ignore-start - static bool debugAssertNotDisposed(EventNotifier notifier) { - assert(() { - if (notifier._debugDisposed) { - throw AssertionError( - 'A ${notifier.runtimeType} was used after being disposed.\n' - 'Once you have called dispose() on a ${notifier.runtimeType}, it ' - 'can no longer be used.', - ); - } - return true; - }()); - return true; - } - // coverage:ignore-end - - /// Copied from Flutter - /// Whether any listeners are currently registered. - /// - /// Clients should not depend on this value for their behavior, because having - /// one listener's logic change when another listener happens to start or stop - /// listening will lead to extremely hard-to-track bugs. Subclasses might use - /// this information to determine whether to do any work when there are no - /// listeners, however; for example, resuming a [Stream] when a listener is - /// added and pausing it when a listener is removed. - /// - /// Typically this is used by overriding [addListener], checking if - /// [hasListeners] is false before calling `super.addListener()`, and if so, - /// starting whatever work is needed to determine when to call - /// [notifyListeners]; and similarly, by overriding [removeListener], checking - /// if [hasListeners] is false after calling `super.removeListener()`, and if - /// so, stopping that same work. - /// - /// This method returns false if [dispose] has been called. - @protected - bool get hasListeners => _count > 0; - - /// Copied from Flutter - /// Register a closure to be called when the object changes. - /// - /// If the given closure is already registered, an additional instance is - /// added, and must be removed the same number of times it is added before it - /// will stop being called. - /// - /// This method must not be called after [dispose] has been called. - /// - /// {@template reactter.EventNotifier.addListener} - /// If a listener is added twice, and is removed once during an iteration - /// (e.g. in response to a notification), it will still be called again. If, - /// on the other hand, it is removed as many times as it was registered, then - /// it will no longer be called. This odd behavior is the result of the - /// [EventNotifier] not being able to determine which listener is being - /// removed, since they are identical, therefore it will conservatively still - /// call all the listeners when it knows that any are still registered. - /// - /// This surprising behavior can be unexpectedly observed when registering a - /// listener on two separate objects which are both forwarding all - /// registrations to a common upstream object. - /// {@endtemplate} - /// - /// See also: - /// - /// * [removeListener], which removes a previously registered closure from - /// the list of closures that are notified when the object changes. - void addListener(Function listener, [bool singleUse = false]) { - assert(EventNotifier.debugAssertNotDisposed(this)); - - if (_count == _listeners.length) { - if (_count == 0) { - _listeners = List.filled(1, null); - } else { - final newListeners = List.filled( - _listeners.length * 2, - null, - ); - - for (int i = 0; i < _count; i++) { - newListeners[i] = _listeners[i]; - } - - _listeners = newListeners; - } - } - - _listeners[_count++] = listener; - - if (singleUse) { - _listenersSingleUse.add(listener); - } + return other is EventNotifierRef && super == other; } - /// Copied from Flutter - // coverage:ignore-start - void _removeAt(int index) { - // The list holding the listeners is not growable for performances reasons. - // We still want to shrink this list if a lot of listeners have been added - // and then removed outside a notifyListeners iteration. - // We do this only when the real number of listeners is half the length - // of our list. - _count -= 1; - if (_count * 2 <= _listeners.length) { - final newListeners = List.filled(_count, null); - - // Listeners before the index are at the same place. - for (int i = 0; i < index; i++) { - newListeners[i] = _listeners[i]; - } - - // Listeners after the index move towards the start of the list. - for (int i = index; i < _count; i++) { - newListeners[i] = _listeners[i + 1]; - } + bool isInstanceOrDependencyRef(Object other) { + if (other == _instanceObj || other == _dependencyRef) return true; - _listeners = newListeners; - } else { - // When there are more listeners than half the length of the list, we only - // shift our listeners, so that we avoid to reallocate memory for the - // whole list. - for (int i = index; i < _count; i++) { - _listeners[i] = _listeners[i + 1]; - } - _listeners[_count] = null; - } - } - // coverage:ignore-end + if (other is DependencyRef) return other == dependencyRef; - /// Copied from Flutter - /// Remove a previously registered closure from the list of closures that are - /// notified when the object changes. - /// - /// If the given listener is not registered, the call is ignored. - /// - /// This method returns immediately if [dispose] has been called. - /// - /// See also: - /// - /// * [addListener], which registers a closure to be called when the object - /// changes. - void removeListener(Function listener) { - // This method is allowed to be called on disposed instances for usability - // reasons. Due to how our frame scheduling logic between render objects and - // overlays, it is common that the owner of this instance would be disposed a - // frame earlier than the listeners. Allowing calls to this method after it - // is disposed makes it easier for listeners to properly clean up. - for (int i = 0; i < _count; i++) { - final Function? listenerAtIndex = _listeners[i]; - if (listenerAtIndex == listener) { - if (_notificationCallStackDepth > 0) { - // We don't resize the list during notifyListeners iterations - // but we set to null, the listeners we want to remove. We will - // effectively resize the list at the end of all notifyListeners - // iterations. - _listeners[i] = null; - _reentrantlyRemovedListeners++; - } else { - // When we are outside the notifyListeners iterations we can - // effectively shrink the list. - _removeAt(i); - } - break; - } - } - } - - /// Copied from Flutter - /// Discards any resources used by the object. After this is called, the - /// object is not in a usable state and should be discarded (calls to - /// [addListener] will throw after the object is disposed). - /// - /// This method should only be called by the object's owner. - /// - /// This method does not notify listeners, and clears the listener list once - /// it is called. Consumers of this class must decide on whether to notify - /// listeners or not immediately before disposal. - @mustCallSuper - void dispose() { - assert(EventNotifier.debugAssertNotDisposed(this)); - assert( - _notificationCallStackDepth == 0, - 'The "dispose()" method on $this was called during the call to ' - '"notifyListeners()". This is likely to cause errors since it modifies ' - 'the list of listeners while the list is being used.', - ); - assert(() { - _debugDisposed = true; - return true; - }()); - - _listeners = _emptyListeners; - _count = 0; + return other == instanceObj; } /// Copied from Flutter @@ -342,91 +106,24 @@ class EventNotifier extends EventNotifierRef { /// Surprising behavior can result when reentrantly removing a listener (e.g. /// in response to a notification) that has been registered multiple times. /// See the discussion at [removeListener]. + @override @protected @visibleForTesting void notifyListeners(Object? param) { - assert(EventNotifier.debugAssertNotDisposed(this)); - - if (_count == 0) { - return; - } - - // To make sure that listeners removed during this iteration are not called, - // we set them to null, but we don't shrink the list right away. - // By doing this, we can continue to iterate on our list until it reaches - // the last listener added before the call to this method. - - // To allow potential listeners to recursively call notifyListener, we track - // the number of times this method is called in _notificationCallStackDepth. - // Once every recursive iteration is finished (i.e. when _notificationCallStackDepth == 0), - // we can safely shrink our list so that it will only contain not null - // listeners. - - _notificationCallStackDepth++; - - final int end = _count; - for (int i = 0; i < end; i++) { - try { - final listener = _listeners[i]; - - if (_listenersSingleUse.contains(listener)) { - removeListener(listener!); - _listenersSingleUse.remove(listener); - } - - listener?.call(instanceObj, param); - } catch (error, _) { - logger.log( - 'An error was caught during notifyListeners on $instanceObj about $event event', - error: error, - level: LogLevel.error, - ); - } - } - - _notificationCallStackDepth--; - - // coverage:ignore-start - // No coverage for the following block because it is only used for - // performance optimization. - if (_notificationCallStackDepth == 0 && _reentrantlyRemovedListeners > 0) { - // We really remove the listeners when all notifications are done. - final int newLength = _count - _reentrantlyRemovedListeners; - if (newLength * 2 <= _listeners.length) { - // As in _removeAt, we only shrink the list when the real number of - // listeners is half the length of our list. - final List newListeners = - List.filled(newLength, null); - - int newIndex = 0; - for (int i = 0; i < _count; i++) { - final Function? listener = _listeners[i]; - if (listener != null) { - newListeners[newIndex++] = listener; - } - } - - _listeners = newListeners; - } else { - // Otherwise we put all the null references at the end. - for (int i = 0; i < newLength; i += 1) { - if (_listeners[i] == null) { - // We swap this item with the next not null item. - int swapIndex = i + 1; - while (_listeners[swapIndex] == null) { - swapIndex += 1; - } - _listeners[i] = _listeners[swapIndex]; - _listeners[swapIndex] = null; - } - } + try { + super.notifyListeners(param); + onNotifyComplete(this); + } catch (err) { + if (err is AssertionError) { + onNotifyComplete(this); } - // coverage:ignore-end - _reentrantlyRemovedListeners = 0; - _count = newLength; - - onNotifyComplete(this); + rethrow; } } + + @override + void listenerCall(Function? listener, Object? param) { + listener?.call(instanceObj, param); + } } diff --git a/packages/reactter/lib/src/core/hook.dart b/packages/reactter/lib/src/core/hook.dart deleted file mode 100644 index b24a22da..00000000 --- a/packages/reactter/lib/src/core/hook.dart +++ /dev/null @@ -1,28 +0,0 @@ -part of 'core.dart'; - -/// An abstract class that provides the base functionality for creating -/// custom hooks in the Reactter library. -abstract class Hook with State implements StateBase { - /// This variable is used to register [Hook] - /// and attach the [StateBase] that are defined here. - @protected - HookRegister get $; - - /// Initializes a new instance of the [Hook] class. - /// - /// This constructor calls the `end` method of the [HookRegister] instance - /// to register the hook and attach the collected states. - Hook() { - $.end(this); - } - - /// Executes [callback], and notifies the listeners about the update. - /// - /// If [callback] is provided, it will be executed before notifying the listeners. - /// If [callback] is not provided, an empty function will be executed. - @override - @mustCallSuper - void update([Function? callback]) { - return super.update(callback ?? () {}); - } -} diff --git a/packages/reactter/lib/src/core/hook_register.dart b/packages/reactter/lib/src/core/hook_register.dart deleted file mode 100644 index c58ef0c6..00000000 --- a/packages/reactter/lib/src/core/hook_register.dart +++ /dev/null @@ -1,10 +0,0 @@ -part of 'core.dart'; - -@internal -class HookRegister extends BindingZone { - /// Stores the instance of the [Hook] and attaches the previously collected states to it. - void end(Hook hook) { - bindInstanceToStates(hook); - BindingZone.recollectState(hook); - } -} diff --git a/packages/reactter/lib/src/core/lifecycle.dart b/packages/reactter/lib/src/core/lifecycle.dart index b147ece9..9947614b 100644 --- a/packages/reactter/lib/src/core/lifecycle.dart +++ b/packages/reactter/lib/src/core/lifecycle.dart @@ -1,16 +1,9 @@ -part of 'core.dart'; +part of '../internals.dart'; enum Lifecycle { /// This event is triggered when the [DependencyInjection] registers the dependency. registered, - /// This event is triggered when the [DependencyInjection] initializes the dependency. - @Deprecated( - 'Use `Lifecycle.created` instead. ' - 'This feature was deprecated after v7.2.0.', - ) - initialized, - /// This event is triggered when the [DependencyInjection] created the dependency instance. created, @@ -20,10 +13,10 @@ enum Lifecycle { /// This event(exclusive to `flutter_reactter`) happens after the dependency has been successfully mounted in the widget tree. didMount, - /// This event is triggered anytime the dependency's state is about to be updated. The event parameter is a [StateBase]. + /// This event is triggered anytime the dependency's state is about to be updated. The event parameter is a [IState]. willUpdate, - /// This event is triggered anytime the dependency's state has been updated. The event parameter is a [StateBase]. + /// This event is triggered anytime the dependency's state has been updated. The event parameter is a [IState]. didUpdate, /// This event(exclusive to `flutter_reactter`) happens when the dependency is going to be unmounted from the widget tree. @@ -32,13 +25,6 @@ enum Lifecycle { /// This event(exclusive to `flutter_reactter`) happens when the dependency has been successfully unmounted from the widget tree. didUnmount, - /// This event is triggered when the [DependencyInjection] destroys the dependency. - @Deprecated( - 'Use `Lifecycle.deleted` instead. ' - 'This feature was deprecated after v7.2.0.', - ) - destroyed, - /// This event is triggered when the [DependencyInjection] destroys the dependency instance. deleted, diff --git a/packages/reactter/lib/src/core/lifecycle_observer.dart b/packages/reactter/lib/src/core/lifecycle_observer.dart index 8c45cebb..8ce2c6c4 100644 --- a/packages/reactter/lib/src/core/lifecycle_observer.dart +++ b/packages/reactter/lib/src/core/lifecycle_observer.dart @@ -1,16 +1,16 @@ -part of 'core.dart'; +part of '../internals.dart'; -/// {@template lifecycle_observer} +/// {@template reactter.dependency_lifecycle} /// It's a mixin that provides a set of methods that can be used to observe /// the lifecycle of a dependency. /// /// Example usage: /// ```dart -/// class MyController with LifecycleObserver { +/// class MyController with DependencyLifecycle { /// final state = UseState('initial'); /// /// @override -/// void onInitialized() { +/// void onCreated() { /// print('MyController has been initialized'); /// } /// @@ -27,53 +27,41 @@ part of 'core.dart'; /// ``` /// {@endtemplate} -abstract class LifecycleObserver { - /// This method is called when the dependency is initialized. - @Deprecated( - 'Use `onCreated` instead. ' - 'This feature was deprecated after v7.2.0.', - ) - void onInitialized() {} - +abstract class DependencyLifecycle { /// This method is called when the dependency instance is created. - void onCreated() {} + void onCreated(); /// This method is called when the dependency is going to be mounted /// in the widget tree(exclusive to `flutter_reactter`). - void onWillMount() {} + void onWillMount(); /// This method is called when the dependency has been successfully mounted /// in the widget tree(exclusive to `flutter_reactter`). - void onDidMount() {} + void onDidMount(); /// This method is called when the dependency's state is about to be updated. - /// The parameter is a [StateBase]. - void onWillUpdate(covariant StateBase? state) {} + /// The parameter is a [IState]. + void onWillUpdate(covariant IState? state); /// This method is called when the dependency's state has been updated. - /// The parameter is a [StateBase]. - void onDidUpdate(covariant StateBase? state) {} + /// The parameter is a [IState]. + void onDidUpdate(covariant IState? state); /// This method is called when the dependency is going to be unmounted /// from the widget tree(exclusive to `flutter_reactter`). - void onWillUnmount() {} + void onWillUnmount(); /// This method is called when the dependency has been successfully unmounted /// from the widget tree(exclusive to `flutter_reactter`). - void onDidUnmount() {} + void onDidUnmount(); } -void _executeLifecycleObserver( - LifecycleObserver observer, +void _processLifecycleCallbacks( + DependencyLifecycle observer, Lifecycle lifecycle, [ - StateBase? state, + dynamic param, ]) { switch (lifecycle) { - // ignore: deprecated_member_use_from_same_package - case Lifecycle.initialized: - // ignore: deprecated_member_use_from_same_package - observer.onInitialized(); - break; case Lifecycle.created: observer.onCreated(); break; @@ -84,10 +72,10 @@ void _executeLifecycleObserver( observer.onDidMount(); break; case Lifecycle.willUpdate: - observer.onWillUpdate(state); + observer.onWillUpdate(param); break; case Lifecycle.didUpdate: - observer.onDidUpdate(state); + observer.onDidUpdate(param); break; case Lifecycle.willUnmount: observer.onWillUnmount(); diff --git a/packages/reactter/lib/src/core/logger.dart b/packages/reactter/lib/src/core/logger.dart deleted file mode 100644 index bf85b286..00000000 --- a/packages/reactter/lib/src/core/logger.dart +++ /dev/null @@ -1,13 +0,0 @@ -part of 'core.dart'; - -enum LogLevel { info, warning, error } - -@internal -abstract class Logger { - /// It's used to determine whether logging is enabled or disabled. - bool isLogEnable = true; - - /// It's used as a callback function for logging purposes in - /// the [RtInterface] class. - LogWriterCallback get log; -} diff --git a/packages/reactter/lib/src/core/notifier.dart b/packages/reactter/lib/src/core/notifier.dart new file mode 100644 index 00000000..d3b8f301 --- /dev/null +++ b/packages/reactter/lib/src/core/notifier.dart @@ -0,0 +1,357 @@ +part of '../internals.dart'; + +@internal +abstract class Notifier { + int _count = 0; + // The _listeners is intentionally set to a fixed-length _GrowableList instead + // of const []. + // + // The const [] creates an instance of _ImmutableList which would be + // different from fixed-length _GrowableList used elsewhere in this class. + // keeping runtime type the same during the lifetime of this class lets the + // compiler to infer concrete type for this property, and thus improves + // performance. + static final List _emptyListeners = + List.filled(0, null); + List _listeners = _emptyListeners as List; + final _listenersSingleUse = {}; + + int _notificationCallStackDepth = 0; + int _reentrantlyRemovedListeners = 0; + bool _debugDisposed = false; + + String get target; + + /// Copied from Flutter + /// Used by subclasses to assert that the [Notifier] has not yet been + /// disposed. + /// + /// {@tool snippet} + /// The [debugAssertNotDisposed] function should only be called inside of an + /// assert, as in this example. + /// + /// ```dart + /// class MyNotifier with Notifier { + /// void doUpdate() { + /// assert(ReacterNotifier.debugAssertNotDisposed(this)); + /// // ... + /// } + /// } + /// ``` + /// {@end-tool} + // This is static and not an instance method because too many people try to + // implement Notifier instead of extending it (and so it is too breaking + // to add a method, especially for debug). + // coverage:ignore-start + static bool debugAssertNotDisposed(Notifier notifier) { + assert(() { + if (notifier._debugDisposed) { + throw AssertionError( + 'A ${notifier.runtimeType} was used after being disposed.\n' + 'Once you have called dispose() on a ${notifier.runtimeType}, it ' + 'can no longer be used.', + ); + } + return true; + }()); + return true; + } + // coverage:ignore-end + + /// Copied from Flutter + /// Whether any listeners are currently registered. + /// + /// Clients should not depend on this value for their behavior, because having + /// one listener's logic change when another listener happens to start or stop + /// listening will lead to extremely hard-to-track bugs. Subclasses might use + /// this information to determine whether to do any work when there are no + /// listeners, however; for example, resuming a [Stream] when a listener is + /// added and pausing it when a listener is removed. + /// + /// Typically this is used by overriding [addListener], checking if + /// [hasListeners] is false before calling `super.addListener()`, and if so, + /// starting whatever work is needed to determine when to call + /// [notifyListeners]; and similarly, by overriding [removeListener], checking + /// if [hasListeners] is false after calling `super.removeListener()`, and if + /// so, stopping that same work. + /// + /// This method returns false if [dispose] has been called. + @protected + bool get hasListeners => _count > 0; + + /// Copied from Flutter + /// Register a closure to be called when the object changes. + /// + /// If the given closure is already registered, an additional instance is + /// added, and must be removed the same number of times it is added before it + /// will stop being called. + /// + /// This method must not be called after [dispose] has been called. + /// + /// {@template reactter.Notifier.addListener} + /// If a listener is added twice, and is removed once during an iteration + /// (e.g. in response to a notification), it will still be called again. If, + /// on the other hand, it is removed as many times as it was registered, then + /// it will no longer be called. This odd behavior is the result of the + /// [Notifier] not being able to determine which listener is being + /// removed, since they are identical, therefore it will conservatively still + /// call all the listeners when it knows that any are still registered. + /// + /// This surprising behavior can be unexpectedly observed when registering a + /// listener on two separate objects which are both forwarding all + /// registrations to a common upstream object. + /// {@endtemplate} + /// + /// See also: + /// + /// * [removeListener], which removes a previously registered closure from + /// the list of closures that are notified when the object changes. + void addListener(T listener, [bool singleUse = false]) { + assert(Notifier.debugAssertNotDisposed(this)); + + if (_count == _listeners.length) { + if (_count == 0) { + _listeners = List.filled(1, null); + } else { + final newListeners = List.filled( + _listeners.length * 2, + null, + ); + + for (int i = 0; i < _count; i++) { + newListeners[i] = _listeners[i]; + } + + _listeners = newListeners; + } + } + + _listeners[_count++] = listener; + + if (singleUse) { + _listenersSingleUse.add(listener); + } + } + + /// Copied from Flutter + // coverage:ignore-start + void _removeAt(int index) { + // The list holding the listeners is not growable for performances reasons. + // We still want to shrink this list if a lot of listeners have been added + // and then removed outside a notifyListeners iteration. + // We do this only when the real number of listeners is half the length + // of our list. + _count -= 1; + if (_count * 2 <= _listeners.length) { + final newListeners = List.filled(_count, null); + + // Listeners before the index are at the same place. + for (int i = 0; i < index; i++) { + newListeners[i] = _listeners[i]; + } + + // Listeners after the index move towards the start of the list. + for (int i = index; i < _count; i++) { + newListeners[i] = _listeners[i + 1]; + } + + _listeners = newListeners; + } else { + // When there are more listeners than half the length of the list, we only + // shift our listeners, so that we avoid to reallocate memory for the + // whole list. + for (int i = index; i < _count; i++) { + _listeners[i] = _listeners[i + 1]; + } + _listeners[_count] = null; + } + } + // coverage:ignore-end + + /// Copied from Flutter + /// Remove a previously registered closure from the list of closures that are + /// notified when the object changes. + /// + /// If the given listener is not registered, the call is ignored. + /// + /// This method returns immediately if [dispose] has been called. + /// + /// See also: + /// + /// * [addListener], which registers a closure to be called when the object + /// changes. + void removeListener(Function listener) { + if (_listenersSingleUse.contains(listener)) { + _listenersSingleUse.remove(listener); + } + + // This method is allowed to be called on disposed instances for usability + // reasons. Due to how our frame scheduling logic between render objects and + // overlays, it is common that the owner of this instance would be disposed a + // frame earlier than the listeners. Allowing calls to this method after it + // is disposed makes it easier for listeners to properly clean up. + for (int i = 0; i < _count; i++) { + final Function? listenerAtIndex = _listeners[i]; + if (listenerAtIndex == listener) { + if (_notificationCallStackDepth > 0) { + // We don't resize the list during notifyListeners iterations + // but we set to null, the listeners we want to remove. We will + // effectively resize the list at the end of all notifyListeners + // iterations. + _listeners[i] = null; + + _reentrantlyRemovedListeners++; + } else { + // When we are outside the notifyListeners iterations we can + // effectively shrink the list. + _removeAt(i); + } + + break; + } + } + } + + /// Copied from Flutter + /// Discards any resources used by the object. After this is called, the + /// object is not in a usable state and should be discarded (calls to + /// [addListener] will throw after the object is disposed). + /// + /// This method should only be called by the object's owner. + /// + /// This method does not notify listeners, and clears the listener list once + /// it is called. Consumers of this class must decide on whether to notify + /// listeners or not immediately before disposal. + @mustCallSuper + void dispose() { + assert(Notifier.debugAssertNotDisposed(this)); + assert( + _notificationCallStackDepth == 0, + 'The "dispose()" method on $this was called during the call to ' + '"notifyListeners()". This is likely to cause errors since it modifies ' + 'the list of listeners while the list is being used.', + ); + assert(() { + _debugDisposed = true; + return true; + }()); + + _listeners = _emptyListeners as List; + _count = 0; + } + + /// Copied from Flutter + /// Call all the registered listeners. + /// + /// Call this method whenever the object changes, to notify any clients the + /// object may have changed. Listeners that are added during this iteration + /// will not be visited. Listeners that are removed during this iteration will + /// not be visited after they are removed. + /// + /// This method must not be called after [dispose] has been called. + /// + /// Surprising behavior can result when reentrantly removing a listener (e.g. + /// in response to a notification) that has been registered multiple times. + /// See the discussion at [removeListener]. + @protected + @visibleForTesting + void notifyListeners(Object? param) { + assert(Notifier.debugAssertNotDisposed(this)); + + if (_count == 0) { + return; + } + + // To make sure that listeners removed during this iteration are not called, + // we set them to null, but we don't shrink the list right away. + // By doing this, we can continue to iterate on our list until it reaches + // the last listener added before the call to this method. + + // To allow potential listeners to recursively call notifyListener, we track + // the number of times this method is called in _notificationCallStackDepth. + // Once every recursive iteration is finished (i.e. when _notificationCallStackDepth == 0), + // we can safely shrink our list so that it will only contain not null + // listeners. + + _notificationCallStackDepth++; + + final int end = _count; + Object? error; + + for (int i = 0; i < end; i++) { + final listener = _listeners[i]; + + if (listener == null) continue; + + try { + if (_listenersSingleUse.contains(listener)) { + _listenersSingleUse.remove(listener); + removeListener(listener); + } + + listenerCall(listener, param); + } catch (err) { + error = err; + } finally { + // ignore: control_flow_in_finally + continue; + } + } + + _notificationCallStackDepth--; + + // coverage:ignore-start + // No coverage for the following block because it is only used for + // performance optimization. + if (_notificationCallStackDepth == 0 && _reentrantlyRemovedListeners > 0) { + // We really remove the listeners when all notifications are done. + final int newLength = _count - _reentrantlyRemovedListeners; + if (newLength * 2 <= _listeners.length) { + // As in _removeAt, we only shrink the list when the real number of + // listeners is half the length of our list. + final List newListeners = List.filled(newLength, null); + + int newIndex = 0; + for (int i = 0; i < _count; i++) { + final T? listener = _listeners[i]; + if (listener != null) { + newListeners[newIndex++] = listener; + } + } + + _listeners = newListeners; + } else { + // Otherwise we put all the null references at the end. + for (int i = 0; i < newLength; i += 1) { + if (_listeners[i] == null) { + // We swap this item with the next not null item. + int swapIndex = i + 1; + while (_listeners[swapIndex] == null) { + swapIndex += 1; + } + _listeners[i] = _listeners[swapIndex]; + _listeners[swapIndex] = null; + } + } + } + // coverage:ignore-end + + _reentrantlyRemovedListeners = 0; + _count = newLength; + } + + assert(() { + if (error == null) return true; + + throw AssertionError( + 'An error was thrown by a listener of $target.\n' + 'The error thrown was:\n' + ' $error\n', + ); + }()); + } + + /// Calls the [listener] with the given [param]. + /// Override this method to provide custom behavior when notifying listeners. + void listenerCall(T? listener, Object? param); +} diff --git a/packages/reactter/lib/src/core/observer_manager.dart b/packages/reactter/lib/src/core/observer_manager.dart new file mode 100644 index 00000000..604afd18 --- /dev/null +++ b/packages/reactter/lib/src/core/observer_manager.dart @@ -0,0 +1,30 @@ +part of '../internals.dart'; + +/// An abstract class representing an observer manager. +abstract class ObserverManager { + /// Adds an observer to the manager. + /// + /// The [observer] parameter is the observer to be added. + void addObserver(covariant IObserver observer) { + if (observer is IStateObserver) { + IStateObserver._observers.add(observer); + } + + if (observer is IDependencyObserver) { + IDependencyObserver._observers.add(observer); + } + } + + /// Removes an observer from the manager. + /// + /// The [observer] parameter is the observer to be removed. + void removeObserver(IObserver observer) { + if (observer is IStateObserver) { + IStateObserver._observers.remove(observer); + } + + if (observer is IDependencyObserver) { + IDependencyObserver._observers.remove(observer); + } + } +} diff --git a/packages/reactter/lib/src/core/state.dart b/packages/reactter/lib/src/core/state.dart deleted file mode 100644 index be0699ba..00000000 --- a/packages/reactter/lib/src/core/state.dart +++ /dev/null @@ -1,125 +0,0 @@ -part of 'core.dart'; - -/// An abstract class that provides common functionality for managing -/// state in Reactter. -@internal -abstract class State implements StateBase { - bool _isUpdating = false; - - /// The reference instance to the current state. - Object? get instanceBinded => _instanceBinded; - Object? _instanceBinded; - - /// Returns `true` if the state has been disposed. - bool get isDisposed => _isDisposed; - bool _isDisposed = false; - - bool get _hasListeners => - eventHandler._hasListeners(this) || - (_instanceBinded != null && eventHandler._hasListeners(_instanceBinded)); - - @mustCallSuper - @override - void bind(Object instance) { - assert(!_isDisposed, "Can't bind when it's been disposed"); - assert( - _instanceBinded == null, - "Can't bind a new instance because an instance is already.\n" - "Use `detachInstance` method, if you want to bind a new instance.", - ); - - eventHandler.one(instance, Lifecycle.deleted, _onInstanceDeleted); - _instanceBinded = instance; - - if (BindingZone.currentZone == null) _validateInstanceBinded(); - } - - @override - @mustCallSuper - void unbind() { - assert(!_isDisposed, "Can't unbind when it's been disposed"); - - if (_instanceBinded == null) return; - - eventHandler.off(_instanceBinded!, Lifecycle.deleted, _onInstanceDeleted); - _instanceBinded = null; - } - - @override - @mustCallSuper - void update(covariant Function fnUpdate) { - assert(!_isDisposed, "Can't update when it's been disposed"); - - if (!_hasListeners || _isUpdating) { - fnUpdate(); - return; - } - - _isUpdating = true; - _notify(Lifecycle.willUpdate); - fnUpdate(); - _notify(Lifecycle.didUpdate); - _isUpdating = false; - } - - @override - @mustCallSuper - void refresh() { - assert(!_isDisposed, "Can't refresh when it's been disposed"); - - if (!_hasListeners || _isUpdating) { - return _notify(Lifecycle.didUpdate); - } - - _isUpdating = true; - _notify(Lifecycle.didUpdate); - _isUpdating = false; - } - - @override - @mustCallSuper - void dispose() { - _isDisposed = true; - - if (_instanceBinded != null) { - eventHandler.off(_instanceBinded!, Lifecycle.deleted, _onInstanceDeleted); - _instanceBinded = null; - } - - eventHandler.offAll(this); - } - - void _validateInstanceBinded() { - if (dependencyInjection.isActive(instanceBinded)) return; - - logger.log( - "The instance binded($instanceBinded) to $this is not in Reactter's context and cannot be disposed automatically.\n" - "You can solve this problem in two ways:\n" - "1. Call the 'dispose' method manually when $this is no longer needed.\n" - "2. Create $instanceBinded using the dependency injection methods.\n" - "**Ignore this message if you are sure that it will be disposed.**", - level: LogLevel.warning, - ); - } - - /// When the instance is destroyed, this object is dispose. - void _onInstanceDeleted(_, __) => dispose(); - - /// Notifies the listeners about the specified [event]. - /// If [Rt._isUntrackedRunning] is true, the notification is skipped. - /// If [Rt._isBatchRunning] is true, the notification is deferred until the batch is completed. - /// The [event] is emitted using [Rt.emit] for the current instance and [_instanceBinded]. - void _notify(Enum event) { - if (stateManagment._isUntrackedRunning) return; - - final emit = stateManagment._isBatchRunning - ? stateManagment._emitDefferred - : eventHandler.emit; - - emit(this, event, this); - - if (_instanceBinded == null) return; - - emit(_instanceBinded!, event, this); - } -} diff --git a/packages/reactter/lib/src/core/state_base.dart b/packages/reactter/lib/src/core/state_base.dart deleted file mode 100644 index 38cad85b..00000000 --- a/packages/reactter/lib/src/core/state_base.dart +++ /dev/null @@ -1,41 +0,0 @@ -part of 'core.dart'; - -/// A abstract class that represents a stare in Reactter. -@internal -abstract class StateBase { - @internal - DependencyInjection get dependencyInjection; - @internal - StateManagement get stateManagment; - @internal - EventHandler get eventHandler; - @internal - Logger get logger; - - /// Stores a reference to an object instance - @mustCallSuper - void bind(Object instance); - - /// Removes the reference to the object instance - @mustCallSuper - void unbind(); - - /// Executes [fnUpdate], and notify the listeners about to update. - /// - /// This method triggers the `Lifecycle.didUpdate` event, - /// which allows listeners to react to the updated state. - @mustCallSuper - void update(covariant Function fnUpdate); - - /// It's used to notify listeners that the state has been updated. - /// It is typically called after making changes to the state object. - /// - /// This method triggers the `Lifecycle.didUpdate` event, - /// which allows listeners to react to the updated state. - @mustCallSuper - void refresh(); - - /// Called when this object is removed - @mustCallSuper - void dispose(); -} diff --git a/packages/reactter/lib/src/core/state_management.dart b/packages/reactter/lib/src/core/state_management.dart index da1a86a0..4f795fbd 100644 --- a/packages/reactter/lib/src/core/state_management.dart +++ b/packages/reactter/lib/src/core/state_management.dart @@ -1,16 +1,45 @@ -part of 'core.dart'; +part of '../internals.dart'; @internal -abstract class StateManagement { +abstract class StateManagement implements IContext { + @override @internal - EventHandler get eventHandler; + StateManagement get stateManagement => this; - bool _isUntrackedRunning = false; - bool _isBatchRunning = false; - final HashMap _deferredEvents = HashMap(); + int _batchRunningCount = 0; + bool get _isBatchRunning => _batchRunningCount > 0; - /// {@template lazy_state} - /// Lazily initializes a state of type [StateBase] and attaches it to the given [instance]. + int _untrackedRunningCount = 0; + bool get _isUntrackedRunning => _untrackedRunningCount > 0; + + final LinkedHashMap _deferredEvents = LinkedHashMap(); + + /// Register a new state by invoking the provided `buildState` function. + /// + /// The `buildState` function should return an instance of a class that extends [S]. + /// The register state is automatically bound to the current binding zone using `BindingZone.autoBinding`. + /// + /// Example usage: + /// ```dart + /// class MyState extends RtState { + /// int _value = 0; + /// int get value => value; + /// set value(int n) { + /// if (n == _value) return; + /// update(() => _value = n); + /// } + /// } + /// + /// final state = Rt.registerState(() => MyState()); + /// ``` + /// + /// Returns [T] state. + T registerState(T Function() buildState) { + return BindingZone.autoBinding(buildState); + } + + /// {@template reactter.lazy_state} + /// Lazily initializes a state of type [IState] and attaches it to the given [instance]. /// /// This method is recommended to use when initializing state inside a class /// using the `late` keyword. @@ -36,16 +65,10 @@ abstract class StateManagement { /// ``` /// {@endtemplate} T lazyState(T Function() buildState, Object instance) { - final zone = BindingZone(); - - try { - return buildState(); - } finally { - zone.bindInstanceToStates(instance); - } + return BindingZone.autoBinding(buildState, instance); } - /// {@template untracked} + /// {@template reactter.untracked} /// Executes the given [callback] function without tracking any state changes. /// This means that any state changes that occur inside the [callback] function /// will not trigger any side effects. @@ -68,20 +91,17 @@ abstract class StateManagement { /// print(computed.value); // 1 -> because the state change is not tracked /// ``` /// {@endtemplate} - T untracked(T Function() callback) { - if (_isUntrackedRunning) { - return callback(); - } - + FutureOr untracked(FutureOr Function() callback) async { try { - _isUntrackedRunning = true; - return callback(); + _untrackedRunningCount++; + + return callback is Future Function() ? await callback() : callback(); } finally { - _isUntrackedRunning = false; + _untrackedRunningCount--; } } - /// {@template batch} + /// {@template reactter.batch} /// Executes the given [callback] function within a batch operation. /// /// A batch operation allows multiple state changes to be grouped together, @@ -110,29 +130,36 @@ abstract class StateManagement { /// print(computed.value); // 3 -> because the batch operation is completed. /// ``` /// {@endtemplate} - T batch(T Function() callback) { - if (_isBatchRunning) { - return callback(); - } - + FutureOr batch(FutureOr Function() callback) async { try { - _isBatchRunning = true; - return callback(); + _batchRunningCount++; + + return callback is Future Function() ? await callback() : callback(); } finally { - _isBatchRunning = false; - _endBatch(); + _batchRunningCount--; + + if (_batchRunningCount == 0) { + _endBatch(); + } } } void _endBatch() { - try { - for (final event in _deferredEvents.entries) { - final notifier = event.key; - final param = event.value; - notifier.notifyListeners(param); + for (final event in _deferredEvents.entries.toList(growable: false)) { + final notifier = event.key; + final param = event.value; + + if (_deferredEvents.isEmpty) { + break; + } + + if (_deferredEvents.containsKey(notifier)) { + _deferredEvents.remove(notifier); + + if (!notifier._debugDisposed) { + notifier.notifyListeners(param); + } } - } finally { - _deferredEvents.clear(); } } diff --git a/packages/reactter/lib/src/devtools.dart b/packages/reactter/lib/src/devtools.dart new file mode 100644 index 00000000..98c86133 --- /dev/null +++ b/packages/reactter/lib/src/devtools.dart @@ -0,0 +1,572 @@ +// coverage:ignore-file +import 'dart:collection'; +import 'dart:developer' as dev; + +import 'package:meta/meta.dart'; +import 'package:reactter/src/signal/signal.dart'; + +import 'framework.dart'; +import 'internals.dart'; + +extension RtDevToolsExt on RtInterface { + /// Initialize the devtools for observing the states and dependencies. + /// This will enable the devtools extension in the `Reactter` tab. + void initializeDevTools() { + RtDevTools.initialize(); + } +} + +/// This class is used internally by the devtools extension only +/// and should not be used directly by the users. +/// +/// To enable it, call the [Rt.initializeDevTools] method. +/// This will initialize the devtools and start observing changes. +/// +/// To access the Reactter devtools, open the devtools extension +/// and navigate to the Reactter tab. +@internal +class RtDevTools implements IStateObserver, IDependencyObserver { + static RtDevTools? instance; + + final LinkedList<_Node> _nodes = LinkedList(); + final LinkedHashMap _nodesByKey = LinkedHashMap(); + + static void initialize() { + assert(() { + if (kDebugMode && instance != null) { + throw RtDevToolsInitializeAssertionError( + 'The devtools has already been initialized.', + ); + } + + return true; + }()); + + if (kDebugMode) { + instance ??= RtDevTools._(); + } + } + + RtDevTools._() { + Rt.addObserver(this); + } + + @override + void onStateCreated(RtState state) { + final stateKey = state.hashCode.toString(); + final stateNode = _addNode(state); + + dev.postEvent('ext.reactter.onStateCreated', { + 'stateKey': stateKey, + 'state': stateNode.toJson(), + }); + } + + @override + void onStateBound(RtState state, Object instance) { + final stateKey = state.hashCode.toString(); + final stateNode = _nodesByKey[stateKey]; + final instanceNode = _addNode(instance); + + if (stateNode != null) instanceNode.addChild(stateNode); + + dev.postEvent('ext.reactter.onStateBound', { + 'state': stateNode?.toJson(), + 'instance': instanceNode.toJson(), + }); + } + + @override + void onStateUnbound(RtState state, Object instance) { + final stateKey = state.hashCode.toString(); + final instanceKey = instance.hashCode.toString(); + final stateNode = _nodesByKey[stateKey]; + final instanceNode = _nodesByKey[instanceKey]; + + if (stateNode != null) instanceNode?.removeChild(stateNode); + + final isInstanceRemoved = _removeNode(instanceKey); + + dev.postEvent('ext.reactter.onStateUnbound', { + 'stateKey': stateKey, + 'instanceKey': instanceKey, + 'isInstanceRemoved': isInstanceRemoved, + }); + } + + @override + void onStateUpdated(RtState state) { + final stateKey = state.hashCode.toString(); + + dev.postEvent('ext.reactter.onStateUpdated', { + 'stateKey': stateKey, + }); + } + + @override + void onStateDisposed(RtState state) { + final stateKey = state.hashCode.toString(); + + final isStateRemoved = _removeNode(stateKey); + + dev.postEvent('ext.reactter.onStateDisposed', { + 'stateKey': stateKey, + 'isStateRemoved': isStateRemoved, + }); + } + + @override + void onDependencyRegistered(DependencyRef dependency) { + final dependencyNode = _addNode(dependency); + + dev.postEvent('ext.reactter.onDependencyRegistered', { + 'dependency': dependencyNode.toJson(), + }); + } + + @override + void onDependencyCreated(DependencyRef dependency, Object? instance) { + final dependencyKey = dependency.hashCode.toString(); + final dependencyNode = _nodesByKey[dependencyKey]; + + _Node? instanceNode; + if (instance != null) instanceNode = _addNode(instance); + + dev.postEvent('ext.reactter.onDependencyCreated', { + 'dependency': dependencyNode?.toJson(), + 'instance': instanceNode?.toJson(), + }); + } + + @override + void onDependencyMounted(DependencyRef dependency, Object? instance) { + final dependencyKey = dependency.hashCode.toString(); + final instanceKey = instance.hashCode.toString(); + + dev.postEvent('ext.reactter.onDependencyMounted', { + 'dependencyKey': dependencyKey, + 'instanceKey': instanceKey, + }); + } + + @override + void onDependencyUnmounted(DependencyRef dependency, Object? instance) { + final dependencyKey = dependency.hashCode.toString(); + final instanceKey = instance.hashCode.toString(); + + dev.postEvent('ext.reactter.onDependencyUnmounted', { + 'dependencyKey': dependencyKey, + 'instanceKey': instanceKey, + }); + } + + @override + void onDependencyDeleted(DependencyRef dependency, Object? instance) { + final dependencyKey = dependency.hashCode.toString(); + final instanceObj = + instance ?? _nodesByKey[dependencyKey]?.children.first.instance; + final instanceKey = instanceObj.hashCode.toString(); + final dependencyNode = _nodesByKey[dependencyKey]; + final instanceNode = _nodesByKey[instanceKey]; + + if (instanceNode != null) dependencyNode?.removeChild(instanceNode); + + final isInstanceRemoved = _removeNode(instanceKey); + + dev.postEvent('ext.reactter.onDependencyDeleted', { + 'dependencyKey': dependencyKey, + 'instanceKey': instanceKey, + 'isInstanceRemoved': isInstanceRemoved, + }); + } + + @override + void onDependencyUnregistered(DependencyRef dependency) { + final dependencyKey = dependency.hashCode.toString(); + + _removeNode(dependencyKey); + + dev.postEvent('ext.reactter.onDependencyUnregistered', { + 'dependencyKey': dependencyKey, + }); + } + + @override + void onDependencyFailed( + covariant DependencyRef dependency, + DependencyFail fail, + ) {} + + Map getNodes(int page, int pageSize) { + final nodesList = _nodes.toList(); + final length = nodesList.length; + final start = page * pageSize; + final end = length < (start + pageSize) ? length : start + pageSize; + final totalPages = (length / pageSize).ceil(); + + final data = + nodesList.sublist(start, end).map((node) => node.toJson()).toList(); + + return { + 'nodes': data, + 'total': length, + 'start': start, + 'end': end, + 'page': page, + 'pageSize': pageSize, + 'totalPages': totalPages, + }; + } + + _Node _addNode(Object instance) { + _Node node; + + if (instance is DependencyRef) { + node = _addDependencyNode(instance); + } else if (instance is RtState) { + node = _addStateNode(instance); + } else { + node = _addInstanceNode(instance); + } + + _nodesByKey[node.key] = node; + + if (node.list == null) { + _nodes.add(node); + node.moveChildren(); + } + + return node; + } + + bool _removeNode(String nodeKey) { + final node = _nodesByKey[nodeKey]; + + if (node == null) return false; + + final isRemoved = node.remove(); + + if (isRemoved) _nodesByKey.remove(nodeKey); + + return isRemoved; + } + + _StateNode _addStateNode(RtState state) { + final stateKey = state.hashCode.toString(); + final stateNode = _nodesByKey.putIfAbsent( + stateKey, + () => _StateNode(instance: state), + ) as _StateNode; + + final boundInstance = state.boundInstance; + + if (boundInstance != null) { + final boundInstanceNode = _addNode(boundInstance); + boundInstanceNode.addChild(stateNode); + } + + return stateNode; + } + + _DependencyNode _addDependencyNode(DependencyRef dependencyRef) { + final dependencyKey = dependencyRef.hashCode.toString(); + final dependencyNode = _nodesByKey.putIfAbsent( + dependencyKey, + () => _DependencyNode(instance: dependencyRef), + ) as _DependencyNode; + + final instance = Rt.getDependencyRegisterByRef(dependencyRef)?.instance; + + if (instance != null) { + final instanceNode = _addNode(instance); + dependencyNode.addChild(instanceNode); + } + + return dependencyNode; + } + + _InstanceNode _addInstanceNode(Object instance) { + return _nodesByKey.putIfAbsent( + instance.hashCode.toString(), + () => _InstanceNode(instance: instance), + ) as _InstanceNode; + } + + Map? getDebugInfo(String stateKey) { + final state = _nodesByKey[stateKey]; + + if (state is! _StateNode) return null; + + return state.instance.debugInfo; + } + + dynamic getBoundInstance(String stateKey) { + final state = _nodesByKey[stateKey]; + + if (state is! _StateNode) return null; + + return state.instance.boundInstance; + } + + dynamic getDependencyRef(String dependencyKey) { + final dependencyRef = _nodesByKey[dependencyKey]; + + if (dependencyRef is! _DependencyNode) return null; + + return dependencyRef.instance; + } + + Map getDependencyInfo(String dependencyKey) { + final dependencyRef = _nodesByKey[dependencyKey]; + + if (dependencyRef is! _DependencyNode) return {}; + + return dependencyRef.toJson(); + } + + Map getPlainInstanceInfo(Object instance) { + if (instance is DependencyRef) { + return _DependencyNode.getInstanceInfo(instance); + } + + if (instance is RtState) { + return _StateNode.getInstanceInfo(instance); + } + + return getInstanceInfo(instance); + } + + Map getInstanceInfo(Object instance) { + if (instance is Enum) { + return { + ..._InstanceNode.getInstanceInfo(instance), + 'type': instance.toString(), + }; + } + + if (instance is Iterable) { + return { + ..._InstanceNode.getInstanceInfo(instance), + 'fields': { + 'length': instance.length, + 'items': instance.toList(), + }, + }; + } + + return _InstanceNode.getInstanceInfo(instance); + } +} + +@internal +abstract class NodeKind { + static const String state = 'state'; + static const String hook = 'hook'; + static const String signal = 'signal'; + static const String instance = 'instance'; + static const String dependency = 'dependency'; +} + +abstract class _Node extends LinkedListEntry<_Node> { + String get key => instance.hashCode.toString(); + + _Node? _parent; + _Node? get parent => _parent; + + final T instance; + final LinkedHashSet<_Node> children = LinkedHashSet(); + + _Node({required this.instance}); + + Map toJson() { + final dependencyRef = Rt.getDependencyRef(instance); + final dependencyId = dependencyRef?.id; + + return { + 'key': key, + 'dependencyId': dependencyId, + 'dependencyKey': dependencyRef?.hashCode.toString(), + }; + } + + _Node? get lastDescendant => + children.isEmpty ? this : children.last.lastDescendant as _Node; + + @override + void unlink() { + if (list == null) return; + + super.unlink(); + } + + void addChild(_Node node) { + if (node._parent == this) return; + + if (node.list != null) node.unlink(); + + node._parent?.children.remove(node); + + if (lastDescendant?.list != null) { + lastDescendant?.insertAfter(node); + } else { + insertAfter(node); + } + + node.moveChildren(); + + children.add(node); + node._parent = this; + } + + void moveChildren() { + _Node? prevChild; + + for (final child in children) { + if (child.list != null) child.unlink(); + + if (prevChild != null) { + prevChild.insertAfter(child); + } else { + insertAfter(child); + } + + child.moveChildren(); + + prevChild = child; + } + } + + void removeChild(_Node node) { + // for (final child in node.children.toList()) { + // if (child.list != null) { + // node.removeChild(child); + // } + // } + + if (node.list != null) { + node.unlink(); + } + + if (node._parent == this) { + children.remove(this); + node._parent = null; + } + + if (children.isEmpty) { + unlink(); + } + } + + bool remove() { + if (children.isEmpty) { + _parent?.removeChild(this); + + if (list != null) unlink(); + } + + return list == null; + } +} + +class _InstanceNode extends _Node { + _InstanceNode({required Object instance}) : super(instance: instance); + + static Map getInstanceInfo(Object instance) { + final value = instance.toString(); + final type = instance.runtimeType.toString(); + + return { + 'kind': NodeKind.instance, + 'key': instance.hashCode.toString(), + 'type': type, + 'value': value.startsWith("Instance of") || value == type ? null : value, + }; + } + + @override + Map toJson() { + return { + ...getInstanceInfo(instance), + ...super.toJson(), + }; + } + + @override + bool remove() { + final json = toJson(); + + if (json['dependencyKey'] == null) { + return super.remove(); + } + + return false; + } +} + +class _StateNode extends _Node { + _StateNode({required RtState instance}) : super(instance: instance); + + static String resolveKind(RtState instance) { + if (instance is Signal) return NodeKind.signal; + if (instance is RtHook) return NodeKind.hook; + return NodeKind.state; + } + + static Map getInstanceInfo(RtState instance) { + return { + 'kind': resolveKind(instance), + 'key': instance.hashCode.toString(), + 'type': instance.runtimeType.toString(), + 'debugLabel': instance.debugLabel, + 'boundInstanceKey': instance.boundInstance?.hashCode.toString(), + }; + } + + @override + Map toJson() { + return { + ...getInstanceInfo(instance), + ...super.toJson(), + }; + } + + @override + bool remove() { + final json = toJson(); + + if (json['dependencyKey'] == null) { + return super.remove(); + } + + return false; + } +} + +class _DependencyNode extends _Node { + _DependencyNode({required DependencyRef instance}) + : super(instance: instance); + + static Map getInstanceInfo(DependencyRef instance) { + final dependencyRef = Rt.getDependencyRegisterByRef(instance); + + return { + 'kind': NodeKind.dependency, + 'key': instance.hashCode.toString(), + 'type': instance.type.toString(), + 'id': instance.id, + 'mode': dependencyRef?.mode.toString(), + 'instanceKey': dependencyRef?.instance.hashCode.toString(), + }; + } + + @override + Map toJson() { + return { + ...getInstanceInfo(instance), + ...super.toJson(), + }; + } +} + +@internal +typedef RtDevToolsInitializeAssertionError = AssertionError; diff --git a/packages/reactter/lib/src/env.dart b/packages/reactter/lib/src/env.dart new file mode 100644 index 00000000..6d12cf82 --- /dev/null +++ b/packages/reactter/lib/src/env.dart @@ -0,0 +1,75 @@ +part of 'internals.dart'; + +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// A constant that is true if the application was compiled in release mode. +/// +/// More specifically, this is a constant that is true if the application was +/// compiled in Dart with the '-Ddart.vm.product=true' flag. +/// +/// Since this is a const value, it can be used to indicate to the compiler that +/// a particular block of code will not be executed in release mode, and hence +/// can be removed. +/// +/// Generally it is better to use [kDebugMode] or `assert` to gate code, since +/// using [kReleaseMode] will introduce differences between release and profile +/// builds, which makes performance testing less representative. +/// +/// See also: +/// +/// * [kDebugMode], which is true in debug builds. +/// * [kProfileMode], which is true in profile builds. +const bool kReleaseMode = bool.fromEnvironment('dart.vm.product'); + +/// A constant that is true if the application was compiled in profile mode. +/// +/// More specifically, this is a constant that is true if the application was +/// compiled in Dart with the '-Ddart.vm.profile=true' flag. +/// +/// Since this is a const value, it can be used to indicate to the compiler that +/// a particular block of code will not be executed in profile mode, an hence +/// can be removed. +/// +/// See also: +/// +/// * [kDebugMode], which is true in debug builds. +/// * [kReleaseMode], which is true in release builds. +const bool kProfileMode = bool.fromEnvironment('dart.vm.profile'); + +/// A constant that is true if the application was compiled in debug mode. +/// +/// More specifically, this is a constant that is true if the application was +/// not compiled with '-Ddart.vm.product=true' and '-Ddart.vm.profile=true'. +/// +/// Since this is a const value, it can be used to indicate to the compiler that +/// a particular block of code will not be executed in debug mode, and hence +/// can be removed. +/// +/// An alternative strategy is to use asserts, as in: +/// +/// ```dart +/// assert(() { +/// // ...debug-only code here... +/// return true; +/// }()); +/// ``` +/// +/// See also: +/// +/// * [kReleaseMode], which is true in release builds. +/// * [kProfileMode], which is true in profile builds. +const bool kDebugMode = !kReleaseMode && !kProfileMode; + +/// A constant that is true if the application was compiled to run on the web. +/// +/// See also: +/// +/// * [defaultTargetPlatform], which is used by themes to find out which +/// platform the application is running on (or, in the case of a web app, +/// which platform the application's browser is running in). Can be overridden +/// in tests with [debugDefaultTargetPlatformOverride]. +/// * [dart:io.Platform], a way to find out the browser's platform that is not +/// overridable in tests. +const bool kIsWeb = bool.fromEnvironment('dart.library.js_util'); diff --git a/packages/reactter/lib/src/framework.dart b/packages/reactter/lib/src/framework.dart new file mode 100644 index 00000000..1a11d421 --- /dev/null +++ b/packages/reactter/lib/src/framework.dart @@ -0,0 +1,12 @@ +import 'package:meta/meta.dart'; + +import 'internals.dart'; + +export 'internals.dart' show DependencyMode, Lifecycle, RtState; + +part 'framework/rt_dependency_ref.dart'; +part 'framework/rt_dependency_observer.dart'; +part 'framework/rt_hook.dart'; +part 'framework/rt_interface.dart'; +part 'framework/rt_dependency_lifecycle.dart'; +part 'framework/rt_state_observer.dart'; diff --git a/packages/reactter/lib/src/framework/framework.dart b/packages/reactter/lib/src/framework/framework.dart deleted file mode 100644 index 31862945..00000000 --- a/packages/reactter/lib/src/framework/framework.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'dart:developer' as dev; -import 'package:meta/meta.dart'; -import 'package:reactter/src/types.dart'; - -import '../core/core.dart'; -export '../core/core.dart' - show Lifecycle, LifecycleObserver, DependencyMode, LogLevel; - -part 'rt_dependency.dart'; -part 'rt_hook.dart'; -part 'rt_interface.dart'; -part 'rt_state.dart'; diff --git a/packages/reactter/lib/src/framework/rt_dependency.dart b/packages/reactter/lib/src/framework/rt_dependency.dart deleted file mode 100644 index 1f98c401..00000000 --- a/packages/reactter/lib/src/framework/rt_dependency.dart +++ /dev/null @@ -1,24 +0,0 @@ -part of 'framework.dart'; - -/// {@template reactter.rt_dependency} -/// Represents dependency managed by Reactter's dependency injection. -/// -/// This class extends [DependencyRef] and provides an optional [id] parameter. -/// {@endtemplate} -class RtDependency extends DependencyRef { - const RtDependency([String? id]) : super(id); -} - -/// {@macro reactter.rt_dependency} -@Deprecated( - 'Use `RtDependency` instead.' - 'This feature was deprecated after v7.1.0.', -) -typedef ReactterInstance = RtDependency; - -/// {@macro reactter.rt_dependency} -@Deprecated( - 'Use `RtDependency` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterDependency = RtDependency; diff --git a/packages/reactter/lib/src/framework/rt_dependency_lifecycle.dart b/packages/reactter/lib/src/framework/rt_dependency_lifecycle.dart new file mode 100644 index 00000000..1f2370d5 --- /dev/null +++ b/packages/reactter/lib/src/framework/rt_dependency_lifecycle.dart @@ -0,0 +1,64 @@ +part of '../framework.dart'; + +/// {@template reactter.rt_dependency_lifecycle} +/// It's a mixin that provides a set of methods that can be used to observe +/// the lifecycle of a dependency. +/// +/// Example usage: +/// ```dart +/// class MyController with RtDependencyLifecycle { +/// final state = UseState('initial'); +/// +/// @override +/// void onCreated() { +/// print('MyController has been initialized'); +/// } +/// +/// @override +/// void onDidUpdate(RtState? state) { +/// print("$state has been changed"); +/// } +/// } +/// +/// // MyController has been initialized +/// final myController = Rt.create(() => MyController()); +/// // state has been changed +/// myController.state.value = "value changed"; +/// ``` + +/// {@endtemplate} +abstract class RtDependencyLifecycle implements DependencyLifecycle { + /// This method is called when the dependency instance is created. + @override + void onCreated() {} + + /// This method is called when the dependency is going to be mounted + /// in the widget tree(exclusive to `flutter_reactter`). + @override + void onWillMount() {} + + /// This method is called when the dependency has been successfully mounted + /// in the widget tree(exclusive to `flutter_reactter`). + @override + void onDidMount() {} + + /// This method is called when the dependency's state is about to be updated. + /// The parameter is a [IState]. + @override + void onWillUpdate(covariant IState? state) {} + + /// This method is called when the dependency's state has been updated. + /// The parameter is a [IState]. + @override + void onDidUpdate(covariant IState? state) {} + + /// This method is called when the dependency is going to be unmounted + /// from the widget tree(exclusive to `flutter_reactter`). + @override + void onWillUnmount() {} + + /// This method is called when the dependency has been successfully unmounted + /// from the widget tree(exclusive to `flutter_reactter`). + @override + void onDidUnmount() {} +} diff --git a/packages/reactter/lib/src/framework/rt_dependency_observer.dart b/packages/reactter/lib/src/framework/rt_dependency_observer.dart new file mode 100644 index 00000000..754e83ca --- /dev/null +++ b/packages/reactter/lib/src/framework/rt_dependency_observer.dart @@ -0,0 +1,116 @@ +part of '../framework.dart'; + +/// {@template reactter.rt_dependency_observer} +/// A class that implements the [IDependencyObserver] interface. +/// This class can be used to monitor the lifecycle of dependencies. +/// +/// It provides a set of callback functions that are called when a dependency is +/// registered, created, mounted, unmounted, deleted, unregistered, or failed. +/// +/// This observer should be added to Reactter's observers for it to work, using the [Rt.addObserver] method, like so: +/// ```dart +/// final dependencyObserver = RtDependencyObserver( +/// onRegistered: (dependency) { +/// print('Dependency registered: $dependency'); +/// }, +/// onCreated: (dependency, instance) { +/// print('Dependency created: $dependency, instance: $instance'); +/// }, +/// onMounted: (dependency, instance) { +/// print('Dependency mounted: $dependency, instance: $instance'); +/// }, +/// onUnmounted: (dependency, instance) { +/// print('Dependency unmounted: $dependency, instance: $instance'); +/// }, +/// onDeleted: (dependency, instance) { +/// print('Dependency deleted: $dependency, instance: $instance'); +/// }, +/// onUnregistered: (dependency) { +/// print('Dependency unregistered: $dependency'); +/// }, +/// onFailed: (dependency, fail) { +/// print('Dependency failed: $dependency, fail: $fail'); +/// }, +/// ); +/// +/// Rt.addObserver(dependencyObserver); +/// ``` +/// +/// See also: +/// - [IDependencyObserver] - An abstract class that defines the interface for observing dependency changes. +/// - [Rt.addObserver] - A method that adds an observer to Reactter's observers. +/// {@endtemplate} +class RtDependencyObserver implements IDependencyObserver { + /// {@macro reactter.i_dependency_observer.on_dependency_registered} + final void Function(DependencyRef dependency)? onRegistered; + + /// {@macro reactter.i_dependency_observer.on_dependency_created} + final void Function(DependencyRef dependency, Object? instance)? onCreated; + + /// {@macro reactter.i_dependency_observer.on_dependency_mounted} + final void Function(DependencyRef dependency, Object? instance)? onMounted; + + /// {@macro reactter.i_dependency_observer.on_dependency_unmounted} + final void Function(DependencyRef dependency, Object? instance)? onUnmounted; + + /// {@macro reactter.i_dependency_observer.on_dependency_deleted} + final void Function(DependencyRef dependency, Object? instance)? onDeleted; + + /// {@macro reactter.i_dependency_observer.on_dependency_unregistered} + final void Function(DependencyRef dependency)? onUnregistered; + + /// {@macro reactter.i_dependency_observer.on_dependency_failed} + final void Function(DependencyRef dependency, DependencyFail fail)? onFailed; + + RtDependencyObserver({ + this.onRegistered, + this.onCreated, + this.onMounted, + this.onUnmounted, + this.onDeleted, + this.onUnregistered, + this.onFailed, + }); + + /// {@macro reactter.i_dependency_observer.on_dependency_registered} + @override + void onDependencyRegistered(DependencyRef dependency) { + onRegistered?.call(dependency); + } + + /// {@macro reactter.i_dependency_observer.on_dependency_created} + @override + void onDependencyCreated(DependencyRef dependency, Object? instance) { + onCreated?.call(dependency, instance); + } + + /// {@macro reactter.i_dependency_observer.on_dependency_mounted} + @override + void onDependencyMounted(DependencyRef dependency, Object? instance) { + onMounted?.call(dependency, instance); + } + + /// {@macro reactter.i_dependency_observer.on_dependency_unmounted} + @override + void onDependencyUnmounted(DependencyRef dependency, Object? instance) { + onUnmounted?.call(dependency, instance); + } + + /// {@macro reactter.i_dependency_observer.on_dependency_deleted} + @override + void onDependencyDeleted(DependencyRef dependency, Object? instance) { + onDeleted?.call(dependency, instance); + } + + /// {@macro reactter.i_dependency_observer.on_dependency_unregistered} + @override + void onDependencyUnregistered(DependencyRef dependency) { + onUnregistered?.call(dependency); + } + + /// {@macro reactter.i_dependency_observer.on_dependency_failed} + @override + void onDependencyFailed(DependencyRef dependency, DependencyFail fail) { + onFailed?.call(dependency, fail); + } +} diff --git a/packages/reactter/lib/src/framework/rt_dependency_ref.dart b/packages/reactter/lib/src/framework/rt_dependency_ref.dart new file mode 100644 index 00000000..7b156b57 --- /dev/null +++ b/packages/reactter/lib/src/framework/rt_dependency_ref.dart @@ -0,0 +1,10 @@ +part of '../framework.dart'; + +/// {@template reactter.rt_dependency_ref} +/// Represents dependency managed by Reactter's dependency injection. +/// +/// This class extends [DependencyRef] and provides an optional [id] parameter. +/// {@endtemplate} +class RtDependencyRef extends DependencyRef { + const RtDependencyRef([String? id]) : super(id); +} diff --git a/packages/reactter/lib/src/framework/rt_hook.dart b/packages/reactter/lib/src/framework/rt_hook.dart index 81c813a9..dfa1ec29 100644 --- a/packages/reactter/lib/src/framework/rt_hook.dart +++ b/packages/reactter/lib/src/framework/rt_hook.dart @@ -1,4 +1,4 @@ -part of 'framework.dart'; +part of '../framework.dart'; /// {@template reactter.rt_hook} /// An abstract-class that provides the functionality of [RtState]. @@ -19,20 +19,20 @@ part of 'framework.dart'; /// void toggle() => _state.value = !_state.value; /// } /// ``` -/// > **IMPORTANT**: All [RtHook] must be registered using the final [$] variable.: +/// > **RECOMMENDED**: All [RtHook] must be registered using the final [$] variable.: /// /// and use it, like so: /// /// >```dart /// > class AppController { -/// > final state = UseToggle(false); +/// > final uToggle = UseToggle(false); /// > /// > UserContext() { -/// > print('initial value: ${state.value}'); +/// > print('initial value: ${uToggle.value}'); /// > -/// > state.toggle(); +/// > uToggle.toggle(); /// > -/// > print('toggle value: ${state.value}'); +/// > print('toggle value: ${uToggle.value}'); /// > } /// > } /// > ``` @@ -40,29 +40,17 @@ part of 'framework.dart'; /// See also: /// * [RtState], adds state management features to [RtHook]. /// {@endtemplate} -abstract class RtHook extends Hook implements RtState { - @override - @internal - DependencyInjection get dependencyInjection => Rt; - @override - @internal - StateManagement get stateManagment => Rt; - @override - @internal - EventHandler get eventHandler => Rt; - @override - @internal - Logger get logger => Rt; - - /// This getter allows access to the [HookRegister] instance - /// which is responsible for registering a [Hook] +abstract class RtHook extends IHook with RtState { + /// {@template reactter.rt_hook.register} + /// This getter allows access to the [HookBindingZone] instance + /// which is responsible for registering a [RtHook] /// and attaching previously collected states to it. - static HookRegister get $register => HookRegister(); -} + /// {@endtemplate} + static get $register => HookBindingZone(); -/// {@macro reactter.rt_hook} -@Deprecated( - 'Use `RtHook` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterHook = RtHook; + @override + @mustCallSuper + void update([Function()? fnUpdate]) { + return super.update(fnUpdate ?? () {}); + } +} diff --git a/packages/reactter/lib/src/framework/rt_interface.dart b/packages/reactter/lib/src/framework/rt_interface.dart index a1e2df2f..10f2e730 100644 --- a/packages/reactter/lib/src/framework/rt_interface.dart +++ b/packages/reactter/lib/src/framework/rt_interface.dart @@ -1,16 +1,4 @@ -// ignore_for_file: non_constant_identifier_names - -part of 'framework.dart'; - -void defaultLogWriterCallback( - String value, { - Object? error, - LogLevel? level = LogLevel.info, -}) { - if (Rt.isLogEnable || error != null) { - dev.log(value, name: 'REACTTER', error: error); - } -} +part of '../framework.dart'; ///{@template reactter.rt_interface} /// A class that represents the interface for Rt. @@ -18,41 +6,15 @@ void defaultLogWriterCallback( /// It is intended to be used as a mixin with other classes. /// {@endtemplate} class RtInterface - with StateManagement, DependencyInjection, EventHandler, Logger { - static final _reactterInterface = RtInterface._(); - factory RtInterface() => _reactterInterface; - RtInterface._(); - - @override - @internal - DependencyInjection get dependencyInjection => this; - @override - @internal - EventHandler get eventHandler => this; - @override - @internal - Logger get logger => this; - - @override - LogWriterCallback get log => defaultLogWriterCallback; -} + with + StateManagement, + DependencyInjection, + EventHandler, + ObserverManager {} /// {@template reactter.rt} /// This class represents the interface for the Reactter framework. /// It provides methods and properties for interacting with the Reactter framework. /// {@endtemplate} +// ignore: non_constant_identifier_names final Rt = RtInterface(); - -/// {@macro reactter.rt_interface} -@Deprecated( - 'Use `RtInterface` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterInterface = RtInterface; - -/// {@macro reactter.rt} -@Deprecated( - 'Use `Rt` instead. ' - 'This feature was deprecated after v7.3.0.', -) -final Reactter = Rt; diff --git a/packages/reactter/lib/src/framework/rt_state.dart b/packages/reactter/lib/src/framework/rt_state.dart index a33e0ea8..7330d8ef 100644 --- a/packages/reactter/lib/src/framework/rt_state.dart +++ b/packages/reactter/lib/src/framework/rt_state.dart @@ -1,46 +1,256 @@ -part of 'framework.dart'; +part of '../internals.dart'; /// {@template reactter.rt_state} -/// A abstract class that represents a stare in Reactter. +/// A base class for creating a state object in Reactter. +/// +/// The state object must be registered using the [Rt.registerState] method or +/// registered as a dependency using the dependency injection. e.g. +/// +/// ```dart +/// class MyState extends RtState { +/// int _value = 0; +/// int get value => value; +/// set value(int n) { +/// if (n == _value) return; +/// update(() => _value = n); +/// } +/// } +/// +/// final state = Rt.registerState(() => MyState()); +/// ``` +/// +/// See also: +/// - [Rt.registerState], for register a state object. /// -/// It provides methods for attaching and detaching an object instance to -/// the state, notifying listeners of state changes, and disposing of the state -/// object when it is no longer needed. /// {@endtemplate} -abstract class RtState extends State { +abstract class RtState implements IState { + /// Debug assertion for registering a state object. + /// + /// This method is used to assert that a state object is being created within + /// a binding zone. If the assertion fails, an [AssertionError] is thrown. + static bool debugAssertRegistering() { + assert(() { + final currentZone = BindingZone.currentZone; + final isRegistering = currentZone != null && !currentZone.isVerified; + + if (!isRegistering) { + throw AssertionError( + "The state must be create within the BindingZone.\n" + "You can use the 'Rt.registerState' method or register as dependency " + "using the dependency injection to ensure that the state is registered.", + ); + } + return true; + }()); + + return true; + } + + // ignore: unused_field + final _debugAssertRegistering = debugAssertRegistering(); + @override @internal DependencyInjection get dependencyInjection => Rt; + @override @internal - StateManagement get stateManagment => Rt; + StateManagement get stateManagement => Rt; + @override @internal EventHandler get eventHandler => Rt; + + bool _isRegistered = false; + bool _isUpdating = false; + + /// A label used for debugging purposes. @override - @internal - Logger get logger => Rt; -} + String? get debugLabel => null; + + /// A map containing information about state for debugging purposes. + @override + Map get debugInfo => {}; + + /// The reference instance to the current state. + @override + Object? get boundInstance => _boundInstance; + Object? _boundInstance; + + /// Returns `true` if the state has been disposed. + @override + bool get isDisposed => _isDisposed; + bool _isDisposed = false; + + bool get _hasListeners => + eventHandler._hasListeners(this) || + (_boundInstance != null && eventHandler._hasListeners(_boundInstance)); + + @override + @protected + @mustCallSuper + void _register() { + if (_isRegistered) return; -/// {@template reactter.rt_state_impl} -/// An implementation of the [RtState]. -/// {@endtemplate} -abstract class RtStateImpl extends RtState { - RtStateImpl() { BindingZone.recollectState(this); + _notifyCreated(); + _isRegistered = true; } -} -/// {@macro reactter.rt_state} -@Deprecated( - 'Use `RtState` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterState = RtState; - -/// {@macro reactter.rt_state_impl} -@Deprecated( - 'Use `RtStateImpl` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterStateImpl = RtStateImpl; + @mustCallSuper + @override + void bind(Object instance) { + assert(!_isDisposed, "Can't bind when it's been disposed"); + assert( + _boundInstance == null, + "Can't bind a new instance because an instance is already.\n" + "You must unbind the current instance before binding a new one.", + ); + + eventHandler.one(instance, Lifecycle.deleted, _onInstanceDeleted); + _boundInstance = instance; + + _notifyBound(instance); + } + + @override + @mustCallSuper + void unbind() { + assert(!_isDisposed, "Can't unbind when it's been disposed"); + + if (_boundInstance == null) return; + + _notifyUnbound(); + + eventHandler.off(_boundInstance!, Lifecycle.deleted, _onInstanceDeleted); + _boundInstance = null; + } + + @override + @mustCallSuper + + /// {@macro reactter.istate.update} + /// + /// The [fnUpdate] must be a function without parameters(Function()). + void update(Function? fnUpdate) { + assert(!_isDisposed, "Can't update when it's been disposed"); + assert( + fnUpdate is Function(), + "The fnUpdate must be a function without parameters", + ); + + if (_isUpdating || !_hasListeners) { + fnUpdate?.call(); + _notifyUpdated(); + return; + } + + try { + _isUpdating = true; + _notify(Lifecycle.willUpdate); + fnUpdate?.call(); + _notify(Lifecycle.didUpdate); + _notifyUpdated(); + } finally { + _isUpdating = false; + } + } + + @override + @mustCallSuper + void notify() { + assert(!_isDisposed, "Can't refresh when it's been disposed"); + + if (_isUpdating || !_hasListeners) { + _notifyUpdated(); + return; + } + + try { + _isUpdating = true; + _notify(Lifecycle.didUpdate); + _notifyUpdated(); + } finally { + _isUpdating = false; + } + } + + @override + @mustCallSuper + void dispose() { + assert(!_isDisposed, "Can't dispose when it's been disposed"); + + eventHandler.emit(this, Lifecycle.deleted); + eventHandler.offAll(this); + + if (_boundInstance != null) { + unbind(); + } + + if (_isDisposed) return; + + _notifyDisposed(); + _isDisposed = true; + } + + /// When the instance is destroyed, this object is dispose. + void _onInstanceDeleted(_, __) => dispose(); + + /// Notifies the listeners about the specified [event]. + /// If [Rt._isUntrackedRunning] is true, the notification is skipped. + /// If [Rt._isBatchRunning] is true, the notification is deferred until the batch is completed. + /// The [event] is emitted using [Rt.emit] for the current instance and [_boundInstance]. + void _notify(Enum event, [dynamic param]) { + if (stateManagement._isUntrackedRunning) return; + + final emit = stateManagement._isBatchRunning + ? stateManagement._emitDefferred + : eventHandler.emit; + + final finalParam = param ?? this; + + emit(this, event, finalParam); + + if (_boundInstance == null) return; + + if (_boundInstance is RtState && !(_boundInstance as RtState)._isDisposed) { + return (_boundInstance as RtState)._notify(event, finalParam); + } + + emit(_boundInstance!, event, finalParam); + } + + void _notifyCreated() { + for (final observer in IStateObserver._observers.toList(growable: false)) { + observer.onStateCreated(this); + } + } + + void _notifyBound(Object instance) { + for (final observer in IStateObserver._observers.toList(growable: false)) { + observer.onStateBound(this, instance); + } + } + + void _notifyUnbound() { + for (final observer in IStateObserver._observers.toList(growable: false)) { + observer.onStateUnbound(this, _boundInstance!); + } + } + + void _notifyUpdated() { + for (final observer in IStateObserver._observers.toList(growable: false)) { + observer.onStateUpdated(this); + + if (boundInstance is RtState) { + observer.onStateUpdated(boundInstance as RtState); + } + } + } + + void _notifyDisposed() { + for (final observer in IStateObserver._observers.toList(growable: false)) { + observer.onStateDisposed(this); + } + } +} diff --git a/packages/reactter/lib/src/framework/rt_state_observer.dart b/packages/reactter/lib/src/framework/rt_state_observer.dart new file mode 100644 index 00000000..8f96c03b --- /dev/null +++ b/packages/reactter/lib/src/framework/rt_state_observer.dart @@ -0,0 +1,91 @@ +part of '../framework.dart'; + +/// {@template reactter.rt_state_observer} +/// A class that implements the [IStateObserver] interface. +/// +/// It provides a set of callback functions that can be used to observe +/// the lifecycle of states. +/// +/// This observer should be added to Reactter's observers for it to work, +/// using the [Rt.addObserver] method, like so: +/// +/// ```dart +/// final stateObserver = RtStateObserver( +/// onCreated: (state) { +/// print('State created: $state'); +/// }, +/// onBound: (state, instance) { +/// print('State bound: $state, instance: $instance'); +/// }, +/// onUnbound: (state, instance) { +/// print('State unbound: $state, instance: $instance'); +/// }, +/// onUpdated: (state) { +/// print('State updated: $state'); +/// }, +/// onDisposed: (state) { +/// print('State disposed: $state'); +/// }, +/// ); +/// +/// Rt.addObserver(stateObserver); +/// ``` +/// +/// See also: +/// - [IStateObserver] - An abstract class that defines the interface for observing lifecycle of states. +/// - [Rt.addObserver] - A method that adds an observer to Reactter's observers. +/// {@endtemplate} +class RtStateObserver implements IStateObserver { + /// {@macro reactter.i_state_observer.on_state_bound} + final void Function(RtState state, Object instance)? onBound; + + /// {@macro reactter.i_state_observer.on_state_created} + final void Function(RtState state)? onCreated; + + /// {@macro reactter.i_state_observer.on_state_mounted} + final void Function(RtState state)? onDisposed; + + /// {@macro reactter.i_state_observer.on_state_unbound} + final void Function(RtState state, Object instance)? onUnbound; + + /// {@macro reactter.i_state_observer.on_state_updated} + final void Function(RtState state)? onUpdated; + + RtStateObserver({ + this.onBound, + this.onCreated, + this.onDisposed, + this.onUnbound, + this.onUpdated, + }); + + /// {@macro reactter.i_state_observer.on_state_bound} + @override + void onStateBound(RtState state, Object instance) { + onBound?.call(state, instance); + } + + /// {@macro reactter.i_state_observer.on_state_created} + @override + void onStateCreated(RtState state) { + onCreated?.call(state); + } + + /// {@macro reactter.i_state_observer.on_state_mounted} + @override + void onStateDisposed(RtState state) { + onDisposed?.call(state); + } + + /// {@macro reactter.i_state_observer.on_state_unbound} + @override + void onStateUnbound(RtState state, Object instance) { + onUnbound?.call(state, instance); + } + + /// {@macro reactter.i_state_observer.on_state_updated} + @override + void onStateUpdated(RtState state) { + onUpdated?.call(state); + } +} diff --git a/packages/reactter/lib/src/hooks/hooks.dart b/packages/reactter/lib/src/hooks/hooks.dart index 632e5db9..8636f900 100644 --- a/packages/reactter/lib/src/hooks/hooks.dart +++ b/packages/reactter/lib/src/hooks/hooks.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'package:meta/meta.dart'; import '../args.dart'; -import '../core/core.dart'; -import '../framework/framework.dart'; +import '../internals.dart'; +import '../framework.dart'; import '../types.dart'; part 'use_async_state.dart'; diff --git a/packages/reactter/lib/src/hooks/use_async_state.dart b/packages/reactter/lib/src/hooks/use_async_state.dart index 230b1c35..723ee8a0 100644 --- a/packages/reactter/lib/src/hooks/use_async_state.dart +++ b/packages/reactter/lib/src/hooks/use_async_state.dart @@ -1,7 +1,7 @@ part of 'hooks.dart'; enum UseAsyncStateStatus { - standby, + idle, loading, done, error, @@ -13,6 +13,16 @@ abstract class UseAsyncStateBase extends RtHook { @override final $ = RtHook.$register; + final String? _debugLabel; + @override + String? get debugLabel => _debugLabel ?? super.debugLabel; + @override + Map get debugInfo => { + 'value': value, + 'error': error, + 'status': status, + }; + /// Stores the initial value. final T _initialValue; @@ -20,47 +30,104 @@ abstract class UseAsyncStateBase extends RtHook { /// Need to call [resolve] to execute. final Function _asyncFunction; - final UseState _value; - final _error = UseState(null); - final _status = UseState(UseAsyncStateStatus.standby); + final UseState _uValue; + late final uValue = Rt.lazyState( + () => UseCompute(() => _uValue.value, [_uValue]), + this, + ); + T get value => _uValue.value; + + final _uError = UseState(null); + late final uError = Rt.lazyState( + () => UseCompute(() => _uError.value, [_uError]), + this, + ); + Object? get error => _uError.value; + + final _uStatus = UseState(UseAsyncStateStatus.idle); + late final uStatus = Rt.lazyState( + () => UseCompute(() => _uStatus.value, [_uStatus]), + this, + ); + UseAsyncStateStatus get status => _uStatus.value; + + late final uIsLoading = Rt.lazyState( + () => UseCompute(() => status == UseAsyncStateStatus.loading, [uStatus]), + this, + ); + bool get isLoading => uIsLoading.value; + + late final uIsDone = Rt.lazyState( + () => UseCompute(() => status == UseAsyncStateStatus.done, [uStatus]), + this, + ); + bool get isDone => uIsDone.value; - T get value => _value.value; - Object? get error => _error.value; - UseAsyncStateStatus get status => _status.value; + late final uIsError = Rt.lazyState( + () => UseCompute(() => status == UseAsyncStateStatus.error, [uStatus]), + this, + ); + bool get isError => uIsError.value; + + Future _future = Completer().future; + Future get future => _future; + + bool _isCanceled = false; UseAsyncStateBase( - T initialValue, Function asyncFunction, - ) : _initialValue = initialValue, + T initialValue, { + String? debugLabel, + }) : _initialValue = initialValue, _asyncFunction = asyncFunction, - _value = UseState(initialValue); + _debugLabel = debugLabel, + _uValue = UseState(initialValue); /// Execute [asyncFunction] to resolve [value]. FutureOr _resolve([A? arg]) async { try { - _status.value = UseAsyncStateStatus.loading; + _uStatus.value = UseAsyncStateStatus.loading; final asyncFunctionExecuting = arg == null ? _asyncFunction() : _asyncFunction(arg); - _value.value = asyncFunctionExecuting is Future - ? await asyncFunctionExecuting - : asyncFunctionExecuting; + _future = asyncFunctionExecuting is Future + ? asyncFunctionExecuting + : Future.value(asyncFunctionExecuting); + + final response = await _future; + + if (_isCanceled) return null; - _status.value = UseAsyncStateStatus.done; + Rt.batch(() { + _uValue.value = response; + _uStatus.value = UseAsyncStateStatus.done; + }); - return _value.value; + return _uValue.value; } catch (e) { - _error.value = e; - _status.value = UseAsyncStateStatus.error; + Rt.batch(() { + _uError.value = e; + _uStatus.value = UseAsyncStateStatus.error; + }); return null; + } finally { + if (_isCanceled) { + _uStatus.value = UseAsyncStateStatus.done; + _isCanceled = false; + } } } + /// Cancels the async function execution. + void cancel() { + if (status == UseAsyncStateStatus.loading) _isCanceled = true; + } + /// Returns a new value of [R] depending on the state of the hook: /// - /// `standby`: When the state has the initial value. + /// `idle`: When the state has the initial value. /// `loading`: When the request for the state is retrieving the value. /// `done`: When the request is done. /// `error`: If any errors happens in the request. @@ -69,14 +136,14 @@ abstract class UseAsyncStateBase extends RtHook { /// /// ```dart /// final valueComputed = appController.asyncState.when( - /// standby: (value) => "⚓️ Standby: ${value}", + /// idle: (value) => "⚓️ Idle: ${value}", /// loading: (value) => "⏳ Loading...", /// done: (value) => "✅ Resolved: ${value}", /// error: (error) => "❌ Error: ${error}", /// ) /// ``` R? when({ - WhenValueReturn? standby, + WhenValueReturn? idle, WhenValueReturn? loading, WhenValueReturn? done, WhenErrorReturn? error, @@ -93,18 +160,20 @@ abstract class UseAsyncStateBase extends RtHook { return done?.call(value); } - return standby?.call(value); + return idle?.call(value); } /// Reset [value], [status] and [error] to its [initial] state. void reset() { - _value.value = _initialValue; - _error.value = null; - _status.value = UseAsyncStateStatus.standby; + Rt.batch(() { + _uValue.value = _initialValue; + _uError.value = null; + _uStatus.value = UseAsyncStateStatus.idle; + }); } } -/// {@template use_async_state} +/// {@template reactter.use_async_state} /// A [ReactteHook] that manages the state as async way. /// /// [T] is use to define the type of [value]. @@ -141,7 +210,7 @@ abstract class UseAsyncStateBase extends RtHook { /// /// ```dart /// final valueComputed = appController.asyncState.when( -/// standby: (value) => "⚓️ Standby: $value", +/// idle: (value) => "⚓️ Standby: $value", /// loading: (value) => "⏳ Loading...", /// done: (value) => "✅ Resolved: $value", /// error: (error) => "❌ Error: $error", @@ -156,18 +225,28 @@ abstract class UseAsyncStateBase extends RtHook { /// * [UseAsyncStateArg], the same as it, but with arguments. /// {@endtemplate} class UseAsyncState extends UseAsyncStateBase { - /// {@macro use_async_state} + /// {@macro reactter.use_async_state} UseAsyncState( - T initialValue, AsyncFunction asyncFunction, - ) : super(initialValue, asyncFunction); + T initialValue, { + String? debugLabel, + }) : super( + asyncFunction, + initialValue, + debugLabel: debugLabel, + ); - /// {@macro use_async_state_arg} + /// {@macro reactter.use_async_state_arg} static UseAsyncStateArg withArg( - T initialValue, AsyncFunctionArg asyncFunction, - ) { - return UseAsyncStateArg(initialValue, asyncFunction); + T initialValue, { + String? debugLabel, + }) { + return UseAsyncStateArg( + asyncFunction, + initialValue, + debugLabel: debugLabel, + ); } /// Execute [asyncFunction] to resolve [value]. @@ -176,7 +255,7 @@ class UseAsyncState extends UseAsyncStateBase { } } -/// {@template use_async_state_arg} +/// {@template reactter.use_async_state_arg} /// A [ReactteHook] that manages the state as async way. /// /// [T] is use to define the type of [value] @@ -213,7 +292,7 @@ class UseAsyncState extends UseAsyncStateBase { /// /// ```dart /// final valueComputed = appController.asyncState.when( -/// standby: (value) => "⚓️ Standby: $value", +/// idle: (value) => "⚓️ Standby: $value", /// loading: (value) => "⏳ Loading...", /// done: (value) => "✅ Resolved: $value", /// error: (error) => "❌ Error: $error", @@ -229,11 +308,16 @@ class UseAsyncState extends UseAsyncStateBase { /// * [Args], a generic arguments. /// {@endtemplate} class UseAsyncStateArg extends UseAsyncStateBase { - /// {@macro use_async_state_arg} + /// {@macro reactter.use_async_state_arg} UseAsyncStateArg( - T initialValue, AsyncFunctionArg asyncFunction, - ) : super(initialValue, asyncFunction); + T initialValue, { + String? debugLabel, + }) : super( + asyncFunction, + initialValue, + debugLabel: debugLabel, + ); /// Execute [asyncFunction] to resolve [value]. FutureOr resolve(A arg) async { diff --git a/packages/reactter/lib/src/hooks/use_compute.dart b/packages/reactter/lib/src/hooks/use_compute.dart index 983d3650..c2eaf60e 100644 --- a/packages/reactter/lib/src/hooks/use_compute.dart +++ b/packages/reactter/lib/src/hooks/use_compute.dart @@ -1,6 +1,6 @@ part of 'hooks.dart'; -/// {@template use_compute} +/// {@template reactter.use_compute} /// A [RtHook] that allows to compute a value /// using a predetermined [compute] function and a list of state [dependencies], /// and which automatically updates the computed [value] if a dependency changes. @@ -38,27 +38,36 @@ class UseCompute extends RtHook { @override final $ = RtHook.$register; - late T _valueComputed; final T Function() compute; final List dependencies; + late T _valueComputed; T get value => _valueComputed; - /// {@macro use_compute} + final String? _debugLabel; + @override + String? get debugLabel => _debugLabel ?? super.debugLabel; + @override + Map get debugInfo => { + 'value': value, + 'dependencies': dependencies, + }; + + /// {@macro reactter.use_compute} UseCompute( this.compute, - this.dependencies, - ) { - _valueComputed = compute(); - - for (var dependency in dependencies) { + this.dependencies, { + String? debugLabel, + }) : _debugLabel = debugLabel, + _valueComputed = compute() { + for (final dependency in dependencies.toList(growable: false)) { Rt.on(dependency, Lifecycle.didUpdate, _onDependencyChanged); } } @override void dispose() { - for (var dependency in dependencies) { + for (final dependency in dependencies.toList(growable: false)) { Rt.off(dependency, Lifecycle.didUpdate, _onDependencyChanged); } diff --git a/packages/reactter/lib/src/hooks/use_dependency.dart b/packages/reactter/lib/src/hooks/use_dependency.dart index 427f0323..e52a08e1 100644 --- a/packages/reactter/lib/src/hooks/use_dependency.dart +++ b/packages/reactter/lib/src/hooks/use_dependency.dart @@ -1,12 +1,6 @@ part of 'hooks.dart'; -@Deprecated( - 'Use `UseDependency` instead. ' - 'This feature was deprecated after v7.1.0.', -) -typedef UseInstance = UseDependency; - -/// {@template use_dependency} +/// {@template reactter.use_dependency} /// A [RtHook] that allows to manages a dependency of [T] with/without [id]. /// /// ```dart @@ -37,36 +31,36 @@ typedef UseInstance = UseDependency; /// The instance must be created by [DependencyInjection] using the following methods: /// /// - **Rt.get**: -/// {@macro get} +/// {@macro reactter.get} /// - **Rt.create**: -/// {@macro create} +/// {@macro reactter.create} /// - **Rt.builder**: -/// {@macro builder} +/// {@macro reactter.builder} /// - **Rt.singleton**: -/// {@macro builder} +/// {@macro reactter.builder} /// /// or created by [RtProvider] of [`flutter_reactter`](https://pub.dev/packages/flutter_reactter) /// /// [UseDependency] providers the following constructors: /// /// - **[UseDependency.register]**: -/// {@macro register} +/// {@macro reactter.register} /// - **[UseDependency.lazyBuilder]**: -/// {@macro lazy_builder} +/// {@macro reactter.lazy_builder} /// - **[UseDependency.lazyFactory]**: /// {@macro lazy_factory} /// - **[UseDependency.lazySingleton]**: -/// {@macro lazy_singleton} +/// {@macro reactter.lazy_singleton} /// - **[UseDependency.create]**: -/// {@macro create} +/// {@macro reactter.create} /// - **[UseDependency.builder]**: -/// {@macro builder} +/// {@macro reactter.builder} /// - **[UseDependency.factory]**: -/// {@macro factory} +/// {@macro reactter.factory} /// - **[UseDependency.singleton]**: -/// {@macro singleton} +/// {@macro reactter.singleton} /// - **[UseDependency.get]**: -/// {@macro get} +/// {@macro reactter.get} /// /// > **IMPORTANT** /// > You should call [dispose] when it's no longer needed. @@ -87,7 +81,7 @@ class UseDependency extends RtHook { /// or `null` if the dependency is not found it /// or has not been created yet. T? get instance { - assert(!_isDisposed); + assert(!_isDisposed, 'Cannot use a disposed hook.'); if (_instance == null) { final instanceFound = Rt.find(id); @@ -108,18 +102,31 @@ class UseDependency extends RtHook { /// It's used to identify the instance of [T] dependency. final String? id; - /// {@macro use_dependency} - UseDependency([this.id]) { + final String? _debugLabel; + @override + String? get debugLabel => _debugLabel ?? super.debugLabel; + @override + Map get debugInfo => { + 'instance': instance, + 'id': id, + }; + + /// {@macro reactter.use_dependency} + UseDependency({ + this.id, + String? debugLabel, + }) : _debugLabel = debugLabel { _instance = Rt.find(id); _listen(); } - /// {@macro register} + /// {@macro reactter.register} UseDependency.register( InstanceBuilder builder, { DependencyMode mode = DependencyMode.builder, this.id, - }) { + String? debugLabel, + }) : _debugLabel = debugLabel { Rt.register( builder, id: id, @@ -128,37 +135,55 @@ class UseDependency extends RtHook { _listen(); } - /// {@macro lazy_builder} - factory UseDependency.lazyBuilder(InstanceBuilder builder, [String? id]) => - UseDependency.register( - builder, - mode: DependencyMode.builder, - id: id, - ); - - /// {@macro lazy_factory} - factory UseDependency.lazyFactory(InstanceBuilder builder, [String? id]) => - UseDependency.register( - builder, - mode: DependencyMode.factory, - id: id, - ); - - /// {@macro lazy_singleton} - factory UseDependency.lazySingleton(InstanceBuilder builder, - [String? id]) => - UseDependency.register( - builder, - mode: DependencyMode.singleton, - id: id, - ); - - /// {@macro create} + /// {@macro reactter.lazy_builder} + factory UseDependency.lazyBuilder( + InstanceBuilder builder, { + String? id, + String? debugLabel, + }) { + return UseDependency.register( + builder, + mode: DependencyMode.builder, + id: id, + debugLabel: debugLabel, + ); + } + + /// {@macro reactter.lazy_factory} + factory UseDependency.lazyFactory( + InstanceBuilder builder, { + String? id, + String? debugLabel, + }) { + return UseDependency.register( + builder, + mode: DependencyMode.factory, + id: id, + debugLabel: debugLabel, + ); + } + + /// {@macro reactter.lazy_singleton} + factory UseDependency.lazySingleton( + InstanceBuilder builder, { + String? id, + String? debugLabel, + }) { + return UseDependency.register( + builder, + mode: DependencyMode.singleton, + id: id, + debugLabel: debugLabel, + ); + } + + /// {@macro reactter.create} UseDependency.create( InstanceBuilder builder, { this.id, DependencyMode mode = DependencyMode.builder, - }) { + String? debugLabel, + }) : _debugLabel = debugLabel { _instance = Rt.create( builder, id: id, @@ -168,32 +193,53 @@ class UseDependency extends RtHook { _listen(); } - /// {@macro builder} - factory UseDependency.builder(InstanceBuilder builder, [String? id]) => - UseDependency.create( - builder, - id: id, - mode: DependencyMode.builder, - ); - - /// {@macro factory} - factory UseDependency.factory(InstanceBuilder builder, [String? id]) => - UseDependency.create( - builder, - id: id, - mode: DependencyMode.factory, - ); - - /// {@macro singleton} - factory UseDependency.singleton(InstanceBuilder builder, [String? id]) => - UseDependency.create( - builder, - id: id, - mode: DependencyMode.singleton, - ); - - /// {@macro get} - UseDependency.get([this.id]) { + /// {@macro reactter.builder} + factory UseDependency.builder( + InstanceBuilder builder, { + String? id, + String? debugLabel, + }) { + return UseDependency.create( + builder, + id: id, + mode: DependencyMode.builder, + debugLabel: debugLabel, + ); + } + + /// {@macro reactter.factory} + factory UseDependency.factory( + InstanceBuilder builder, { + String? id, + String? debugLabel, + }) { + return UseDependency.create( + builder, + id: id, + mode: DependencyMode.factory, + debugLabel: debugLabel, + ); + } + + /// {@macro reactter.singleton} + factory UseDependency.singleton( + InstanceBuilder builder, { + String? id, + String? debugLabel, + }) { + return UseDependency.create( + builder, + id: id, + mode: DependencyMode.singleton, + debugLabel: debugLabel, + ); + } + + /// {@macro reactter.get} + UseDependency.get({ + this.id, + String? debugLabel, + }) : _debugLabel = debugLabel { _instance = Rt.get(id, this); _listen(); } @@ -203,26 +249,27 @@ class UseDependency extends RtHook { void dispose() { if (_isDisposed) return; - _isDisposed = true; - _unlisten(); Rt.delete(id, this); - update(() => _instance = null); + update(() { + _instance = null; + super.dispose(); + }); - super.dispose(); + _isDisposed = true; } void _listen() { - Rt.on(RtDependency(id), Lifecycle.created, _onInstance); - Rt.on(RtDependency(id), Lifecycle.willMount, _onInstance); - Rt.on(RtDependency(id), Lifecycle.deleted, _onInstance); + Rt.on(RtDependencyRef(id), Lifecycle.created, _onInstance); + Rt.on(RtDependencyRef(id), Lifecycle.willMount, _onInstance); + Rt.on(RtDependencyRef(id), Lifecycle.deleted, _onInstance); } void _unlisten() { - Rt.off(RtDependency(id), Lifecycle.created, _onInstance); - Rt.off(RtDependency(id), Lifecycle.willMount, _onInstance); - Rt.off(RtDependency(id), Lifecycle.deleted, _onInstance); + Rt.off(RtDependencyRef(id), Lifecycle.created, _onInstance); + Rt.off(RtDependencyRef(id), Lifecycle.willMount, _onInstance); + Rt.off(RtDependencyRef(id), Lifecycle.deleted, _onInstance); } void _onInstance(inst, param) { diff --git a/packages/reactter/lib/src/hooks/use_effect.dart b/packages/reactter/lib/src/hooks/use_effect.dart index 1d65c41b..06168aaa 100644 --- a/packages/reactter/lib/src/hooks/use_effect.dart +++ b/packages/reactter/lib/src/hooks/use_effect.dart @@ -1,6 +1,6 @@ part of 'hooks.dart'; -/// {@template use_effect} +/// {@template reactter.use_effect} /// A [RtHook] that manages `side-effect`. /// /// @@ -71,11 +71,11 @@ part of 'hooks.dart'; /// ); /// ``` /// -/// You can also use the [DispatchEffect] mixin to execute the effect +/// You can also use the [AutoDispatchEffect] mixin to execute the effect /// on initialization: /// /// ```dart -/// class AppController with DispatchEffect { +/// class AppController with AutoDispatchEffect { /// AppController() { /// UseEffect( /// () { @@ -142,15 +142,31 @@ class UseEffect extends RtHook { /// It's used to store the states as dependencies of [UseEffect]. final List dependencies; - /// {@macro use_effect} - UseEffect(this.callback, this.dependencies) { + final String? _debugLabel; + @override + String? get debugLabel => _debugLabel ?? super.debugLabel; + @override + Map get debugInfo => { + 'dependencies': dependencies, + }; + + /// {@macro reactter.use_effect} + UseEffect( + this.callback, + this.dependencies, { + String? debugLabel, + }) : _debugLabel = debugLabel { if (BindingZone.currentZone != null) return; _watchDependencies(); } - /// {@macro use_effect} - UseEffect.runOnInit(this.callback, this.dependencies) : super() { + /// {@macro reactter.use_effect} + UseEffect.runOnInit( + this.callback, + this.dependencies, { + String? debugLabel, + }) : _debugLabel = debugLabel { _runCallback(this, this); _isUpdating = false; _isDispatched = true; @@ -162,7 +178,7 @@ class UseEffect extends RtHook { @override void bind(Object instance) { - final shouldListen = instanceBinded == null; + final shouldListen = boundInstance == null; super.bind(instance); @@ -170,13 +186,12 @@ class UseEffect extends RtHook { _watchInstanceAttached(); - if (!_isDispatched && instanceBinded is DispatchEffect) { - _runCleanupAndUnwatchDependencies(instanceBinded); - _runCallbackAndWatchDependencies(instanceBinded); + if (!_isDispatched && boundInstance is AutoDispatchEffect) { + _runCleanupAndUnwatchDependencies(boundInstance); + _runCallbackAndWatchDependencies(boundInstance); return; } - _unwatchDependencies(); _watchDependencies(); } @@ -200,25 +215,27 @@ class UseEffect extends RtHook { void _watchInstanceAttached() { Rt.on( - instanceBinded!, + boundInstance!, Lifecycle.didMount, _runCallbackAndWatchDependencies, ); Rt.on( - instanceBinded!, + boundInstance!, Lifecycle.willUnmount, _runCleanupAndUnwatchDependencies, ); } void _unwatchInstanceAttached() { + if (boundInstance == null) return; + Rt.off( - instanceBinded!, + boundInstance!, Lifecycle.didMount, _runCallbackAndWatchDependencies, ); Rt.off( - instanceBinded!, + boundInstance!, Lifecycle.willUnmount, _runCleanupAndUnwatchDependencies, ); @@ -235,14 +252,16 @@ class UseEffect extends RtHook { } void _watchDependencies() { - for (final dependency in dependencies) { + _unwatchDependencies(); + + for (final dependency in dependencies.toList(growable: false)) { Rt.on(dependency, Lifecycle.willUpdate, _runCleanup); Rt.on(dependency, Lifecycle.didUpdate, _runCallback); } } void _unwatchDependencies() { - for (final dependency in dependencies) { + for (final dependency in dependencies.toList(growable: false)) { Rt.off(dependency, Lifecycle.willUpdate, _runCleanup); Rt.off(dependency, Lifecycle.didUpdate, _runCallback); } @@ -262,10 +281,13 @@ class UseEffect extends RtHook { _cleanupCallback = cleanupCallback; } } catch (error) { - logger.log( - 'An error occurred while executing the effect', - level: LogLevel.error, - ); + assert(() { + throw AssertionError( + 'An error occurred while executing the effect in $this${debugLabel != null ? '($debugLabel)' : ''}.\n' + 'The error thrown was:\n' + ' $error\n', + ); + }()); rethrow; } finally { @@ -277,10 +299,13 @@ class UseEffect extends RtHook { try { _cleanupCallback?.call(); } catch (error) { - logger.log( - 'An error occurred while executing the cleanup effect', - level: LogLevel.error, - ); + assert(() { + throw AssertionError( + 'An error occurred while executing the cleanup effect in $this${debugLabel != null ? '($debugLabel)' : ''}.\n' + 'The error thrown was:\n' + ' $error\n', + ); + }()); rethrow; } finally { @@ -289,4 +314,5 @@ class UseEffect extends RtHook { } } -abstract class DispatchEffect {} +/// A mixin to execute the effect on initialization. +abstract class AutoDispatchEffect {} diff --git a/packages/reactter/lib/src/hooks/use_reducer.dart b/packages/reactter/lib/src/hooks/use_reducer.dart index 3572f0bb..9baf7d03 100644 --- a/packages/reactter/lib/src/hooks/use_reducer.dart +++ b/packages/reactter/lib/src/hooks/use_reducer.dart @@ -5,7 +5,7 @@ part of 'hooks.dart'; /// /// [UseReducer] accepts a [reducer] method /// and returns the current state paired with a [dispatch] method. -/// (If you’re familiar with Redux, you already know how this works.) +/// (If you're familiar with Redux, you already know how this works.) /// /// Contains a [value] of type [T] which represents the current state. /// @@ -51,20 +51,28 @@ class UseReducer extends RtHook { @override final $ = RtHook.$register; - late final UseState _state; + final UseState _state; /// Calculates a new state with state([T]) and action([RtAction]) given. final Reducer reducer; T get value => _state.value; - /// {@macro use_reducer} + final String? _debugLabel; + @override + String? get debugLabel => _debugLabel ?? super.debugLabel; + @override + Map get debugInfo => { + 'value': value, + }; + + /// {@macro reactter.use_reducer} UseReducer( this.reducer, - T initialState, - ) : _state = UseState(initialState) { - UseEffect(update, [_state]); - } + T initialState, { + String? debugLabel, + }) : _state = UseState(initialState), + _debugLabel = debugLabel; /// Receives a [RtAction] and sends it to [reducer] method for resolved void dispatch(A action) { @@ -170,17 +178,3 @@ abstract class RtActionCallable extends RtAction

{ /// the action has been applied). T call(T state); } - -/// {@macro reactter.rt_action} -@Deprecated( - 'Use `RtAction` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterAction = RtAction; - -/// {@macro reactter.rt_action_callable} -@Deprecated( - 'Use `RtActionCallable` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef ReactterActionCallable = RtActionCallable; diff --git a/packages/reactter/lib/src/hooks/use_state.dart b/packages/reactter/lib/src/hooks/use_state.dart index 1bb7b6b0..b8dc2b18 100644 --- a/packages/reactter/lib/src/hooks/use_state.dart +++ b/packages/reactter/lib/src/hooks/use_state.dart @@ -1,6 +1,6 @@ part of 'hooks.dart'; -/// {@template use_state} +/// {@template reactter.use_state} /// A [RtHook] that manages a state. /// /// Contains a [value] of type [T] which represents the current state. @@ -44,8 +44,11 @@ class UseState extends RtHook { @override final $ = RtHook.$register; - /// {@macro use_state} - UseState(T initialValue) : _value = initialValue; + final String? _debugLabel; + @override + String? get debugLabel => _debugLabel ?? super.debugLabel; + @override + Map get debugInfo => {'value': value}; T _value; @@ -58,4 +61,11 @@ class UseState extends RtHook { update(() => _value = value); } } + + /// {@macro reactter.use_state} + UseState( + T initialValue, { + String? debugLabel, + }) : _value = initialValue, + _debugLabel = debugLabel; } diff --git a/packages/reactter/lib/src/interfaces/context.dart b/packages/reactter/lib/src/interfaces/context.dart new file mode 100644 index 00000000..674deee2 --- /dev/null +++ b/packages/reactter/lib/src/interfaces/context.dart @@ -0,0 +1,23 @@ +part of '../internals.dart'; + +/// Represents an interface for the context of the application. +/// This interface provides access to various components and services used within the application. +/// +/// The [IContext] interface includes the following properties: +/// - [dependencyInjection]: An instance of the [DependencyInjection] class that handles dependency injection. +/// - [stateManagement]: An instance of the [StateManagement] class that manages the state of the application. +/// - [eventHandler]: An instance of the [EventHandler] class that handles events within the application. +/// - [logger]: An instance of the [Logger] class that provides logging functionality. +abstract class IContext { + @internal + @protected + DependencyInjection get dependencyInjection; + + @internal + @protected + StateManagement get stateManagement; + + @internal + @protected + EventHandler get eventHandler; +} diff --git a/packages/reactter/lib/src/interfaces/hook.dart b/packages/reactter/lib/src/interfaces/hook.dart new file mode 100644 index 00000000..ba7d305d --- /dev/null +++ b/packages/reactter/lib/src/interfaces/hook.dart @@ -0,0 +1,42 @@ +part of '../internals.dart'; + +/// An abstract class that represents a hook in Reactter. +abstract class IHook implements IState { + /// This variable is used to register [IHook] + /// and attach the [IState] that are defined here. + @protected + HookBindingZone get $; + + /// Initializes a new instance of the [IHook] class. + /// + /// This constructor calls the `end` method of the [BindingHookZone] instance + /// to register the hook and attach the collected states. + IHook() { + initHook(); + $._bindInstanceToStates(this); + } + + /// Initializes the hook. + /// This method is called when the hook is created. + /// You can override this method to ensure that the hook is properly + /// initialized and to bind the instance to the states. + /// This is particularly useful for setting up state dependencies or + /// performing side effects when the hook is first created. + /// + /// For example, you can use the [UseEffect] hook to execute a side effect when a state changes: + /// + /// ```dart + /// @override + /// void initHook() { + /// UseEffect( + /// () { + /// print("Executed by state changed"); + /// }, + /// [state], + /// ); + /// } + void initHook() {} +} + +@internal +class HookBindingZone extends BindingZone {} diff --git a/packages/reactter/lib/src/interfaces/observer.dart b/packages/reactter/lib/src/interfaces/observer.dart new file mode 100644 index 00000000..5a6f4396 --- /dev/null +++ b/packages/reactter/lib/src/interfaces/observer.dart @@ -0,0 +1,152 @@ +part of '../internals.dart'; + +/// {@template reactter.i_observer} +/// An abstract class representing an observer. +/// {@endtemplate} +@internal +abstract class IObserver {} + +/// {@template reactter.i_state_observer} +/// An abstract class that defines the interface for observing lifecycle of states. +/// {@endtemplate} +@internal +abstract class IStateObserver implements IObserver { + /// A set of all registered state observers. + static final _observers = {}; + + /// {@template reactter.i_state_observer.on_state_created} + /// Called when a state is created. + /// + /// [state] - The state that was created. + /// {@endtemplate} + void onStateCreated(covariant IState state); + + /// {@template reactter.i_state_observer.on_state_bound} + /// Called when a state is bound to an instance. + /// + /// [state] - The state that was bound. + /// + /// [instance] - The instance to which the state was bound. + /// {@endtemplate} + void onStateBound(covariant IState state, Object instance); + + /// {@template reactter.i_state_observer.on_state_unbound} + /// Called when a state is unbound from an instance. + /// + /// [state] - The state that was unbound. + /// + /// [instance] - The instance from which the state was unbound. + /// {@endtemplate} + void onStateUnbound(covariant IState state, Object instance); + + /// {@template reactter.i_state_observer.on_state_updated} + /// Called when a state is updated. + /// + /// [state] - The state that was updated. + /// {@endtemplate} + void onStateUpdated(covariant IState state); + + /// {@template reactter.i_state_observer.on_state_mounted} + /// Called when a state is disposed. + /// + /// [state] - The state that was disposed. + /// {@endtemplate} + void onStateDisposed(covariant IState state); +} + +/// {@template reactter.i_dependency_observer} +/// An abstract class that defines the interface for observing lifecycle of dependencies. +/// {@endtemplate} +@internal +abstract class IDependencyObserver implements IObserver { + /// A set of all registered dependency observers. + static final _observers = {}; + + /// {@template reactter.i_dependency_observer.on_dependency_registered} + /// Called when a dependency is registered. + /// + /// [dependency] - The dependency that was registered. + /// {@endtemplate} + void onDependencyRegistered(covariant DependencyRef dependency); + + /// {@template reactter.i_dependency_observer.on_dependency_created} + /// Called when a dependency is created. + /// + /// [dependency] - The dependency that was created. + /// + /// [instance] - The instance of the dependency. + /// {@endtemplate} + void onDependencyCreated( + covariant DependencyRef dependency, + Object? instance, + ); + + /// {@template reactter.i_dependency_observer.on_dependency_mounted} + /// Called when a dependency is mounted. + /// + /// [dependency] - The dependency that was mounted. + /// + /// [instance] - The instance of the dependency. + /// {@endtemplate} + void onDependencyMounted( + covariant DependencyRef dependency, + Object? instance, + ); + + /// {@template reactter.i_dependency_observer.on_dependency_unmounted} + /// Called when a dependency is unmounted. + /// + /// [dependency] - The dependency that was unmounted. + /// + /// [instance] - The instance of the dependency. + /// {@endtemplate} + void onDependencyUnmounted( + covariant DependencyRef dependency, + Object? instance, + ); + + /// {@template reactter.i_dependency_observer.on_dependency_deleted} + /// Called when a dependency is deleted. + /// + /// [dependency] - The dependency that was deleted. + /// + /// [instance] - The instance of the dependency. + /// {@endtemplate} + void onDependencyDeleted( + covariant DependencyRef dependency, + Object? instance, + ); + + /// {@template reactter.i_dependency_observer.on_dependency_unregistered} + /// Called when a dependency is unregistered. + /// + /// [dependency] - The dependency that was unregistered. + /// {@endtemplate} + void onDependencyUnregistered(covariant DependencyRef dependency); + + /// {@template reactter.i_dependency_observer.on_dependency_failed} + /// Called when a dependency fails. + /// + /// [dependency] - The dependency that failed. + /// + /// [fail] - The reason for the failure. + /// {@endtemplate} + void onDependencyFailed( + covariant DependencyRef dependency, + DependencyFail fail, + ); +} + +/// {@template reactter.dependency_fail} +/// An enum representing the reasons for a dependency failure. +/// {@endtemplate} +enum DependencyFail { + alreadyRegistered, + alreadyCreated, + alreadyDeleted, + alreadyUnregistered, + missingInstanceBuilder, + builderRetained, + dependencyRetained, + cannotUnregisterActiveInstance, +} diff --git a/packages/reactter/lib/src/interfaces/state.dart b/packages/reactter/lib/src/interfaces/state.dart new file mode 100644 index 00000000..0ceaae29 --- /dev/null +++ b/packages/reactter/lib/src/interfaces/state.dart @@ -0,0 +1,54 @@ +part of '../internals.dart'; + +/// {@template reactter.rt_state} +/// A abstract class that represents a stare in Reactter. +/// {@endtemplate} +abstract class IState implements IContext { + bool get isDisposed; + + /// The reference instance to the current state. + Object? get boundInstance; + + /// A label used for debugging purposes. + String? get debugLabel; + + /// A map containing information about state for debugging purposes. + Map get debugInfo; + + /// This method is typically used for internal + /// registration purposes within the state management system. + @protected + @mustCallSuper + void _register(); + + /// Stores a reference to an object instance + @mustCallSuper + void bind(Object instance); + + /// Removes the reference to the object instance + @mustCallSuper + void unbind(); + + /// {@template reactter.istate.update} + /// Executes [fnUpdate], and notify the listeners about to update. + /// + /// If [fnUpdate] is provided, it will be executed before notifying the listeners. + /// If [fnUpdate] is not provided, an empty function will be executed. + /// + /// This method triggers the `Lifecycle.willUpdate` and `Lifecycle.didUpdate` events, which allows listeners to react to the updated state. + /// {@endtemplate} + @mustCallSuper + void update(covariant Function? fnUpdate); + + /// It's used to notify listeners that the state has been updated. + /// It is typically called after making changes to the state object. + /// + /// This method triggers the `Lifecycle.didUpdate` event, + /// which allows listeners to react to the updated state. + @mustCallSuper + void notify(); + + /// Called when this object is removed + @mustCallSuper + void dispose(); +} diff --git a/packages/reactter/lib/src/internals.dart b/packages/reactter/lib/src/internals.dart new file mode 100644 index 00000000..067d4982 --- /dev/null +++ b/packages/reactter/lib/src/internals.dart @@ -0,0 +1,23 @@ +import 'dart:async'; +import 'dart:collection'; +import 'package:meta/meta.dart'; +import 'package:reactter/reactter.dart'; + +part 'core/binding_zone.dart'; +part 'core/dependency_injection.dart'; +part 'core/dependency_mode.dart'; +part 'core/dependency_ref.dart'; +part 'core/dependency_register.dart'; +part 'core/event_handler.dart'; +part 'core/event_notifier.dart'; +part 'core/lifecycle_observer.dart'; +part 'core/lifecycle.dart'; +part 'core/notifier.dart'; +part 'core/observer_manager.dart'; +part 'core/state_management.dart'; +part 'framework/rt_state.dart'; +part 'interfaces/context.dart'; +part 'interfaces/hook.dart'; +part 'interfaces/observer.dart'; +part 'interfaces/state.dart'; +part 'env.dart'; diff --git a/packages/reactter/lib/src/logger.dart b/packages/reactter/lib/src/logger.dart new file mode 100644 index 00000000..d3587d83 --- /dev/null +++ b/packages/reactter/lib/src/logger.dart @@ -0,0 +1,336 @@ +import 'dart:developer' as dev; + +import 'package:meta/meta.dart'; + +import 'framework.dart'; +import 'internals.dart'; +import 'types.dart'; + +extension RtLoggerExt on RtInterface { + /// Initializes the logger. + /// + /// This function sets up the logger for the application. You can provide + /// a custom name for the logger and specify the output destination for + /// the log messages. + /// + /// If no name is provided, the default name 'REACTTER' will be used. + /// + /// If no output is provided, the default output will be used. + /// + /// Parameters: + /// - [name]: An optional name for the logger. Defaults to 'REACTTER'. + /// - [output]: An optional [LogOutput] function to specify the log output destination. + /// The default output is the `dart:developer`'s `log` function. + void initializeLogger({ + String name = 'REACTTER', + LogOutput output = dev.log, + }) { + RtLogger.initialize(name: name, output: output); + } +} + +@internal +class RtLogger implements IStateObserver, IDependencyObserver { + static RtLogger? instance; + + static void initialize({ + String name = 'REACTTER', + LogOutput output = dev.log, + }) { + assert(instance == null, 'The logger has already been initialized.'); + + if (kDebugMode) { + instance ??= RtLogger._(name: name, output: output); + } + } + + final LogOutput output; + final String name; + + RtLogger._({ + required this.name, + required this.output, + }) { + Rt.addObserver(this); + } + + void log(String message, {int level = 0, StackTrace? stackTrace}) { + output.call( + message, + name: name, + level: level, + stackTrace: stackTrace ?? StackTrace.current, + ); + } + + @override + void onStateCreated(RtState state) { + log( + '${prettyFormat(state)} created.', + level: LogLevel.finer, + stackTrace: StackTrace.current, + ); + } + + @override + void onStateBound(RtState state, Object instance) { + log( + '${prettyFormat(state)} bound to ${prettyFormat(instance)}.', + level: LogLevel.finer, + stackTrace: StackTrace.current, + ); + + if (instance is! RtState && !Rt.isActive(instance)) { + final T = instance.runtimeType; + + log( + "The bound instance(${prettyFormat(instance)}) to state(${prettyFormat(state)}) is not in Reactter's context and cannot be disposed automatically.\n" + "You can solve this problem in one of the following ways:\n" + "\t- Call `dispose` method manually when state is no longer needed:\n" + "\t\t`state.dispose();`\n" + "\t- Create bound instance using the dependency injection methods:\n" + "\t\t`Rt.register<$T>(() => $T(...));`\n" + "\t\t`Rt.create<$T>(() => $T(...));`\n" + "**Ignore this message if you are sure that it will be disposed.**", + level: LogLevel.warning, + stackTrace: StackTrace.current, + ); + } + } + + @override + void onStateUnbound(RtState state, Object instance) { + log( + '${prettyFormat(state)} unbound from ${prettyFormat(instance)}.', + level: LogLevel.finer, + stackTrace: StackTrace.current, + ); + } + + @override + void onStateUpdated(RtState state) { + log( + '${prettyFormat(state)} updated.', + level: LogLevel.finer, + stackTrace: StackTrace.current, + ); + } + + @override + void onStateDisposed(RtState state) { + log( + '${prettyFormat(state)} disposed.', + level: LogLevel.finer, + stackTrace: StackTrace.current, + ); + } + + @override + void onDependencyRegistered(DependencyRef dependency) { + log( + '${prettyFormat(dependency)} registered.', + level: LogLevel.fine, + stackTrace: StackTrace.current, + ); + } + + @override + void onDependencyCreated(DependencyRef dependency, Object? instance) { + log( + '${prettyFormat(dependency)} created. Its instance: ${prettyFormat(instance)}.', + level: LogLevel.fine, + stackTrace: StackTrace.current, + ); + } + + @override + void onDependencyMounted(DependencyRef dependency, Object? instance) { + log( + '${prettyFormat(dependency)} mounted. Its instance: ${prettyFormat(instance)}.', + level: LogLevel.fine, + stackTrace: StackTrace.current, + ); + } + + @override + void onDependencyUnmounted(DependencyRef dependency, Object? instance) { + log( + '${prettyFormat(dependency)} unmounted. Its instance: ${prettyFormat(instance)}.', + level: LogLevel.fine, + stackTrace: StackTrace.current, + ); + } + + @override + void onDependencyDeleted(DependencyRef dependency, Object? instance) { + log( + '${prettyFormat(dependency)} deleted. Its instance: ${prettyFormat(instance)}.', + level: LogLevel.fine, + stackTrace: StackTrace.current, + ); + } + + @override + void onDependencyUnregistered(DependencyRef dependency) { + log( + '${prettyFormat(dependency)} unregistered.', + level: LogLevel.fine, + stackTrace: StackTrace.current, + ); + } + + @override + void onDependencyFailed( + covariant DependencyRef dependency, + DependencyFail fail, + ) { + final T = dependency.type; + final id = dependency.id; + final idParam = id != null ? "id: '$id, '" : ''; + + switch (fail) { + case DependencyFail.alreadyRegistered: + log( + '${prettyFormat(dependency)} already registered.', + level: LogLevel.info, + stackTrace: StackTrace.current, + ); + break; + case DependencyFail.alreadyCreated: + log( + '${prettyFormat(dependency)} already created.', + level: LogLevel.info, + stackTrace: StackTrace.current, + ); + break; + case DependencyFail.alreadyDeleted: + log( + '${prettyFormat(dependency)} already deleted.', + level: LogLevel.info, + stackTrace: StackTrace.current, + ); + break; + case DependencyFail.alreadyUnregistered: + log( + '${prettyFormat(dependency)} already unregistered.', + level: LogLevel.info, + stackTrace: StackTrace.current, + ); + break; + case DependencyFail.builderRetained: + log( + "${prettyFormat(dependency)}'s instance retained because is in factory mode.", + level: LogLevel.info, + stackTrace: StackTrace.current, + ); + break; + case DependencyFail.dependencyRetained: + log( + "${prettyFormat(dependency)} retained because is in singleton mode.", + level: LogLevel.info, + stackTrace: StackTrace.current, + ); + break; + case DependencyFail.missingInstanceBuilder: + log( + "${prettyFormat(dependency)} builder was not registered previously.\n" + "You should register the instance build with: \n" + "\t`Rt.register<$T>(() => $T(...)$idParam);`\n" + "\t`Rt.create<$T>(() => $T(...)$idParam);`", + level: LogLevel.warning, + stackTrace: StackTrace.current, + ); + break; + case DependencyFail.cannotUnregisterActiveInstance: + log( + "${prettyFormat(dependency)} couldn't unregister " + "because ${prettyFormat(instance)} is active.\n" + "You should delete the instance before with:\n" + "\t`Rt.delete<$T>(${id ?? ''});`\n" + "\t`Rt.destroy<$T>($idParam, onlyInstance: true);`\n", + level: LogLevel.severe, + stackTrace: StackTrace.current, + ); + break; + } + } +} + +@internal +String prettyFormat(Object? instance) { + if (instance is DependencyRef) { + final type = instance.type.toString().replaceAll('?', ''); + final id = instance.id; + final idStr = id != null ? "id: '$id'" : null; + final mode = instance is DependencyRegister + ? instance.mode.label + : Rt.getDependencyRegisterByRef(instance)?.mode.label; + final modeStr = mode != null ? "mode: '$mode'" : null; + final params = [ + if (idStr != null) idStr, + if (modeStr != null) modeStr, + ].join(', '); + final paramsStr = params.isNotEmpty ? '($params)' : ''; + + return '[DEPENDENCY | $type$paramsStr]'; + } + + if (instance is RtState) { + final type = instance.runtimeType.toString(); + final label = instance.debugLabel; + final labelStr = label != null ? "(debugLabel: '$label')" : ''; + + if (instance is RtHook) { + return '[HOOK | $type$labelStr | #${instance.hashCode}]'; + } + + return '[STATE | $type$labelStr | #${instance.hashCode}]'; + } + + return '[UNKNOWN | ${instance.runtimeType} | #${instance.hashCode}]'; +} + +@internal +typedef RtLoggerInitializeAssertionError = AssertionError; + +/// Copy from `package:logging`. +/// [LogLevel]s to control logging output. Logging can be enabled to include all +/// levels above certain [LogLevel]. The predefined [LogLevel] constants below are sorted as +/// follows (in descending order): [LogLevel.shout], [LogLevel.severe], +/// [LogLevel.warning], [LogLevel.info], [LogLevel.config], [LogLevel.fine], [LogLevel.finer], +/// [LogLevel.finest], and [LogLevel.all]. +/// +/// We recommend using one of the predefined logging levels. If you define your +/// own level, make sure you use a value between those used in [LogLevel.all] and +/// [LogLevel.off]. +class LogLevel { + /// Special key to turn on logging for all levels (0). + static const int all = 0; + + /// Key for highly detailed tracing (300). + static const int finest = 300; + + /// Key for fairly detailed tracing (400). + static const int finer = 400; + + /// Key for tracing information (500). + static const int fine = 500; + + /// Key for static configuration messages (700). + static const int config = 700; + + /// Key for informational messages (800). + static const int info = 800; + + /// Key for potential problems (900). + static const int warning = 900; + + /// Key for serious failures (1000). + static const int severe = 1000; + + /// Key for extra debugging loudness (1200). + static const int shout = 1200; + + /// Special key to turn off all logging (2000). + static const int off = 2000; +} diff --git a/packages/reactter/lib/src/memo/interceptors/memo_safe_async_interceptor.dart b/packages/reactter/lib/src/memo/interceptors/memo_safe_async_interceptor.dart index 1bd8cb98..9f7a1852 100644 --- a/packages/reactter/lib/src/memo/interceptors/memo_safe_async_interceptor.dart +++ b/packages/reactter/lib/src/memo/interceptors/memo_safe_async_interceptor.dart @@ -17,10 +17,3 @@ class MemoSafeAsyncInterceptor extends MemoInterceptor { } } } - -/// {@macro reactter.memo_safe_async_interceptor} -@Deprecated( - 'Use `MemoSafeAsyncInterceptor` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef AsyncMemoSafe = MemoSafeAsyncInterceptor; diff --git a/packages/reactter/lib/src/memo/interceptors/memo_temporary_cache_interceptor.dart b/packages/reactter/lib/src/memo/interceptors/memo_temporary_cache_interceptor.dart index 9fe54187..fbed3c50 100644 --- a/packages/reactter/lib/src/memo/interceptors/memo_temporary_cache_interceptor.dart +++ b/packages/reactter/lib/src/memo/interceptors/memo_temporary_cache_interceptor.dart @@ -19,10 +19,3 @@ class MemoTemporaryCacheInterceptor extends MemoInterceptor { } } } - -/// {@macro reactter.memo_temporary_cache_interceptor} -@Deprecated( - 'Use `MemoTemporaryCacheInterceptor` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef TemporaryCacheMemo = MemoTemporaryCacheInterceptor; diff --git a/packages/reactter/lib/src/memo/interceptors/memo_wrapper_interceptor.dart b/packages/reactter/lib/src/memo/interceptors/memo_wrapper_interceptor.dart index c5c948ee..d8f028b1 100644 --- a/packages/reactter/lib/src/memo/interceptors/memo_wrapper_interceptor.dart +++ b/packages/reactter/lib/src/memo/interceptors/memo_wrapper_interceptor.dart @@ -54,10 +54,3 @@ class MemoWrapperInterceptor extends MemoInterceptor { _onFinish?.call(memo, arg); } } - -/// {@macro reactter.memo_wrapper_interceptor} -@Deprecated( - 'Use `MemoWrapperInterceptor` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef MemoInterceptorWrapper = MemoWrapperInterceptor; diff --git a/packages/reactter/lib/src/memo/interceptors/memo_multi_interceptor.dart b/packages/reactter/lib/src/memo/interceptors/multi_memo_interceptor.dart similarity index 73% rename from packages/reactter/lib/src/memo/interceptors/memo_multi_interceptor.dart rename to packages/reactter/lib/src/memo/interceptors/multi_memo_interceptor.dart index 4a005b96..9ada7e61 100644 --- a/packages/reactter/lib/src/memo/interceptors/memo_multi_interceptor.dart +++ b/packages/reactter/lib/src/memo/interceptors/multi_memo_interceptor.dart @@ -3,11 +3,11 @@ part of '../memo.dart'; /// {@template reactter.memo_multi_interceptor} /// It allows multiple memoization interceptors to be used together. /// {@endtemplate} -class MemoMultiInterceptor extends MemoInterceptor { +class MultiMemoInterceptor extends MemoInterceptor { final List> interceptors; /// {@macro reactter.memo_multi_interceptor} - const MemoMultiInterceptor(this.interceptors); + const MultiMemoInterceptor(this.interceptors); @override void onInit(Memo memo, A arg) { @@ -37,10 +37,3 @@ class MemoMultiInterceptor extends MemoInterceptor { } } } - -/// {@macro reactter.memo_multi_interceptor} -@Deprecated( - 'Use `MemoMultiInterceptor` instead. ' - 'This feature was deprecated after v7.3.0.', -) -typedef MemoInterceptors = MemoMultiInterceptor; diff --git a/packages/reactter/lib/src/memo/memo.dart b/packages/reactter/lib/src/memo/memo.dart index bb4c1048..7f48695f 100644 --- a/packages/reactter/lib/src/memo/memo.dart +++ b/packages/reactter/lib/src/memo/memo.dart @@ -7,5 +7,5 @@ part 'memo_impl.dart'; part 'interceptors/memo_safe_async_interceptor.dart'; part 'interceptors/memo_wrapper_interceptor.dart'; part 'interceptors/memo_interceptor.dart'; -part 'interceptors/memo_multi_interceptor.dart'; +part 'interceptors/multi_memo_interceptor.dart'; part 'interceptors/memo_temporary_cache_interceptor.dart'; diff --git a/packages/reactter/lib/src/memo/memo_impl.dart b/packages/reactter/lib/src/memo/memo_impl.dart index 8cbbb8fd..12714213 100644 --- a/packages/reactter/lib/src/memo/memo_impl.dart +++ b/packages/reactter/lib/src/memo/memo_impl.dart @@ -1,6 +1,6 @@ part of 'memo.dart'; -/// {@template memo} +/// {@template reactter.memo} /// A class callable that is used for memoizing values([T]) /// returned by a calcutate function([calculateValue]). /// @@ -46,7 +46,7 @@ class Memo { /// and handle various events during the memoization process. final MemoInterceptor? _interceptor; - /// {@macro memo} + /// {@macro reactter.memo} Memo( FunctionArg computeValue, [ MemoInterceptor? interceptor, diff --git a/packages/reactter/lib/src/obj/extensions/obj_list.dart b/packages/reactter/lib/src/obj/extensions/obj_list.dart deleted file mode 100644 index ddcdb509..00000000 --- a/packages/reactter/lib/src/obj/extensions/obj_list.dart +++ /dev/null @@ -1,1114 +0,0 @@ -// coverage:ignore-file -part of '../obj.dart'; - -extension ObjListExt on Obj> { - /// The object at the given [index] in the list. - /// - /// The [index] must be a valid index of this list, - /// which means that `index` must be non-negative and - /// less than [length]. - E operator [](int index) => value[index]; - - /// Sets the value at the given [index] in the list to [value]. - /// - /// The [index] must be a valid index of this list, - /// which means that `index` must be non-negative and - /// less than [length]. - void operator []=(int index, E valueToSet) => value[index] = valueToSet; - - /// Returns the concatenation of this list and [other]. - /// - /// Returns a new list containing the elements of this list followed by - /// the elements of [other]. - /// - /// The default behavior is to return a normal growable list. - /// Some list types may choose to return a list of the same type as themselves - /// (see [Uint8List.+]); - Obj> operator +(Obj> other) => Obj(value + other.value); - - /// Returns a view of this list as a list of [R] instances. - /// - /// If this list contains only instances of [R], all read operations - /// will work correctly. If any operation tries to read an element - /// that is not an instance of [R], the access will throw instead. - /// - /// Elements added to the list (e.g., by using [add] or [addAll]) - /// must be instances of [R] to be valid arguments to the adding function, - /// and they must also be instances of [E] to be accepted by - /// this list as well. - /// - /// Methods which accept `Object?` as argument, like [contains] and [remove], - /// will pass the argument directly to the this list's method - /// without any checks. - /// That means that you can do `listOfStrings.cast().remove("a")` - /// successfully, even if it looks like it shouldn't have any effect. - /// - /// Typically implemented as `List.castFrom(this)`. - List cast() => value.cast(); - - /// The first element of the list. - /// - /// The list must be non-empty when accessing its first element. - /// - /// The first element of a list can be modified, unlike an [Iterable]. - /// A `list.first` is equivalent to `list[0]`, - /// both for getting and setting the value. - /// - /// ```dart - /// final numbers = [1, 2, 3]; - /// print(numbers.first); // 1 - /// numbers.first = 10; - /// print(numbers.first); // 10 - /// numbers.clear(); - /// numbers.first; // Throws. - /// ``` - set first(E valueToSet) => value.first = valueToSet; - - /// The last element of the list. - /// - /// The list must be non-empty when accessing its last element. - /// - /// The last element of a list can be modified, unlike an [Iterable]. - /// A `list.last` is equivalent to `theList[theList.length - 1]`, - /// both for getting and setting the value. - /// - /// ```dart - /// final numbers = [1, 2, 3]; - /// print(numbers.last); // 3 - /// numbers.last = 10; - /// print(numbers.last); // 10 - /// numbers.clear(); - /// numbers.last; // Throws. - /// ``` - set last(E valueToSet) => value.last = valueToSet; - - /// The number of objects in this list. - /// - /// The valid indices for a list are `0` through `length - 1`. - /// ```dart - /// final numbers = [1, 2, 3]; - /// print(numbers.length); // 3 - /// ``` - int get length => value.length; - - /// Setting the `length` changes the number of elements in the list. - /// - /// The list must be growable. - /// If [newLength] is greater than current length, - /// new entries are initialized to `null`, - /// so [newLength] must not be greater than the current length - /// if the element type [E] is non-nullable. - /// - /// ```dart - /// final maybeNumbers = [1, null, 3]; - /// maybeNumbers.length = 5; - /// print(maybeNumbers); // [1, null, 3, null, null] - /// maybeNumbers.length = 2; - /// print(maybeNumbers); // [1, null] - /// - /// final numbers = [1, 2, 3]; - /// numbers.length = 1; - /// print(numbers); // [1] - /// numbers.length = 5; // Throws, cannot add `null`s. - /// ``` - set length(int newLength) => value.length = newLength; - - /// Adds [value] to the end of this list, - /// extending the length by one. - /// - /// The list must be growable. - /// - /// ```dart - /// final numbers = [1, 2, 3]; - /// numbers.add(4); - /// print(numbers); // [1, 2, 3, 4] - /// ``` - void add(E valueToAdd) => value.add(valueToAdd); - - /// Appends all objects of [iterable] to the end of this list. - /// - /// Extends the length of the list by the number of objects in [iterable]. - /// The list must be growable. - /// - /// ```dart - /// final numbers = [1, 2, 3]; - /// numbers.addAll([4, 5, 6]); - /// print(numbers); // [1, 2, 3, 4, 5, 6] - /// ``` - void addAll(Iterable iterable) => value.addAll(iterable); - - /// An [Iterable] of the objects in this list in reverse order. - /// ```dart - /// final numbers = ['two', 'three', 'four']; - /// final reverseOrder = numbers.reversed; - /// print(reverseOrder.toList()); // [four, three, two] - /// ``` - Iterable get reversed => value.reversed; - - /// Sorts this list according to the order specified by the [compare] function. - /// - /// The [compare] function must act as a [Comparator]. - /// ```dart - /// final numbers = ['two', 'three', 'four']; - /// // Sort from shortest to longest. - /// numbers.sort((a, b) => a.length.compareTo(b.length)); - /// print(numbers); // [two, four, three] - /// ``` - /// The default [List] implementations use [Comparable.compare] if - /// [compare] is omitted. - /// ```dart - /// final numbers = [13, 2, -11, 0]; - /// numbers.sort(); - /// print(numbers); // [-11, 0, 2, 13] - /// ``` - /// In that case, the elements of the list must be [Comparable] to - /// each other. - /// - /// A [Comparator] may compare objects as equal (return zero), even if they - /// are distinct objects. - /// The sort function is not guaranteed to be stable, so distinct objects - /// that compare as equal may occur in any order in the result: - /// ```dart - /// final numbers = ['one', 'two', 'three', 'four']; - /// numbers.sort((a, b) => a.length.compareTo(b.length)); - /// print(numbers); // [one, two, four, three] OR [two, one, four, three] - /// ``` - void sort([int Function(E a, E b)? compare]) => value.sort(compare); - - /// Shuffles the elements of this list randomly. - /// ```dart - /// final numbers = [1, 2, 3, 4, 5]; - /// numbers.shuffle(); - /// print(numbers); // [1, 3, 4, 5, 2] OR some other random result. - /// ``` - void shuffle([Random? random]) => value.shuffle(random); - - /// The first index of [element] in this list. - /// - /// Searches the list from index [start] to the end of the list. - /// The first time an object `o` is encountered so that `o == element`, - /// the index of `o` is returned. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// print(notes.indexOf('re')); // 1 - /// - /// final indexWithStart = notes.indexOf('re', 2); // 3 - /// ``` - /// Returns -1 if [element] is not found. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final index = notes.indexOf('fa'); // -1 - /// ``` - int indexOf(E element, [int start = 0]) => value.indexOf(element, start); - - /// The first index in the list that satisfies the provided [test]. - /// - /// Searches the list from index [start] to the end of the list. - /// The first time an object `o` is encountered so that `test(o)` is true, - /// the index of `o` is returned. - /// - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final first = notes.indexWhere((note) => note.startsWith('r')); // 1 - /// final second = notes.indexWhere((note) => note.startsWith('r'), 2); // 3 - /// ``` - /// - /// Returns -1 if [element] is not found. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final index = notes.indexWhere((note) => note.startsWith('k')); // -1 - /// ``` - int indexWhere(bool Function(E element) test, [int start = 0]) => - value.indexWhere(test); - - /// The last index in the list that satisfies the provided [test]. - /// - /// Searches the list from index [start] to 0. - /// The first time an object `o` is encountered so that `test(o)` is true, - /// the index of `o` is returned. - /// If [start] is omitted, it defaults to the [length] of the list. - /// - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final first = notes.lastIndexWhere((note) => note.startsWith('r')); // 3 - /// final second = notes.lastIndexWhere((note) => note.startsWith('r'), - /// 2); // 1 - /// ``` - /// - /// Returns -1 if [element] is not found. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final index = notes.lastIndexWhere((note) => note.startsWith('k')); - /// print(index); // -1 - /// ``` - int lastIndexWhere(bool Function(E element) test, [int? start]) => - value.lastIndexWhere(test, start); - - /// The last index of [element] in this list. - /// - /// Searches the list backwards from index [start] to 0. - /// - /// The first time an object `o` is encountered so that `o == element`, - /// the index of `o` is returned. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// const startIndex = 2; - /// final index = notes.lastIndexOf('re', startIndex); // 1 - /// ``` - /// If [start] is not provided, this method searches from the end of the - /// list. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final index = notes.lastIndexOf('re'); // 3 - /// ``` - /// Returns -1 if [element] is not found. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final index = notes.lastIndexOf('fa'); // -1 - /// ``` - int lastIndexOf(E element, [int? start]) => value.lastIndexOf(element, start); - - /// Removes all objects from this list; the length of the list becomes zero. - /// - /// The list must be growable. - /// - /// ```dart - /// final numbers = [1, 2, 3]; - /// numbers.clear(); - /// print(numbers.length); // 0 - /// print(numbers); // [] - /// ``` - void clear() => value.clear(); - - /// Inserts [element] at position [index] in this list. - /// - /// This increases the length of the list by one and shifts all objects - /// at or after the index towards the end of the list. - /// - /// The list must be growable. - /// The [index] value must be non-negative and no greater than [length]. - /// - /// ```dart - /// final numbers = [1, 2, 3, 4]; - /// const index = 2; - /// numbers.insert(index, 10); - /// print(numbers); // [1, 2, 10, 3, 4] - /// ``` - void insert(int index, E element) => value.insert(index, element); - - /// Inserts all objects of [iterable] at position [index] in this list. - /// - /// This increases the length of the list by the length of [iterable] and - /// shifts all later objects towards the end of the list. - /// - /// The list must be growable. - /// The [index] value must be non-negative and no greater than [length]. - /// ```dart - /// final numbers = [1, 2, 3, 4]; - /// final insertItems = [10, 11]; - /// numbers.insertAll(2, insertItems); - /// print(numbers); // [1, 2, 10, 11, 3, 4] - /// ``` - void insertAll(int index, Iterable iterable) => - value.insertAll(index, iterable); - - /// Overwrites elements with the objects of [iterable]. - /// - /// The elements of [iterable] are written into this list, - /// starting at position [index]. - /// This operation does not increase the length of the list. - /// - /// The [index] must be non-negative and no greater than [length]. - /// - /// The [iterable] must not have more elements than what can fit from [index] - /// to [length]. - /// - /// If `iterable` is based on this list, its values may change _during_ the - /// `setAll` operation. - /// ```dart - /// final list = ['a', 'b', 'c', 'd']; - /// list.setAll(1, ['bee', 'sea']); - /// print(list); // [a, bee, sea, d] - /// ``` - void setAll(int index, Iterable iterable) => value.setAll(index, iterable); - - /// Removes the first occurrence of [value] from this list. - /// - /// Returns true if [value] was in the list, false otherwise. - /// The list must be growable. - /// - /// ```dart - /// final parts = ['head', 'shoulders', 'knees', 'toes']; - /// final retVal = parts.remove('head'); // true - /// print(parts); // [shoulders, knees, toes] - /// ``` - /// The method has no effect if [value] was not in the list. - /// ```dart - /// final parts = ['shoulders', 'knees', 'toes']; - /// // Note: 'head' has already been removed. - /// final retVal = parts.remove('head'); // false - /// print(parts); // [shoulders, knees, toes] - /// ``` - bool remove(Object? valueToRemove) => value.remove(valueToRemove); - - /// Removes the object at position [index] from this list. - /// - /// This method reduces the length of `this` by one and moves all later objects - /// down by one position. - /// - /// Returns the removed value. - /// - /// The [index] must be in the range `0 ≤ index < length`. - /// The list must be growable. - /// ```dart - /// final parts = ['head', 'shoulder', 'knees', 'toes']; - /// final retVal = parts.removeAt(2); // knees - /// print(parts); // [head, shoulder, toes] - /// ``` - E removeAt(int index) => value.removeAt(index); - - /// Removes and returns the last object in this list. - /// - /// The list must be growable and non-empty. - /// ```dart - /// final parts = ['head', 'shoulder', 'knees', 'toes']; - /// final retVal = parts.removeLast(); // toes - /// print(parts); // [head, shoulder, knees] - /// ``` - E removeLast() => value.removeLast(); - - /// Removes all objects from this list that satisfy [test]. - /// - /// An object `o` satisfies [test] if `test(o)` is true. - /// ```dart - /// final numbers = ['one', 'two', 'three', 'four']; - /// numbers.removeWhere((item) => item.length == 3); - /// print(numbers); // [three, four] - /// ``` - /// The list must be growable. - void removeWhere(bool Function(E element) test) => value.removeWhere(test); - - /// Removes all objects from this list that fail to satisfy [test]. - /// - /// An object `o` satisfies [test] if `test(o)` is true. - /// ```dart - /// final numbers = ['one', 'two', 'three', 'four']; - /// numbers.retainWhere((item) => item.length == 3); - /// print(numbers); // [one, two] - /// ``` - /// The list must be growable. - void retainWhere(bool Function(E element) test) => value.retainWhere(test); - - /// Returns a new list containing the elements between [start] and [end]. - /// - /// The new list is a `List` containing the elements of this list at - /// positions greater than or equal to [start] and less than [end] in the same - /// order as they occur in this list. - /// - /// ```dart - /// final colors = ['red', 'green', 'blue', 'orange', 'pink']; - /// print(colors.sublist(1, 3)); // [green, blue] - /// ``` - /// - /// If [end] is omitted, it defaults to the [length] of this list. - /// - /// ```dart - /// final colors = ['red', 'green', 'blue', 'orange', 'pink']; - /// print(colors.sublist(3)); // [orange, pink] - /// ``` - /// - /// The `start` and `end` positions must satisfy the relations - /// 0 ≤ `start` ≤ `end` ≤ [length]. - /// If `end` is equal to `start`, then the returned list is empty. - List sublist(int start, [int? end]) => value.sublist(start, end); - - /// Creates an [Iterable] that iterates over a range of elements. - /// - /// The returned iterable iterates over the elements of this list - /// with positions greater than or equal to [start] and less than [end]. - /// - /// The provided range, [start] and [end], must be valid at the time - /// of the call. - /// A range from [start] to [end] is valid if 0 ≤ `start` ≤ `end` ≤ [length]. - /// An empty range (with `end == start`) is valid. - /// - /// The returned [Iterable] behaves like `skip(start).take(end - start)`. - /// That is, it does *not* break if this list changes size, it just - /// ends early if it reaches the end of the list early - /// (if `end`, or even `start`, becomes greater than [length]). - /// ```dart - /// final colors = ['red', 'green', 'blue', 'orange', 'pink']; - /// final firstRange = colors.getRange(0, 3); - /// print(firstRange.join(', ')); // red, green, blue - /// - /// final secondRange = colors.getRange(2, 5); - /// print(secondRange.join(', ')); // blue, orange, pink - /// ``` - Iterable getRange(int start, int end) => value.getRange(start, end); - - /// Writes some elements of [iterable] into a range of this list. - /// - /// Copies the objects of [iterable], skipping [skipCount] objects first, - /// into the range from [start], inclusive, to [end], exclusive, of this list. - /// ```dart - /// final list1 = [1, 2, 3, 4]; - /// final list2 = [5, 6, 7, 8, 9]; - /// // Copies the 4th and 5th items in list2 as the 2nd and 3rd items - /// // of list1. - /// const skipCount = 3; - /// list1.setRange(1, 3, list2, skipCount); - /// print(list1); // [1, 8, 9, 4] - /// ``` - /// The provided range, given by [start] and [end], must be valid. - /// A range from [start] to [end] is valid if 0 ≤ `start` ≤ `end` ≤ [length]. - /// An empty range (with `end == start`) is valid. - /// - /// The [iterable] must have enough objects to fill the range from `start` - /// to `end` after skipping [skipCount] objects. - /// - /// If `iterable` is this list, the operation correctly copies the elements - /// originally in the range from `skipCount` to `skipCount + (end - start)` to - /// the range `start` to `end`, even if the two ranges overlap. - /// - /// If `iterable` depends on this list in some other way, no guarantees are - /// made. - void setRange(int start, int end, Iterable iterable, - [int skipCount = 0]) => - value.setRange(start, end, iterable, skipCount); - - /// Removes a range of elements from the list. - /// - /// Removes the elements with positions greater than or equal to [start] - /// and less than [end], from the list. - /// This reduces the list's length by `end - start`. - /// - /// The provided range, given by [start] and [end], must be valid. - /// A range from [start] to [end] is valid if 0 ≤ `start` ≤ `end` ≤ [length]. - /// An empty range (with `end == start`) is valid. - /// - /// The list must be growable. - /// ```dart - /// final numbers = [1, 2, 3, 4, 5]; - /// numbers.removeRange(1, 4); - /// print(numbers); // [1, 5] - /// ``` - void removeRange(int start, int end) => value.removeRange(start, end); - - /// Overwrites a range of elements with [fillValue]. - /// - /// Sets the positions greater than or equal to [start] and less than [end], - /// to [fillValue]. - /// - /// The provided range, given by [start] and [end], must be valid. - /// A range from [start] to [end] is valid if 0 ≤ `start` ≤ `end` ≤ [length]. - /// An empty range (with `end == start`) is valid. - /// - /// If the element type is not nullable, the [fillValue] must be provided and - /// must be non-`null`. - /// - /// Example: - /// ```dart - /// final words = List.filled(5, 'old'); - /// print(words); // [old, old, old, old, old] - /// words.fillRange(1, 3, 'new'); - /// print(words); // [old, new, new, old, old] - /// ``` - void fillRange(int start, int end, [E? fillValue]) => - value.fillRange(start, end, fillValue); - - /// Replaces a range of elements with the elements of [replacements]. - /// - /// Removes the objects in the range from [start] to [end], - /// then inserts the elements of [replacements] at [start]. - /// ```dart - /// final numbers = [1, 2, 3, 4, 5]; - /// final replacements = [6, 7]; - /// numbers.replaceRange(1, 4, replacements); - /// print(numbers); // [1, 6, 7, 5] - /// ``` - /// The provided range, given by [start] and [end], must be valid. - /// A range from [start] to [end] is valid if 0 ≤ `start` ≤ `end` ≤ [length]. - /// An empty range (with `end == start`) is valid. - /// - /// The operation `list.replaceRange(start, end, replacements)` - /// is roughly equivalent to: - /// ```dart - /// final numbers = [1, 2, 3, 4, 5]; - /// numbers.removeRange(1, 4); - /// final replacements = [6, 7]; - /// numbers.insertAll(1, replacements); - /// print(numbers); // [1, 6, 7, 5] - /// ``` - /// but may be more efficient. - /// - /// The list must be growable. - /// This method does not work on fixed-length lists, even when [replacements] - /// has the same number of elements as the replaced range. In that case use - /// [setRange] instead. - void replaceRange(int start, int end, Iterable replacements) => - value.replaceRange(start, end, replacements); - - /// An unmodifiable [Map] view of this list. - /// - /// The map uses the indices of this list as keys and the corresponding objects - /// as values. The `Map.keys` [Iterable] iterates the indices of this list - /// in numerical order. - /// ```dart - /// var words = ['fee', 'fi', 'fo', 'fum']; - /// var map = words.asMap(); // {0: fee, 1: fi, 2: fo, 3: fum} - /// map.keys.toList(); // [0, 1, 2, 3] - /// ``` - Map asMap() => value.asMap(); -} - -extension ObjListNullExt on Obj?> { - /// The object at the given [index] in the list. - /// - /// The [index] must be a valid index of this list, - /// which means that `index` must be non-negative and - /// less than [length]. - E? operator [](int index) => value?[index]; - - /// Sets the value at the given [index] in the list to [value]. - /// - /// The [index] must be a valid index of this list, - /// which means that `index` must be non-negative and - /// less than [length]. - void operator []=(int index, E valueToSet) => value?[index] = valueToSet; - - /// Returns a view of this list as a list of [R] instances. - /// - /// If this list contains only instances of [R], all read operations - /// will work correctly. If any operation tries to read an element - /// that is not an instance of [R], the access will throw instead. - /// - /// Elements added to the list (e.g., by using [add] or [addAll]) - /// must be instances of [R] to be valid arguments to the adding function, - /// and they must also be instances of [E] to be accepted by - /// this list as well. - /// - /// Methods which accept `Object?` as argument, like [contains] and [remove], - /// will pass the argument directly to the this list's method - /// without any checks. - /// That means that you can do `listOfStrings.cast().remove("a")` - /// successfully, even if it looks like it shouldn't have any effect. - /// - /// Typically implemented as `List.castFrom(this)`. - List? cast() => value?.cast(); - - /// The first element of the list. - /// - /// The list must be non-empty when accessing its first element. - /// - /// The first element of a list can be modified, unlike an [Iterable]. - /// A `list.first` is equivalent to `list[0]`, - /// both for getting and setting the value. - /// - /// ```dart - /// final numbers = [1, 2, 3]; - /// print(numbers.first); // 1 - /// numbers.first = 10; - /// print(numbers.first); // 10 - /// numbers.clear(); - /// numbers.first; // Throws. - /// ``` - set first(E valueToSet) => value?.first = valueToSet; - - /// The last element of the list. - /// - /// The list must be non-empty when accessing its last element. - /// - /// The last element of a list can be modified, unlike an [Iterable]. - /// A `list.last` is equivalent to `theList[theList.length - 1]`, - /// both for getting and setting the value. - /// - /// ```dart - /// final numbers = [1, 2, 3]; - /// print(numbers.last); // 3 - /// numbers.last = 10; - /// print(numbers.last); // 10 - /// numbers.clear(); - /// numbers.last; // Throws. - /// ``` - set last(E valueToSet) => value?.last = valueToSet; - - /// The number of objects in this list. - /// - /// The valid indices for a list are `0` through `length - 1`. - /// ```dart - /// final numbers = [1, 2, 3]; - /// print(numbers.length); // 3 - /// ``` - int get length => value?.length ?? 0; - - /// Setting the `length` changes the number of elements in the list. - /// - /// The list must be growable. - /// If [newLength] is greater than current length, - /// new entries are initialized to `null`, - /// so [newLength] must not be greater than the current length - /// if the element type [E] is non-nullable. - /// - /// ```dart - /// final maybeNumbers = [1, null, 3]; - /// maybeNumbers.length = 5; - /// print(maybeNumbers); // [1, null, 3, null, null] - /// maybeNumbers.length = 2; - /// print(maybeNumbers); // [1, null] - /// - /// final numbers = [1, 2, 3]; - /// numbers.length = 1; - /// print(numbers); // [1] - /// numbers.length = 5; // Throws, cannot add `null`s. - /// ``` - set length(int newLength) => value?.length = newLength; - - /// Adds [value] to the end of this list, - /// extending the length by one. - /// - /// The list must be growable. - /// - /// ```dart - /// final numbers = [1, 2, 3]; - /// numbers.add(4); - /// print(numbers); // [1, 2, 3, 4] - /// ``` - void add(E valueToAdd) => value?.add(valueToAdd); - - /// Appends all objects of [iterable] to the end of this list. - /// - /// Extends the length of the list by the number of objects in [iterable]. - /// The list must be growable. - /// - /// ```dart - /// final numbers = [1, 2, 3]; - /// numbers.addAll([4, 5, 6]); - /// print(numbers); // [1, 2, 3, 4, 5, 6] - /// ``` - void addAll(Iterable iterable) => value?.addAll(iterable); - - /// An [Iterable] of the objects in this list in reverse order. - /// ```dart - /// final numbers = ['two', 'three', 'four']; - /// final reverseOrder = numbers.reversed; - /// print(reverseOrder.toList()); // [four, three, two] - /// ``` - Iterable? get reversed => value?.reversed; - - /// Sorts this list according to the order specified by the [compare] function. - /// - /// The [compare] function must act as a [Comparator]. - /// ```dart - /// final numbers = ['two', 'three', 'four']; - /// // Sort from shortest to longest. - /// numbers.sort((a, b) => a.length.compareTo(b.length)); - /// print(numbers); // [two, four, three] - /// ``` - /// The default [List] implementations use [Comparable.compare] if - /// [compare] is omitted. - /// ```dart - /// final numbers = [13, 2, -11, 0]; - /// numbers.sort(); - /// print(numbers); // [-11, 0, 2, 13] - /// ``` - /// In that case, the elements of the list must be [Comparable] to - /// each other. - /// - /// A [Comparator] may compare objects as equal (return zero), even if they - /// are distinct objects. - /// The sort function is not guaranteed to be stable, so distinct objects - /// that compare as equal may occur in any order in the result: - /// ```dart - /// final numbers = ['one', 'two', 'three', 'four']; - /// numbers.sort((a, b) => a.length.compareTo(b.length)); - /// print(numbers); // [one, two, four, three] OR [two, one, four, three] - /// ``` - void sort([int Function(E a, E b)? compare]) => value?.sort(compare); - - /// Shuffles the elements of this list randomly. - /// ```dart - /// final numbers = [1, 2, 3, 4, 5]; - /// numbers.shuffle(); - /// print(numbers); // [1, 3, 4, 5, 2] OR some other random result. - /// ``` - void shuffle([Random? random]) => value?.shuffle(random); - - /// The first index of [element] in this list. - /// - /// Searches the list from index [start] to the end of the list. - /// The first time an object `o` is encountered so that `o == element`, - /// the index of `o` is returned. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// print(notes.indexOf('re')); // 1 - /// - /// final indexWithStart = notes.indexOf('re', 2); // 3 - /// ``` - /// Returns -1 if [element] is not found. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final index = notes.indexOf('fa'); // -1 - /// ``` - int? indexOf(E element, [int start = 0]) => value?.indexOf(element, start); - - /// The first index in the list that satisfies the provided [test]. - /// - /// Searches the list from index [start] to the end of the list. - /// The first time an object `o` is encountered so that `test(o)` is true, - /// the index of `o` is returned. - /// - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final first = notes.indexWhere((note) => note.startsWith('r')); // 1 - /// final second = notes.indexWhere((note) => note.startsWith('r'), 2); // 3 - /// ``` - /// - /// Returns -1 if [element] is not found. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final index = notes.indexWhere((note) => note.startsWith('k')); // -1 - /// ``` - int? indexWhere(bool Function(E element) test, [int start = 0]) => - value?.indexWhere(test); - - /// The last index in the list that satisfies the provided [test]. - /// - /// Searches the list from index [start] to 0. - /// The first time an object `o` is encountered so that `test(o)` is true, - /// the index of `o` is returned. - /// If [start] is omitted, it defaults to the [length] of the list. - /// - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final first = notes.lastIndexWhere((note) => note.startsWith('r')); // 3 - /// final second = notes.lastIndexWhere((note) => note.startsWith('r'), - /// 2); // 1 - /// ``` - /// - /// Returns -1 if [element] is not found. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final index = notes.lastIndexWhere((note) => note.startsWith('k')); - /// print(index); // -1 - /// ``` - int? lastIndexWhere(bool Function(E element) test, [int? start]) => - value?.lastIndexWhere(test, start); - - /// The last index of [element] in this list. - /// - /// Searches the list backwards from index [start] to 0. - /// - /// The first time an object `o` is encountered so that `o == element`, - /// the index of `o` is returned. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// const startIndex = 2; - /// final index = notes.lastIndexOf('re', startIndex); // 1 - /// ``` - /// If [start] is not provided, this method searches from the end of the - /// list. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final index = notes.lastIndexOf('re'); // 3 - /// ``` - /// Returns -1 if [element] is not found. - /// ```dart - /// final notes = ['do', 're', 'mi', 're']; - /// final index = notes.lastIndexOf('fa'); // -1 - /// ``` - int? lastIndexOf(E element, [int? start]) => - value?.lastIndexOf(element, start); - - /// Removes all objects from this list; the length of the list becomes zero. - /// - /// The list must be growable. - /// - /// ```dart - /// final numbers = [1, 2, 3]; - /// numbers.clear(); - /// print(numbers.length); // 0 - /// print(numbers); // [] - /// ``` - void clear() => value?.clear(); - - /// Inserts [element] at position [index] in this list. - /// - /// This increases the length of the list by one and shifts all objects - /// at or after the index towards the end of the list. - /// - /// The list must be growable. - /// The [index] value must be non-negative and no greater than [length]. - /// - /// ```dart - /// final numbers = [1, 2, 3, 4]; - /// const index = 2; - /// numbers.insert(index, 10); - /// print(numbers); // [1, 2, 10, 3, 4] - /// ``` - void insert(int index, E element) => value?.insert(index, element); - - /// Inserts all objects of [iterable] at position [index] in this list. - /// - /// This increases the length of the list by the length of [iterable] and - /// shifts all later objects towards the end of the list. - /// - /// The list must be growable. - /// The [index] value must be non-negative and no greater than [length]. - /// ```dart - /// final numbers = [1, 2, 3, 4]; - /// final insertItems = [10, 11]; - /// numbers.insertAll(2, insertItems); - /// print(numbers); // [1, 2, 10, 11, 3, 4] - /// ``` - void insertAll(int index, Iterable iterable) => - value?.insertAll(index, iterable); - - /// Overwrites elements with the objects of [iterable]. - /// - /// The elements of [iterable] are written into this list, - /// starting at position [index]. - /// This operation does not increase the length of the list. - /// - /// The [index] must be non-negative and no greater than [length]. - /// - /// The [iterable] must not have more elements than what can fit from [index] - /// to [length]. - /// - /// If `iterable` is based on this list, its values may change _during_ the - /// `setAll` operation. - /// ```dart - /// final list = ['a', 'b', 'c', 'd']; - /// list.setAll(1, ['bee', 'sea']); - /// print(list); // [a, bee, sea, d] - /// ``` - void setAll(int index, Iterable iterable) => - value?.setAll(index, iterable); - - /// Removes the first occurrence of [value] from this list. - /// - /// Returns true if [value] was in the list, false otherwise. - /// The list must be growable. - /// - /// ```dart - /// final parts = ['head', 'shoulders', 'knees', 'toes']; - /// final retVal = parts.remove('head'); // true - /// print(parts); // [shoulders, knees, toes] - /// ``` - /// The method has no effect if [value] was not in the list. - /// ```dart - /// final parts = ['shoulders', 'knees', 'toes']; - /// // Note: 'head' has already been removed. - /// final retVal = parts.remove('head'); // false - /// print(parts); // [shoulders, knees, toes] - /// ``` - bool? remove(Object? valueToRemove) => value?.remove(valueToRemove); - - /// Removes the object at position [index] from this list. - /// - /// This method reduces the length of `this` by one and moves all later objects - /// down by one position. - /// - /// Returns the removed value. - /// - /// The [index] must be in the range `0 ≤ index < length`. - /// The list must be growable. - /// ```dart - /// final parts = ['head', 'shoulder', 'knees', 'toes']; - /// final retVal = parts.removeAt(2); // knees - /// print(parts); // [head, shoulder, toes] - /// ``` - E? removeAt(int index) => value?.removeAt(index); - - /// Removes and returns the last object in this list. - /// - /// The list must be growable and non-empty. - /// ```dart - /// final parts = ['head', 'shoulder', 'knees', 'toes']; - /// final retVal = parts.removeLast(); // toes - /// print(parts); // [head, shoulder, knees] - /// ``` - E? removeLast() => value?.removeLast(); - - /// Removes all objects from this list that satisfy [test]. - /// - /// An object `o` satisfies [test] if `test(o)` is true. - /// ```dart - /// final numbers = ['one', 'two', 'three', 'four']; - /// numbers.removeWhere((item) => item.length == 3); - /// print(numbers); // [three, four] - /// ``` - /// The list must be growable. - void removeWhere(bool Function(E element) test) => value?.removeWhere(test); - - /// Removes all objects from this list that fail to satisfy [test]. - /// - /// An object `o` satisfies [test] if `test(o)` is true. - /// ```dart - /// final numbers = ['one', 'two', 'three', 'four']; - /// numbers.retainWhere((item) => item.length == 3); - /// print(numbers); // [one, two] - /// ``` - /// The list must be growable. - void retainWhere(bool Function(E element) test) => value?.retainWhere(test); - - /// Returns a new list containing the elements between [start] and [end]. - /// - /// The new list is a `List` containing the elements of this list at - /// positions greater than or equal to [start] and less than [end] in the same - /// order as they occur in this list. - /// - /// ```dart - /// final colors = ['red', 'green', 'blue', 'orange', 'pink']; - /// print(colors.sublist(1, 3)); // [green, blue] - /// ``` - /// - /// If [end] is omitted, it defaults to the [length] of this list. - /// - /// ```dart - /// final colors = ['red', 'green', 'blue', 'orange', 'pink']; - /// print(colors.sublist(3)); // [orange, pink] - /// ``` - /// - /// The `start` and `end` positions must satisfy the relations - /// 0 ≤ `start` ≤ `end` ≤ [length]. - /// If `end` is equal to `start`, then the returned list is empty. - List? sublist(int start, [int? end]) => value?.sublist(start, end); - - /// Creates an [Iterable] that iterates over a range of elements. - /// - /// The returned iterable iterates over the elements of this list - /// with positions greater than or equal to [start] and less than [end]. - /// - /// The provided range, [start] and [end], must be valid at the time - /// of the call. - /// A range from [start] to [end] is valid if 0 ≤ `start` ≤ `end` ≤ [length]. - /// An empty range (with `end == start`) is valid. - /// - /// The returned [Iterable] behaves like `skip(start).take(end - start)`. - /// That is, it does *not* break if this list changes size, it just - /// ends early if it reaches the end of the list early - /// (if `end`, or even `start`, becomes greater than [length]). - /// ```dart - /// final colors = ['red', 'green', 'blue', 'orange', 'pink']; - /// final firstRange = colors.getRange(0, 3); - /// print(firstRange.join(', ')); // red, green, blue - /// - /// final secondRange = colors.getRange(2, 5); - /// print(secondRange.join(', ')); // blue, orange, pink - /// ``` - Iterable? getRange(int start, int end) => value?.getRange(start, end); - - /// Writes some elements of [iterable] into a range of this list. - /// - /// Copies the objects of [iterable], skipping [skipCount] objects first, - /// into the range from [start], inclusive, to [end], exclusive, of this list. - /// ```dart - /// final list1 = [1, 2, 3, 4]; - /// final list2 = [5, 6, 7, 8, 9]; - /// // Copies the 4th and 5th items in list2 as the 2nd and 3rd items - /// // of list1. - /// const skipCount = 3; - /// list1.setRange(1, 3, list2, skipCount); - /// print(list1); // [1, 8, 9, 4] - /// ``` - /// The provided range, given by [start] and [end], must be valid. - /// A range from [start] to [end] is valid if 0 ≤ `start` ≤ `end` ≤ [length]. - /// An empty range (with `end == start`) is valid. - /// - /// The [iterable] must have enough objects to fill the range from `start` - /// to `end` after skipping [skipCount] objects. - /// - /// If `iterable` is this list, the operation correctly copies the elements - /// originally in the range from `skipCount` to `skipCount + (end - start)` to - /// the range `start` to `end`, even if the two ranges overlap. - /// - /// If `iterable` depends on this list in some other way, no guarantees are - /// made. - void setRange(int start, int end, Iterable iterable, - [int skipCount = 0]) => - value?.setRange(start, end, iterable, skipCount); - - /// Removes a range of elements from the list. - /// - /// Removes the elements with positions greater than or equal to [start] - /// and less than [end], from the list. - /// This reduces the list's length by `end - start`. - /// - /// The provided range, given by [start] and [end], must be valid. - /// A range from [start] to [end] is valid if 0 ≤ `start` ≤ `end` ≤ [length]. - /// An empty range (with `end == start`) is valid. - /// - /// The list must be growable. - /// ```dart - /// final numbers = [1, 2, 3, 4, 5]; - /// numbers.removeRange(1, 4); - /// print(numbers); // [1, 5] - /// ``` - void removeRange(int start, int end) => value?.removeRange(start, end); - - /// Overwrites a range of elements with [fillValue]. - /// - /// Sets the positions greater than or equal to [start] and less than [end], - /// to [fillValue]. - /// - /// The provided range, given by [start] and [end], must be valid. - /// A range from [start] to [end] is valid if 0 ≤ `start` ≤ `end` ≤ [length]. - /// An empty range (with `end == start`) is valid. - /// - /// If the element type is not nullable, the [fillValue] must be provided and - /// must be non-`null`. - /// - /// Example: - /// ```dart - /// final words = List.filled(5, 'old'); - /// print(words); // [old, old, old, old, old] - /// words.fillRange(1, 3, 'new'); - /// print(words); // [old, new, new, old, old] - /// ``` - void fillRange(int start, int end, [E? fillValue]) => - value?.fillRange(start, end, fillValue); - - /// Replaces a range of elements with the elements of [replacements]. - /// - /// Removes the objects in the range from [start] to [end], - /// then inserts the elements of [replacements] at [start]. - /// ```dart - /// final numbers = [1, 2, 3, 4, 5]; - /// final replacements = [6, 7]; - /// numbers.replaceRange(1, 4, replacements); - /// print(numbers); // [1, 6, 7, 5] - /// ``` - /// The provided range, given by [start] and [end], must be valid. - /// A range from [start] to [end] is valid if 0 ≤ `start` ≤ `end` ≤ [length]. - /// An empty range (with `end == start`) is valid. - /// - /// The operation `list.replaceRange(start, end, replacements)` - /// is roughly equivalent to: - /// ```dart - /// final numbers = [1, 2, 3, 4, 5]; - /// numbers.removeRange(1, 4); - /// final replacements = [6, 7]; - /// numbers.insertAll(1, replacements); - /// print(numbers); // [1, 6, 7, 5] - /// ``` - /// but may be more efficient. - /// - /// The list must be growable. - /// This method does not work on fixed-length lists, even when [replacements] - /// has the same number of elements as the replaced range. In that case use - /// [setRange] instead. - void replaceRange(int start, int end, Iterable replacements) => - value?.replaceRange(start, end, replacements); - - /// An unmodifiable [Map] view of this list. - /// - /// The map uses the indices of this list as keys and the corresponding objects - /// as values. The `Map.keys` [Iterable] iterates the indices of this list - /// in numerical order. - /// ```dart - /// var words = ['fee', 'fi', 'fo', 'fum']; - /// var map = words.asMap(); // {0: fee, 1: fi, 2: fo, 3: fum} - /// map.keys.toList(); // [0, 1, 2, 3] - /// ``` - Map? asMap() => value?.asMap(); -} diff --git a/packages/reactter/lib/src/obj/extensions/obj_map.dart b/packages/reactter/lib/src/obj/extensions/obj_map.dart deleted file mode 100644 index fa3b931f..00000000 --- a/packages/reactter/lib/src/obj/extensions/obj_map.dart +++ /dev/null @@ -1,500 +0,0 @@ -// coverage:ignore-file -part of '../obj.dart'; - -extension ObjMapExt on Obj> { - /// Provides a view of this map as having [RK] keys and [RV] instances, - /// if necessary. - /// - /// If this map is already a `Map`, it is returned unchanged. - /// - /// If this set contains only keys of type [RK] and values of type [RV], - /// all read operations will work correctly. - /// If any operation exposes a non-[RK] key or non-[RV] value, - /// the operation will throw instead. - /// - /// Entries added to the map must be valid for both a `Map` and a - /// `Map`. - /// - /// Methods which accept `Object?` as argument, - /// like [containsKey], [remove] and [operator []], - /// will pass the argument directly to the this map's method - /// without any checks. - /// That means that you can do `mapWithStringKeys.cast().remove("a")` - /// successfully, even if it looks like it shouldn't have any effect. - Map cast() => value.cast(); - - /// Whether this map contains the given [value]. - /// - /// Returns true if any of the values in the map are equal to `value` - /// according to the `==` operator. - /// ```dart - /// final moonCount = {'Mercury': 0, 'Venus': 0, 'Earth': 1, - /// 'Mars': 2, 'Jupiter': 79, 'Saturn': 82, 'Uranus': 27, 'Neptune': 14 }; - /// final moons3 = moonCount.containsValue(3); // false - /// final moons82 = moonCount.containsValue(82); // true - /// ``` - bool containsValue(Object? valueToEvaluate) => - value.containsValue(valueToEvaluate); - - /// Whether this map contains the given [key]. - /// - /// Returns true if any of the keys in the map are equal to `key` - /// according to the equality used by the map. - /// ```dart - /// final moonCount = {'Mercury': 0, 'Venus': 0, 'Earth': 1, - /// 'Mars': 2, 'Jupiter': 79, 'Saturn': 82, 'Uranus': 27, 'Neptune': 14 }; - /// final containsUranus = moonCount.containsKey('Uranus'); // true - /// final containsPluto = moonCount.containsKey('Pluto'); // false - /// ``` - bool containsKey(Object? key) => value.containsKey(key); - - /// The value for the given [key], or `null` if [key] is not in the map. - /// - /// Some maps allow `null` as a value. - /// For those maps, a lookup using this operator cannot distinguish between a - /// key not being in the map, and the key being there with a `null` value. - /// Methods like [containsKey] or [putIfAbsent] can be used if the distinction - /// is important. - V? operator [](Object? key) => value[key]; - - /// Associates the [key] with the given [value]. - /// - /// If the key was already in the map, its associated value is changed. - /// Otherwise the key/value pair is added to the map. - void operator []=(K key, V valueToSet) => value[key] = valueToSet; - - /// The map entries of [this]. - Iterable> get entries => value.entries; - - /// Returns a new map where all entries of this map are transformed by - /// the given [convert] function. - Map map(MapEntry Function(K key, V value) convert) => - value.map(convert); - - /// Adds all key/value pairs of [newEntries] to this map. - /// - /// If a key of [newEntries] is already in this map, - /// the corresponding value is overwritten. - /// - /// The operation is equivalent to doing `this[entry.key] = entry.value` - /// for each [MapEntry] of the iterable. - /// ```dart - /// final planets = {1: 'Mercury', 2: 'Venus', - /// 3: 'Earth', 4: 'Mars'}; - /// final gasGiants = {5: 'Jupiter', 6: 'Saturn'}; - /// final iceGiants = {7: 'Uranus', 8: 'Neptune'}; - /// planets.addEntries(gasGiants.entries); - /// planets.addEntries(iceGiants.entries); - /// print(planets); - /// // {1: Mercury, 2: Venus, 3: Earth, 4: Mars, 5: Jupiter, 6: Saturn, - /// // 7: Uranus, 8: Neptune} - /// ``` - void addEntries(Iterable> newEntries) => - value.addEntries(newEntries); - - /// Updates the value for the provided [key]. - /// - /// Returns the new value associated with the key. - /// - /// If the key is present, invokes [update] with the current value and stores - /// the new value in the map. - /// - /// If the key is not present and [ifAbsent] is provided, calls [ifAbsent] - /// and adds the key with the returned value to the map. - /// - /// If the key is not present, [ifAbsent] must be provided. - /// ```dart - /// final planetsFromSun = {1: 'Mercury', 2: 'unknown', - /// 3: 'Earth'}; - /// // Update value for known key value 2. - /// planetsFromSun.update(2, (value) => 'Venus'); - /// print(planetsFromSun); // {1: Mercury, 2: Venus, 3: Earth} - /// - /// final largestPlanets = {1: 'Jupiter', 2: 'Saturn', - /// 3: 'Neptune'}; - /// // Key value 8 is missing from list, add it using [ifAbsent]. - /// largestPlanets.update(8, (value) => 'New', ifAbsent: () => 'Mercury'); - /// print(largestPlanets); // {1: Jupiter, 2: Saturn, 3: Neptune, 8: Mercury} - /// ``` - V updateMap(K key, V Function(V value) update, {V Function()? ifAbsent}) => - value.update(key, update, ifAbsent: ifAbsent); - - /// Updates all values. - /// - /// Iterates over all entries in the map and updates them with the result - /// of invoking [update]. - /// ```dart - /// final terrestrial = {1: 'Mercury', 2: 'Venus', 3: 'Earth'}; - /// terrestrial.updateAll((key, value) => value.toUpperCase()); - /// print(terrestrial); // {1: MERCURY, 2: VENUS, 3: EARTH} - /// ``` - void updateAll(V Function(K key, V value) update) => value.updateAll(update); - - /// Removes all entries of this map that satisfy the given [test]. - /// ```dart - /// final terrestrial = {1: 'Mercury', 2: 'Venus', 3: 'Earth'}; - /// terrestrial.removeWhere((key, value) => value.startsWith('E')); - /// print(terrestrial); // {1: Mercury, 2: Venus} - /// ``` - void removeWhere(bool Function(K key, V value) test) => - value.removeWhere(test); - - /// Look up the value of [key], or add a new entry if it isn't there. - /// - /// Returns the value associated to [key], if there is one. - /// Otherwise calls [ifAbsent] to get a new value, associates [key] to - /// that value, and then returns the new value. - /// ```dart - /// final diameters = {1.0: 'Earth'}; - /// final otherDiameters = {0.383: 'Mercury', 0.949: 'Venus'}; - /// - /// for (final item in otherDiameters.entries) { - /// diameters.putIfAbsent(item.key, () => item.value); - /// } - /// print(diameters); // {1.0: Earth, 0.383: Mercury, 0.949: Venus} - /// - /// // If the key already exists, the current value is returned. - /// final result = diameters.putIfAbsent(0.383, () => 'Random'); - /// print(result); // Mercury - /// print(diameters); // {1.0: Earth, 0.383: Mercury, 0.949: Venus} - /// ``` - /// Calling [ifAbsent] must not add or remove keys from the map. - V putIfAbsent(K key, V Function() ifAbsent) => - value.putIfAbsent(key, ifAbsent); - - /// Adds all key/value pairs of [other] to this map. - /// - /// If a key of [other] is already in this map, its value is overwritten. - /// - /// The operation is equivalent to doing `this[key] = value` for each key - /// and associated value in other. It iterates over [other], which must - /// therefore not change during the iteration. - /// ```dart - /// final planets = {1: 'Mercury', 2: 'Earth'}; - /// planets.addAll({5: 'Jupiter', 6: 'Saturn'}); - /// print(planets); // {1: Mercury, 2: Earth, 5: Jupiter, 6: Saturn} - /// ``` - void addAll(Map other) => value.addAll(other); - - /// Removes [key] and its associated value, if present, from the map. - /// - /// Returns the value associated with `key` before it was removed. - /// Returns `null` if `key` was not in the map. - /// - /// Note that some maps allow `null` as a value, - /// so a returned `null` value doesn't always mean that the key was absent. - /// ```dart - /// final terrestrial = {1: 'Mercury', 2: 'Venus', 3: 'Earth'}; - /// final removedValue = terrestrial.remove(2); // Venus - /// print(terrestrial); // {1: Mercury, 3: Earth} - /// ``` - V? remove(Object? key) => value.remove(key); - - /// Removes all entries from the map. - /// - /// After this, the map is empty. - /// ```dart - /// final planets = {1: 'Mercury', 2: 'Venus', 3: 'Earth'}; - /// planets.clear(); // {} - /// ``` - void clear() => value.clear(); - - /// Applies [action] to each key/value pair of the map. - /// - /// Calling `action` must not add or remove keys from the map. - /// ```dart - /// final planetsByMass = {0.81: 'Venus', 1: 'Earth', - /// 0.11: 'Mars', 17.15: 'Neptune'}; - /// - /// planetsByMass.forEach((key, value) { - /// print('$key: $value'); - /// // 0.81: Venus - /// // 1: Earth - /// // 0.11: Mars - /// // 17.15: Neptune - /// }); - /// ``` - void forEach(void Function(K key, V value) action) => value.forEach(action); - - /// The keys of [this]. - /// - /// The returned iterable has efficient `length` and `contains` operations, - /// based on [length] and [containsKey] of the map. - /// - /// The order of iteration is defined by the individual `Map` implementation, - /// but must be consistent between changes to the map. - /// - /// Modifying the map while iterating the keys may break the iteration. - Iterable get keys => value.keys; - - /// The values of [this]. - /// - /// The values are iterated in the order of their corresponding keys. - /// This means that iterating [keys] and [values] in parallel will - /// provide matching pairs of keys and values. - /// - /// The returned iterable has an efficient `length` method based on the - /// [length] of the map. Its [Iterable.contains] method is based on - /// `==` comparison. - /// - /// Modifying the map while iterating the values may break the iteration. - Iterable get values => value.values; - - /// The number of key/value pairs in the map. - int get length => value.length; - - /// Whether there is no key/value pair in the map. - bool get isEmpty => value.isEmpty; - - /// Whether there is at least one key/value pair in the map. - bool get isNotEmpty => value.isNotEmpty; -} - -extension ObjMapNullExt on Obj?> { - /// Provides a view of this map as having [RK] keys and [RV] instances, - /// if necessary. - /// - /// If this map is already a `Map`, it is returned unchanged. - /// - /// If this set contains only keys of type [RK] and values of type [RV], - /// all read operations will work correctly. - /// If any operation exposes a non-[RK] key or non-[RV] value, - /// the operation will throw instead. - /// - /// Entries added to the map must be valid for both a `Map` and a - /// `Map`. - /// - /// Methods which accept `Object?` as argument, - /// like [containsKey], [remove] and [operator []], - /// will pass the argument directly to the this map's method - /// without any checks. - /// That means that you can do `mapWithStringKeys.cast().remove("a")` - /// successfully, even if it looks like it shouldn't have any effect. - Map? cast() => value?.cast(); - - /// Whether this map contains the given [value]. - /// - /// Returns true if any of the values in the map are equal to `value` - /// according to the `==` operator. - /// ```dart - /// final moonCount = {'Mercury': 0, 'Venus': 0, 'Earth': 1, - /// 'Mars': 2, 'Jupiter': 79, 'Saturn': 82, 'Uranus': 27, 'Neptune': 14 }; - /// final moons3 = moonCount.containsValue(3); // false - /// final moons82 = moonCount.containsValue(82); // true - /// ``` - bool? containsValue(Object? valueToEvaluate) => - value?.containsValue(valueToEvaluate); - - /// Whether this map contains the given [key]. - /// - /// Returns true if any of the keys in the map are equal to `key` - /// according to the equality used by the map. - /// ```dart - /// final moonCount = {'Mercury': 0, 'Venus': 0, 'Earth': 1, - /// 'Mars': 2, 'Jupiter': 79, 'Saturn': 82, 'Uranus': 27, 'Neptune': 14 }; - /// final containsUranus = moonCount.containsKey('Uranus'); // true - /// final containsPluto = moonCount.containsKey('Pluto'); // false - /// ``` - bool? containsKey(Object? key) => value?.containsKey(key); - - /// The value for the given [key], or `null` if [key] is not in the map. - /// - /// Some maps allow `null` as a value. - /// For those maps, a lookup using this operator cannot distinguish between a - /// key not being in the map, and the key being there with a `null` value. - /// Methods like [containsKey] or [putIfAbsent] can be used if the distinction - /// is important. - V? operator [](Object? key) => value?[key]; - - /// Associates the [key] with the given [value]. - /// - /// If the key was already in the map, its associated value is changed. - /// Otherwise the key/value pair is added to the map. - void operator []=(K key, V valueToSet) => value?[key] = valueToSet; - - /// The map entries of [this]. - Iterable>? get entries => value?.entries; - - /// Returns a new map where all entries of this map are transformed by - /// the given [convert] function. - Map? map(MapEntry Function(K key, V value) convert) => - value?.map(convert); - - /// Adds all key/value pairs of [newEntries] to this map. - /// - /// If a key of [newEntries] is already in this map, - /// the corresponding value is overwritten. - /// - /// The operation is equivalent to doing `this[entry.key] = entry.value` - /// for each [MapEntry] of the iterable. - /// ```dart - /// final planets = {1: 'Mercury', 2: 'Venus', - /// 3: 'Earth', 4: 'Mars'}; - /// final gasGiants = {5: 'Jupiter', 6: 'Saturn'}; - /// final iceGiants = {7: 'Uranus', 8: 'Neptune'}; - /// planets.addEntries(gasGiants.entries); - /// planets.addEntries(iceGiants.entries); - /// print(planets); - /// // {1: Mercury, 2: Venus, 3: Earth, 4: Mars, 5: Jupiter, 6: Saturn, - /// // 7: Uranus, 8: Neptune} - /// ``` - void addEntries(Iterable> newEntries) => - value?.addEntries(newEntries); - - /// Updates the value for the provided [key]. - /// - /// Returns the new value associated with the key. - /// - /// If the key is present, invokes [update] with the current value and stores - /// the new value in the map. - /// - /// If the key is not present and [ifAbsent] is provided, calls [ifAbsent] - /// and adds the key with the returned value to the map. - /// - /// If the key is not present, [ifAbsent] must be provided. - /// ```dart - /// final planetsFromSun = {1: 'Mercury', 2: 'unknown', - /// 3: 'Earth'}; - /// // Update value for known key value 2. - /// planetsFromSun.update(2, (value) => 'Venus'); - /// print(planetsFromSun); // {1: Mercury, 2: Venus, 3: Earth} - /// - /// final largestPlanets = {1: 'Jupiter', 2: 'Saturn', - /// 3: 'Neptune'}; - /// // Key value 8 is missing from list, add it using [ifAbsent]. - /// largestPlanets.update(8, (value) => 'New', ifAbsent: () => 'Mercury'); - /// print(largestPlanets); // {1: Jupiter, 2: Saturn, 3: Neptune, 8: Mercury} - /// ``` - V? updateMap(K key, V Function(V value) update, {V Function()? ifAbsent}) => - value?.update(key, update, ifAbsent: ifAbsent); - - /// Updates all values. - /// - /// Iterates over all entries in the map and updates them with the result - /// of invoking [update]. - /// ```dart - /// final terrestrial = {1: 'Mercury', 2: 'Venus', 3: 'Earth'}; - /// terrestrial.updateAll((key, value) => value.toUpperCase()); - /// print(terrestrial); // {1: MERCURY, 2: VENUS, 3: EARTH} - /// ``` - void updateAll(V Function(K key, V value) update) => value?.updateAll(update); - - /// Removes all entries of this map that satisfy the given [test]. - /// ```dart - /// final terrestrial = {1: 'Mercury', 2: 'Venus', 3: 'Earth'}; - /// terrestrial.removeWhere((key, value) => value.startsWith('E')); - /// print(terrestrial); // {1: Mercury, 2: Venus} - /// ``` - void removeWhere(bool Function(K key, V value) test) => - value?.removeWhere(test); - - /// Look up the value of [key], or add a new entry if it isn't there. - /// - /// Returns the value associated to [key], if there is one. - /// Otherwise calls [ifAbsent] to get a new value, associates [key] to - /// that value, and then returns the new value. - /// ```dart - /// final diameters = {1.0: 'Earth'}; - /// final otherDiameters = {0.383: 'Mercury', 0.949: 'Venus'}; - /// - /// for (final item in otherDiameters.entries) { - /// diameters.putIfAbsent(item.key, () => item.value); - /// } - /// print(diameters); // {1.0: Earth, 0.383: Mercury, 0.949: Venus} - /// - /// // If the key already exists, the current value is returned. - /// final result = diameters.putIfAbsent(0.383, () => 'Random'); - /// print(result); // Mercury - /// print(diameters); // {1.0: Earth, 0.383: Mercury, 0.949: Venus} - /// ``` - /// Calling [ifAbsent] must not add or remove keys from the map. - V? putIfAbsent(K key, V Function() ifAbsent) => - value?.putIfAbsent(key, ifAbsent); - - /// Adds all key/value pairs of [other] to this map. - /// - /// If a key of [other] is already in this map, its value is overwritten. - /// - /// The operation is equivalent to doing `this[key] = value` for each key - /// and associated value in other. It iterates over [other], which must - /// therefore not change during the iteration. - /// ```dart - /// final planets = {1: 'Mercury', 2: 'Earth'}; - /// planets.addAll({5: 'Jupiter', 6: 'Saturn'}); - /// print(planets); // {1: Mercury, 2: Earth, 5: Jupiter, 6: Saturn} - /// ``` - void addAll(Map other) => value?.addAll(other); - - /// Removes [key] and its associated value, if present, from the map. - /// - /// Returns the value associated with `key` before it was removed. - /// Returns `null` if `key` was not in the map. - /// - /// Note that some maps allow `null` as a value, - /// so a returned `null` value doesn't always mean that the key was absent. - /// ```dart - /// final terrestrial = {1: 'Mercury', 2: 'Venus', 3: 'Earth'}; - /// final removedValue = terrestrial.remove(2); // Venus - /// print(terrestrial); // {1: Mercury, 3: Earth} - /// ``` - V? remove(Object? key) => value?.remove(key); - - /// Removes all entries from the map. - /// - /// After this, the map is empty. - /// ```dart - /// final planets = {1: 'Mercury', 2: 'Venus', 3: 'Earth'}; - /// planets.clear(); // {} - /// ``` - void clear() => value?.clear(); - - /// Applies [action] to each key/value pair of the map. - /// - /// Calling `action` must not add or remove keys from the map. - /// ```dart - /// final planetsByMass = {0.81: 'Venus', 1: 'Earth', - /// 0.11: 'Mars', 17.15: 'Neptune'}; - /// - /// planetsByMass.forEach((key, value) { - /// print('$key: $value'); - /// // 0.81: Venus - /// // 1: Earth - /// // 0.11: Mars - /// // 17.15: Neptune - /// }); - /// ``` - void forEach(void Function(K key, V value) action) => value?.forEach(action); - - /// The keys of [this]. - /// - /// The returned iterable has efficient `length` and `contains` operations, - /// based on [length] and [containsKey] of the map. - /// - /// The order of iteration is defined by the individual `Map` implementation, - /// but must be consistent between changes to the map. - /// - /// Modifying the map while iterating the keys may break the iteration. - Iterable? get keys => value?.keys; - - /// The values of [this]. - /// - /// The values are iterated in the order of their corresponding keys. - /// This means that iterating [keys] and [values] in parallel will - /// provide matching pairs of keys and values. - /// - /// The returned iterable has an efficient `length` method based on the - /// [length] of the map. Its [Iterable.contains] method is based on - /// `==` comparison. - /// - /// Modifying the map while iterating the values may break the iteration. - Iterable? get values => value?.values; - - /// The number of key/value pairs in the map. - int? get length => value?.length; - - /// Whether there is no key/value pair in the map. - bool? get isEmpty => value?.isEmpty; - - /// Whether there is at least one key/value pair in the map. - bool? get isNotEmpty => value?.isNotEmpty; -} diff --git a/packages/reactter/lib/src/obj/extensions/obj_set.dart b/packages/reactter/lib/src/obj/extensions/obj_set.dart deleted file mode 100644 index 0c02d3b0..00000000 --- a/packages/reactter/lib/src/obj/extensions/obj_set.dart +++ /dev/null @@ -1,422 +0,0 @@ -// coverage:ignore-file -part of '../obj.dart'; - -extension ObjSetExt on Obj> { - /// Provides a view of this set as a set of [R] instances. - /// - /// If this set contains only instances of [R], all read operations - /// will work correctly. If any operation tries to access an element - /// that is not an instance of [R], the access will throw instead. - /// - /// Elements added to the set (e.g., by using [add] or [addAll]) - /// must be instances of [R] to be valid arguments to the adding function, - /// and they must be instances of [E] as well to be accepted by - /// this set as well. - /// - /// Methods which accept one or more `Object?` as argument, - /// like [contains], [remove] and [removeAll], - /// will pass the argument directly to the this set's method - /// without any checks. - /// That means that you can do `setOfStrings.cast().remove("a")` - /// successfully, even if it looks like it shouldn't have any effect. - Set cast() => value.cast(); - - /// An iterator that iterates over the elements of this set. - /// - /// The order of iteration is defined by the individual `Set` implementation, - /// but must be consistent between changes to the set. - Iterator get iterator => value.iterator; - - /// Whether [value] is in the set. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// final containsB = characters.contains('B'); // true - /// final containsD = characters.contains('D'); // false - /// ``` - bool contains(Object? valueToEvaluate) => value.contains(valueToEvaluate); - - /// Adds [value] to the set. - /// - /// Returns `true` if [value] (or an equal value) was not yet in the set. - /// Otherwise returns `false` and the set is not changed. - /// - /// Example: - /// ```dart - /// final dateTimes = {}; - /// final time1 = DateTime.fromMillisecondsSinceEpoch(0); - /// final time2 = DateTime.fromMillisecondsSinceEpoch(0); - /// // time1 and time2 are equal, but not identical. - /// assert(time1 == time2); - /// assert(!identical(time1, time2)); - /// final time1Added = dateTimes.add(time1); - /// print(time1Added); // true - /// // A value equal to time2 exists already in the set, and the call to - /// // add doesn't change the set. - /// final time2Added = dateTimes.add(time2); - /// print(time2Added); // false - /// - /// print(dateTimes); // {1970-01-01 02:00:00.000} - /// assert(dateTimes.length == 1); - /// assert(identical(time1, dateTimes.first)); - /// print(dateTimes.length); - /// ``` - bool add(E valueToAdd) => value.add(valueToAdd); - - /// Adds all [elements] to this set. - /// - /// Equivalent to adding each element in [elements] using [add], - /// but some collections may be able to optimize it. - /// ```dart - /// final characters = {'A', 'B'}; - /// characters.addAll({'A', 'B', 'C'}); - /// print(characters); // {A, B, C} - /// ``` - void addAll(Iterable elements) => value.addAll(elements); - - /// Removes [value] from the set. - /// - /// Returns `true` if [value] was in the set, and `false` if not. - /// The method has no effect if [value] was not in the set. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// final didRemoveB = characters.remove('B'); // true - /// final didRemoveD = characters.remove('D'); // false - /// print(characters); // {A, C} - /// ``` - bool remove(Object? valueToRemove) => value.remove(valueToRemove); - - /// If an object equal to [object] is in the set, return it. - /// - /// Checks whether [object] is in the set, like [contains], and if so, - /// returns the object in the set, otherwise returns `null`. - /// - /// If the equality relation used by the set is not identity, - /// then the returned object may not be *identical* to [object]. - /// Some set implementations may not be able to implement this method. - /// If the [contains] method is computed, - /// rather than being based on an actual object instance, - /// then there may not be a specific object instance representing the - /// set element. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// final containsB = characters.lookup('B'); - /// print(containsB); // B - /// final containsD = characters.lookup('D'); - /// print(containsD); // null - /// ``` - E? lookup(Object? object) => value.lookup(object); - - /// Removes each element of [elements] from this set. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// characters.removeAll({'A', 'B', 'X'}); - /// print(characters); // {C} - /// ``` - void removeAll(Iterable elements) => value.removeAll(elements); - - /// Removes all elements of this set that are not elements in [elements]. - /// - /// Checks for each element of [elements] whether there is an element in this - /// set that is equal to it (according to `this.contains`), and if so, the - /// equal element in this set is retained, and elements that are not equal - /// to any element in [elements] are removed. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// characters.retainAll({'A', 'B', 'X'}); - /// print(characters); // {A, B} - /// ``` - void retainAll(Iterable elements) => value.retainAll(elements); - - /// Removes all elements of this set that satisfy [test]. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// characters.removeWhere((element) => element.startsWith('B')); - /// print(characters); // {A, C} - /// ``` - void removeWhere(bool Function(E element) test) => value.removeWhere(test); - - /// Removes all elements of this set that fail to satisfy [test]. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// characters.retainWhere( - /// (element) => element.startsWith('B') || element.startsWith('C')); - /// print(characters); // {B, C} - /// ``` - void retainWhere(bool Function(E element) test) => value.removeWhere(test); - - /// Whether this set contains all the elements of [other]. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// final containsAB = characters.containsAll({'A', 'B'}); - /// print(containsAB); // true - /// final containsAD = characters.containsAll({'A', 'D'}); - /// print(containsAD); // false - /// ``` - bool containsAll(Iterable other) => value.containsAll(other); - - /// Creates a new set which is the intersection between this set and [other]. - /// - /// That is, the returned set contains all the elements of this [Set] that - /// are also elements of [other] according to `other.contains`. - /// ```dart - /// final characters1 = {'A', 'B', 'C'}; - /// final characters2 = {'A', 'E', 'F'}; - /// final unionSet = characters1.intersection(characters2); - /// print(unionSet); // {A} - /// ``` - Set intersection(Set other) => value.intersection(other); - - /// Creates a new set which contains all the elements of this set and [other]. - /// - /// That is, the returned set contains all the elements of this [Set] and - /// all the elements of [other]. - /// ```dart - /// final characters1 = {'A', 'B', 'C'}; - /// final characters2 = {'A', 'E', 'F'}; - /// final unionSet1 = characters1.union(characters2); - /// print(unionSet1); // {A, B, C, E, F} - /// final unionSet2 = characters2.union(characters1); - /// print(unionSet2); // {A, E, F, B, C} - /// ``` - Set union(Set other) => value.union(other); - - /// Creates a new set with the elements of this that are not in [other]. - /// - /// That is, the returned set contains all the elements of this [Set] that - /// are not elements of [other] according to `other.contains`. - /// ```dart - /// final characters1 = {'A', 'B', 'C'}; - /// final characters2 = {'A', 'E', 'F'}; - /// final differenceSet1 = characters1.difference(characters2); - /// print(differenceSet1); // {B, C} - /// final differenceSet2 = characters2.difference(characters1); - /// print(differenceSet2); // {E, F} - /// ``` - Set difference(Set other) => value.difference(other); - - /// Removes all elements from the set. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// characters.clear(); // {} - /// ``` - void clear() => value.clear(); - - /// Creates a [Set] with the same elements and behavior as this `Set`. - /// - /// The returned set behaves the same as this set - /// with regard to adding and removing elements. - /// It initially contains the same elements. - /// If this set specifies an ordering of the elements, - /// the returned set will have the same order. - Set toSet() => value.toSet(); -} - -extension ObjSetNullExt on Obj?> { - /// Provides a view of this set as a set of [R] instances. - /// - /// If this set contains only instances of [R], all read operations - /// will work correctly. If any operation tries to access an element - /// that is not an instance of [R], the access will throw instead. - /// - /// Elements added to the set (e.g., by using [add] or [addAll]) - /// must be instances of [R] to be valid arguments to the adding function, - /// and they must be instances of [E] as well to be accepted by - /// this set as well. - /// - /// Methods which accept one or more `Object?` as argument, - /// like [contains], [remove] and [removeAll], - /// will pass the argument directly to the this set's method - /// without any checks. - /// That means that you can do `setOfStrings.cast().remove("a")` - /// successfully, even if it looks like it shouldn't have any effect. - Set? cast() => value?.cast(); - - /// An iterator that iterates over the elements of this set. - /// - /// The order of iteration is defined by the individual `Set` implementation, - /// but must be consistent between changes to the set. - Iterator? get iterator => value?.iterator; - - /// Whether [value] is in the set. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// final containsB = characters.contains('B'); // true - /// final containsD = characters.contains('D'); // false - /// ``` - bool? contains(Object? valueToEvaluate) => value?.contains(valueToEvaluate); - - /// Adds [value] to the set. - /// - /// Returns `true` if [value] (or an equal value) was not yet in the set. - /// Otherwise returns `false` and the set is not changed. - /// - /// Example: - /// ```dart - /// final dateTimes = {}; - /// final time1 = DateTime.fromMillisecondsSinceEpoch(0); - /// final time2 = DateTime.fromMillisecondsSinceEpoch(0); - /// // time1 and time2 are equal, but not identical. - /// assert(time1 == time2); - /// assert(!identical(time1, time2)); - /// final time1Added = dateTimes.add(time1); - /// print(time1Added); // true - /// // A value equal to time2 exists already in the set, and the call to - /// // add doesn't change the set. - /// final time2Added = dateTimes.add(time2); - /// print(time2Added); // false - /// - /// print(dateTimes); // {1970-01-01 02:00:00.000} - /// assert(dateTimes.length == 1); - /// assert(identical(time1, dateTimes.first)); - /// print(dateTimes.length); - /// ``` - bool? add(E valueToAdd) => value?.add(valueToAdd); - - /// Adds all [elements] to this set. - /// - /// Equivalent to adding each element in [elements] using [add], - /// but some collections may be able to optimize it. - /// ```dart - /// final characters = {'A', 'B'}; - /// characters.addAll({'A', 'B', 'C'}); - /// print(characters); // {A, B, C} - /// ``` - void addAll(Iterable elements) => value?.addAll(elements); - - /// Removes [value] from the set. - /// - /// Returns `true` if [value] was in the set, and `false` if not. - /// The method has no effect if [value] was not in the set. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// final didRemoveB = characters.remove('B'); // true - /// final didRemoveD = characters.remove('D'); // false - /// print(characters); // {A, C} - /// ``` - bool? remove(Object? valueToRemove) => value?.remove(valueToRemove); - - /// If an object equal to [object] is in the set, return it. - /// - /// Checks whether [object] is in the set, like [contains], and if so, - /// returns the object in the set, otherwise returns `null`. - /// - /// If the equality relation used by the set is not identity, - /// then the returned object may not be *identical* to [object]. - /// Some set implementations may not be able to implement this method. - /// If the [contains] method is computed, - /// rather than being based on an actual object instance, - /// then there may not be a specific object instance representing the - /// set element. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// final containsB = characters.lookup('B'); - /// print(containsB); // B - /// final containsD = characters.lookup('D'); - /// print(containsD); // null - /// ``` - E? lookup(Object? object) => value?.lookup(object); - - /// Removes each element of [elements] from this set. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// characters.removeAll({'A', 'B', 'X'}); - /// print(characters); // {C} - /// ``` - void removeAll(Iterable elements) => value?.removeAll(elements); - - /// Removes all elements of this set that are not elements in [elements]. - /// - /// Checks for each element of [elements] whether there is an element in this - /// set that is equal to it (according to `this.contains`), and if so, the - /// equal element in this set is retained, and elements that are not equal - /// to any element in [elements] are removed. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// characters.retainAll({'A', 'B', 'X'}); - /// print(characters); // {A, B} - /// ``` - void retainAll(Iterable elements) => value?.retainAll(elements); - - /// Removes all elements of this set that satisfy [test]. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// characters.removeWhere((element) => element.startsWith('B')); - /// print(characters); // {A, C} - /// ``` - void removeWhere(bool Function(E element) test) => value?.removeWhere(test); - - /// Removes all elements of this set that fail to satisfy [test]. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// characters.retainWhere( - /// (element) => element.startsWith('B') || element.startsWith('C')); - /// print(characters); // {B, C} - /// ``` - void retainWhere(bool Function(E element) test) => value?.removeWhere(test); - - /// Whether this set contains all the elements of [other]. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// final containsAB = characters.containsAll({'A', 'B'}); - /// print(containsAB); // true - /// final containsAD = characters.containsAll({'A', 'D'}); - /// print(containsAD); // false - /// ``` - bool? containsAll(Iterable other) => value?.containsAll(other); - - /// Creates a new set which is the intersection between this set and [other]. - /// - /// That is, the returned set contains all the elements of this [Set] that - /// are also elements of [other] according to `other.contains`. - /// ```dart - /// final characters1 = {'A', 'B', 'C'}; - /// final characters2 = {'A', 'E', 'F'}; - /// final unionSet = characters1.intersection(characters2); - /// print(unionSet); // {A} - /// ``` - Set? intersection(Set other) => value?.intersection(other); - - /// Creates a new set which contains all the elements of this set and [other]. - /// - /// That is, the returned set contains all the elements of this [Set] and - /// all the elements of [other]. - /// ```dart - /// final characters1 = {'A', 'B', 'C'}; - /// final characters2 = {'A', 'E', 'F'}; - /// final unionSet1 = characters1.union(characters2); - /// print(unionSet1); // {A, B, C, E, F} - /// final unionSet2 = characters2.union(characters1); - /// print(unionSet2); // {A, E, F, B, C} - /// ``` - Set? union(Set other) => value?.union(other); - - /// Creates a new set with the elements of this that are not in [other]. - /// - /// That is, the returned set contains all the elements of this [Set] that - /// are not elements of [other] according to `other.contains`. - /// ```dart - /// final characters1 = {'A', 'B', 'C'}; - /// final characters2 = {'A', 'E', 'F'}; - /// final differenceSet1 = characters1.difference(characters2); - /// print(differenceSet1); // {B, C} - /// final differenceSet2 = characters2.difference(characters1); - /// print(differenceSet2); // {E, F} - /// ``` - Set? difference(Set other) => value?.difference(other); - - /// Removes all elements from the set. - /// ```dart - /// final characters = {'A', 'B', 'C'}; - /// characters.clear(); // {} - /// ``` - void clear() => value?.clear(); - - /// Creates a [Set] with the same elements and behavior as this `Set`. - /// - /// The returned set behaves the same as this set - /// with regard to adding and removing elements. - /// It initially contains the same elements. - /// If this set specifies an ordering of the elements, - /// the returned set will have the same order. - Set? toSet() => value?.toSet(); -} diff --git a/packages/reactter/lib/src/obj/obj.dart b/packages/reactter/lib/src/obj/obj.dart deleted file mode 100644 index dc74a963..00000000 --- a/packages/reactter/lib/src/obj/obj.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:math'; - -part 'obj_impl.dart'; -part 'extensions/obj_bigint.dart'; -part 'extensions/obj_bool.dart'; -part 'extensions/obj_date_time.dart'; -part 'extensions/obj_double.dart'; -part 'extensions/obj_int.dart'; -part 'extensions/obj_iterable.dart'; -part 'extensions/obj_list.dart'; -part 'extensions/obj_map.dart'; -part 'extensions/obj_num.dart'; -part 'extensions/obj_set.dart'; -part 'extensions/obj_string.dart'; diff --git a/packages/reactter/lib/src/obj/obj_impl.dart b/packages/reactter/lib/src/obj/obj_impl.dart deleted file mode 100644 index 556af6ff..00000000 --- a/packages/reactter/lib/src/obj/obj_impl.dart +++ /dev/null @@ -1,97 +0,0 @@ -part of 'obj.dart'; - -/// {@template obj} -/// A base-class that store a value of [T]. -/// -/// You can create a new [Obj]: -/// -/// ```dart -/// // usign the `Obj` class -/// final strObj = Obj("initial value"); -/// final intObj = Obj(0); -/// final userObj = Obj(User()); -/// ``` -/// -/// You can get the [value]: -/// -/// ```dart -/// // usign a `value` getter -/// print(strObj.value); -/// // or using the callable -/// print(strObj()); -/// // or using `toString` implicit -/// print("$intObj"); -/// ``` -/// -/// You can set a new [value]: -/// -/// ```dart -/// // usign a `value` setter -/// strObj.value = "change value"; -/// // or using the callable -/// strObj("change value"); -/// ``` -/// {@endtemplate} -@Deprecated( - 'This feature was deprecated after v7.2.0 and will be removed in v8.0.0.', -) -class Obj with ObjBase { - @override - T value; - - /// {@macro obj} - Obj(this.value); -} - -abstract class ObjBase { - set value(T value); - T get value; - - /// The equality operator. - /// - /// It's checking if the [other] object is of the same type as the [Obj], if it is, it compares the - /// [value], else it compares like [Object] using [hashCode]. - /// - /// Examples: - /// ```dart - /// final obj = Obj(0); - /// final obj2 = Obj(1); - /// final obj3 = obj(0); - /// final objCopy = obj; - /// - /// print(obj == 0); // true - /// print(obj == 1); // false - /// print(obj == obj2); // false - /// print(obj == obj3); // true - /// print(obj == objCopy); // true - /// ``` - /// - @override - bool operator ==(Object other) => - other is Obj ? identical(this, other) : value == other; - - @override - // ignore: unnecessary_overrides - int get hashCode => super.hashCode; - - /// Gets and/or sets to [value] like a function - T call([T? val]) { - if (null is T || val != null) { - value = val as T; - } - - return value; - } - - @override - String toString() => value.toString(); -} - -extension ObjNullExt on Obj { - /// Returns a new `Obj` with the value of the current `Obj` - /// if it's not null. - Obj get notNull { - assert(value != null); - return Obj(value as T); - } -} diff --git a/packages/reactter/lib/src/obj/extensions/obj_bigint.dart b/packages/reactter/lib/src/signal/extensions/signal_bigint.dart similarity index 76% rename from packages/reactter/lib/src/obj/extensions/obj_bigint.dart rename to packages/reactter/lib/src/signal/extensions/signal_bigint.dart index d9da49d7..feda689b 100644 --- a/packages/reactter/lib/src/obj/extensions/obj_bigint.dart +++ b/packages/reactter/lib/src/signal/extensions/signal_bigint.dart @@ -1,27 +1,27 @@ // coverage:ignore-file -part of '../obj.dart'; +part of '../signal.dart'; -extension ObjBigIntExt on Obj { +extension SignalBigIntExt on Signal { /// Return the negative value of this integer. /// /// The result of negating an integer always has the opposite sign, except /// for zero, which is its own negation. - Obj operator -() => Obj(-value); + BigInt operator -() => -value; /// Adds [other] to this big integer. /// /// The result is again a big integer. - Obj operator +(Obj other) => Obj(value + other.value); + BigInt operator +(BigInt other) => value + other; /// Subtracts [other] from this big integer. /// /// The result is again a big integer. - Obj operator -(Obj other) => Obj(value - other.value); + BigInt operator -(BigInt other) => value - other; /// Multiplies [other] by this big integer. /// /// The result is again a big integer. - Obj operator *(Obj other) => Obj(value * other.value); + BigInt operator *(BigInt other) => value * other; /// Double division operator. /// @@ -34,10 +34,10 @@ extension ObjBigIntExt on Obj { /// /// Example: /// ```dart - /// print(Obj(BigInt.from(1)) / Obj(BigInt.from(2))); // Obj(0.5) - /// print(Obj(BigInt.from(1.99999)) / Obj(BigInt.from(2))); // Obj(0.5) + /// print(Signal(BigInt.from(1)) / BigInt.from(2)); // 0.5 + /// print(Signal(BigInt.from(1.99999)) / BigInt.from(2)); // 0.5 /// ``` - Obj operator /(Obj other) => Obj(value / other.value); + double operator /(BigInt other) => value / other; /// Truncating integer division operator. /// @@ -47,8 +47,8 @@ extension ObjBigIntExt on Obj { /// /// Examples: /// ```dart - /// var seven = Obj(BigInt.from(7)); - /// var three = Obj(BigInt.from(3)); + /// var seven = Signal(BigInt.from(7)); + /// var three = BigInt.from(3); /// seven ~/ three; // => 2 /// (-seven) ~/ three; // => -2 /// seven ~/ -three; // => -2 @@ -56,7 +56,7 @@ extension ObjBigIntExt on Obj { /// (-seven).remainder(three); // => -1 /// seven.remainder(-three); // => 1 /// ``` - Obj operator ~/(Obj other) => Obj(value ~/ other.value); + BigInt operator ~/(BigInt other) => value ~/ other; /// Euclidean modulo operator. /// @@ -70,12 +70,12 @@ extension ObjBigIntExt on Obj { /// /// Example: /// ```dart - /// print(Obj(BigInt.from(5)) % Obj(BigInt.from(3))); // Obj(2) - /// print(Obj(BigInt.from(-)5) % Obj(BigInt.from(3))); // Obj(1) - /// print(Obj(BigInt.from(5)) % Obj(BigInt.from(-)3)); // Obj(2) - /// print(Obj(BigInt.from(-)5) % Obj(BigInt.from(-)3)); // Obj(1) + /// print(Signal(BigInt.from(5)) % BigInt.from(3)); // 2 + /// print(Signal(BigInt.from(-5)) % BigInt.from(3)); // 1 + /// print(Signal(BigInt.from(5)) % BigInt.from(-3)); // 2 + /// print(Signal(BigInt.from(-5)) % BigInt.from(-3)); // 1 /// ``` - Obj operator %(Obj other) => Obj(value % other.value); + BigInt operator %(BigInt other) => value % other; /// Shift the bits of this integer to the left by [shiftAmount]. /// @@ -87,8 +87,7 @@ extension ObjBigIntExt on Obj { /// mask. /// /// It is an error if [shiftAmount] is negative. - Obj operator <<(Obj shiftAmount) => - Obj(value << shiftAmount.value); + BigInt operator <<(int shiftAmount) => value << shiftAmount; /// Shift the bits of this integer to the right by [shiftAmount]. /// @@ -97,8 +96,7 @@ extension ObjBigIntExt on Obj { ///`pow(2, shiftIndex)`. /// /// It is an error if [shiftAmount] is negative. - Obj operator >>(Obj shiftAmount) => - Obj(value >> shiftAmount.value); + BigInt operator >>(int shiftAmount) => value >> shiftAmount; /// Bit-wise and operator. /// @@ -108,7 +106,7 @@ extension ObjBigIntExt on Obj { /// /// Of both operands are negative, the result is negative, otherwise /// the result is non-negative. - Obj operator &(Obj other) => Obj(value & other.value); + BigInt operator &(BigInt other) => value & other; /// Bit-wise or operator. /// @@ -118,7 +116,7 @@ extension ObjBigIntExt on Obj { /// /// If both operands are non-negative, the result is non-negative, /// otherwise the result is negative. - Obj operator |(Obj other) => Obj(value | other.value); + BigInt operator |(BigInt other) => value | other; /// Bit-wise exclusive-or operator. /// @@ -128,7 +126,7 @@ extension ObjBigIntExt on Obj { /// /// If the operands have the same sign, the result is non-negative, /// otherwise the result is negative. - Obj operator ^(Obj other) => Obj(value ^ other.value); + BigInt operator ^(BigInt other) => value ^ other; /// The bit-wise negate operator. /// @@ -136,19 +134,19 @@ extension ObjBigIntExt on Obj { /// the result is a number with the opposite bits set. /// /// This maps any integer `x` to `-x - 1`. - Obj operator ~() => Obj(~value); + BigInt operator ~() => ~value; /// Whether this big integer is numerically smaller than [other]. - bool operator <(Obj other) => value < other.value; + bool operator <(BigInt other) => value < other; /// Whether [other] is numerically greater than this big integer. - bool operator <=(Obj other) => value <= other.value; + bool operator <=(BigInt other) => value <= other; /// Whether this big integer is numerically greater than [other]. - bool operator >(Obj other) => value > other.value; + bool operator >(BigInt other) => value > other; /// Whether [other] is numerically smaller than this big integer. - bool operator >=(Obj other) => value >= other.value; + bool operator >=(BigInt other) => value >= other; /// Returns the absolute value of this integer. /// @@ -163,10 +161,10 @@ extension ObjBigIntExt on Obj { /// /// Example: /// ```dart - /// print(Obj(BigInt.from(5)).remainder(BigInt.from(3))); // 2 - /// print(Obj(BigInt.from(-5)).remainder(BigInt.from(3))); // -2 - /// print(Obj(BigInt.from(5)).remainder(BigInt.from(-3))); // 2 - /// print(Obj(BigInt.from(-5)).remainder(BigInt.from(-3))); // -2 + /// print(Signal(BigInt.from(5)).remainder(BigInt.from(3))); // 2 + /// print(Signal(BigInt.from(-5)).remainder(BigInt.from(3))); // -2 + /// print(Signal(BigInt.from(5)).remainder(BigInt.from(-3))); // 2 + /// print(Signal(BigInt.from(-5)).remainder(BigInt.from(-3))); // -2 /// ``` BigInt remainder(BigInt other) => value.remainder(other); @@ -177,9 +175,9 @@ extension ObjBigIntExt on Obj { /// /// Example: /// ```dart - /// print(Obj(BigInt.from(1)).compareTo(BigInt.from(2))); // => -1 - /// print(Obj(BigInt.from(2)).compareTo(BigInt.from(1))); // => 1 - /// print(Obj(BigInt.from(1)).compareTo(BigInt.from(1))); // => 0 + /// print(Signal(BigInt.from(1)).compareTo(BigInt.from(2))); // => -1 + /// print(Signal(BigInt.from(2)).compareTo(BigInt.from(1))); // => 1 + /// print(Signal(BigInt.from(1)).compareTo(BigInt.from(1))); // => 0 /// ``` int compareTo(BigInt other) => value.compareTo(other); @@ -195,14 +193,14 @@ extension ObjBigIntExt on Obj { /// ```dart /// x.bitLength == (-x-1).bitLength; /// - /// Obj(BigInt.from(3)).bitLength == 2; // 00000011 - /// Obj(BigInt.from(2)).bitLength == 2; // 00000010 - /// Obj(BigInt.from(1)).bitLength == 1; // 00000001 - /// Obj(BigInt.from(0)).bitLength == 0; // 00000000 - /// Obj(BigInt.from(-1)).bitLength == 0; // 11111111 - /// Obj(BigInt.from(-2)).bitLength == 1; // 11111110 - /// Obj(BigInt.from(-3)).bitLength == 2; // 11111101 - /// Obj(BigInt.from(-4)).bitLength == 2; // 11111100 + /// Signal(BigInt.from(3)).bitLength == 2; // 00000011 + /// Signal(BigInt.from(2)).bitLength == 2; // 00000010 + /// Signal(BigInt.from(1)).bitLength == 1; // 00000001 + /// Signal(BigInt.from(0)).bitLength == 0; // 00000000 + /// Signal(BigInt.from(-1)).bitLength == 0; // 11111111 + /// Signal(BigInt.from(-2)).bitLength == 1; // 11111110 + /// Signal(BigInt.from(-3)).bitLength == 2; // 11111101 + /// Signal(BigInt.from(-4)).bitLength == 2; // 11111100 /// ``` int get bitLength => value.bitLength; @@ -232,7 +230,7 @@ extension ObjBigIntExt on Obj { /// /// Example: /// ```dart - /// var value = Obj(BigInt.from(1000)); + /// var value = Signal(BigInt.from(1000)); /// print(value.pow(0)); // 1 /// print(value.pow(1)); // 1000 /// print(value.pow(2)); // 1000000 @@ -275,11 +273,11 @@ extension ObjBigIntExt on Obj { /// /// Example: /// ```dart - /// print(Obj(BigInt.from(4)).gcd(BigInt.from(2))); // 2 - /// print(Obj(BigInt.from(8)).gcd(BigInt.from(4))); // 4 - /// print(Obj(BigInt.from(10)).gcd(BigInt.from(12))); // 2 - /// print(Obj(BigInt.from(10)).gcd(BigInt.from(10))); // 10 - /// print(Obj(BigInt.from(-2)).gcd(BigInt.from(-3))); // 1 + /// print(Signal(BigInt.from(4)).gcd(BigInt.from(2))); // 2 + /// print(Signal(BigInt.from(8)).gcd(BigInt.from(4))); // 4 + /// print(Signal(BigInt.from(10)).gcd(BigInt.from(12))); // 2 + /// print(Signal(BigInt.from(10)).gcd(BigInt.from(10))); // 10 + /// print(Signal(BigInt.from(-2)).gcd(BigInt.from(-3))); // 1 /// ``` BigInt gcd(BigInt other) => value.gcd(other); @@ -288,7 +286,7 @@ extension ObjBigIntExt on Obj { /// zeros in all bit positions higher than [width]. /// /// ```dart - /// Obj(BigInt.from(-1)).toUnsigned(5) == 31 // 11111111 -> 00011111 + /// Signal(BigInt.from(-1)).toUnsigned(5) == 31 // 11111111 -> 00011111 /// ``` /// /// This operation can be used to simulate arithmetic from low level languages. @@ -315,9 +313,9 @@ extension ObjBigIntExt on Obj { /// returned value has the same bit value in all positions higher than [width]. /// /// ```dart - /// var big15 = Obj(BigInt.from(15)); - /// var big16 = Obj(BigInt.from(16)); - /// var big239 = Obj(BigInt.from(239)); + /// var big15 = Signal(BigInt.from(15)); + /// var big16 = Signal(BigInt.from(16)); + /// var big239 = Signal(BigInt.from(239)); /// // V--sign bit-V /// big16.toSigned(5) == -big16; // 00010000 -> 11110000 /// big239.toSigned(5) == big15; // 11101111 -> 00001111 @@ -399,25 +397,25 @@ extension ObjBigIntExt on Obj { /// Example: /// ```dart /// // Binary (base 2). - /// print(Obj(BigInt.from(12)).toRadixString(2)); // 1100 - /// print(Obj(BigInt.from(31)).toRadixString(2)); // 11111 - /// print(Obj(BigInt.from(2021)).toRadixString(2)); // 11111100101 - /// print(Obj(BigInt.from(-)12).toRadixString(2)); // -1100 + /// print(Signal(BigInt.from(12)).toRadixString(2)); // 1100 + /// print(Signal(BigInt.from(31)).toRadixString(2)); // 11111 + /// print(Signal(BigInt.from(2021)).toRadixString(2)); // 11111100101 + /// print(Signal(BigInt.from(-)12).toRadixString(2)); // -1100 /// // Octal (base 8). - /// print(Obj(BigInt.from(12)).toRadixString(8)); // 14 - /// print(Obj(BigInt.from(31)).toRadixString(8)); // 37 - /// print(Obj(BigInt.from(2021)).toRadixString(8)); // 3745 + /// print(Signal(BigInt.from(12)).toRadixString(8)); // 14 + /// print(Signal(BigInt.from(31)).toRadixString(8)); // 37 + /// print(Signal(BigInt.from(2021)).toRadixString(8)); // 3745 /// // Hexadecimal (base 16). - /// print(Obj(BigInt.from(12)).toRadixString(16)); // c - /// print(Obj(BigInt.from(31)).toRadixString(16)); // 1f - /// print(Obj(BigInt.from(2021)).toRadixString(16)); // 7e5 + /// print(Signal(BigInt.from(12)).toRadixString(16)); // c + /// print(Signal(BigInt.from(31)).toRadixString(16)); // 1f + /// print(Signal(BigInt.from(2021)).toRadixString(16)); // 7e5 /// // Base 36. - /// print(Obj(BigInt.from(35 * 36 + 1)).toRadixString(36)); // z1 + /// print(Signal(BigInt.from(35 * 36 + 1)).toRadixString(36)); // z1 /// ``` String toRadixString(int radix) => value.toRadixString(radix); } -extension ObjBigIntNullExt on Obj { +extension SignalBigIntNullExt on Signal { /// Returns the absolute value of this integer. /// /// For any integer `x`, the result is the same as `x < 0 ? -x : x`. @@ -431,10 +429,10 @@ extension ObjBigIntNullExt on Obj { /// /// Example: /// ```dart - /// print(Obj(BigInt.from(5)).remainder(BigInt.from(3))); // 2 - /// print(Obj(BigInt.from(-5)).remainder(BigInt.from(3))); // -2 - /// print(Obj(BigInt.from(5)).remainder(BigInt.from(-3))); // 2 - /// print(Obj(BigInt.from(-5)).remainder(BigInt.from(-3))); // -2 + /// print(Signal(BigInt.from(5)).remainder(BigInt.from(3))); // 2 + /// print(Signal(BigInt.from(-5)).remainder(BigInt.from(3))); // -2 + /// print(Signal(BigInt.from(5)).remainder(BigInt.from(-3))); // 2 + /// print(Signal(BigInt.from(-5)).remainder(BigInt.from(-3))); // -2 /// ``` BigInt? remainder(BigInt other) => value?.remainder(other); @@ -445,9 +443,9 @@ extension ObjBigIntNullExt on Obj { /// /// Example: /// ```dart - /// print(Obj(BigInt.from(1)).compareTo(BigInt.from(2))); // => -1 - /// print(Obj(BigInt.from(2)).compareTo(BigInt.from(1))); // => 1 - /// print(Obj(BigInt.from(1)).compareTo(BigInt.from(1))); // => 0 + /// print(Signal(BigInt.from(1)).compareTo(BigInt.from(2))); // => -1 + /// print(Signal(BigInt.from(2)).compareTo(BigInt.from(1))); // => 1 + /// print(Signal(BigInt.from(1)).compareTo(BigInt.from(1))); // => 0 /// ``` int? compareTo(BigInt other) => value?.compareTo(other); @@ -463,14 +461,14 @@ extension ObjBigIntNullExt on Obj { /// ```dart /// x.bitLength == (-x-1).bitLength; /// - /// Obj(BigInt.from(3)).bitLength == 2; // 00000011 - /// Obj(BigInt.from(2)).bitLength == 2; // 00000010 - /// Obj(BigInt.from(1)).bitLength == 1; // 00000001 - /// Obj(BigInt.from(0)).bitLength == 0; // 00000000 - /// Obj(BigInt.from(-1)).bitLength == 0; // 11111111 - /// Obj(BigInt.from(-2)).bitLength == 1; // 11111110 - /// Obj(BigInt.from(-3)).bitLength == 2; // 11111101 - /// Obj(BigInt.from(-4)).bitLength == 2; // 11111100 + /// Signal(BigInt.from(3)).bitLength == 2; // 00000011 + /// Signal(BigInt.from(2)).bitLength == 2; // 00000010 + /// Signal(BigInt.from(1)).bitLength == 1; // 00000001 + /// Signal(BigInt.from(0)).bitLength == 0; // 00000000 + /// Signal(BigInt.from(-1)).bitLength == 0; // 11111111 + /// Signal(BigInt.from(-2)).bitLength == 1; // 11111110 + /// Signal(BigInt.from(-3)).bitLength == 2; // 11111101 + /// Signal(BigInt.from(-4)).bitLength == 2; // 11111100 /// ``` int? get bitLength => value?.bitLength; @@ -500,7 +498,7 @@ extension ObjBigIntNullExt on Obj { /// /// Example: /// ```dart - /// var value = Obj(BigInt.from(1000)); + /// var value = Signal(BigInt.from(1000)); /// print(value.pow(0)); // 1 /// print(value.pow(1)); // 1000 /// print(value.pow(2)); // 1000000 @@ -543,11 +541,11 @@ extension ObjBigIntNullExt on Obj { /// /// Example: /// ```dart - /// print(Obj(BigInt.from(4)).gcd(BigInt.from(2))); // 2 - /// print(Obj(BigInt.from(8)).gcd(BigInt.from(4))); // 4 - /// print(Obj(BigInt.from(10)).gcd(BigInt.from(12))); // 2 - /// print(Obj(BigInt.from(10)).gcd(BigInt.from(10))); // 10 - /// print(Obj(BigInt.from(-2)).gcd(BigInt.from(-3))); // 1 + /// print(Signal(BigInt.from(4)).gcd(BigInt.from(2))); // 2 + /// print(Signal(BigInt.from(8)).gcd(BigInt.from(4))); // 4 + /// print(Signal(BigInt.from(10)).gcd(BigInt.from(12))); // 2 + /// print(Signal(BigInt.from(10)).gcd(BigInt.from(10))); // 10 + /// print(Signal(BigInt.from(-2)).gcd(BigInt.from(-3))); // 1 /// ``` BigInt? gcd(BigInt other) => value?.gcd(other); @@ -556,7 +554,7 @@ extension ObjBigIntNullExt on Obj { /// zeros in all bit positions higher than [width]. /// /// ```dart - /// Obj(BigInt.from(-1)).toUnsigned(5) == 31 // 11111111 -> 00011111 + /// Signal(BigInt.from(-1)).toUnsigned(5) == 31 // 11111111 -> 00011111 /// ``` /// /// This operation can be used to simulate arithmetic from low level languages. @@ -583,9 +581,9 @@ extension ObjBigIntNullExt on Obj { /// returned value has the same bit value in all positions higher than [width]. /// /// ```dart - /// var big15 = Obj(BigInt.from(15)); - /// var big16 = Obj(BigInt.from(16)); - /// var big239 = Obj(BigInt.from(239)); + /// var big15 = Signal(BigInt.from(15)); + /// var big16 = Signal(BigInt.from(16)); + /// var big239 = Signal(BigInt.from(239)); /// // V--sign bit-V /// big16.toSigned(5) == -big16; // 00010000 -> 11110000 /// big239.toSigned(5) == big15; // 11101111 -> 00001111 @@ -667,20 +665,20 @@ extension ObjBigIntNullExt on Obj { /// Example: /// ```dart /// // Binary (base 2). - /// print(Obj(BigInt.from(12)).toRadixString(2)); // 1100 - /// print(Obj(BigInt.from(31)).toRadixString(2)); // 11111 - /// print(Obj(BigInt.from(2021)).toRadixString(2)); // 11111100101 - /// print(Obj(BigInt.from(-12)).toRadixString(2)); // -1100 + /// print(Signal(BigInt.from(12)).toRadixString(2)); // 1100 + /// print(Signal(BigInt.from(31)).toRadixString(2)); // 11111 + /// print(Signal(BigInt.from(2021)).toRadixString(2)); // 11111100101 + /// print(Signal(BigInt.from(-12)).toRadixString(2)); // -1100 /// // Octal (base 8). - /// print(Obj(BigInt.from(12)).toRadixString(8)); // 14 - /// print(Obj(BigInt.from(31)).toRadixString(8)); // 37 - /// print(Obj(BigInt.from(2021)).toRadixString(8)); // 3745 + /// print(Signal(BigInt.from(12)).toRadixString(8)); // 14 + /// print(Signal(BigInt.from(31)).toRadixString(8)); // 37 + /// print(Signal(BigInt.from(2021)).toRadixString(8)); // 3745 /// // Hexadecimal (base 16). - /// print(Obj(BigInt.from(12)).toRadixString(16)); // c - /// print(Obj(BigInt.from(31)).toRadixString(16)); // 1f - /// print(Obj(BigInt.from(2021)).toRadixString(16)); // 7e5 + /// print(Signal(BigInt.from(12)).toRadixString(16)); // c + /// print(Signal(BigInt.from(31)).toRadixString(16)); // 1f + /// print(Signal(BigInt.from(2021)).toRadixString(16)); // 7e5 /// // Base 36. - /// print(Obj(BigInt.from(35 * 36 + 1)).toRadixString(36)); // z1 + /// print(Signal(BigInt.from(35 * 36 + 1)).toRadixString(36)); // z1 /// ``` String? toRadixString(int radix) => value?.toRadixString(radix); } diff --git a/packages/reactter/lib/src/obj/extensions/obj_bool.dart b/packages/reactter/lib/src/signal/extensions/signal_bool.dart similarity index 64% rename from packages/reactter/lib/src/obj/extensions/obj_bool.dart rename to packages/reactter/lib/src/signal/extensions/signal_bool.dart index 03f2eafd..cbed62ac 100644 --- a/packages/reactter/lib/src/obj/extensions/obj_bool.dart +++ b/packages/reactter/lib/src/signal/extensions/signal_bool.dart @@ -1,19 +1,19 @@ // coverage:ignore-file -part of '../obj.dart'; +part of '../signal.dart'; -extension ObjBoolExt on Obj { +extension SignalBoolExt on Signal { /// The logical conjunction ("and") of this and [other]. /// /// Returns `true` if both this and [other] are `true`, and `false` otherwise. - Obj operator &(Obj other) => Obj(value && other.value); + bool operator &(bool other) => value && other; /// The logical disjunction ("inclusive or") of this and [other]. /// /// Returns `true` if either this or [other] is `true`, and `false` otherwise. - Obj operator |(Obj other) => Obj(value || other.value); + bool operator |(bool other) => value || other; /// The logical exclusive disjunction ("exclusive or") of this and [other]. /// /// Returns whether this and [other] are neither both `true` nor both `false`. - Obj operator ^(Obj other) => Obj(value ^ other.value); + bool operator ^(bool other) => value ^ other; } diff --git a/packages/reactter/lib/src/obj/extensions/obj_date_time.dart b/packages/reactter/lib/src/signal/extensions/signal_date_time.dart similarity index 99% rename from packages/reactter/lib/src/obj/extensions/obj_date_time.dart rename to packages/reactter/lib/src/signal/extensions/signal_date_time.dart index f6812b4f..3727277f 100644 --- a/packages/reactter/lib/src/obj/extensions/obj_date_time.dart +++ b/packages/reactter/lib/src/signal/extensions/signal_date_time.dart @@ -1,7 +1,7 @@ // coverage:ignore-file -part of '../obj.dart'; +part of '../signal.dart'; -extension ObjDateTimeExt on Obj { +extension SignalDateTimeExt on Signal { /// Returns true if [this] occurs before [other]. /// /// The comparison is independent @@ -62,7 +62,7 @@ extension ObjDateTimeExt on Obj { /// ``` bool isAtSameMomentAs(DateTime other) => value.isAtSameMomentAs(other); - /// Compares this DateTime object to [other], + /// Compares this DateTime Object to [other], /// returning zero if the values are equal. /// /// A [compareTo] function returns: @@ -332,7 +332,7 @@ extension ObjDateTimeExt on Obj { int get weekday => value.weekday; } -extension ObjDateTimeNullExt on Obj { +extension SignalDateTimeNullExt on Signal { /// Returns true if [this] occurs before [other]. /// /// The comparison is independent @@ -393,7 +393,7 @@ extension ObjDateTimeNullExt on Obj { /// ``` bool? isAtSameMomentAs(DateTime other) => value?.isAtSameMomentAs(other); - /// Compares this DateTime object to [other], + /// Compares this DateTime Object to [other], /// returning zero if the values are equal. /// /// A [compareTo] function returns: diff --git a/packages/reactter/lib/src/obj/extensions/obj_double.dart b/packages/reactter/lib/src/signal/extensions/signal_double.dart similarity index 58% rename from packages/reactter/lib/src/obj/extensions/obj_double.dart rename to packages/reactter/lib/src/signal/extensions/signal_double.dart index 4a060856..087ad353 100644 --- a/packages/reactter/lib/src/obj/extensions/obj_double.dart +++ b/packages/reactter/lib/src/signal/extensions/signal_double.dart @@ -1,20 +1,20 @@ // coverage:ignore-file -part of '../obj.dart'; +part of '../signal.dart'; -extension ObjDoubleExt on Obj { - Obj operator +(Obj other) => Obj(value + other.value); +extension SignalDoubleExt on Signal { + double operator +(num other) => value + other; - Obj operator -(Obj other) => Obj(value - other.value); + double operator -(num other) => value - other; - Obj operator *(Obj other) => Obj(value * other.value); + double operator *(num other) => value * other; - Obj operator %(Obj other) => Obj(value % other.value); + double operator %(num other) => value % other; - Obj operator /(Obj other) => Obj(value / other.value); + double operator /(num other) => value / other; - Obj operator ~/(Obj other) => Obj(value ~/ other.value); + int operator ~/(num other) => value ~/ other; - Obj operator -() => Obj(-value); + double operator -() => -value; double remainder(num other) => value.remainder(other); @@ -36,16 +36,16 @@ extension ObjDoubleExt on Obj { /// Returns the integer closest to this number. /// /// Rounds away from zero when there is no closest integer: - /// `Obj(3.5).round() == 4` and `Obj(-3.5).round() == -4`. + /// `Signal(3.5).round() == 4` and `Signal(-3.5).round() == -4`. /// /// Throws an [UnsupportedError] if this number is not finite /// (NaN or an infinity). /// ```dart - /// print(Obj(3.0).round()); // 3 - /// print(Obj(3.25).round()); // 3 - /// print(Obj(3.5).round()); // 4 - /// print(Obj(3.75).round()); // 4 - /// print(Obj(-3.5).round()); // -4 + /// print(Signal(3.0).round()); // 3 + /// print(Signal(3.25).round()); // 3 + /// print(Signal(3.5).round()); // 4 + /// print(Signal(3.75).round()); // 4 + /// print(Signal(-3.5).round()); // -4 /// ``` int round() => value.round(); @@ -56,12 +56,12 @@ extension ObjDoubleExt on Obj { /// Throws an [UnsupportedError] if this number is not finite /// (NaN or infinity). /// ```dart - /// print(Obj(1.99999).floor()); // 1 - /// print(Obj(2.0).floor()); // 2 - /// print(Obj(2.99999).floor()); // 2 - /// print(Obj(-1.99999).floor()); // -2 - /// print(Obj(-2.0).floor()); // -2 - /// print(Obj(-2.00001).floor()); // -3 + /// print(Signal(1.99999).floor()); // 1 + /// print(Signal(2.0).floor()); // 2 + /// print(Signal(2.99999).floor()); // 2 + /// print(Signal(-1.99999).floor()); // -2 + /// print(Signal(-2.0).floor()); // -2 + /// print(Signal(-2.00001).floor()); // -3 /// ``` int floor() => value.floor(); @@ -72,12 +72,12 @@ extension ObjDoubleExt on Obj { /// Throws an [UnsupportedError] if this number is not finite /// (NaN or an infinity). /// ```dart - /// print(Obj(1.99999).ceil()); // 2 - /// print(Obj(2.0).ceil()); // 2 - /// print(Obj(2.00001).ceil()); // 3 - /// print(Obj(-1.99999).ceil()); // -1 - /// print(Obj(-2.0).ceil()); // -2 - /// print(Obj(-2.00001).ceil()); // -2 + /// print(Signal(1.99999).ceil()); // 2 + /// print(Signal(2.0).ceil()); // 2 + /// print(Signal(2.00001).ceil()); // 3 + /// print(Signal(-1.99999).ceil()); // -1 + /// print(Signal(-2.0).ceil()); // -2 + /// print(Signal(-2.00001).ceil()); // -2 /// ``` int ceil() => value.ceil(); @@ -89,19 +89,19 @@ extension ObjDoubleExt on Obj { /// Throws an [UnsupportedError] if this number is not finite /// (NaN or an infinity). /// ```dart - /// print(Obj(2.00001).truncate()); // 2 - /// print(Obj(1.99999).truncate()); // 1 - /// print(Obj(0.5).truncate()); // 0 - /// print(Obj(-0.5).truncate()); // 0 - /// print(Obj(-1.5).truncate()); // -1 - /// print(Obj(-2.5).truncate()); // -2 + /// print(Signal(2.00001).truncate()); // 2 + /// print(Signal(1.99999).truncate()); // 1 + /// print(Signal(0.5).truncate()); // 0 + /// print(Signal(-0.5).truncate()); // 0 + /// print(Signal(-1.5).truncate()); // -1 + /// print(Signal(-2.5).truncate()); // -2 /// ``` int truncate() => value.truncate(); /// Returns the integer double value closest to `this`. /// /// Rounds away from zero when there is no closest integer: - /// `Obj(3.5).roundToDouble() == 4` and `Obj(-3.5).roundToDouble() == -4`. + /// `Signal(3.5).roundToDouble() == 4` and `Signal(-3.5).roundToDouble() == -4`. /// /// If this is already an integer valued double, including `-0.0`, or it is not /// a finite value, the value is returned unmodified. @@ -111,11 +111,11 @@ extension ObjDoubleExt on Obj { /// This means that for a value `d` in the range `-0.5 < d < 0.0`, /// the result is `-0.0`. /// ```dart - /// print(Obj(3.0).roundToDouble()); // 3.0 - /// print(Obj(3.25).roundToDouble()); // 3.0 - /// print(Obj(3.5).roundToDouble()); // 4.0 - /// print(Obj(3.75).roundToDouble()); // 4.0 - /// print(Obj(-3.5).roundToDouble()); // -4.0 + /// print(Signal(3.0).roundToDouble()); // 3.0 + /// print(Signal(3.25).roundToDouble()); // 3.0 + /// print(Signal(3.5).roundToDouble()); // 4.0 + /// print(Signal(3.75).roundToDouble()); // 4.0 + /// print(Signal(-3.5).roundToDouble()); // -4.0 /// ``` double roundToDouble() => value.roundToDouble(); @@ -127,12 +127,12 @@ extension ObjDoubleExt on Obj { /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `0.0 < d < 1.0` will return `0.0`. /// ```dart - /// print(Obj(1.99999).floorToDouble()); // 1.0 - /// print(Obj(2.0).floorToDouble()); // 2.0 - /// print(Obj(2.99999).floorToDouble()); // 2.0 - /// print(Obj(-1.99999).floorToDouble()); // -2.0 - /// print(Obj(-2.0).floorToDouble()); // -2.0 - /// print(Obj(-2.00001).floorToDouble()); // -3.0 + /// print(Signal(1.99999).floorToDouble()); // 1.0 + /// print(Signal(2.0).floorToDouble()); // 2.0 + /// print(Signal(2.99999).floorToDouble()); // 2.0 + /// print(Signal(-1.99999).floorToDouble()); // -2.0 + /// print(Signal(-2.0).floorToDouble()); // -2.0 + /// print(Signal(-2.00001).floorToDouble()); // -3.0 /// ``` double floorToDouble() => value.floorToDouble(); @@ -144,12 +144,12 @@ extension ObjDoubleExt on Obj { /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `-1.0 < d < 0.0` will return `-0.0`. /// ```dart - /// print(Obj(1.99999).ceilToDouble()); // 2.0 - /// print(Obj(2.0).ceilToDouble()); // 2.0 - /// print(Obj(2.00001).ceilToDouble()); // 3.0 - /// print(Obj(-1.99999).ceilToDouble()); // -1.0 - /// print(Obj(-2.0).ceilToDouble()); // -2.0 - /// print(Obj(-2.00001).ceilToDouble()); // -2.0 + /// print(Signal(1.99999).ceilToDouble()); // 2.0 + /// print(Signal(2.0).ceilToDouble()); // 2.0 + /// print(Signal(2.00001).ceilToDouble()); // 3.0 + /// print(Signal(-1.99999).ceilToDouble()); // -1.0 + /// print(Signal(-2.0).ceilToDouble()); // -2.0 + /// print(Signal(-2.00001).ceilToDouble()); // -2.0 /// ``` double ceilToDouble() => value.ceilToDouble(); @@ -163,18 +163,18 @@ extension ObjDoubleExt on Obj { /// A number `d` in the range `-1.0 < d < 0.0` will return `-0.0`, and /// in the range `0.0 < d < 1.0` it will return 0.0. /// ```dart - /// print(Obj(2.5).truncateToDouble()); // 2.0 - /// print(Obj(2.00001).truncateToDouble()); // 2.0 - /// print(Obj(1.99999).truncateToDouble()); // 1.0 - /// print(Obj(0.5).truncateToDouble()); // 0.0 - /// print(Obj(-0.5).truncateToDouble()); // -0.0 - /// print(Obj(-1.5).truncateToDouble()); // -1.0 - /// print(Obj(-2.5).truncateToDouble()); // -2.0 + /// print(Signal(2.5).truncateToDouble()); // 2.0 + /// print(Signal(2.00001).truncateToDouble()); // 2.0 + /// print(Signal(1.99999).truncateToDouble()); // 1.0 + /// print(Signal(0.5).truncateToDouble()); // 0.0 + /// print(Signal(-0.5).truncateToDouble()); // -0.0 + /// print(Signal(-1.5).truncateToDouble()); // -1.0 + /// print(Signal(-2.5).truncateToDouble()); // -2.0 /// ``` double truncateToDouble() => value.truncateToDouble(); } -extension ObjDoubleNullExt on Obj { +extension SignalDoubleNullExt on Signal { double? remainder(num other) => value?.remainder(other); /// Returns the absolute value of this integer. @@ -195,16 +195,16 @@ extension ObjDoubleNullExt on Obj { /// Returns the integer closest to this number. /// /// Rounds away from zero when there is no closest integer: - /// `Obj(3.5).round() == 4` and `Obj(-3.5).round() == -4`. + /// `Signal(3.5).round() == 4` and `Signal(-3.5).round() == -4`. /// /// Throws an [UnsupportedError] if this number is not finite /// (NaN or an infinity). /// ```dart - /// print(Obj(3.0).round()); // 3 - /// print(Obj(3.25).round()); // 3 - /// print(Obj(3.5).round()); // 4 - /// print(Obj(3.75).round()); // 4 - /// print(Obj(-3.5).round()); // -4 + /// print(Signal(3.0).round()); // 3 + /// print(Signal(3.25).round()); // 3 + /// print(Signal(3.5).round()); // 4 + /// print(Signal(3.75).round()); // 4 + /// print(Signal(-3.5).round()); // -4 /// ``` int? round() => value?.round(); @@ -215,12 +215,12 @@ extension ObjDoubleNullExt on Obj { /// Throws an [UnsupportedError] if this number is not finite /// (NaN or infinity). /// ```dart - /// print(Obj(1.99999).floor()); // 1 - /// print(Obj(2.0).floor()); // 2 - /// print(Obj(2.99999).floor()); // 2 - /// print(Obj(-1.99999).floor()); // -2 - /// print(Obj(-2.0).floor()); // -2 - /// print(Obj(-2.00001).floor()); // -3 + /// print(Signal(1.99999).floor()); // 1 + /// print(Signal(2.0).floor()); // 2 + /// print(Signal(2.99999).floor()); // 2 + /// print(Signal(-1.99999).floor()); // -2 + /// print(Signal(-2.0).floor()); // -2 + /// print(Signal(-2.00001).floor()); // -3 /// ``` int? floor() => value?.floor(); @@ -231,12 +231,12 @@ extension ObjDoubleNullExt on Obj { /// Throws an [UnsupportedError] if this number is not finite /// (NaN or an infinity). /// ```dart - /// print(Obj(1.99999).ceil()); // 2 - /// print(Obj(2.0).ceil()); // 2 - /// print(Obj(2.00001).ceil()); // 3 - /// print(Obj(-1.99999).ceil()); // -1 - /// print(Obj(-2.0).ceil()); // -2 - /// print(Obj(-2.00001).ceil()); // -2 + /// print(Signal(1.99999).ceil()); // 2 + /// print(Signal(2.0).ceil()); // 2 + /// print(Signal(2.00001).ceil()); // 3 + /// print(Signal(-1.99999).ceil()); // -1 + /// print(Signal(-2.0).ceil()); // -2 + /// print(Signal(-2.00001).ceil()); // -2 /// ``` int? ceil() => value?.ceil(); @@ -248,19 +248,19 @@ extension ObjDoubleNullExt on Obj { /// Throws an [UnsupportedError] if this number is not finite /// (NaN or an infinity). /// ```dart - /// print(Obj(2.00001).truncate()); // 2 - /// print(Obj(1.99999).truncate()); // 1 - /// print(Obj(0.5).truncate()); // 0 - /// print(Obj(-0.5).truncate()); // 0 - /// print(Obj(-1.5).truncate()); // -1 - /// print(Obj(-2.5).truncate()); // -2 + /// print(Signal(2.00001).truncate()); // 2 + /// print(Signal(1.99999).truncate()); // 1 + /// print(Signal(0.5).truncate()); // 0 + /// print(Signal(-0.5).truncate()); // 0 + /// print(Signal(-1.5).truncate()); // -1 + /// print(Signal(-2.5).truncate()); // -2 /// ``` int? truncate() => value?.truncate(); /// Returns the integer double value closest to `this`. /// /// Rounds away from zero when there is no closest integer: - /// `Obj(3.5).roundToDouble() == 4` and `Obj(-3.5).roundToDouble() == -4`. + /// `Signal(3.5).roundToDouble() == 4` and `Signal(-3.5).roundToDouble() == -4`. /// /// If this is already an integer valued double, including `-0.0`, or it is not /// a finite value, the value is returned unmodified. @@ -270,11 +270,11 @@ extension ObjDoubleNullExt on Obj { /// This means that for a value `d` in the range `-0.5 < d < 0.0`, /// the result is `-0.0`. /// ```dart - /// print(Obj(3.0).roundToDouble()); // 3.0 - /// print(Obj(3.25).roundToDouble()); // 3.0 - /// print(Obj(3.5).roundToDouble()); // 4.0 - /// print(Obj(3.75).roundToDouble()); // 4.0 - /// print(Obj(-3.5).roundToDouble()); // -4.0 + /// print(Signal(3.0).roundToDouble()); // 3.0 + /// print(Signal(3.25).roundToDouble()); // 3.0 + /// print(Signal(3.5).roundToDouble()); // 4.0 + /// print(Signal(3.75).roundToDouble()); // 4.0 + /// print(Signal(-3.5).roundToDouble()); // -4.0 /// ``` double? roundToDouble() => value?.roundToDouble(); @@ -286,12 +286,12 @@ extension ObjDoubleNullExt on Obj { /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `0.0 < d < 1.0` will return `0.0`. /// ```dart - /// print(Obj(1.99999).floorToDouble()); // 1.0 - /// print(Obj(2.0).floorToDouble()); // 2.0 - /// print(Obj(2.99999).floorToDouble()); // 2.0 - /// print(Obj(-1.99999).floorToDouble()); // -2.0 - /// print(Obj(-2.0).floorToDouble()); // -2.0 - /// print(Obj(-2.00001).floorToDouble()); // -3.0 + /// print(Signal(1.99999).floorToDouble()); // 1.0 + /// print(Signal(2.0).floorToDouble()); // 2.0 + /// print(Signal(2.99999).floorToDouble()); // 2.0 + /// print(Signal(-1.99999).floorToDouble()); // -2.0 + /// print(Signal(-2.0).floorToDouble()); // -2.0 + /// print(Signal(-2.00001).floorToDouble()); // -3.0 /// ``` double? floorToDouble() => value?.floorToDouble(); @@ -303,12 +303,12 @@ extension ObjDoubleNullExt on Obj { /// For the purpose of rounding, `-0.0` is considered to be below `0.0`. /// A number `d` in the range `-1.0 < d < 0.0` will return `-0.0`. /// ```dart - /// print(Obj(1.99999).ceilToDouble()); // 2.0 - /// print(Obj(2.0).ceilToDouble()); // 2.0 - /// print(Obj(2.00001).ceilToDouble()); // 3.0 - /// print(Obj(-1.99999).ceilToDouble()); // -1.0 - /// print(Obj(-2.0).ceilToDouble()); // -2.0 - /// print(Obj(-2.00001).ceilToDouble()); // -2.0 + /// print(Signal(1.99999).ceilToDouble()); // 2.0 + /// print(Signal(2.0).ceilToDouble()); // 2.0 + /// print(Signal(2.00001).ceilToDouble()); // 3.0 + /// print(Signal(-1.99999).ceilToDouble()); // -1.0 + /// print(Signal(-2.0).ceilToDouble()); // -2.0 + /// print(Signal(-2.00001).ceilToDouble()); // -2.0 /// ``` double? ceilToDouble() => value?.ceilToDouble(); @@ -322,13 +322,13 @@ extension ObjDoubleNullExt on Obj { /// A number `d` in the range `-1.0 < d < 0.0` will return `-0.0`, and /// in the range `0.0 < d < 1.0` it will return 0.0. /// ```dart - /// print(Obj(2.5).truncateToDouble()); // 2.0 - /// print(Obj(2.00001).truncateToDouble()); // 2.0 - /// print(Obj(1.99999).truncateToDouble()); // 1.0 - /// print(Obj(0.5).truncateToDouble()); // 0.0 - /// print(Obj(-0.5).truncateToDouble()); // -0.0 - /// print(Obj(-1.5).truncateToDouble()); // -1.0 - /// print(Obj(-2.5).truncateToDouble()); // -2.0 + /// print(Signal(2.5).truncateToDouble()); // 2.0 + /// print(Signal(2.00001).truncateToDouble()); // 2.0 + /// print(Signal(1.99999).truncateToDouble()); // 1.0 + /// print(Signal(0.5).truncateToDouble()); // 0.0 + /// print(Signal(-0.5).truncateToDouble()); // -0.0 + /// print(Signal(-1.5).truncateToDouble()); // -1.0 + /// print(Signal(-2.5).truncateToDouble()); // -2.0 /// ``` double? truncateToDouble() => value?.truncateToDouble(); } diff --git a/packages/reactter/lib/src/obj/extensions/obj_int.dart b/packages/reactter/lib/src/signal/extensions/signal_int.dart similarity index 74% rename from packages/reactter/lib/src/obj/extensions/obj_int.dart rename to packages/reactter/lib/src/signal/extensions/signal_int.dart index 2312d23f..b573575a 100644 --- a/packages/reactter/lib/src/obj/extensions/obj_int.dart +++ b/packages/reactter/lib/src/signal/extensions/signal_int.dart @@ -1,7 +1,7 @@ // coverage:ignore-file -part of '../obj.dart'; +part of '../signal.dart'; -extension ObjIntExt on Obj { +extension SignalIntExt on Signal { /// Bit-wise and operator. /// /// Treating both `this` and [other] as sufficiently large two's component @@ -11,11 +11,11 @@ extension ObjIntExt on Obj { /// If both operands are negative, the result is negative, otherwise /// the result is non-negative. /// ```dart - /// print((Obj(2) & Obj(1)).toRadixString(2)); // 0010 & 0001 -> 0000 - /// print((Obj(3) & Obj(1)).toRadixString(2)); // 0011 & 0001 -> 0001 - /// print((Obj(10) & Obj(2)).toRadixString(2)); // 1010 & 0010 -> 0010 + /// print((Signal(2) & 1).toRadixString(2)); // 0010 & 0001 -> 0000 + /// print((Signal(3) & 1).toRadixString(2)); // 0011 & 0001 -> 0001 + /// print((Signal(10) & 2).toRadixString(2)); // 1010 & 0010 -> 0010 /// ``` - Obj operator &(Obj other) => Obj(value & other.value); + int operator &(int other) => value & other; /// Bit-wise or operator. /// @@ -28,11 +28,11 @@ extension ObjIntExt on Obj { /// /// Example: /// ```dart - /// print((Obj(2) | Obj(1)).toRadixString(2)); // 0010 | 0001 -> 0011 - /// print((Obj(3) | Obj(1)).toRadixString(2)); // 0011 | 0001 -> 0011 - /// print((Obj(10) | Obj(2)).toRadixString(2)); // 1010 | 0010 -> 1010 + /// print((Signal(2) | 1).toRadixString(2)); // 0010 | 0001 -> 0011 + /// print((Signal(3) | 1).toRadixString(2)); // 0011 | 0001 -> 0011 + /// print((Signal(10) | 2).toRadixString(2)); // 1010 | 0010 -> 1010 /// ``` - Obj operator |(Obj other) => Obj(value | other.value); + int operator |(int other) => value | other; /// Bit-wise exclusive-or operator. /// @@ -45,11 +45,11 @@ extension ObjIntExt on Obj { /// /// Example: /// ```dart - /// print((Obj(2) ^ Obj(1)).toRadixString(2)); // 0010 ^ 0001 -> 0011 - /// print((Obj(3) ^ Obj(1)).toRadixString(2)); // 0011 ^ 0001 -> 0010 - /// print((Obj(10) ^ Obj(2)).toRadixString(2)); // 1010 ^ 0010 -> 1000 + /// print((Signal(2) ^ 1).toRadixString(2)); // 0010 ^ 0001 -> 0011 + /// print((Signal(3) ^ 1).toRadixString(2)); // 0011 ^ 0001 -> 0010 + /// print((Signal(10) ^ 2).toRadixString(2)); // 1010 ^ 0010 -> 1000 /// ``` - Obj operator ^(Obj other) => Obj(value ^ other.value); + int operator ^(int other) => value ^ other; /// The bit-wise negate operator. /// @@ -57,7 +57,7 @@ extension ObjIntExt on Obj { /// the result is a number with the opposite bits set. /// /// This maps any integer `x` to `-x - 1`. - Obj operator ~() => Obj(~value); + int operator ~() => ~value; /// Shift the bits of this integer to the left by [shiftAmount]. /// @@ -72,11 +72,11 @@ extension ObjIntExt on Obj { /// /// Example: /// ```dart - /// print((Obj(3) << Obj(1)).toRadixString(2)); // 0011 -> 0110 - /// print((Obj(9) << Obj(2)).toRadixString(2)); // 1001 -> 100100 - /// print((Obj(10) << Obj(3)).toRadixString(2)); // 1010 -> 1010000 + /// print((Signal(3) << 1).toRadixString(2)); // 0011 -> 0110 + /// print((Signal(9) << 2).toRadixString(2)); // 1001 -> 100100 + /// print((Signal(10) << 3).toRadixString(2)); // 1010 -> 1010000 /// ``` - Obj operator <<(Obj shiftAmount) => Obj(value << shiftAmount.value); + int operator <<(int shiftAmount) => value << shiftAmount; /// Shift the bits of this integer to the right by [shiftAmount]. /// @@ -88,13 +88,13 @@ extension ObjIntExt on Obj { /// /// Example: /// ```dart - /// print((Obj(3) >> Obj(1)).toRadixString(2)); // 0011 -> 0001 - /// print((Obj(9) >> Obj(2)).toRadixString(2)); // 1001 -> 0010 - /// print((Obj(10) >> Obj(3)).toRadixString(2)); // 1010 -> 0001 - /// print((Obj(-6) >> Obj(2)).toRadixString); // 111...1010 -> 111...1110 == -2 - /// print((Obj(-85) >> Obj(3)).toRadixString); // 111...10101011 -> 111...11110101 == -11 + /// print((Signal(3) >> 1).toRadixString(2)); // 0011 -> 0001 + /// print((Signal(9) >> 2).toRadixString(2)); // 1001 -> 0010 + /// print((Signal(10) >> 3).toRadixString(2)); // 1010 -> 0001 + /// print((Signal(-6) >> 2).toRadixString); // 111...1010 -> 111...1110 == -2 + /// print((Signal(-85) >> 3).toRadixString); // 111...10101011 -> 111...11110101 == -11 /// ``` - Obj operator >>(Obj shiftAmount) => Obj(value >> shiftAmount.value); + int operator >>(int shiftAmount) => value >> shiftAmount; /// Bitwise unsigned right shift by [shiftAmount] bits. /// @@ -106,18 +106,17 @@ extension ObjIntExt on Obj { /// /// Example: /// ```dart - /// print((Obj(3) >>> Obj(1)).toRadixString(2)); // 0011 -> 0001 - /// print((Obj(9) >>> Obj(2)).toRadixString(2)); // 1001 -> 0010 - /// print((Obj(-9) >>> Obj(2)).toRadixString(2)); // 111...1011 -> 001...1110 (> 0) + /// print((Signal(3) >>> 1).toRadixString(2)); // 0011 -> 0001 + /// print((Signal(9) >>> 2).toRadixString(2)); // 1001 -> 0010 + /// print((Signal(-9) >>> 2).toRadixString(2)); // 111...1011 -> 001...1110 (> 0) /// ``` - Obj operator >>>(Obj shiftAmount) => - Obj(value >>> shiftAmount.value); + int operator >>>(int shiftAmount) => value >>> shiftAmount; /// Return the negative value of this integer. /// /// The result of negating an integer always has the opposite sign, except /// for zero, which is its own negation. - Obj operator -() => Obj(-value); + int operator -() => -value; /// Returns this integer to the power of [exponent] modulo [modulus]. /// @@ -147,11 +146,11 @@ extension ObjIntExt on Obj { /// /// Example: /// ```dart - /// print(Obj(4).gcd(2)); // 2 - /// print(Obj(8).gcd(4)); // 4 - /// print(Obj(10).gcd(12)); // 2 - /// print(Obj(10).gcd(0)); // 10 - /// print(Obj(-2).gcd(-3)); // 1 + /// print(Signal(4).gcd(2)); // 2 + /// print(Signal(8).gcd(4)); // 4 + /// print(Signal(10).gcd(12)); // 2 + /// print(Signal(10).gcd(0)); // 10 + /// print(Signal(-2).gcd(-3)); // 1 /// ``` int gcd(int other) => value.gcd(other); @@ -172,14 +171,14 @@ extension ObjIntExt on Obj { /// ```dart /// x.bitLength == (-x-1).bitLength; /// - /// Obj(3).bitLength == 2; // 00000011 - /// Obj(2).bitLength == 2; // 00000010 - /// Obj(1).bitLength == 1; // 00000001 - /// Obj(0).bitLength == 0; // 00000000 - /// Obj(-1).bitLength == 0; // 11111111 - /// Obj(-2).bitLength == 1; // 11111110 - /// Obj(-3).bitLength == 2; // 11111101 - /// Obj(-4).bitLength == 2; // 11111100 + /// Signal(3).bitLength == 2; // 00000011 + /// Signal(2).bitLength == 2; // 00000010 + /// Signal(1).bitLength == 1; // 00000001 + /// Signal(0).bitLength == 0; // 00000000 + /// Signal(-1).bitLength == 0; // 11111111 + /// Signal(-2).bitLength == 1; // 11111110 + /// Signal(-3).bitLength == 2; // 11111101 + /// Signal(-4).bitLength == 2; // 11111100 /// ``` int get bitLength => value.bitLength; @@ -187,7 +186,7 @@ extension ObjIntExt on Obj { /// non-negative number (i.e. unsigned representation). The returned value has /// zeros in all bit positions higher than [width]. /// ```dart - /// Obj(-1).toUnsigned(5) == 31 // 11111111 -> 00011111 + /// Signal(-1).toUnsigned(5) == 31 // 11111111 -> 00011111 /// ``` /// This operation can be used to simulate arithmetic from low level languages. /// For example, to increment an 8 bit quantity: @@ -211,8 +210,8 @@ extension ObjIntExt on Obj { /// /// ```dart /// // V--sign bit-V - /// Obj(16).toSigned(5) == -16; // 00010000 -> 11110000 - /// Obj(239).toSigned(5) == 15; // 11101111 -> 00001111 + /// Signal(16).toSigned(5) == -16; // 00010000 -> 11110000 + /// Signal(239).toSigned(5) == 15; // 11101111 -> 00001111 /// // ^ ^ /// ``` /// This operation can be used to simulate arithmetic from low level languages. @@ -279,25 +278,25 @@ extension ObjIntExt on Obj { /// Example: /// ```dart /// // Binary (base 2). - /// print(Obj(12).toRadixString(2)); // 1100 - /// print(Obj(31).toRadixString(2)); // 11111 - /// print(Obj(2021).toRadixString(2)); // 11111100101 - /// print(Obj(-12).toRadixString(2)); // -1100 + /// print(Signal(12).toRadixString(2)); // 1100 + /// print(Signal(31).toRadixString(2)); // 11111 + /// print(Signal(2021).toRadixString(2)); // 11111100101 + /// print(Signal(-12).toRadixString(2)); // -1100 /// // Octal (base 8). - /// print(Obj(12).toRadixString(8)); // 14 - /// print(Obj(31).toRadixString(8)); // 37 - /// print(Obj(2021).toRadixString(8)); // 3745 + /// print(Signal(12).toRadixString(8)); // 14 + /// print(Signal(31).toRadixString(8)); // 37 + /// print(Signal(2021).toRadixString(8)); // 3745 /// // Hexadecimal (base 16). - /// print(Obj(12).toRadixString(16)); // c - /// print(Obj(31).toRadixString(16)); // 1f - /// print(Obj(2021).toRadixString(16)); // 7e5 + /// print(Signal(12).toRadixString(16)); // c + /// print(Signal(31).toRadixString(16)); // 1f + /// print(Signal(2021).toRadixString(16)); // 7e5 /// // Base 36. - /// print(Obj(35 * 36 + 1).toRadixString(36)); // z1 + /// print(Signal(35 * 36 + 1).toRadixString(36)); // z1 /// ``` String toRadixString(int radix) => value.toRadixString(radix); } -extension ObjIntNullExt on Obj { +extension SignalIntNullExt on Signal { /// Returns this integer to the power of [exponent] modulo [modulus]. /// /// The [exponent] must be non-negative and [modulus] must be @@ -326,11 +325,11 @@ extension ObjIntNullExt on Obj { /// /// Example: /// ```dart - /// print(Obj(4).gcd(2)); // 2 - /// print(Obj(8).gcd(4)); // 4 - /// print(Obj(10).gcd(12)); // 2 - /// print(Obj(10).gcd(0)); // 10 - /// print(Obj(-2).gcd(-3)); // 1 + /// print(Signal(4).gcd(2)); // 2 + /// print(Signal(8).gcd(4)); // 4 + /// print(Signal(10).gcd(12)); // 2 + /// print(Signal(10).gcd(0)); // 10 + /// print(Signal(-2).gcd(-3)); // 1 /// ``` int? gcd(int other) => value?.gcd(other); @@ -351,14 +350,14 @@ extension ObjIntNullExt on Obj { /// ```dart /// x.bitLength == (-x-1).bitLength; /// - /// Obj(3).bitLength == 2; // 00000011 - /// Obj(2).bitLength == 2; // 00000010 - /// Obj(1).bitLength == 1; // 00000001 - /// Obj(0).bitLength == 0; // 00000000 - /// Obj(-1).bitLength == 0; // 11111111 - /// Obj(-2).bitLength == 1; // 11111110 - /// Obj(-3).bitLength == 2; // 11111101 - /// Obj(-4).bitLength == 2; // 11111100 + /// Signal(3).bitLength == 2; // 00000011 + /// Signal(2).bitLength == 2; // 00000010 + /// Signal(1).bitLength == 1; // 00000001 + /// Signal(0).bitLength == 0; // 00000000 + /// Signal(-1).bitLength == 0; // 11111111 + /// Signal(-2).bitLength == 1; // 11111110 + /// Signal(-3).bitLength == 2; // 11111101 + /// Signal(-4).bitLength == 2; // 11111100 /// ``` int? get bitLength => value?.bitLength; @@ -366,7 +365,7 @@ extension ObjIntNullExt on Obj { /// non-negative number (i.e. unsigned representation). The returned value has /// zeros in all bit positions higher than [width]. /// ```dart - /// Obj(-1).toUnsigned(5) == 31 // 11111111 -> 00011111 + /// Signal(-1).toUnsigned(5) == 31 // 11111111 -> 00011111 /// ``` /// This operation can be used to simulate arithmetic from low level languages. /// For example, to increment an 8 bit quantity: @@ -390,8 +389,8 @@ extension ObjIntNullExt on Obj { /// /// ```dart /// // V--sign bit-V - /// Obj(16).toSigned(5) == -16; // 00010000 -> 11110000 - /// Obj(239).toSigned(5) == 15; // 11101111 -> 00001111 + /// Signal(16).toSigned(5) == -16; // 00010000 -> 11110000 + /// Signal(239).toSigned(5) == 15; // 11101111 -> 00001111 /// // ^ ^ /// ``` /// This operation can be used to simulate arithmetic from low level languages. @@ -458,20 +457,20 @@ extension ObjIntNullExt on Obj { /// Example: /// ```dart /// // Binary (base 2). - /// print(Obj(12).toRadixString(2)); // 1100 - /// print(Obj(31).toRadixString(2)); // 11111 - /// print(Obj(2021).toRadixString(2)); // 11111100101 - /// print(Obj(-12).toRadixString(2)); // -1100 + /// print(Signal(12).toRadixString(2)); // 1100 + /// print(Signal(31).toRadixString(2)); // 11111 + /// print(Signal(2021).toRadixString(2)); // 11111100101 + /// print(Signal(-12).toRadixString(2)); // -1100 /// // Octal (base 8). - /// print(Obj(12).toRadixString(8)); // 14 - /// print(Obj(31).toRadixString(8)); // 37 - /// print(Obj(2021).toRadixString(8)); // 3745 + /// print(Signal(12).toRadixString(8)); // 14 + /// print(Signal(31).toRadixString(8)); // 37 + /// print(Signal(2021).toRadixString(8)); // 3745 /// // Hexadecimal (base 16). - /// print(Obj(12).toRadixString(16)); // c - /// print(Obj(31).toRadixString(16)); // 1f - /// print(Obj(2021).toRadixString(16)); // 7e5 + /// print(Signal(12).toRadixString(16)); // c + /// print(Signal(31).toRadixString(16)); // 1f + /// print(Signal(2021).toRadixString(16)); // 7e5 /// // Base 36. - /// print(Obj(35 * 36 + 1).toRadixString(36)); // z1 + /// print(Signal(35 * 36 + 1).toRadixString(36)); // z1 /// ``` String? toRadixString(int radix) => value?.toRadixString(radix); } diff --git a/packages/reactter/lib/src/obj/extensions/obj_iterable.dart b/packages/reactter/lib/src/signal/extensions/signal_iterable.dart similarity index 99% rename from packages/reactter/lib/src/obj/extensions/obj_iterable.dart rename to packages/reactter/lib/src/signal/extensions/signal_iterable.dart index d538e2bb..75bcd116 100644 --- a/packages/reactter/lib/src/obj/extensions/obj_iterable.dart +++ b/packages/reactter/lib/src/signal/extensions/signal_iterable.dart @@ -1,7 +1,7 @@ // coverage:ignore-file -part of '../obj.dart'; +part of '../signal.dart'; -extension ObjIterableExt on Obj> { +extension SignalIterableExt on Signal> { /// Returns a new `Iterator` that allows iterating the elements of this /// `Iterable`. /// @@ -535,7 +535,7 @@ extension ObjIterableExt on Obj> { E elementAt(int index) => value.elementAt(index); } -extension ObjIterableNullExt on Obj?> { +extension SignalIterableNullExt on Signal?> { /// Returns a new `Iterator` that allows iterating the elements of this /// `Iterable`. /// diff --git a/packages/reactter/lib/src/signal/extensions/signal_list.dart b/packages/reactter/lib/src/signal/extensions/signal_list.dart index 285f7a2c..97d7bd81 100644 --- a/packages/reactter/lib/src/signal/extensions/signal_list.dart +++ b/packages/reactter/lib/src/signal/extensions/signal_list.dart @@ -413,6 +413,188 @@ extension SignalListExt on Signal> { /// [setRange] instead. void replaceRange(int start, int end, Iterable replacements) => update((_) => value.replaceRange(start, end, replacements)); + + /// Returns the concatenation of this list and [other]. + /// + /// Returns a new list containing the elements of this list followed by + /// the elements of [other]. + /// + /// The default behavior is to return a normal growable list. + /// Some list types may choose to return a list of the same type as themselves + /// (see [Uint8List.+]); + List operator +(List other) => value + other; + + /// Returns a view of this list as a list of [R] instances. + /// + /// If this list contains only instances of [R], all read operations + /// will work correctly. If any operation tries to read an element + /// that is not an instance of [R], the access will throw instead. + /// + /// Elements added to the list (e.g., by using [add] or [addAll]) + /// must be instances of [R] to be valid arguments to the adding function, + /// and they must also be instances of [E] to be accepted by + /// this list as well. + /// + /// Methods which accept `Object?` as argument, like [contains] and [remove], + /// will pass the argument directly to the this list's method + /// without any checks. + /// That means that you can do `listOfStrings.cast().remove("a")` + /// successfully, even if it looks like it shouldn't have any effect. + /// + /// Typically implemented as `List.castFrom(this)`. + List cast() => value.cast(); + + /// An [Iterable] of the objects in this list in reverse order. + /// ```dart + /// final numbers = ['two', 'three', 'four']; + /// final reverseOrder = numbers.reversed; + /// print(reverseOrder.toList()); // [four, three, two] + /// ``` + Iterable get reversed => value.reversed; + + /// The first index of [element] in this list. + /// + /// Searches the list from index [start] to the end of the list. + /// The first time an object `o` is encountered so that `o == element`, + /// the index of `o` is returned. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// print(notes.indexOf('re')); // 1 + /// + /// final indexWithStart = notes.indexOf('re', 2); // 3 + /// ``` + /// Returns -1 if [element] is not found. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final index = notes.indexOf('fa'); // -1 + /// ``` + int indexOf(E element, [int start = 0]) => value.indexOf(element, start); + + /// The first index in the list that satisfies the provided [test]. + /// + /// Searches the list from index [start] to the end of the list. + /// The first time an object `o` is encountered so that `test(o)` is true, + /// the index of `o` is returned. + /// + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final first = notes.indexWhere((note) => note.startsWith('r')); // 1 + /// final second = notes.indexWhere((note) => note.startsWith('r'), 2); // 3 + /// ``` + /// + /// Returns -1 if [element] is not found. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final index = notes.indexWhere((note) => note.startsWith('k')); // -1 + /// ``` + int indexWhere(bool Function(E element) test, [int start = 0]) => + value.indexWhere(test); + + /// The last index in the list that satisfies the provided [test]. + /// + /// Searches the list from index [start] to 0. + /// The first time an object `o` is encountered so that `test(o)` is true, + /// the index of `o` is returned. + /// If [start] is omitted, it defaults to the [length] of the list. + /// + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final first = notes.lastIndexWhere((note) => note.startsWith('r')); // 3 + /// final second = notes.lastIndexWhere((note) => note.startsWith('r'), + /// 2); // 1 + /// ``` + /// + /// Returns -1 if [element] is not found. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final index = notes.lastIndexWhere((note) => note.startsWith('k')); + /// print(index); // -1 + /// ``` + int lastIndexWhere(bool Function(E element) test, [int? start]) => + value.lastIndexWhere(test, start); + + /// The last index of [element] in this list. + /// + /// Searches the list backwards from index [start] to 0. + /// + /// The first time an object `o` is encountered so that `o == element`, + /// the index of `o` is returned. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// const startIndex = 2; + /// final index = notes.lastIndexOf('re', startIndex); // 1 + /// ``` + /// If [start] is not provided, this method searches from the end of the + /// list. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final index = notes.lastIndexOf('re'); // 3 + /// ``` + /// Returns -1 if [element] is not found. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final index = notes.lastIndexOf('fa'); // -1 + /// ``` + int lastIndexOf(E element, [int? start]) => value.lastIndexOf(element, start); + + /// Returns a new list containing the elements between [start] and [end]. + /// + /// The new list is a `List` containing the elements of this list at + /// positions greater than or equal to [start] and less than [end] in the same + /// order as they occur in this list. + /// + /// ```dart + /// final colors = ['red', 'green', 'blue', 'orange', 'pink']; + /// print(colors.sublist(1, 3)); // [green, blue] + /// ``` + /// + /// If [end] is omitted, it defaults to the [length] of this list. + /// + /// ```dart + /// final colors = ['red', 'green', 'blue', 'orange', 'pink']; + /// print(colors.sublist(3)); // [orange, pink] + /// ``` + /// + /// The `start` and `end` positions must satisfy the relations + /// 0 ≤ `start` ≤ `end` ≤ [length]. + /// If `end` is equal to `start`, then the returned list is empty. + List sublist(int start, [int? end]) => value.sublist(start, end); + + /// Creates an [Iterable] that iterates over a range of elements. + /// + /// The returned iterable iterates over the elements of this list + /// with positions greater than or equal to [start] and less than [end]. + /// + /// The provided range, [start] and [end], must be valid at the time + /// of the call. + /// A range from [start] to [end] is valid if 0 ≤ `start` ≤ `end` ≤ [length]. + /// An empty range (with `end == start`) is valid. + /// + /// The returned [Iterable] behaves like `skip(start).take(end - start)`. + /// That is, it does *not* break if this list changes size, it just + /// ends early if it reaches the end of the list early + /// (if `end`, or even `start`, becomes greater than [length]). + /// ```dart + /// final colors = ['red', 'green', 'blue', 'orange', 'pink']; + /// final firstRange = colors.getRange(0, 3); + /// print(firstRange.join(', ')); // red, green, blue + /// + /// final secondRange = colors.getRange(2, 5); + /// print(secondRange.join(', ')); // blue, orange, pink + /// ``` + Iterable getRange(int start, int end) => value.getRange(start, end); + + /// An unmodifiable [Map] view of this list. + /// + /// The map uses the indices of this list as keys and the corresponding objects + /// as values. The `Map.keys` [Iterable] iterates the indices of this list + /// in numerical order. + /// ```dart + /// var words = ['fee', 'fi', 'fo', 'fum']; + /// var map = words.asMap(); // {0: fee, 1: fi, 2: fo, 3: fum} + /// map.keys.toList(); // [0, 1, 2, 3] + /// ``` + Map asMap() => value.asMap(); } extension SignalListNullExt on Signal?> { @@ -818,4 +1000,211 @@ extension SignalListNullExt on Signal?> { if (value == null) return; update((_) => value?.replaceRange(start, end, replacements)); } + + /// The object at the given [index] in the list. + /// + /// The [index] must be a valid index of this list, + /// which means that `index` must be non-negative and + /// less than [length]. + E? operator [](int index) => value?[index]; + + /// Returns a view of this list as a list of [R] instances. + /// + /// If this list contains only instances of [R], all read operations + /// will work correctly. If any operation tries to read an element + /// that is not an instance of [R], the access will throw instead. + /// + /// Elements added to the list (e.g., by using [add] or [addAll]) + /// must be instances of [R] to be valid arguments to the adding function, + /// and they must also be instances of [E] to be accepted by + /// this list as well. + /// + /// Methods which accept `Object?` as argument, like [contains] and [remove], + /// will pass the argument directly to the this list's method + /// without any checks. + /// That means that you can do `listOfStrings.cast().remove("a")` + /// successfully, even if it looks like it shouldn't have any effect. + /// + /// Typically implemented as `List.castFrom(this)`. + List? cast() => value?.cast(); + + /// The first element of the list. + /// + /// The list must be non-empty when accessing its first element. + /// + /// The first element of a list can be modified, unlike an [Iterable]. + /// A `list.first` is equivalent to `list[0]`, + /// both for getting and setting the value. + /// + /// ```dart + /// final numbers = [1, 2, 3]; + /// print(numbers.first); // 1 + /// numbers.first = 10; + /// print(numbers.first); // 10 + /// numbers.clear(); + /// numbers.first; // Throws. + /// ``` + set first(E valueToSet) => value?.first = valueToSet; + + /// The number of objects in this list. + /// + /// The valid indices for a list are `0` through `length - 1`. + /// ```dart + /// final numbers = [1, 2, 3]; + /// print(numbers.length); // 3 + /// ``` + int get length => value?.length ?? 0; + + /// An [Iterable] of the objects in this list in reverse order. + /// ```dart + /// final numbers = ['two', 'three', 'four']; + /// final reverseOrder = numbers.reversed; + /// print(reverseOrder.toList()); // [four, three, two] + /// ``` + Iterable? get reversed => value?.reversed; + + /// The first index of [element] in this list. + /// + /// Searches the list from index [start] to the end of the list. + /// The first time an object `o` is encountered so that `o == element`, + /// the index of `o` is returned. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// print(notes.indexOf('re')); // 1 + /// + /// final indexWithStart = notes.indexOf('re', 2); // 3 + /// ``` + /// Returns -1 if [element] is not found. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final index = notes.indexOf('fa'); // -1 + /// ``` + int? indexOf(E element, [int start = 0]) => value?.indexOf(element, start); + + /// The first index in the list that satisfies the provided [test]. + /// + /// Searches the list from index [start] to the end of the list. + /// The first time an object `o` is encountered so that `test(o)` is true, + /// the index of `o` is returned. + /// + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final first = notes.indexWhere((note) => note.startsWith('r')); // 1 + /// final second = notes.indexWhere((note) => note.startsWith('r'), 2); // 3 + /// ``` + /// + /// Returns -1 if [element] is not found. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final index = notes.indexWhere((note) => note.startsWith('k')); // -1 + /// ``` + int? indexWhere(bool Function(E element) test, [int start = 0]) => + value?.indexWhere(test); + + /// The last index in the list that satisfies the provided [test]. + /// + /// Searches the list from index [start] to 0. + /// The first time an object `o` is encountered so that `test(o)` is true, + /// the index of `o` is returned. + /// If [start] is omitted, it defaults to the [length] of the list. + /// + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final first = notes.lastIndexWhere((note) => note.startsWith('r')); // 3 + /// final second = notes.lastIndexWhere((note) => note.startsWith('r'), + /// 2); // 1 + /// ``` + /// + /// Returns -1 if [element] is not found. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final index = notes.lastIndexWhere((note) => note.startsWith('k')); + /// print(index); // -1 + /// ``` + int? lastIndexWhere(bool Function(E element) test, [int? start]) => + value?.lastIndexWhere(test, start); + + /// The last index of [element] in this list. + /// + /// Searches the list backwards from index [start] to 0. + /// + /// The first time an object `o` is encountered so that `o == element`, + /// the index of `o` is returned. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// const startIndex = 2; + /// final index = notes.lastIndexOf('re', startIndex); // 1 + /// ``` + /// If [start] is not provided, this method searches from the end of the + /// list. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final index = notes.lastIndexOf('re'); // 3 + /// ``` + /// Returns -1 if [element] is not found. + /// ```dart + /// final notes = ['do', 're', 'mi', 're']; + /// final index = notes.lastIndexOf('fa'); // -1 + /// ``` + int? lastIndexOf(E element, [int? start]) => + value?.lastIndexOf(element, start); + + /// Returns a new list containing the elements between [start] and [end]. + /// + /// The new list is a `List` containing the elements of this list at + /// positions greater than or equal to [start] and less than [end] in the same + /// order as they occur in this list. + /// + /// ```dart + /// final colors = ['red', 'green', 'blue', 'orange', 'pink']; + /// print(colors.sublist(1, 3)); // [green, blue] + /// ``` + /// + /// If [end] is omitted, it defaults to the [length] of this list. + /// + /// ```dart + /// final colors = ['red', 'green', 'blue', 'orange', 'pink']; + /// print(colors.sublist(3)); // [orange, pink] + /// ``` + /// + /// The `start` and `end` positions must satisfy the relations + /// 0 ≤ `start` ≤ `end` ≤ [length]. + /// If `end` is equal to `start`, then the returned list is empty. + List? sublist(int start, [int? end]) => value?.sublist(start, end); + + /// Creates an [Iterable] that iterates over a range of elements. + /// + /// The returned iterable iterates over the elements of this list + /// with positions greater than or equal to [start] and less than [end]. + /// + /// The provided range, [start] and [end], must be valid at the time + /// of the call. + /// A range from [start] to [end] is valid if 0 ≤ `start` ≤ `end` ≤ [length]. + /// An empty range (with `end == start`) is valid. + /// + /// The returned [Iterable] behaves like `skip(start).take(end - start)`. + /// That is, it does *not* break if this list changes size, it just + /// ends early if it reaches the end of the list early + /// (if `end`, or even `start`, becomes greater than [length]). + /// ```dart + /// final colors = ['red', 'green', 'blue', 'orange', 'pink']; + /// final firstRange = colors.getRange(0, 3); + /// print(firstRange.join(', ')); // red, green, blue + /// + /// final secondRange = colors.getRange(2, 5); + /// print(secondRange.join(', ')); // blue, orange, pink + /// ``` + Iterable? getRange(int start, int end) => value?.getRange(start, end); + + /// An unmodifiable [Map] view of this list. + /// + /// The map uses the indices of this list as keys and the corresponding objects + /// as values. The `Map.keys` [Iterable] iterates the indices of this list + /// in numerical order. + /// ```dart + /// var words = ['fee', 'fi', 'fo', 'fum']; + /// var map = words.asMap(); // {0: fee, 1: fi, 2: fo, 3: fum} + /// map.keys.toList(); // [0, 1, 2, 3] + /// ``` + Map? asMap() => value?.asMap(); } diff --git a/packages/reactter/lib/src/signal/extensions/signal_map.dart b/packages/reactter/lib/src/signal/extensions/signal_map.dart index 2259b97d..21fab21c 100644 --- a/packages/reactter/lib/src/signal/extensions/signal_map.dart +++ b/packages/reactter/lib/src/signal/extensions/signal_map.dart @@ -153,6 +153,119 @@ extension SignalMapExt on Signal> { /// planets.clear(); // {} /// ``` void clear() => update((_) => value.clear()); + + /// Provides a view of this map as having [RK] keys and [RV] instances, + /// if necessary. + /// + /// If this map is already a `Map`, it is returned unchanged. + /// + /// If this set contains only keys of type [RK] and values of type [RV], + /// all read operations will work correctly. + /// If any operation exposes a non-[RK] key or non-[RV] value, + /// the operation will throw instead. + /// + /// Entries added to the map must be valid for both a `Map` and a + /// `Map`. + /// + /// Methods which accept `Object?` as argument, + /// like [containsKey], [remove] and [operator []], + /// will pass the argument directly to the this map's method + /// without any checks. + /// That means that you can do `mapWithStringKeys.cast().remove("a")` + /// successfully, even if it looks like it shouldn't have any effect. + Map cast() => value.cast(); + + /// Whether this map contains the given [value]. + /// + /// Returns true if any of the values in the map are equal to `value` + /// according to the `==` operator. + /// ```dart + /// final moonCount = {'Mercury': 0, 'Venus': 0, 'Earth': 1, + /// 'Mars': 2, 'Jupiter': 79, 'Saturn': 82, 'Uranus': 27, 'Neptune': 14 }; + /// final moons3 = moonCount.containsValue(3); // false + /// final moons82 = moonCount.containsValue(82); // true + /// ``` + bool containsValue(Object? valueToEvaluate) => + value.containsValue(valueToEvaluate); + + /// Whether this map contains the given [key]. + /// + /// Returns true if any of the keys in the map are equal to `key` + /// according to the equality used by the map. + /// ```dart + /// final moonCount = {'Mercury': 0, 'Venus': 0, 'Earth': 1, + /// 'Mars': 2, 'Jupiter': 79, 'Saturn': 82, 'Uranus': 27, 'Neptune': 14 }; + /// final containsUranus = moonCount.containsKey('Uranus'); // true + /// final containsPluto = moonCount.containsKey('Pluto'); // false + /// ``` + bool containsKey(Object? key) => value.containsKey(key); + + /// The value for the given [key], or `null` if [key] is not in the map. + /// + /// Some maps allow `null` as a value. + /// For those maps, a lookup using this operator cannot distinguish between a + /// key not being in the map, and the key being there with a `null` value. + /// Methods like [containsKey] or [putIfAbsent] can be used if the distinction + /// is important. + V? operator [](Object? key) => value[key]; + + /// The map entries of [this]. + Iterable> get entries => value.entries; + + /// Returns a new map where all entries of this map are transformed by + /// the given [convert] function. + Map map(MapEntry Function(K key, V value) convert) => + value.map(convert); + + /// Applies [action] to each key/value pair of the map. + /// + /// Calling `action` must not add or remove keys from the map. + /// ```dart + /// final planetsByMass = {0.81: 'Venus', 1: 'Earth', + /// 0.11: 'Mars', 17.15: 'Neptune'}; + /// + /// planetsByMass.forEach((key, value) { + /// print('$key: $value'); + /// // 0.81: Venus + /// // 1: Earth + /// // 0.11: Mars + /// // 17.15: Neptune + /// }); + /// ``` + void forEach(void Function(K key, V value) action) => value.forEach(action); + + /// The keys of [this]. + /// + /// The returned iterable has efficient `length` and `contains` operations, + /// based on [length] and [containsKey] of the map. + /// + /// The order of iteration is defined by the individual `Map` implementation, + /// but must be consistent between changes to the map. + /// + /// Modifying the map while iterating the keys may break the iteration. + Iterable get keys => value.keys; + + /// The values of [this]. + /// + /// The values are iterated in the order of their corresponding keys. + /// This means that iterating [keys] and [values] in parallel will + /// provide matching pairs of keys and values. + /// + /// The returned iterable has an efficient `length` method based on the + /// [length] of the map. Its [Iterable.contains] method is based on + /// `==` comparison. + /// + /// Modifying the map while iterating the values may break the iteration. + Iterable get values => value.values; + + /// The number of key/value pairs in the map. + int get length => value.length; + + /// Whether there is no key/value pair in the map. + bool get isEmpty => value.isEmpty; + + /// Whether there is at least one key/value pair in the map. + bool get isNotEmpty => value.isNotEmpty; } extension SignalMapNullExt on Signal?> { @@ -324,4 +437,117 @@ extension SignalMapNullExt on Signal?> { if (value == null) return; update((_) => value!.clear()); } + + /// Provides a view of this map as having [RK] keys and [RV] instances, + /// if necessary. + /// + /// If this map is already a `Map`, it is returned unchanged. + /// + /// If this set contains only keys of type [RK] and values of type [RV], + /// all read operations will work correctly. + /// If any operation exposes a non-[RK] key or non-[RV] value, + /// the operation will throw instead. + /// + /// Entries added to the map must be valid for both a `Map` and a + /// `Map`. + /// + /// Methods which accept `Object?` as argument, + /// like [containsKey], [remove] and [operator []], + /// will pass the argument directly to the this map's method + /// without any checks. + /// That means that you can do `mapWithStringKeys.cast().remove("a")` + /// successfully, even if it looks like it shouldn't have any effect. + Map? cast() => value?.cast(); + + /// Whether this map contains the given [value]. + /// + /// Returns true if any of the values in the map are equal to `value` + /// according to the `==` operator. + /// ```dart + /// final moonCount = {'Mercury': 0, 'Venus': 0, 'Earth': 1, + /// 'Mars': 2, 'Jupiter': 79, 'Saturn': 82, 'Uranus': 27, 'Neptune': 14 }; + /// final moons3 = moonCount.containsValue(3); // false + /// final moons82 = moonCount.containsValue(82); // true + /// ``` + bool? containsValue(Object? valueToEvaluate) => + value?.containsValue(valueToEvaluate); + + /// Whether this map contains the given [key]. + /// + /// Returns true if any of the keys in the map are equal to `key` + /// according to the equality used by the map. + /// ```dart + /// final moonCount = {'Mercury': 0, 'Venus': 0, 'Earth': 1, + /// 'Mars': 2, 'Jupiter': 79, 'Saturn': 82, 'Uranus': 27, 'Neptune': 14 }; + /// final containsUranus = moonCount.containsKey('Uranus'); // true + /// final containsPluto = moonCount.containsKey('Pluto'); // false + /// ``` + bool? containsKey(Object? key) => value?.containsKey(key); + + /// The value for the given [key], or `null` if [key] is not in the map. + /// + /// Some maps allow `null` as a value. + /// For those maps, a lookup using this operator cannot distinguish between a + /// key not being in the map, and the key being there with a `null` value. + /// Methods like [containsKey] or [putIfAbsent] can be used if the distinction + /// is important. + V? operator [](Object? key) => value?[key]; + + /// The map entries of [this]. + Iterable>? get entries => value?.entries; + + /// Returns a new map where all entries of this map are transformed by + /// the given [convert] function. + Map? map(MapEntry Function(K key, V value) convert) => + value?.map(convert); + + /// Applies [action] to each key/value pair of the map. + /// + /// Calling `action` must not add or remove keys from the map. + /// ```dart + /// final planetsByMass = {0.81: 'Venus', 1: 'Earth', + /// 0.11: 'Mars', 17.15: 'Neptune'}; + /// + /// planetsByMass.forEach((key, value) { + /// print('$key: $value'); + /// // 0.81: Venus + /// // 1: Earth + /// // 0.11: Mars + /// // 17.15: Neptune + /// }); + /// ``` + void forEach(void Function(K key, V value) action) => value?.forEach(action); + + /// The keys of [this]. + /// + /// The returned iterable has efficient `length` and `contains` operations, + /// based on [length] and [containsKey] of the map. + /// + /// The order of iteration is defined by the individual `Map` implementation, + /// but must be consistent between changes to the map. + /// + /// Modifying the map while iterating the keys may break the iteration. + Iterable? get keys => value?.keys; + + /// The values of [this]. + /// + /// The values are iterated in the order of their corresponding keys. + /// This means that iterating [keys] and [values] in parallel will + /// provide matching pairs of keys and values. + /// + /// The returned iterable has an efficient `length` method based on the + /// [length] of the map. Its [Iterable.contains] method is based on + /// `==` comparison. + /// + /// Modifying the map while iterating the values may break the iteration. + Iterable? get values => value?.values; + + /// The number of key/value pairs in the map. + int? get length => value?.length; + + /// Whether there is no key/value pair in the map. + bool? get isEmpty => value?.isEmpty; + + /// Whether there is at least one key/value pair in the map. + bool? get isNotEmpty => value?.isNotEmpty; } diff --git a/packages/reactter/lib/src/obj/extensions/obj_num.dart b/packages/reactter/lib/src/signal/extensions/signal_num.dart similarity index 82% rename from packages/reactter/lib/src/obj/extensions/obj_num.dart rename to packages/reactter/lib/src/signal/extensions/signal_num.dart index c4432137..082b66a6 100644 --- a/packages/reactter/lib/src/obj/extensions/obj_num.dart +++ b/packages/reactter/lib/src/signal/extensions/signal_num.dart @@ -1,27 +1,27 @@ // coverage:ignore-file -part of '../obj.dart'; +part of '../signal.dart'; -extension ObjNumExt on Obj { +extension SignalNumExt on Signal { /// Adds [other] to this number. /// - /// The result is an [Obj] of [double], as described by [double.+], - /// if both `this` and [other] is an [Obj] of [double], - /// otherwise the result is a [Obj] of [int]. - Obj operator +(Obj other) => Obj(value + other.value); + /// The result is a [double], as described by [double.+], + /// if both `this` and [other] is a [double], + /// otherwise the result is an [int]. + num operator +(num other) => value + other; /// Subtracts [other] from this number. /// - /// The result is an [Obj] of [double], as described by [double.-], - /// if both `this` and [other] is an [Obj] of [double], - /// otherwise the result is a [Obj] of [int]. - Obj operator -(Obj other) => Obj(value - other.value); + /// The result is a [double], as described by [double.-], + /// if both `this` and [other] is a [double], + /// otherwise the result is a [int]. + num operator -(num other) => value - other; /// Multiplies this number by [other]. /// - /// The result is an [Obj] of [double], as described by [double.*], - /// if both `this` and [other] is an [Obj] of [double], - /// otherwise the result is a [Obj] of [int]. - Obj operator *(Obj other) => Obj(value * other.value); + /// The result is a [double], as described by [double.*], + /// if both `this` and [other] is a [double], + /// otherwise the result is an [int]. + num operator *(num other) => value * other; /// Euclidean modulo of this number by [other]. /// @@ -38,21 +38,21 @@ extension ObjNumExt on Obj { /// /// See [remainder] for the remainder of the truncating division. /// - /// The result is an [Obj] of [double], as described by [double.%], - /// if both `this` and [other] are [Obj] of [double], - /// otherwise the result is a [Obj] of [int]. + /// The result is a [double], as described by [double.%], + /// if both `this` and [other] are [double], + /// otherwise the result is an [int]. /// /// Example: /// ```dart - /// print(Obj(5) % Obj(3)); // Obj(2) - /// print(Obj(-5) % Obj(3)); // Obj(1) - /// print(Obj(5) % Obj(-3)); // Obj(2) - /// print(Obj(-5) % Obj(-3)); // Obj(1) + /// print(Signal(5) % 3); // 2 + /// print(Signal(-5) % 3); // 1 + /// print(Signal(5) % -3); // 2 + /// print(Signal(-5) % -3); // 1 /// ``` - Obj operator %(Obj other) => Obj(value % other.value); + num operator %(num other) => value % other; /// Divides this number by [other]. - Obj operator /(Obj other) => Obj(value / other.value); + double operator /(num other) => value / other; /// Truncating division operator. /// @@ -69,7 +69,7 @@ extension ObjNumExt on Obj { /// Then `a ~/ b` is equivalent to `(a / b).truncate()`. /// This means that the intermediate result of the double division /// must be a finite integer (not an infinity or [double.nan]). - Obj operator ~/(Obj other) => Obj(value ~/ other.value); + int operator ~/(num other) => value ~/ other; /// The negation of this value. /// @@ -87,35 +87,35 @@ extension ObjNumExt on Obj { /// /// (Both properties generally also hold for the other type, /// but with a few edge case exceptions). - Obj operator -() => Obj(-value); + num operator -() => -value; /// Whether this number is numerically smaller than [other]. /// /// Returns `true` if this number is smaller than [other]. /// Returns `false` if this number is greater than or equal to [other] /// or if either value is a NaN value like [double.nan]. - bool operator <(Obj other) => value < other.value; + bool operator <(num other) => value < other; /// Whether this number is numerically smaller than or equal to [other]. /// /// Returns `true` if this number is smaller than or equal to [other]. /// Returns `false` if this number is greater than [other] /// or if either value is a NaN value like [double.nan]. - bool operator <=(Obj other) => value <= other.value; + bool operator <=(num other) => value <= other; /// Whether this number is numerically greater than [other]. /// /// Returns `true` if this number is greater than [other]. /// Returns `false` if this number is smaller than or equal to [other] /// or if either value is a NaN value like [double.nan]. - bool operator >(Obj other) => value > other.value; + bool operator >(num other) => value > other; /// Whether this number is numerically greater than or equal to [other]. /// /// Returns `true` if this number is greater than or equal to [other]. /// Returns `false` if this number is smaller than [other] /// or if either value is a NaN value like [double.nan]. - bool operator >=(Obj other) => value >= other.value; + bool operator >=(num other) => value >= other; /// Compares this to `other`. /// @@ -144,13 +144,13 @@ extension ObjNumExt on Obj { /// /// Examples: /// ```dart - /// print(Obj(1).compareTo(2)); // => -1 - /// print(Obj(2).compareTo(1)); // => 1 - /// print(Obj(1).compareTo(1)); // => 0 + /// print(Signal(1).compareTo(2)); // => -1 + /// print(Signal(2).compareTo(1)); // => 1 + /// print(Signal(1).compareTo(1)); // => 0 /// /// // The following comparisons yield different results than the /// // corresponding comparison operators. - /// print(Obj(-0.0).compareTo(0.0)); // => -1 + /// print(Signal(-0.0).compareTo(0.0)); // => -1 /// print(double.nan.compareTo(double.nan)); // => 0 /// print(double.infinity.compareTo(double.nan)); // => -1 /// @@ -177,10 +177,10 @@ extension ObjNumExt on Obj { /// /// Example: /// ```dart - /// print(Obj(5).remainder(3)); // 2 - /// print(Obj(-5).remainder(3)); // -2 - /// print(Obj(5).remainder(-3)); // 2 - /// print(Obj(-5).remainder(-3)); // -2 + /// print(Signal(5).remainder(3)); // 2 + /// print(Signal(-5).remainder(3)); // -2 + /// print(Signal(5).remainder(-3)); // 2 + /// print(Signal(-5).remainder(-3)); // -2 /// ``` num remainder(num other) => value.remainder(other); @@ -228,8 +228,8 @@ extension ObjNumExt on Obj { /// Integer overflow may cause the result of `-value` to stay negative. /// /// ```dart - /// print(Obj(2).abs()); // 2 - /// print(Obj(-2.5).abs()); // 2.5 + /// print(Signal(2).abs()); // 2 + /// print(Signal(-2.5).abs()); // 2.5 /// ``` num abs() => value.abs(); @@ -243,7 +243,7 @@ extension ObjNumExt on Obj { /// Returns NaN if this number is a [double] NaN value. /// /// Returns a number of the same type as this number. - /// For doubles, `Obj(-0.0).sign` is `-0.0`. + /// For doubles, `Signal(-0.0).sign` is `-0.0`. /// /// The result satisfies: /// ```dart @@ -255,7 +255,7 @@ extension ObjNumExt on Obj { /// The integer closest to this number. /// /// Rounds away from zero when there is no closest integer: - /// `Obj(3.5).round() == 4` and `Obj(-3.5).round() == -4`. + /// `Signal(3.5).round() == 4` and `Signal(-3.5).round() == -4`. /// /// The number must be finite (see [isFinite]). /// @@ -304,7 +304,7 @@ extension ObjNumExt on Obj { /// The double integer value closest to this value. /// /// Rounds away from zero when there is no closest integer: - /// `Obj(3.5).roundToDouble() == 4` and `Obj(-3.5).roundToDouble() == -4`. + /// `Signal(3.5).roundToDouble() == 4` and `Signal(-3.5).roundToDouble() == -4`. /// /// If this is already an integer valued double, including `-0.0`, or it is a /// non-finite double value, the value is returned unmodified. @@ -355,10 +355,10 @@ extension ObjNumExt on Obj { /// /// Example: /// ```dart - /// var result = Obj(10.5).clamp(5, 10.0); // 10.0 - /// result = Obj(0.75).clamp(5, 10.0); // 5 - /// result = Obj(-10).clamp(-5, 5.0); // -5 - /// result = Obj(-0.0).clamp(-5, 5.0); // -0.0 + /// var result = Signal(10.5).clamp(5, 10.0); // 10.0 + /// result = Signal(0.75).clamp(5, 10.0); // 5 + /// result = Signal(-10).clamp(-5, 5.0); // -5 + /// result = Signal(-0.0).clamp(-5, 5.0); // -0.0 /// ``` num clamp(num lowerLimit, num upperLimit) => value.clamp(lowerLimit, upperLimit); @@ -392,12 +392,12 @@ extension ObjNumExt on Obj { /// /// Examples: /// ```dart - /// Obj(1).toStringAsFixed(3); // 1.000 - /// Obj(4321.12345678).toStringAsFixed(3); // 4321.123 - /// Obj(4321.12345678).toStringAsFixed(5); // 4321.12346 - /// Obj(123456789012345).toStringAsFixed(3); // 123456789012345.000 - /// Obj(10000000000000000).toStringAsFixed(4); // 10000000000000000.0000 - /// Obj(5.25).toStringAsFixed(0); // 5 + /// Signal(1).toStringAsFixed(3); // 1.000 + /// Signal(4321.12345678).toStringAsFixed(3); // 4321.123 + /// Signal(4321.12345678).toStringAsFixed(5); // 4321.12346 + /// Signal(123456789012345).toStringAsFixed(3); // 123456789012345.000 + /// Signal(10000000000000000).toStringAsFixed(4); // 10000000000000000.0000 + /// Signal(5.25).toStringAsFixed(0); // 5 /// ``` String toStringAsFixed(int fractionDigits) => value.toStringAsFixed(fractionDigits); @@ -416,11 +416,11 @@ extension ObjNumExt on Obj { /// If [fractionDigits] equals 0, then the decimal point is omitted. /// Examples: /// ```dart - /// Obj(1).toStringAsExponential(); // 1e+0 + /// Signal(1).toStringAsExponential(); // 1e+0 /// Signa(1).toStringAsExponential(3); // 1.000e+0 - /// Obj(123456).toStringAsExponential(); // 1.23456e+5 - /// Obj(123456).toStringAsExponential(3); // 1.235e+5 - /// Obj(123).toStringAsExponential(0); // 1e+2 + /// Signal(123456).toStringAsExponential(); // 1.23456e+5 + /// Signal(123456).toStringAsExponential(3); // 1.235e+5 + /// Signal(123).toStringAsExponential(0); // 1e+2 /// ``` String toStringAsExponential([int? fractionDigits]) => value.toStringAsExponential(fractionDigits); @@ -436,20 +436,20 @@ extension ObjNumExt on Obj { /// /// Examples: /// ```dart - /// Obj(1).toStringAsPrecision(2); // 1.0 - /// Obj(1e15).toStringAsPrecision(3); // 1.00e+15 - /// Obj(1234567).toStringAsPrecision(3); // 1.23e+6 - /// Obj(1234567).toStringAsPrecision(9); // 1234567.00 - /// Obj(12345678901234567890).toStringAsPrecision(20); // 12345678901234567168 - /// Obj(12345678901234567890).toStringAsPrecision(14); // 1.2345678901235e+19 - /// Obj(0.00000012345).toStringAsPrecision(15); // 1.23450000000000e-7 - /// Obj(0.0000012345).toStringAsPrecision(15); // 0.00000123450000000000 + /// Signal(1).toStringAsPrecision(2); // 1.0 + /// Signal(1e15).toStringAsPrecision(3); // 1.00e+15 + /// Signal(1234567).toStringAsPrecision(3); // 1.23e+6 + /// Signal(1234567).toStringAsPrecision(9); // 1234567.00 + /// Signal(12345678901234567890).toStringAsPrecision(20); // 12345678901234567168 + /// Signal(12345678901234567890).toStringAsPrecision(14); // 1.2345678901235e+19 + /// Signal(0.00000012345).toStringAsPrecision(15); // 1.23450000000000e-7 + /// Signal(0.0000012345).toStringAsPrecision(15); // 0.00000123450000000000 /// ``` String toStringAsPrecision(int precision) => value.toStringAsPrecision(precision); } -extension ObjNumNullExt on Obj { +extension SignalNumNullExt on Signal { /// Compares this to `other`. /// /// Returns a negative number if `this` is less than `other`, zero if they are @@ -477,13 +477,13 @@ extension ObjNumNullExt on Obj { /// /// Examples: /// ```dart - /// print(Obj(1).compareTo(2)); // => -1 - /// print(Obj(2).compareTo(1)); // => 1 - /// print(Obj(1).compareTo(1)); // => 0 + /// print(Signal(1).compareTo(2)); // => -1 + /// print(Signal(2).compareTo(1)); // => 1 + /// print(Signal(1).compareTo(1)); // => 0 /// /// // The following comparisons yield different results than the /// // corresponding comparison operators. - /// print(Obj(-0.0).compareTo(0.0)); // => -1 + /// print(Signal(-0.0).compareTo(0.0)); // => -1 /// print(double.nan.compareTo(double.nan)); // => 0 /// print(double.infinity.compareTo(double.nan)); // => -1 /// @@ -510,10 +510,10 @@ extension ObjNumNullExt on Obj { /// /// Example: /// ```dart - /// print(Obj(5).remainder(3)); // 2 - /// print(Obj(-5).remainder(3)); // -2 - /// print(Obj(5).remainder(-3)); // 2 - /// print(Obj(-5).remainder(-3)); // -2 + /// print(Signal(5).remainder(3)); // 2 + /// print(Signal(-5).remainder(3)); // -2 + /// print(Signal(5).remainder(-3)); // 2 + /// print(Signal(-5).remainder(-3)); // -2 /// ``` num? remainder(num other) => value?.remainder(other); @@ -561,8 +561,8 @@ extension ObjNumNullExt on Obj { /// Integer overflow may cause the result of `-value` to stay negative. /// /// ```dart - /// print(Obj(2).abs()); // 2 - /// print(Obj(-2.5).abs()); // 2.5 + /// print(Signal(2).abs()); // 2 + /// print(Signal(-2.5).abs()); // 2.5 /// ``` num? abs() => value?.abs(); @@ -576,7 +576,7 @@ extension ObjNumNullExt on Obj { /// Returns NaN if this number is a [double] NaN value. /// /// Returns a number of the same type as this number. - /// For doubles, `Obj(-0.0).sign` is `-0.0`. + /// For doubles, `Signal(-0.0).sign` is `-0.0`. /// /// The result satisfies: /// ```dart @@ -588,7 +588,7 @@ extension ObjNumNullExt on Obj { /// The integer closest to this number. /// /// Rounds away from zero when there is no closest integer: - /// `Obj(3.5).round() == 4` and `Obj(-3.5).round() == -4`. + /// `Signal(3.5).round() == 4` and `Signal(-3.5).round() == -4`. /// /// The number must be finite (see [isFinite]). /// @@ -637,7 +637,7 @@ extension ObjNumNullExt on Obj { /// The double integer value closest to this value. /// /// Rounds away from zero when there is no closest integer: - /// `Obj(3.5).roundToDouble() == 4` and `Obj(-3.5).roundToDouble() == -4`. + /// `Signal(3.5).roundToDouble() == 4` and `Signal(-3.5).roundToDouble() == -4`. /// /// If this is already an integer valued double, including `-0.0`, or it is a /// non-finite double value, the value is returned unmodified. @@ -688,10 +688,10 @@ extension ObjNumNullExt on Obj { /// /// Example: /// ```dart - /// var result = Obj(10.5).clamp(5, 10.0); // 10.0 - /// result = Obj(0.75).clamp(5, 10.0); // 5 - /// result = Obj(-10).clamp(-5, 5.0); // -5 - /// result = Obj(-0.0).clamp(-5, 5.0); // -0.0 + /// var result = Signal(10.5).clamp(5, 10.0); // 10.0 + /// result = Signal(0.75).clamp(5, 10.0); // 5 + /// result = Signal(-10).clamp(-5, 5.0); // -5 + /// result = Signal(-0.0).clamp(-5, 5.0); // -0.0 /// ``` num? clamp(num lowerLimit, num upperLimit) => value?.clamp(lowerLimit, upperLimit); @@ -725,12 +725,12 @@ extension ObjNumNullExt on Obj { /// /// Examples: /// ```dart - /// Obj(1).toStringAsFixed(3); // 1.000 - /// Obj(4321.12345678).toStringAsFixed(3); // 4321.123 + /// Signal(1).toStringAsFixed(3); // 1.000 + /// Signal(4321.12345678).toStringAsFixed(3); // 4321.123 /// (4321.12345678).toStringAsFixed(5); // 4321.12346 - /// Obj(123456789012345).toStringAsFixed(3); // 123456789012345.000 - /// Obj(10000000000000000).toStringAsFixed(4); // 10000000000000000.0000 - /// Obj(5.25).toStringAsFixed(0); // 5 + /// Signal(123456789012345).toStringAsFixed(3); // 123456789012345.000 + /// Signal(10000000000000000).toStringAsFixed(4); // 10000000000000000.0000 + /// Signal(5.25).toStringAsFixed(0); // 5 /// ``` String? toStringAsFixed(int fractionDigits) => value?.toStringAsFixed(fractionDigits); @@ -749,11 +749,11 @@ extension ObjNumNullExt on Obj { /// If [fractionDigits] equals 0, then the decimal point is omitted. /// Examples: /// ```dart - /// Obj(1).toStringAsExponential(); // 1e+0 - /// Obj(1).toStringAsExponential(3); // 1.000e+0 - /// Obj(123456).toStringAsExponential(); // 1.23456e+5 - /// Obj(123456).toStringAsExponential(3); // 1.235e+5 - /// Obj(123).toStringAsExponential(0); // 1e+2 + /// Signal(1).toStringAsExponential(); // 1e+0 + /// Signal(1).toStringAsExponential(3); // 1.000e+0 + /// Signal(123456).toStringAsExponential(); // 1.23456e+5 + /// Signal(123456).toStringAsExponential(3); // 1.235e+5 + /// Signal(123).toStringAsExponential(0); // 1e+2 /// ``` String? toStringAsExponential([int? fractionDigits]) => value?.toStringAsExponential(fractionDigits); @@ -769,14 +769,14 @@ extension ObjNumNullExt on Obj { /// /// Examples: /// ```dart - /// Obj(1).toStringAsPrecision(2); // 1.0 - /// Obj(1e15).toStringAsPrecision(3); // 1.00e+15 - /// Obj(1234567).toStringAsPrecision(3); // 1.23e+6 - /// Obj(1234567).toStringAsPrecision(9); // 1234567.00 - /// Obj(12345678901234567890).toStringAsPrecision(20); // 12345678901234567168 - /// Obj(12345678901234567890).toStringAsPrecision(14); // 1.2345678901235e+19 - /// Obj(0.00000012345).toStringAsPrecision(15); // 1.23450000000000e-7 - /// Obj(0.0000012345).toStringAsPrecision(15); // 0.00000123450000000000 + /// Signal(1).toStringAsPrecision(2); // 1.0 + /// Signal(1e15).toStringAsPrecision(3); // 1.00e+15 + /// Signal(1234567).toStringAsPrecision(3); // 1.23e+6 + /// Signal(1234567).toStringAsPrecision(9); // 1234567.00 + /// Signal(12345678901234567890).toStringAsPrecision(20); // 12345678901234567168 + /// Signal(12345678901234567890).toStringAsPrecision(14); // 1.2345678901235e+19 + /// Signal(0.00000012345).toStringAsPrecision(15); // 1.23450000000000e-7 + /// Signal(0.0000012345).toStringAsPrecision(15); // 0.00000123450000000000 /// ``` String? toStringAsPrecision(int precision) => value?.toStringAsPrecision(precision); diff --git a/packages/reactter/lib/src/signal/extensions/signal_set.dart b/packages/reactter/lib/src/signal/extensions/signal_set.dart index 11d279bf..94bb2f1d 100644 --- a/packages/reactter/lib/src/signal/extensions/signal_set.dart +++ b/packages/reactter/lib/src/signal/extensions/signal_set.dart @@ -108,6 +108,119 @@ extension SignalSetExt on Signal> { /// characters.clear(); // {} /// ``` void clear() => update((_) => value.clear()); + + /// Provides a view of this set as a set of [R] instances. + /// + /// If this set contains only instances of [R], all read operations + /// will work correctly. If any operation tries to access an element + /// that is not an instance of [R], the access will throw instead. + /// + /// Elements added to the set (e.g., by using [add] or [addAll]) + /// must be instances of [R] to be valid arguments to the adding function, + /// and they must be instances of [E] as well to be accepted by + /// this set as well. + /// + /// Methods which accept one or more `Object?` as argument, + /// like [contains], [remove] and [removeAll], + /// will pass the argument directly to the this set's method + /// without any checks. + /// That means that you can do `setOfStrings.cast().remove("a")` + /// successfully, even if it looks like it shouldn't have any effect. + Set cast() => value.cast(); + + /// An iterator that iterates over the elements of this set. + /// + /// The order of iteration is defined by the individual `Set` implementation, + /// but must be consistent between changes to the set. + Iterator get iterator => value.iterator; + + /// Whether [value] is in the set. + /// ```dart + /// final characters = {'A', 'B', 'C'}; + /// final containsB = characters.contains('B'); // true + /// final containsD = characters.contains('D'); // false + /// ``` + bool contains(Object? valueToEvaluate) => value.contains(valueToEvaluate); + + /// If an object equal to [object] is in the set, return it. + /// + /// Checks whether [object] is in the set, like [contains], and if so, + /// returns the object in the set, otherwise returns `null`. + /// + /// If the equality relation used by the set is not identity, + /// then the returned object may not be *identical* to [object]. + /// Some set implementations may not be able to implement this method. + /// If the [contains] method is computed, + /// rather than being based on an actual object instance, + /// then there may not be a specific object instance representing the + /// set element. + /// ```dart + /// final characters = {'A', 'B', 'C'}; + /// final containsB = characters.lookup('B'); + /// print(containsB); // B + /// final containsD = characters.lookup('D'); + /// print(containsD); // null + /// ``` + E? lookup(Object? object) => value.lookup(object); + + /// Whether this set contains all the elements of [other]. + /// ```dart + /// final characters = {'A', 'B', 'C'}; + /// final containsAB = characters.containsAll({'A', 'B'}); + /// print(containsAB); // true + /// final containsAD = characters.containsAll({'A', 'D'}); + /// print(containsAD); // false + /// ``` + bool containsAll(Iterable other) => value.containsAll(other); + + /// Creates a new set which is the intersection between this set and [other]. + /// + /// That is, the returned set contains all the elements of this [Set] that + /// are also elements of [other] according to `other.contains`. + /// ```dart + /// final characters1 = {'A', 'B', 'C'}; + /// final characters2 = {'A', 'E', 'F'}; + /// final unionSet = characters1.intersection(characters2); + /// print(unionSet); // {A} + /// ``` + Set intersection(Set other) => value.intersection(other); + + /// Creates a new set which contains all the elements of this set and [other]. + /// + /// That is, the returned set contains all the elements of this [Set] and + /// all the elements of [other]. + /// ```dart + /// final characters1 = {'A', 'B', 'C'}; + /// final characters2 = {'A', 'E', 'F'}; + /// final unionSet1 = characters1.union(characters2); + /// print(unionSet1); // {A, B, C, E, F} + /// final unionSet2 = characters2.union(characters1); + /// print(unionSet2); // {A, E, F, B, C} + /// ``` + Set union(Set other) => value.union(other); + + /// Creates a new set with the elements of this that are not in [other]. + /// + /// That is, the returned set contains all the elements of this [Set] that + /// are not elements of [other] according to `other.contains`. + /// ```dart + /// final characters1 = {'A', 'B', 'C'}; + /// final characters2 = {'A', 'E', 'F'}; + /// final differenceSet1 = characters1.difference(characters2); + /// print(differenceSet1); // {B, C} + /// final differenceSet2 = characters2.difference(characters1); + /// print(differenceSet2); // {E, F} + /// ``` + Set difference(Set other) => value.difference(other); + + /// Creates a [Set] with the same elements and behavior as this `Set`. + /// + /// The returned set behaves the same as this set + /// with regard to adding and removing elements. + /// It initially contains the same elements. + /// If this set specifies an ordering of the elements, + /// the returned set will have the same order. + Set toSet() => value.toSet(); } extension SignalSetNullExt on Signal?> { @@ -233,4 +346,117 @@ extension SignalSetNullExt on Signal?> { if (value == null) return; update((_) => value!.clear()); } + + /// Provides a view of this set as a set of [R] instances. + /// + /// If this set contains only instances of [R], all read operations + /// will work correctly. If any operation tries to access an element + /// that is not an instance of [R], the access will throw instead. + /// + /// Elements added to the set (e.g., by using [add] or [addAll]) + /// must be instances of [R] to be valid arguments to the adding function, + /// and they must be instances of [E] as well to be accepted by + /// this set as well. + /// + /// Methods which accept one or more `Object?` as argument, + /// like [contains], [remove] and [removeAll], + /// will pass the argument directly to the this set's method + /// without any checks. + /// That means that you can do `setOfStrings.cast().remove("a")` + /// successfully, even if it looks like it shouldn't have any effect. + Set? cast() => value?.cast(); + + /// An iterator that iterates over the elements of this set. + /// + /// The order of iteration is defined by the individual `Set` implementation, + /// but must be consistent between changes to the set. + Iterator? get iterator => value?.iterator; + + /// Whether [value] is in the set. + /// ```dart + /// final characters = {'A', 'B', 'C'}; + /// final containsB = characters.contains('B'); // true + /// final containsD = characters.contains('D'); // false + /// ``` + bool? contains(Object? valueToEvaluate) => value?.contains(valueToEvaluate); + + /// If an object equal to [object] is in the set, return it. + /// + /// Checks whether [object] is in the set, like [contains], and if so, + /// returns the object in the set, otherwise returns `null`. + /// + /// If the equality relation used by the set is not identity, + /// then the returned object may not be *identical* to [object]. + /// Some set implementations may not be able to implement this method. + /// If the [contains] method is computed, + /// rather than being based on an actual object instance, + /// then there may not be a specific object instance representing the + /// set element. + /// ```dart + /// final characters = {'A', 'B', 'C'}; + /// final containsB = characters.lookup('B'); + /// print(containsB); // B + /// final containsD = characters.lookup('D'); + /// print(containsD); // null + /// ``` + E? lookup(Object? object) => value?.lookup(object); + + /// Whether this set contains all the elements of [other]. + /// ```dart + /// final characters = {'A', 'B', 'C'}; + /// final containsAB = characters.containsAll({'A', 'B'}); + /// print(containsAB); // true + /// final containsAD = characters.containsAll({'A', 'D'}); + /// print(containsAD); // false + /// ``` + bool? containsAll(Iterable other) => value?.containsAll(other); + + /// Creates a new set which is the intersection between this set and [other]. + /// + /// That is, the returned set contains all the elements of this [Set] that + /// are also elements of [other] according to `other.contains`. + /// ```dart + /// final characters1 = {'A', 'B', 'C'}; + /// final characters2 = {'A', 'E', 'F'}; + /// final unionSet = characters1.intersection(characters2); + /// print(unionSet); // {A} + /// ``` + Set? intersection(Set other) => value?.intersection(other); + + /// Creates a new set which contains all the elements of this set and [other]. + /// + /// That is, the returned set contains all the elements of this [Set] and + /// all the elements of [other]. + /// ```dart + /// final characters1 = {'A', 'B', 'C'}; + /// final characters2 = {'A', 'E', 'F'}; + /// final unionSet1 = characters1.union(characters2); + /// print(unionSet1); // {A, B, C, E, F} + /// final unionSet2 = characters2.union(characters1); + /// print(unionSet2); // {A, E, F, B, C} + /// ``` + Set? union(Set other) => value?.union(other); + + /// Creates a new set with the elements of this that are not in [other]. + /// + /// That is, the returned set contains all the elements of this [Set] that + /// are not elements of [other] according to `other.contains`. + /// ```dart + /// final characters1 = {'A', 'B', 'C'}; + /// final characters2 = {'A', 'E', 'F'}; + /// final differenceSet1 = characters1.difference(characters2); + /// print(differenceSet1); // {B, C} + /// final differenceSet2 = characters2.difference(characters1); + /// print(differenceSet2); // {E, F} + /// ``` + Set? difference(Set other) => value?.difference(other); + + /// Creates a [Set] with the same elements and behavior as this `Set`. + /// + /// The returned set behaves the same as this set + /// with regard to adding and removing elements. + /// It initially contains the same elements. + /// If this set specifies an ordering of the elements, + /// the returned set will have the same order. + Set? toSet() => value?.toSet(); } diff --git a/packages/reactter/lib/src/obj/extensions/obj_string.dart b/packages/reactter/lib/src/signal/extensions/signal_string.dart similarity index 99% rename from packages/reactter/lib/src/obj/extensions/obj_string.dart rename to packages/reactter/lib/src/signal/extensions/signal_string.dart index cfbe3bf4..074ea378 100644 --- a/packages/reactter/lib/src/obj/extensions/obj_string.dart +++ b/packages/reactter/lib/src/signal/extensions/signal_string.dart @@ -1,7 +1,7 @@ // coverage:ignore-file -part of '../obj.dart'; +part of '../signal.dart'; -extension ObjStringExt on Obj { +extension SignalStringExt on Signal { /// The character (as a single-code-unit [String]) at the given [index]. /// /// The returned string represents exactly one UTF-16 code unit, which may be @@ -23,7 +23,7 @@ extension ObjStringExt on Obj { /// ```dart /// const string = 'dart' + 'lang'; // 'dartlang' /// ``` - String operator +(Obj other) => value + other.value; + String operator +(Signal other) => value + other.value; /// Creates a new string by concatenating this string with itself a number /// of times. @@ -323,7 +323,7 @@ extension ObjStringExt on Obj { /// /// Returns a new string, which is this string /// except that the first match of [from], starting from [startIndex], - /// is replaced by the result of calling [replace] with the match object. + /// is replaced by the result of calling [replace] with the match Object. /// /// The [startIndex] must be non-negative and no greater than [length]. String replaceFirstMapped(Pattern from, String Function(Match match) replace, @@ -348,7 +348,7 @@ extension ObjStringExt on Obj { /// /// Creates a new string in which the non-overlapping substrings that match /// [from] (the ones iterated by `from.allMatches(thisString)`) are replaced - /// by the result of calling [replace] on the corresponding [Match] object. + /// by the result of calling [replace] on the corresponding [Match] Object. /// /// This can be used to replace matches with new content that depends on the /// match, unlike [replaceAll] where the replacement string is always the same. @@ -539,7 +539,7 @@ extension ObjStringExt on Obj { String toUpperCase() => value.toUpperCase(); } -extension ObjStringNullExt on Obj { +extension SignalStringNullExt on Signal { /// The character (as a single-code-unit [String]) at the given [index]. /// /// The returned string represents exactly one UTF-16 code unit, which may be @@ -839,7 +839,7 @@ extension ObjStringNullExt on Obj { /// /// Returns a new string, which is this string /// except that the first match of [from], starting from [startIndex], - /// is replaced by the result of calling [replace] with the match object. + /// is replaced by the result of calling [replace] with the match Object. /// /// The [startIndex] must be non-negative and no greater than [length]. String? replaceFirstMapped(Pattern from, String Function(Match match) replace, @@ -864,7 +864,7 @@ extension ObjStringNullExt on Obj { /// /// Creates a new string in which the non-overlapping substrings that match /// [from] (the ones iterated by `from.allMatches(thisString)`) are replaced - /// by the result of calling [replace] on the corresponding [Match] object. + /// by the result of calling [replace] on the corresponding [Match] Object. /// /// This can be used to replace matches with new content that depends on the /// match, unlike [replaceAll] where the replacement string is always the same. diff --git a/packages/reactter/lib/src/signal/signal.dart b/packages/reactter/lib/src/signal/signal.dart index 857a1592..685103c8 100644 --- a/packages/reactter/lib/src/signal/signal.dart +++ b/packages/reactter/lib/src/signal/signal.dart @@ -1,9 +1,202 @@ import 'dart:math'; +import 'package:reactter/src/framework.dart'; -import '../framework/framework.dart'; -import '../obj/obj.dart'; - -part 'signal_impl.dart'; +part 'extensions/signal_bigint.dart'; +part 'extensions/signal_bool.dart'; +part 'extensions/signal_date_time.dart'; +part 'extensions/signal_double.dart'; +part 'extensions/signal_int.dart'; +part 'extensions/signal_iterable.dart'; part 'extensions/signal_list.dart'; part 'extensions/signal_map.dart'; +part 'extensions/signal_num.dart'; part 'extensions/signal_set.dart'; +part 'extensions/signal_string.dart'; + +/// This enumeration is used to represent different events that can occur when +/// getting or setting the value of a `Signal` object. +enum SignalEvent { onGetValue, onSetValue } + +/// {@template reactter.signal} +/// A base-class that store a value of [T] and notify the listeners +/// when the value is updated. +/// +/// You can create a new [Signal]: +/// +/// ```dart +/// // usign the `Signal` class +/// final strSignal = Signal("initial value"); +/// final intSignal = Signal(0); +/// final userSignal = Signal(User()); +/// ``` +/// +/// You can get the [value]: +/// +/// ```dart +/// // usign a `value` getter +/// print(strSignal.value); +/// // or using the callable +/// print(strSignal()); +/// // or using `toString` implicit +/// print("$intSignal"); +/// ``` +/// +/// You can set a new [value]: +/// +/// ```dart +/// // usign a `value` setter +/// strSignal.value = "change value"; +/// // or using the callable +/// strSignal("change value"); +/// ``` +/// +/// You can use `Lifecycle.willUpdate` event for before the [value] is changed, +/// or use `Lifecicle.didUpdate` event for after the [value] is changed: +/// +/// ```dart +/// Rt.on( +/// strSignal, +/// Lifecycle.willUpdate, +/// (signal, _) => print("Previous value: $signal"), +/// ); +/// +/// Rt.on( +/// strSignal, +/// Lifecycle.didUpdate, +/// (signal, _) => print("Current value: $signal"), +/// ); +/// ``` +/// +/// Use `update` method to notify changes after run a set of instructions: +/// +/// ```dart +/// userSignal.update((user) { +/// user.firstname = "Leo"; +/// user.lastname = "Leon"; +/// }); +/// ``` +/// +/// Use `refresh` method to force to notify changes. +/// +/// ```dart +/// userSignal.refresh(); +/// ``` +/// +/// If you use flutter, add [`flutter_reactter`](https://pub.dev/packages/flutter_reactter) +/// package on your dependencies and use its Widgets. +/// +/// {@endtemplate} +class Signal with RtState { + bool _shouldGetValueNotify = true; + bool _shouldSetValueNotify = true; + + T _value; + final String? _debugLabel; + + @override + String? get debugLabel => _debugLabel ?? super.debugLabel; + + @override + Map get debugInfo => {'value': value}; + + /// Returns the [value] of the signal. + T get value { + _notifyGetValue(); + + return _value; + } + + /// Updates the [value] of the signal + /// and notifies the observers when changes occur. + set value(T val) { + if (_value == val) return; + + update((_) { + _value = val; + _notifySetValue(); + }); + } + + /// {@macro reactter.signal} + Signal._( + T value, { + String? debugLabel, + }) : _value = value, + _debugLabel = debugLabel; + + factory Signal(T value, {String? debugLabel}) { + return Rt.registerState(() => Signal._(value, debugLabel: debugLabel)); + } + + /// Gets and/or sets to [value] like a function + /// This method doesn't allow setting its value to null. + /// If you need to set null as value, use `.value = null`. + T call([T? val]) { + assert(!isDisposed, "You can call when it's been disposed"); + + if (val != null) value = val; + + return value; + } + + /// Executes [callback], and notifies the listeners about the update. + @override + void update(void Function(T value) fnUpdate) { + super.update(() => fnUpdate(value)); + } + + @override + String toString() => value.toString(); + + @override + // ignore: unnecessary_overrides + int get hashCode => super.hashCode; + + /// The equality operator. + /// + /// It's checking if the [other] object is of the same type as the [Signal], + /// if it is, it compares the [value], else it compares like [Object] using [hashCode]. + /// + /// Examples: + /// ```dart + /// final s = Signal(0); + /// final s2 = Signal(1); + /// final s3 = s(0); + /// final sCopy = s; + /// + /// print(s == 0); // true + /// print(s == 1); // false + /// print(s == s2); // false + /// print(s == s3); // true + /// print(s == sCopy); // true + /// ``` + /// + @override + bool operator ==(Object other) => + other is Signal ? identical(this, other) : value == other; + + void _notifyGetValue() { + if (!_shouldGetValueNotify) return; + + _shouldGetValueNotify = false; + Rt.emit(Signal, SignalEvent.onGetValue, this); + _shouldGetValueNotify = true; + } + + void _notifySetValue() { + if (!_shouldSetValueNotify) return; + + _shouldSetValueNotify = false; + Rt.emit(Signal, SignalEvent.onSetValue, this); + _shouldSetValueNotify = true; + } +} + +extension SignalNullExt on Signal { + /// Returns a new `Signal` with the value of the current `Signal` + /// if it's not null. + Signal get notNull { + assert(value != null); + return Signal(value as T); + } +} diff --git a/packages/reactter/lib/src/signal/signal_impl.dart b/packages/reactter/lib/src/signal/signal_impl.dart deleted file mode 100644 index a19c316e..00000000 --- a/packages/reactter/lib/src/signal/signal_impl.dart +++ /dev/null @@ -1,165 +0,0 @@ -part of 'signal.dart'; - -/// This enumeration is used to represent different events that can occur when -/// getting or setting the value of a `Signal` object. -enum SignalEvent { onGetValue, onSetValue } - -/// {@template signal} -/// A base-class that store a value of [T] and notify the listeners -/// when the value is updated. -/// -/// You can create a new [Signal]: -/// -/// ```dart -/// // usign the `Signal` class -/// final strSignal = Signal("initial value"); -/// final intSignal = Signal(0); -/// final userSignal = Signal(User()); -/// ``` -/// -/// You can get the [value]: -/// -/// ```dart -/// // usign a `value` getter -/// print(strSignal.value); -/// // or using the callable -/// print(strSignal()); -/// // or using `toString` implicit -/// print("$intSignal"); -/// ``` -/// -/// You can set a new [value]: -/// -/// ```dart -/// // usign a `value` setter -/// strSignal.value = "change value"; -/// // or using the callable -/// strSignal("change value"); -/// ``` -/// -/// You can use `Lifecycle.willUpdate` event for before the [value] is changed, -/// or use `Lifecicle.didUpdate` event for after the [value] is changed: -/// -/// ```dart -/// Rt.on( -/// strSignal, -/// Lifecycle.willUpdate, -/// (signal, _) => print("Previous value: $signal"), -/// ); -/// -/// Rt.on( -/// strSignal, -/// Lifecycle.didUpdate, -/// (signal, _) => print("Current value: $signal"), -/// ); -/// ``` -/// -/// Use `update` method to notify changes after run a set of instructions: -/// -/// ```dart -/// userSignal.update((user) { -/// user.firstname = "Leo"; -/// user.lastname = "Leon"; -/// }); -/// ``` -/// -/// Use `refresh` method to force to notify changes. -/// -/// ```dart -/// userSignal.refresh(); -/// ``` -/// -/// If you use flutter, add [`flutter_reactter`](https://pub.dev/packages/flutter_reactter) -/// package on your dependencies and use its Widgets. -/// -/// See also: -/// -/// * [Obj], a base-class that can be used to store a value of [T]. -/// {@endtemplate} -class Signal extends RtStateImpl with ObjBase implements Obj { - T _value; - - /// {@macro signal} - Signal(T value) : _value = value; - - bool _shouldGetValueNotify = true; - bool _shouldSetValueNotify = true; - - /// Returns the [value] of the signal. - @override - T get value { - _notifyGetValue(); - - return _value; - } - - /// Updates the [value] of the signal - /// and notifies the observers when changes occur. - @override - set value(T val) { - if (_value == val) return; - - update((_) { - _value = val; - _notifySetValue(); - }); - } - - /// Gets and/or sets to [value] like a function - /// This method doesn't allow setting its value to null. - /// If you need to set null as value, use `.value = null`. - @override - T call([T? val]) { - assert(!isDisposed, "You can call when it's been disposed"); - - if (val != null) value = val; - - return value; - } - - @override - void update(void Function(T value) fnUpdate) { - super.update(() => fnUpdate(value)); - } - - void _notifyGetValue() { - if (!_shouldGetValueNotify) return; - - _shouldGetValueNotify = false; - Rt.emit(Signal, SignalEvent.onGetValue, this); - _shouldGetValueNotify = true; - } - - void _notifySetValue() { - if (!_shouldSetValueNotify) return; - - _shouldSetValueNotify = false; - Rt.emit(Signal, SignalEvent.onSetValue, this); - _shouldSetValueNotify = true; - } -} - -extension SignalNullExt on Signal { - /// Returns a new `Signal` with the value of the current `Signal` - /// if it's not null. - Signal get notNull { - assert(value != null); - return Signal(value as T); - } -} - -extension ObjToSignalExt on Obj { - /// Returns a new Signal with the value of the current Obj. - @Deprecated( - 'This feature was deprecated after v7.2.0 and will be removed in v8.0.0.', - ) - Signal get toSignal => Signal(value); -} - -extension SignalToObjExt on Signal { - /// Returns a new Obj with the value of the current Signal. - @Deprecated( - 'This feature was deprecated after v7.2.0 and will be removed in v8.0.0.', - ) - Obj get toObj => Obj(value); -} diff --git a/packages/reactter/lib/src/types.dart b/packages/reactter/lib/src/types.dart index 66998650..e57db0e5 100644 --- a/packages/reactter/lib/src/types.dart +++ b/packages/reactter/lib/src/types.dart @@ -1,20 +1,28 @@ import 'dart:async'; import 'args.dart'; -import 'core/core.dart' show LogLevel; import 'hooks/hooks.dart'; import 'memo/memo.dart' show Memo; +import 'logger.dart' show LogLevel; + +/// {@template log_output} +/// An function to specify the log output destination. +/// +/// - [message]: The log message. +/// - [name]: The name of the logger. +/// - [level]: The integer value of the [LogLevel]. +/// - [stackTrace]: The stack trace of the log message. +/// {@endtemplate} +typedef LogOutput = void Function( + String message, { + required String name, + required int level, + StackTrace? stackTrace, +}); /// A function to generate the instance of [T] dependency. typedef InstanceBuilder = T Function(); -/// Rt.log type -typedef LogWriterCallback = void Function( - String text, { - Object error, - LogLevel level, -}); - /// UseAsyncState.when's parameter type for representing a value typedef WhenValueReturn = R Function(T value); diff --git a/packages/reactter/pubspec.lock b/packages/reactter/pubspec.lock deleted file mode 100644 index 9037a8fa..00000000 --- a/packages/reactter/pubspec.lock +++ /dev/null @@ -1,389 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" - url: "https://pub.dev" - source: hosted - version: "67.0.0" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" - url: "https://pub.dev" - source: hosted - version: "6.4.1" - args: - dependency: transitive - description: - name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" - url: "https://pub.dev" - source: hosted - version: "2.5.0" - async: - dependency: transitive - description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.dev" - source: hosted - version: "1.1.1" - collection: - dependency: transitive - description: - name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a - url: "https://pub.dev" - source: hosted - version: "1.18.0" - convert: - dependency: transitive - description: - name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" - url: "https://pub.dev" - source: hosted - version: "3.1.1" - coverage: - dependency: transitive - description: - name: coverage - sha256: "3945034e86ea203af7a056d98e98e42a5518fff200d6e8e6647e1886b07e936e" - url: "https://pub.dev" - source: hosted - version: "1.8.0" - crypto: - dependency: transitive - description: - name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab - url: "https://pub.dev" - source: hosted - version: "3.0.3" - fake_async: - dependency: "direct dev" - description: - name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.dev" - source: hosted - version: "1.3.1" - file: - dependency: transitive - description: - name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" - glob: - dependency: transitive - description: - name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" - url: "https://pub.dev" - source: hosted - version: "3.2.1" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" - url: "https://pub.dev" - source: hosted - version: "4.0.2" - io: - dependency: transitive - description: - name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" - url: "https://pub.dev" - source: hosted - version: "1.0.4" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" - lints: - dependency: "direct dev" - description: - name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - logging: - dependency: transitive - description: - name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" - meta: - dependency: "direct main" - description: - name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 - url: "https://pub.dev" - source: hosted - version: "1.15.0" - mime: - dependency: transitive - description: - name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e - url: "https://pub.dev" - source: hosted - version: "1.0.4" - node_preamble: - dependency: transitive - description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - package_config: - dependency: transitive - description: - name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - path: - dependency: transitive - description: - name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.dev" - source: hosted - version: "1.9.0" - pool: - dependency: transitive - description: - name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" - url: "https://pub.dev" - source: hosted - version: "1.5.1" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - shelf: - dependency: transitive - description: - name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 - url: "https://pub.dev" - source: hosted - version: "1.4.1" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - shelf_static: - dependency: transitive - description: - name: shelf_static - sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e - url: "https://pub.dev" - source: hosted - version: "1.1.2" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" - url: "https://pub.dev" - source: hosted - version: "0.10.12" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" - url: "https://pub.dev" - source: hosted - version: "1.11.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test: - dependency: "direct dev" - description: - name: test - sha256: d11b55850c68c1f6c0cf00eabded4e66c4043feaf6c0d7ce4a36785137df6331 - url: "https://pub.dev" - source: hosted - version: "1.25.5" - test_api: - dependency: transitive - description: - name: test_api - sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794" - url: "https://pub.dev" - source: hosted - version: "0.7.1" - test_core: - dependency: transitive - description: - name: test_core - sha256: "4d070a6bc36c1c4e89f20d353bfd71dc30cdf2bd0e14349090af360a029ab292" - url: "https://pub.dev" - source: hosted - version: "0.6.2" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: a2662fb1f114f4296cf3f5a50786a2d888268d7776cf681aa17d660ffa23b246 - url: "https://pub.dev" - source: hosted - version: "14.0.0" - watcher: - dependency: transitive - description: - name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b - url: "https://pub.dev" - source: hosted - version: "2.4.0" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - yaml: - dependency: transitive - description: - name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" - url: "https://pub.dev" - source: hosted - version: "3.1.2" -sdks: - dart: ">=3.0.0 <4.0.0" diff --git a/packages/reactter/pubspec.yaml b/packages/reactter/pubspec.yaml index 0eb4847f..cd4f4e1f 100644 --- a/packages/reactter/pubspec.yaml +++ b/packages/reactter/pubspec.yaml @@ -4,10 +4,22 @@ homepage: https://2devs-team.github.io/reactter/ documentation: https://2devs-team.github.io/reactter/ repository: https://github.com/2devs-team/reactter/tree/master/packages/reactter license: MIT License -version: 7.3.0 +version: 8.0.0-dev.31 + +topics: + - reactive + - state + - dependency + - event + +screenshots: + - description: "Reactter DevTools Extension" + path: doc/screenshots/devtools.png + - description: "Reactter Example App" + path: doc/screenshots/example_app.png environment: - sdk: ">=2.14.0 <4.0.0" + sdk: ">=2.19.2 <4.0.0" dependencies: meta: ^1.7.0 @@ -15,4 +27,11 @@ dependencies: dev_dependencies: fake_async: ^1.3.1 lints: ^2.0.1 - test: ^1.25.2 + flutter_test: + sdk: flutter + +scripts: + test: "fvm flutter test --coverage" + analyze: "fvm dart analyze ." + public-dry-run: "fvm dart pub publish --dry-run" + public: "fvm dart pub publish" \ No newline at end of file diff --git a/packages/reactter/test/args_test.dart b/packages/reactter/test/args_test.dart index 446725cf..854319c4 100644 --- a/packages/reactter/test/args_test.dart +++ b/packages/reactter/test/args_test.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; typedef AryFunctionType = FutureOr Function(Args args); diff --git a/packages/reactter/test/core/dependency_injection_test.dart b/packages/reactter/test/core/dependency_injection_test.dart index b1fdc29c..2cb89746 100644 --- a/packages/reactter/test/core/dependency_injection_test.dart +++ b/packages/reactter/test/core/dependency_injection_test.dart @@ -1,8 +1,12 @@ import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; import '../shareds/test_controllers.dart'; +class MyTest { + final uState = UseState(0); +} + void main() { group("DependencyInjection", () { test("should register a dependency", () { @@ -415,14 +419,13 @@ void main() { Rt.destroy(id: id); }); - test("should get hashcode ref by index", () { + test("should get ref by index", () { final ref = 'myRef'; Rt.create(() => TestController(), ref: ref); - final hashCodeRef = Rt.getHashCodeRefAt(0); - expect(hashCodeRef, isA()); - expect(hashCodeRef, ref.hashCode); + final hashCodeRef = Rt.getRefAt(0); + expect(hashCodeRef, ref); Rt.destroy(); }); diff --git a/packages/reactter/test/core/event_handler_test.dart b/packages/reactter/test/core/event_handler_test.dart index d27b69bc..e56e8042 100644 --- a/packages/reactter/test/core/event_handler_test.dart +++ b/packages/reactter/test/core/event_handler_test.dart @@ -1,7 +1,7 @@ // ignore_for_file: constant_identifier_names import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; import '../shareds/test_controllers.dart'; @@ -43,19 +43,19 @@ void main() { }); test( - "should listen and emit event using RtDependency", + "should listen and emit event using RtDependencyRef", () { _testListenAndEmitEvent( - RtDependency(), - RtDependency(), + RtDependencyRef(), + RtDependencyRef(), instanceMatcher: isNull, ); final testController = Rt.create(() => TestController())!; _testListenAndEmitEvent( - RtDependency(), - RtDependency(), + RtDependencyRef(), + RtDependencyRef(), instanceMatcher: testController, ); @@ -64,11 +64,11 @@ void main() { ); test( - "should listen and emit event using RtDependency with id", + "should listen and emit event using RtDependencyRef with id", () { _testListenAndEmitEvent( - RtDependency('uniqueId'), - RtDependency('uniqueId'), + RtDependencyRef('uniqueId'), + RtDependencyRef('uniqueId'), instanceMatcher: isNull, ); @@ -78,8 +78,8 @@ void main() { )!; _testListenAndEmitEvent( - RtDependency('uniqueId'), - RtDependency('uniqueId'), + RtDependencyRef('uniqueId'), + RtDependencyRef('uniqueId'), instanceMatcher: testController, ); @@ -88,13 +88,13 @@ void main() { ); test( - "should listen event using dependency and emit event using RtDependency", + "should listen event using dependency and emit event using RtDependencyRef", () { final testController = Rt.create(() => TestController())!; _testListenAndEmitEvent( testController, - RtDependency(), + RtDependencyRef(), instanceMatcher: testController, ); @@ -103,7 +103,7 @@ void main() { ); test( - "should listen event using the dependency and emit event using RtDependency with id", + "should listen event using the dependency and emit event using RtDependencyRef with id", () { final testController = Rt.create( () => TestController(), @@ -112,7 +112,7 @@ void main() { _testListenAndEmitEvent( testController, - RtDependency('uniqueId'), + RtDependencyRef('uniqueId'), instanceMatcher: testController, ); @@ -121,12 +121,12 @@ void main() { ); test( - "should listen event using RtDependency and emit event using the dependency", + "should listen event using RtDependencyRef and emit event using the dependency", () { final testController = Rt.create(() => TestController())!; _testListenAndEmitEvent( - RtDependency(), + RtDependencyRef(), testController, instanceMatcher: testController, ); @@ -136,7 +136,7 @@ void main() { ); test( - "should listen event using RtDependency and emit event using the dependency with id", + "should listen event using RtDependencyRef and emit event using the dependency with id", () { final testController = Rt.create( () => TestController(), @@ -144,7 +144,7 @@ void main() { )!; _testListenAndEmitEvent( - RtDependency('uniqueId'), + RtDependencyRef('uniqueId'), testController, instanceMatcher: testController, ); @@ -164,21 +164,21 @@ void main() { ); _testListenAndEmitEvent( - RtDependency(), - RtDependency(), + RtDependencyRef(), + RtDependencyRef(), instanceMatcher: testController, isOnce: true, ); _testListenAndEmitEvent( testController, - RtDependency(), + RtDependencyRef(), instanceMatcher: testController, isOnce: true, ); _testListenAndEmitEvent( - RtDependency(), + RtDependencyRef(), testController, instanceMatcher: testController, isOnce: true, @@ -201,21 +201,21 @@ void main() { ); _testListenAndEmitEvent( - RtDependency('uniqueId'), - RtDependency('uniqueId'), + RtDependencyRef('uniqueId'), + RtDependencyRef('uniqueId'), instanceMatcher: testController, isOnce: true, ); _testListenAndEmitEvent( testController, - RtDependency('uniqueId'), + RtDependencyRef('uniqueId'), instanceMatcher: testController, isOnce: true, ); _testListenAndEmitEvent( - RtDependency('uniqueId'), + RtDependencyRef('uniqueId'), testController, instanceMatcher: testController, isOnce: true, @@ -229,6 +229,29 @@ void main() { "should unlisten event with id", () => _testUnlistenEvent(withId: true), ); + + test('should catch error while emit event', () { + final testController = Rt.create(() => TestController()); + + Rt.on( + testController, + Events.TestEvent, + (inst, param) { + throw Error(); + }, + ); + + expect( + () => Rt.emit( + testController, + Events.TestEvent, + TEST_EVENT_PARAM_NAME, + ), + throwsA(isA()), + ); + + Rt.offAll(testController); + }); }); } @@ -293,7 +316,7 @@ void _testUnlistenEvent({bool withId = false}) { Rt.on(testController, Events.TestEvent, onTestEvent); Rt.on( - RtDependency(id), + RtDependencyRef(id), Events.TestEvent, (inst, param) { expect(inst, testController); @@ -301,7 +324,7 @@ void _testUnlistenEvent({bool withId = false}) { }, ); Rt.on( - RtDependency(id), + RtDependencyRef(id), Events.TestEvent2, onTestEvent2, ); @@ -315,7 +338,7 @@ void _testUnlistenEvent({bool withId = false}) { expect(countEvent2, 0); Rt.emit( - RtDependency(id), + RtDependencyRef(id), Events.TestEvent, TEST_EVENT_PARAM_NAME, ); @@ -324,7 +347,7 @@ void _testUnlistenEvent({bool withId = false}) { expect(countEvent2, 0); Rt.emit( - RtDependency(id), + RtDependencyRef(id), Events.TestEvent2, TEST_EVENT2_PARAM_NAME, ); @@ -334,7 +357,7 @@ void _testUnlistenEvent({bool withId = false}) { Rt.off(testController, Events.TestEvent, onTestEvent); Rt.emit( - RtDependency(id), + RtDependencyRef(id), Events.TestEvent, TEST_EVENT_PARAM_NAME, ); @@ -349,13 +372,13 @@ void _testUnlistenEvent({bool withId = false}) { expect(countEvent2, 2); Rt.off( - RtDependency(id), + RtDependencyRef(id), Events.TestEvent2, onTestEvent2, ); Rt.emit(testController, Events.TestEvent2, TEST_EVENT2_PARAM_NAME); Rt.emit( - RtDependency(id), + RtDependencyRef(id), Events.TestEvent2, TEST_EVENT2_PARAM_NAME, ); diff --git a/packages/reactter/test/core/lifecycle_observer_test.dart b/packages/reactter/test/core/lifecycle_observer_test.dart index 7da53afd..2f9edd86 100644 --- a/packages/reactter/test/core/lifecycle_observer_test.dart +++ b/packages/reactter/test/core/lifecycle_observer_test.dart @@ -1,12 +1,12 @@ import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; import '../shareds/test_controllers.dart'; void main() { - group("LifecycleObserver", () { + group("DependencyLifecycle", () { test( - "should resolve the lifecycle event of a LifecycleObserver instance", + "should resolve the lifecycle event of a DependencyLifecycle instance", () { late TestLifecycleController? testLifecycleController; @@ -21,7 +21,7 @@ void main() { expect(testLifecycleController, isA()); Rt.emit( - RtDependency(), + RtDependencyRef(), Lifecycle.willMount, ); @@ -35,7 +35,7 @@ void main() { expect(testLifecycleController.lastState, null); Rt.emit( - RtDependency(), + RtDependencyRef(), Lifecycle.didMount, ); @@ -77,7 +77,7 @@ void main() { ); Rt.emit( - RtDependency(), + RtDependencyRef(), Lifecycle.willUnmount, ); @@ -94,7 +94,7 @@ void main() { ); Rt.emit( - RtDependency(), + RtDependencyRef(), Lifecycle.didUnmount, ); diff --git a/packages/reactter/test/core/state_management_test.dart b/packages/reactter/test/core/state_management_test.dart index e87a1c4d..6530709a 100644 --- a/packages/reactter/test/core/state_management_test.dart +++ b/packages/reactter/test/core/state_management_test.dart @@ -1,5 +1,5 @@ import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; void main() { group("lazyState", () { @@ -63,10 +63,49 @@ void main() { expect(computed.value, 1); }); + + test("should run the async callback without tracking the changes", + () async { + final state = UseState(0); + final computed = UseCompute(() => state.value + 1, [state]); + + await Rt.untracked(() async { + await Future.delayed(Duration(milliseconds: 10)); + + state.value = 2; + + expect(computed.value, 1); + }); + + expect(computed.value, 1); + }); + + test("should run the async callback as nested untracked", () async { + final state = UseState(0); + final computed = UseCompute(() => state.value + 1, [state]); + + await Rt.untracked(() async { + await Future.delayed(Duration(milliseconds: 10)); + + await Rt.untracked(() async { + await Future.delayed(Duration(milliseconds: 10)); + + state.value = 2; + + expect(computed.value, 1); + }); + + state.value += 1; + + expect(computed.value, 1); + }); + + expect(computed.value, 1); + }); }); group("batch", () { - test("should run the callback and change the state after the batch", () { + test("should run the callback and reflect the state after the batch", () { final state = UseState(0); final computed = UseCompute(() => state.value + 1, [state]); @@ -80,22 +119,93 @@ void main() { }); test("should run the callback as nested batch", () { - final state = UseState(0); - final computed = UseCompute(() => state.value + 1, [state]); + final stateA = UseState(0); + final stateB = UseState(0); + final stateC = UseState(0); + final computed = UseCompute( + () => stateA.value + stateB.value + stateC.value, + [stateA, stateB, stateC], + ); + + UseEffect(() { + Rt.batch(() { + stateB.value += 1; // 3 + stateC.value += 1; // 1 + + // stateA(2) + stateB(2) + stateC(0) + expect(computed.value, 4); + }); + + // stateA(2) + stateB(3) + stateC(1) + expect(computed.value, 6); + }, [stateA]); Rt.batch(() { + stateA.value += 1; // 1 + Rt.batch(() { - state.value = 2; + stateB.value += 1; // 1 - expect(computed.value, 1); + expect(computed.value, 0); }); - state.value += 1; + stateB.value += 1; // 2 + stateA.value += 1; // 2 - expect(computed.value, 1); + expect(computed.value, 0); + }); // -> go to UseEffect + + // stateA(2) + stateB(3) + stateC(1) + expect(computed.value, 6); + }); + + test("should run the async callback and reflect the state after the batch", + () async { + final stateA = UseState(0); + final stateB = UseState(0); + final computed = UseCompute( + () => stateA.value + stateB.value, + [stateA, stateB], + ); + + await Rt.batch(() async { + stateA.value += 1; // 1 + + await Future.delayed(Duration(milliseconds: 10)); + + stateB.value += 2; // 2 + + expect(computed.value, 0); + }); + + // stateA(1) + stateB(2) + expect(computed.value, 3); + }); + + test("should run the async callback as nested batch", () async { + final stateA = UseState(0); + final stateB = UseState(0); + final computed = UseCompute( + () => stateA.value + stateB.value, + [stateA, stateB], + ); + + await Rt.batch(() async { + stateA.value += 1; // 1 + + await Future.delayed(Duration(milliseconds: 10)); + + await Rt.batch(() async { + stateB.value += 2; // 2 + + expect(computed.value, 0); + }); + + expect(computed.value, 0); }); - expect(computed.value, 4); + // stateA(1) + stateB(2) + expect(computed.value, 3); }); }); } diff --git a/packages/reactter/test/devtools_test.dart b/packages/reactter/test/devtools_test.dart new file mode 100644 index 00000000..82f381ee --- /dev/null +++ b/packages/reactter/test/devtools_test.dart @@ -0,0 +1,70 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reactter/reactter.dart'; +import 'package:reactter/src/devtools.dart'; + +import 'shareds/test_controllers.dart'; + +void main() { + group("Devtools", () { + test('should be initialized', () { + try { + Rt.initializeDevTools(); + } catch (e) { + expect(e, isInstanceOf()); + } + + expect(RtDevTools.instance, isNotNull); + }); + + test('should be register states and dependencies', () { + try { + Rt.initializeDevTools(); + } catch (e) { + expect(e, isInstanceOf()); + } + + final devtools = RtDevTools.instance; + + final Type dependencyType = TestController; + Rt.create(() => TestController()); + + final response = devtools?.getNodes(0, 100); + + expect(response, isNotNull); + + final nodes = response?['nodes']; + + bool isDependencyRegistered = false; + void checkIsDependencyRegistered({ + required String kind, + required String type, + }) { + isDependencyRegistered = isDependencyRegistered || + kind == NodeKind.dependency.toString() && + type.startsWith(dependencyType.toString()); + } + + bool isDependencyCreated = false; + void checkIsDependencyCreated({ + required String kind, + required String type, + }) { + isDependencyCreated = isDependencyCreated || + kind == NodeKind.instance.toString() && + type.startsWith(dependencyType.toString()); + } + + for (final node in nodes) { + final kind = node['kind']; + final type = node['type']; + + checkIsDependencyRegistered(kind: kind, type: type); + checkIsDependencyCreated(kind: kind, type: type); + } + + expect(nodes, isNotNull); + expect(isDependencyRegistered, isTrue); + expect(isDependencyCreated, isTrue); + }); + }); +} diff --git a/packages/reactter/test/framework/rt_dependency_observer_test.dart b/packages/reactter/test/framework/rt_dependency_observer_test.dart new file mode 100644 index 00000000..486bc5d7 --- /dev/null +++ b/packages/reactter/test/framework/rt_dependency_observer_test.dart @@ -0,0 +1,206 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reactter/reactter.dart'; +import 'package:reactter/src/internals.dart'; + +import '../shareds/test_controllers.dart'; + +void main() { + group("RtDependencyObserver", () { + test("should be observed when a dependency is registered", () { + bool onRegisteredCalled = false; + + final observer = RtDependencyObserver( + onRegistered: (dependencyRef) { + expect(dependencyRef, isA>()); + onRegisteredCalled = true; + }, + ); + Rt.addObserver(observer); + + Rt.register(() => TestController()); + + expect(onRegisteredCalled, isTrue); + + Rt.removeObserver(observer); + Rt.unregister(); + }); + + test("should be observed when a dependency is created", () { + bool onCreatedCalled = false; + dynamic instanceCreated; + + final observer = RtDependencyObserver( + onCreated: (dependencyRef, instance) { + expect(dependencyRef, isA>()); + expect(instance, isA()); + onCreatedCalled = true; + instanceCreated = instance; + }, + ); + Rt.addObserver(observer); + + final instance = Rt.create(() => TestController()); + + expect(onCreatedCalled, isTrue); + expect(instanceCreated, instance); + + Rt.removeObserver(observer); + Rt.destroy(); + }); + + test("should be observed when a dependency is mounted", () { + bool onMountedCalled = false; + dynamic instanceMounted; + + final observer = RtDependencyObserver( + onMounted: (dependencyRef, instance) { + expect(dependencyRef, isA>()); + expect(instance, isA()); + onMountedCalled = true; + instanceMounted = instance; + }, + ); + Rt.addObserver(observer); + + final instance = Rt.create(() => TestController()); + Rt.emit(RtDependencyRef(), Lifecycle.didMount, instance); + + expect(onMountedCalled, isTrue); + expect(instanceMounted, instance); + + Rt.removeObserver(observer); + Rt.destroy(); + }); + + test("should be observed when a dependency is unmounted", () { + bool onUnmountedCalled = false; + dynamic instanceUnmounted; + + final observer = RtDependencyObserver( + onUnmounted: (dependencyRef, instance) { + expect(dependencyRef, isA>()); + expect(instance, isA()); + onUnmountedCalled = true; + instanceUnmounted = instance; + }, + ); + Rt.addObserver(observer); + + final instance = Rt.create(() => TestController()); + Rt.emit( + RtDependencyRef(), Lifecycle.didUnmount, instance); + + expect(onUnmountedCalled, isTrue); + expect(instanceUnmounted, instance); + + Rt.removeObserver(observer); + Rt.destroy(); + }); + + test("should be observed when a dependency is deleted", () { + bool onDeletedCalled = false; + dynamic instanceDeleted; + + final observer = RtDependencyObserver( + onDeleted: (dependencyRef, instance) { + expect(dependencyRef, isA>()); + expect(instance, isA()); + onDeletedCalled = true; + instanceDeleted = instance; + }, + ); + Rt.addObserver(observer); + + final instance = Rt.create(() => TestController()); + Rt.delete(); + + expect(onDeletedCalled, isTrue); + expect(instanceDeleted, instance); + + Rt.removeObserver(observer); + }); + + test("should be observed when a dependency is unregistered", () { + bool onUnregisteredCalled = false; + + final observer = RtDependencyObserver( + onUnregistered: (dependencyRef) { + expect(dependencyRef, isA>()); + onUnregisteredCalled = true; + }, + ); + Rt.addObserver(observer); + + Rt.register(() => TestController()); + Rt.unregister(); + + expect(onUnregisteredCalled, isTrue); + + Rt.removeObserver(observer); + }); + + test("should be observed when a dependency is failed", () { + int onFailedCalledCount = 0; + DependencyFail? lastFail; + + final observer = RtDependencyObserver( + onFailed: (dependencyRef, fail) { + expect(dependencyRef is DependencyRef, isTrue); + expect(fail, isA()); + onFailedCalledCount++; + lastFail = fail; + }, + ); + Rt.addObserver(observer); + + Rt.create(() => TestController()); + Rt.register(() => TestController()); + + expect(onFailedCalledCount, 1); + expect(lastFail, DependencyFail.alreadyRegistered); + + Rt.create(() => TestController()); + + // before the last fail, it should be `DependencyFail.alreadyRegistered` again. + expect(onFailedCalledCount, 3); + expect(lastFail, DependencyFail.alreadyCreated); + + Rt.delete(); + Rt.delete(); + + expect(onFailedCalledCount, 4); + expect(lastFail, DependencyFail.alreadyDeleted); + + Rt.unregister(); + + expect(onFailedCalledCount, 5); + expect(lastFail, DependencyFail.alreadyUnregistered); + + Rt.get(); + + expect(onFailedCalledCount, 6); + expect(lastFail, DependencyFail.missingInstanceBuilder); + + Rt.create(() => TestController(), mode: DependencyMode.factory); + Rt.delete(); + + expect(onFailedCalledCount, 7); + expect(lastFail, DependencyFail.builderRetained); + + Rt.destroy(); + Rt.create(() => TestController(), mode: DependencyMode.singleton); + Rt.delete(); + + expect(onFailedCalledCount, 8); + expect(lastFail, DependencyFail.dependencyRetained); + + Rt.unregister(); + + expect(onFailedCalledCount, 9); + expect(lastFail, DependencyFail.cannotUnregisterActiveInstance); + + Rt.removeObserver(observer); + Rt.destroy(); + }); + }); +} diff --git a/packages/reactter/test/framework/rt_dependency_test.dart b/packages/reactter/test/framework/rt_dependency_test.dart index 959d175f..1fb5684e 100644 --- a/packages/reactter/test/framework/rt_dependency_test.dart +++ b/packages/reactter/test/framework/rt_dependency_test.dart @@ -1,10 +1,10 @@ import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; import '../shareds/test_controllers.dart'; void main() { - group("RtDependency", () { + group("RtDependencyRef", () { test("should throw the life-cycle events", () async { late TestController? instance; late bool willUpdateChecked; @@ -12,14 +12,14 @@ void main() { late bool isDeleted; Rt.on( - RtDependency(), + RtDependencyRef(), Lifecycle.created, (TestController? inst, __) { instance = inst; }, ); Rt.on( - RtDependency(), + RtDependencyRef(), Lifecycle.willUpdate, (TestController? inst, UseState hook) { willUpdateChecked = true; @@ -28,7 +28,7 @@ void main() { }, ); Rt.on( - RtDependency(), + RtDependencyRef(), Lifecycle.didUpdate, (TestController? inst, UseState hook) { didUpdateChecked = true; @@ -37,7 +37,7 @@ void main() { }, ); Rt.on( - RtDependency(), + RtDependencyRef(), Lifecycle.deleted, (_, __) { isDeleted = true; diff --git a/packages/reactter/test/framework/rt_state_base_test.dart b/packages/reactter/test/framework/rt_state_base_test.dart new file mode 100644 index 00000000..ffa3fdac --- /dev/null +++ b/packages/reactter/test/framework/rt_state_base_test.dart @@ -0,0 +1,83 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reactter/reactter.dart'; + +class CountTest extends RtState { + int _count = 0; + int get count => _count; + set count(int value) { + if (_count != value) { + update(() { + _count = value; + }); + } + } + + @override + String? get debugLabel => 'CountTest'; + + @override + Map get debugInfo => { + "count": _count, + }; +} + +class StateTest with RtState { + StateTest._() { + assert(dependencyInjection == Rt); + assert(stateManagement == Rt); + assert(eventHandler == Rt); + } + + factory StateTest() { + return Rt.registerState(() => StateTest._()); + } + + @override + String? get debugLabel => 'State2'; +} + +void main() { + group('RtState', () { + test('should create a state object within the BindingZone', () { + expect(() => CountTest(), throwsA(isA())); + + final countState = Rt.registerState(() => CountTest()); + expect(countState.debugLabel, 'CountTest'); + + final state = StateTest(); + expect(state.debugLabel, 'State2'); + }); + + test('should create a state using Dependency Injection', () { + final countState = Rt.create(() => CountTest()); + + expect(countState?.debugLabel, 'CountTest'); + + Rt.delete(); + + final state = Rt.create(() => StateTest()); + expect(state?.debugLabel, 'State2'); + + Rt.delete(); + }); + + test('should update the state', () { + final countState = Rt.registerState(() => CountTest()); + expect(countState.count, 0); + + countState.count = 1; + expect(countState.count, 1); + }); + + test('should have debug info', () { + final countState = Rt.registerState(() => CountTest()); + expect(countState.debugInfo, {"count": 0}); + + countState.count = 1; + expect(countState.debugInfo, {"count": 1}); + + final state = StateTest(); + expect(state.debugInfo, {}); + }); + }); +} diff --git a/packages/reactter/test/framework/rt_state_observer_test.dart b/packages/reactter/test/framework/rt_state_observer_test.dart new file mode 100644 index 00000000..654bb70b --- /dev/null +++ b/packages/reactter/test/framework/rt_state_observer_test.dart @@ -0,0 +1,188 @@ +import 'package:reactter/reactter.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group("RtStateObserver", () { + test("should be observed when a state is created", () { + int onCreatedCalledCount = 0; + String? lastStateCreated; + + final observer = RtStateObserver( + onCreated: (state) { + expect(state, isA()); + onCreatedCalledCount++; + lastStateCreated = state.debugLabel; + }, + ); + Rt.addObserver(observer); + + UseState(0, debugLabel: "stateA"); + + expect(onCreatedCalledCount, 1); + expect(lastStateCreated, "stateA"); + + UseState(1, debugLabel: "stateB"); + + expect(onCreatedCalledCount, 2); + expect(lastStateCreated, "stateB"); + + Rt.removeObserver(observer); + }); + + test("should be observed when a state is bound", () { + int onBoundCalledCount = 0; + String? lastStateBound; + dynamic lastInstanceBound; + + final observer = RtStateObserver( + onBound: (state, instance) { + expect(state, isA()); + onBoundCalledCount++; + lastStateBound = state.debugLabel; + lastInstanceBound = instance; + }, + ); + Rt.addObserver(observer); + + final stateA = UseState(0, debugLabel: "stateA"); + final instanceA = Object(); + stateA.bind(instanceA); + + expect(onBoundCalledCount, 1); + expect(lastStateBound, "stateA"); + expect(lastInstanceBound, instanceA); + + final stateB = UseState(1, debugLabel: "stateB"); + final instanceB = Object(); + stateB.bind(instanceB); + + expect(onBoundCalledCount, 2); + expect(lastStateBound, "stateB"); + expect(lastInstanceBound, instanceB); + + Rt.removeObserver(observer); + }); + + test("should be observed when a state is unbound", () { + int onUnboundCalledCount = 0; + String? lastStateUnbound; + dynamic lastInstanceUnbound; + + final observer = RtStateObserver( + onUnbound: (state, instance) { + expect(state, isA()); + onUnboundCalledCount++; + lastStateUnbound = state.debugLabel; + lastInstanceUnbound = instance; + }, + ); + Rt.addObserver(observer); + + final stateA = UseState(0, debugLabel: "stateA"); + final instanceA = Object(); + stateA.bind(instanceA); + stateA.unbind(); + + expect(onUnboundCalledCount, 1); + expect(lastStateUnbound, "stateA"); + expect(lastInstanceUnbound, instanceA); + + final stateB = UseState(1, debugLabel: "stateB"); + final instanceB = Object(); + stateB.bind(instanceB); + stateB.unbind(); + + expect(onUnboundCalledCount, 2); + expect(lastStateUnbound, "stateB"); + expect(lastInstanceUnbound, instanceB); + + Rt.removeObserver(observer); + }); + + test("should be observed when a state is updated", () { + int onUpdatedCalledCount = 0; + String? lastStateUpdated; + + final observer = RtStateObserver( + onUpdated: (state) { + expect(state, isA()); + onUpdatedCalledCount++; + lastStateUpdated = state.debugLabel; + }, + ); + Rt.addObserver(observer); + + final stateA = UseState(0, debugLabel: "stateA"); + stateA.value = 1; + + expect(onUpdatedCalledCount, 1); + expect(lastStateUpdated, "stateA"); + + final stateB = UseState(1, debugLabel: "stateB"); + stateB.value = 2; + + expect(onUpdatedCalledCount, 2); + expect(lastStateUpdated, "stateB"); + + Rt.removeObserver(observer); + }); + + test("should be observed when a state is disposed", () { + int onDisposedCalledCount = 0; + String? lastStateDisposed; + + final observer = RtStateObserver( + onDisposed: (state) { + expect(state, isA()); + onDisposedCalledCount++; + lastStateDisposed = state.debugLabel; + }, + ); + Rt.addObserver(observer); + + final stateA = UseState(0, debugLabel: "stateA"); + stateA.dispose(); + + expect(onDisposedCalledCount, 1); + expect(lastStateDisposed, "stateA"); + + final stateB = UseState(1, debugLabel: "stateB"); + stateB.dispose(); + + expect(onDisposedCalledCount, 2); + expect(lastStateDisposed, "stateB"); + + Rt.removeObserver(observer); + }); + + test("should be observed when a nested state is updated", () { + int onStateUpdatedCalledCount = 0; + String? lastStateUpdated; + + final observer = RtStateObserver( + onUpdated: (state) { + expect(state, isA()); + onStateUpdatedCalledCount++; + lastStateUpdated = state.debugLabel; + }, + ); + Rt.addObserver(observer); + + final stateA = UseState(0, debugLabel: "stateA"); + final stateB = UseState(1, debugLabel: "stateB"); + stateB.bind(stateA); + + stateB.value = 2; + + expect(onStateUpdatedCalledCount, 2); + expect(lastStateUpdated, "stateA"); + + stateA.value = 3; + + expect(onStateUpdatedCalledCount, 3); + expect(lastStateUpdated, "stateA"); + + Rt.removeObserver(observer); + }); + }); +} diff --git a/packages/reactter/test/hooks/use_async_state_test.dart b/packages/reactter/test/hooks/use_async_state_test.dart index 813dd4a3..323e5f86 100644 --- a/packages/reactter/test/hooks/use_async_state_test.dart +++ b/packages/reactter/test/hooks/use_async_state_test.dart @@ -1,5 +1,5 @@ import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; import '../shareds/test_controllers.dart'; @@ -10,58 +10,157 @@ void main() { final stateAsync = testController.stateAsync; expect(stateAsync.value, "initial"); + expect(stateAsync.status, UseAsyncStateStatus.idle); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, false); + expect(stateAsync.isDone, false); + expect(stateAsync.isError, false); - await stateAsync.resolve(); + final executing = stateAsync.resolve(); + + expect(stateAsync.value, "initial"); + expect(stateAsync.status, UseAsyncStateStatus.loading); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, true); + expect(stateAsync.isDone, false); + expect(stateAsync.isError, false); + + final response = await executing; + expect(response, "resolved"); expect(stateAsync.value, "resolved"); + expect(stateAsync.status, UseAsyncStateStatus.done); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, false); + expect(stateAsync.isDone, true); + expect(stateAsync.isError, false); }); test("should catch error", () async { final testController = TestController(); final stateAsync = testController.stateAsyncWithError; - await stateAsync.resolve(); + expect(stateAsync.value, "initial"); + expect(stateAsync.status, UseAsyncStateStatus.idle); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, false); + expect(stateAsync.isDone, false); + expect(stateAsync.isError, false); + + final executing = stateAsync.resolve(); + expect(stateAsync.value, "initial"); + expect(stateAsync.status, UseAsyncStateStatus.loading); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, true); + expect(stateAsync.isDone, false); + expect(stateAsync.isError, false); + + final response = await executing; + + expect(response, null); expect(stateAsync.value, "initial"); expect(stateAsync.status, UseAsyncStateStatus.error); expect(stateAsync.error.toString(), "Exception: has a error"); + expect(stateAsync.isLoading, false); + expect(stateAsync.isDone, false); + expect(stateAsync.isError, true); }); test("should reset state", () async { final testController = TestController(); final stateAsync = testController.stateAsync; - await stateAsync.resolve(); + expect(stateAsync.value, "initial"); + expect(stateAsync.status, UseAsyncStateStatus.idle); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, false); + expect(stateAsync.isDone, false); + expect(stateAsync.isError, false); + + final executing = stateAsync.resolve(); + expect(stateAsync.value, "initial"); + expect(stateAsync.status, UseAsyncStateStatus.loading); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, true); + expect(stateAsync.isDone, false); + expect(stateAsync.isError, false); + + final response = await executing; + + expect(response, "resolved"); expect(stateAsync.value, "resolved"); + expect(stateAsync.status, UseAsyncStateStatus.done); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, false); + expect(stateAsync.isDone, true); + expect(stateAsync.isError, false); stateAsync.reset(); expect(stateAsync.value, "initial"); + expect(stateAsync.status, UseAsyncStateStatus.idle); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, false); + expect(stateAsync.isDone, false); + expect(stateAsync.isError, false); }); test("should resolve state with arguments", () async { final testController = TestController(); final stateAsync = testController.stateAsyncWithArg; - await stateAsync.resolve(Args1('arg1')); + expect(stateAsync.value, "initial"); + expect(stateAsync.status, UseAsyncStateStatus.idle); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, false); + expect(stateAsync.isDone, false); + expect(stateAsync.isError, false); + + final executing = stateAsync.resolve(Args1('arg1')); + + expect(stateAsync.value, "initial"); + expect(stateAsync.status, UseAsyncStateStatus.loading); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, true); + expect(stateAsync.isDone, false); + expect(stateAsync.isError, false); + final response = await executing; + + expect(response, "resolved with args: arg1"); expect(stateAsync.value, "resolved with args: arg1"); + expect(stateAsync.status, UseAsyncStateStatus.done); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, false); + expect(stateAsync.isDone, true); + expect(stateAsync.isError, false); await stateAsync.resolve(Args2('arg1', 'arg2')); expect(stateAsync.value, "resolved with args: arg1,arg2"); + expect(stateAsync.status, UseAsyncStateStatus.done); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, false); + expect(stateAsync.isDone, true); + expect(stateAsync.isError, false); await stateAsync.resolve(Args3('arg1', 'arg2', 'arg3')); expect(stateAsync.value, "resolved with args: arg1,arg2,arg3"); + expect(stateAsync.status, UseAsyncStateStatus.done); + expect(stateAsync.error, null); + expect(stateAsync.isLoading, false); + expect(stateAsync.isDone, true); + expect(stateAsync.isError, false); }); test("should get value when", () async { final testController = TestController(); final stateAsync = testController.stateAsyncWithArg; - final s1 = stateAsync.when(standby: (value) => value); + final s1 = stateAsync.when(idle: (value) => value); expect(s1, "initial"); final futureResolve = stateAsync.resolve(Args1(null)); @@ -81,8 +180,75 @@ void main() { stateAsync.reset(); - final s5 = stateAsync.when(standby: (value) => value); + final s5 = stateAsync.when(idle: (value) => value); expect(s5, "initial"); }); + + test("should get the future", () async { + final stateAsync = UseAsyncState( + () async => Future.value("resolved"), + "initial", + ); + + expect(stateAsync.future, doesNotComplete); + + await stateAsync.resolve(); + + expect(stateAsync.future, completes); + expect(await stateAsync.future, "resolved"); + }); + + test("should execute a sync method", () async { + final stateAsync = UseAsyncState( + () => "RESOLVED", + "initial", + ); + + await stateAsync.resolve(); + + expect(stateAsync.value, "RESOLVED"); + expect(stateAsync.status, UseAsyncStateStatus.done); + }); + + test('should cancel the async function', () async { + final stateAsync = UseAsyncState( + () async { + await Future.delayed(Duration(milliseconds: 100)); + return "resolved"; + }, + "initial", + ); + + final executing = stateAsync.resolve(); + stateAsync.cancel(); + await executing; + + expect(stateAsync.value, "initial"); + expect(stateAsync.status, UseAsyncStateStatus.done); + + stateAsync.cancel(); + await stateAsync.resolve(); + + expect(stateAsync.value, "resolved"); + expect(stateAsync.status, UseAsyncStateStatus.done); + }); + + test("should get debugLabel", () { + final testController = TestController(); + final stateAsync = testController.stateAsync; + + expect(stateAsync.debugLabel, "stateAsync"); + }); + + test('should get debugInfo', () { + final testController = TestController(); + final stateAsync = testController.stateAsync; + + expect(stateAsync.debugInfo, { + 'value': "initial", + 'error': null, + 'status': UseAsyncStateStatus.idle, + }); + }); }); } diff --git a/packages/reactter/test/hooks/use_compute_test.dart b/packages/reactter/test/hooks/use_compute_test.dart index 3cf1a94c..47ed5629 100644 --- a/packages/reactter/test/hooks/use_compute_test.dart +++ b/packages/reactter/test/hooks/use_compute_test.dart @@ -1,5 +1,5 @@ import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; import '../shareds/test_controllers.dart'; @@ -55,6 +55,35 @@ void main() { Rt.delete(); }, ); + + test('should get debug label', () { + final testController = Rt.create( + () => TestController(), + )!; + + expect(testController.stateCompute.debugLabel, 'stateCompute'); + + Rt.delete(); + }); + + test('should get debug info', () { + final testController = Rt.create( + () => TestController(), + )!; + + expect( + testController.stateCompute.debugInfo, + { + 'value': 5, + 'dependencies': [ + testController.stateInt, + testController.stateDouble, + ], + }, + ); + + Rt.delete(); + }); }, ); } diff --git a/packages/reactter/test/hooks/use_dependency_test.dart b/packages/reactter/test/hooks/use_dependency_test.dart index e8da2cb0..d957a5c1 100644 --- a/packages/reactter/test/hooks/use_dependency_test.dart +++ b/packages/reactter/test/hooks/use_dependency_test.dart @@ -1,7 +1,7 @@ // ignore_for_file: constant_identifier_names import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; import '../shareds/test_controllers.dart'; @@ -61,8 +61,10 @@ void main() { }); test("should register a dependency with id in builder mode", () { - final useDependency = - UseDependency.lazyBuilder(() => TestController(), ID); + final useDependency = UseDependency.lazyBuilder( + () => TestController(), + id: ID, + ); expect(useDependency.instance, null); final instance = Rt.get(ID); @@ -94,8 +96,10 @@ void main() { }); test("should register a dependency with id in factory mode", () { - final useDependency = - UseDependency.lazyFactory(() => TestController(), ID); + final useDependency = UseDependency.lazyFactory( + () => TestController(), + id: ID, + ); expect(useDependency.instance, null); final instance = Rt.get(ID); @@ -127,8 +131,10 @@ void main() { }); test("should register a dependency with id in singleton mode", () { - final useDependency = - UseDependency.lazySingleton(() => TestController(), ID); + final useDependency = UseDependency.lazySingleton( + () => TestController(), + id: ID, + ); expect(useDependency.instance, null); final instance = Rt.get(ID); @@ -167,7 +173,10 @@ void main() { }); test("should create a dependency with id in builder mode", () { - final useDependency = UseDependency.builder(() => TestController(), ID); + final useDependency = UseDependency.builder( + () => TestController(), + id: ID, + ); expect(useDependency.instance, isA()); final isActive = Rt.isActive(useDependency.instance); @@ -193,7 +202,10 @@ void main() { }); test("should create a dependency with id in factory mode", () { - final useDependency = UseDependency.factory(() => TestController(), ID); + final useDependency = UseDependency.factory( + () => TestController(), + id: ID, + ); expect(useDependency.instance, isA()); final isActive = Rt.isActive(useDependency.instance); @@ -219,7 +231,10 @@ void main() { }); test("should create a dependency with id in singleton mode", () { - final useDependency = UseDependency.singleton(() => TestController(), ID); + final useDependency = UseDependency.singleton( + () => TestController(), + id: ID, + ); expect(useDependency.instance, isA()); final isActive = Rt.isActive(useDependency.instance); @@ -246,7 +261,7 @@ void main() { test("should get a dependency with id", () { Rt.register(() => TestController(), id: ID); - final useDependency = UseDependency.get(ID); + final useDependency = UseDependency.get(id: ID); expect(useDependency.instance, isA()); final isActive = Rt.isActive(useDependency.instance); @@ -254,13 +269,44 @@ void main() { Rt.destroy(id: ID); }); + + test('should get debug label', () { + final useDependency = UseDependency.lazySingleton( + () => TestController(), + debugLabel: 'useDependency', + ); + + expect(useDependency.debugLabel, 'useDependency'); + + Rt.destroy(); + }); + + test('should get debug info', () { + final testController = Rt.create(() => TestController(), id: 'TEST')!; + + final useDependency = UseDependency.create( + () => testController, + id: 'TEST', + debugLabel: 'useDependency', + ); + + expect( + useDependency.debugInfo, + { + 'id': 'TEST', + 'instance': testController, + }, + ); + + Rt.destroy(id: 'TEST'); + }); }); } void _testController([String? id]) { Rt.create(() => TestController(), id: id); - final useDependency = UseDependency(id); + final useDependency = UseDependency(id: id); expect(useDependency.instance, isA()); @@ -269,9 +315,9 @@ void _testController([String? id]) { void _testControllerLate([String? id]) { late final TestController instance; - final useDependency = UseDependency(id); + final useDependency = UseDependency(id: id); - UseEffect(() { + final uEffect = UseEffect(() { if (useDependency.instance != null) { instance = useDependency.instance!; } @@ -280,5 +326,8 @@ void _testControllerLate([String? id]) { Rt.create(() => TestController(), id: id); Rt.destroy(id: id); + uEffect.dispose(); + useDependency.dispose(); + expectLater(instance, isA()); } diff --git a/packages/reactter/test/hooks/use_effect_test.dart b/packages/reactter/test/hooks/use_effect_test.dart index 66f09ce0..f8a1de05 100644 --- a/packages/reactter/test/hooks/use_effect_test.dart +++ b/packages/reactter/test/hooks/use_effect_test.dart @@ -1,5 +1,5 @@ import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; import '../shareds/test_controllers.dart'; @@ -8,11 +8,11 @@ void main() { final testController = Rt.create(() => TestController()); final uEffect = UseEffect(() {}, [])..bind(testController!); - expect(uEffect.instanceBinded, isA()); + expect(uEffect.boundInstance, isA()); uEffect.unbind(); - expect(uEffect.instanceBinded, isNull); + expect(uEffect.boundInstance, isNull); Rt.delete(); }); @@ -128,7 +128,8 @@ void main() { Rt.delete(); }); - test("should be called with DispatchEffect when it initialized later", () { + test("should be called with AutoDispatchEffect when it initialized later", + () { final testController = UseEffectDispatchController(); int nCalls = 0; @@ -146,7 +147,7 @@ void main() { expect(nCalls, 0); expect(uEffect, isA()); - expect(uEffect.instanceBinded, isA()); + expect(uEffect.boundInstance, isA()); expect(nCalls, 1); }); @@ -171,6 +172,16 @@ void main() { expect(nCalls, 2); }); + test("should catch error", () { + expect( + () { + UseEffect.runOnInit(() { + throw Exception("Error"); + }, []); + }, + throwsA(isA()), + ); + }); }); group("UseEffect's cleaup", () { @@ -243,7 +254,7 @@ void main() { Rt.delete(); }); - test("should be called with dispatchEffect", () { + test("should be called with autodispatchEffect", () { final testController = TestController(); final stateA = testController.stateBool; final stateB = testController.stateInt; @@ -254,17 +265,21 @@ void main() { UseEffect( () { return () { - nCalls += 1; + nCalls++; if (nCalls == 1) { - expect(stateA.value, true); + expect(stateA.value, false); expect(stateB.value, 0); expect(stateC.value, "initial"); } else if (nCalls == 2) { expect(stateA.value, true); - expect(stateB.value, 1); + expect(stateB.value, 0); expect(stateC.value, "initial"); } else if (nCalls == 3) { + expect(stateA.value, true); + expect(stateB.value, 1); + expect(stateC.value, "initial"); + } else if (nCalls == 4) { expect(stateA.value, true); expect(stateB.value, 1); expect(stateC.value, "new value"); @@ -324,10 +339,58 @@ void main() { Rt.unregister(); }); + + test("should catch error", () { + expect( + () { + UseEffect.runOnInit(() { + () => throw Exception("Error"); + }, []); + }, + isNot(throwsA(isA())), + ); + + expect( + () { + final stateA = UseState('initial'); + + UseEffect.runOnInit(() { + return () => throw Exception(stateA.value); + }, [stateA]); + + stateA.value = 'throw error'; + }, + throwsA(isA()), + ); + }); + }); + + test("UseEffect should get debug label", () { + final uEffect = UseEffect(() {}, [], debugLabel: "uEffect"); + + expect(uEffect.debugLabel, "uEffect"); + }); + + test("UseEffect should get debug info", () { + final testController = Rt.create(() => UseEffectTestController())!; + final stateA = testController.stateBool; + final stateB = testController.stateInt; + final stateC = testController.signalString; + + final uEffect = UseEffect(() {}, [stateA, stateB, stateC]); + + expect( + uEffect.debugInfo, + { + "dependencies": [stateA, stateB, stateC], + }, + ); + + Rt.delete(); }); } -class UseEffectDispatchController extends DispatchEffect {} +class UseEffectDispatchController extends AutoDispatchEffect {} class UseEffectTestController extends TestController { final testControllerInner = TestController(); diff --git a/packages/reactter/test/hooks/use_reducer_test.dart b/packages/reactter/test/hooks/use_reducer_test.dart index 97818b0f..e75ffde0 100644 --- a/packages/reactter/test/hooks/use_reducer_test.dart +++ b/packages/reactter/test/hooks/use_reducer_test.dart @@ -1,5 +1,5 @@ import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; import '../shareds/test_controllers.dart'; @@ -53,5 +53,21 @@ void main() { expectLater(isStateChanged, true); }); + + test('should get debug label', () { + final testController = TestController(); + + expect(testController.stateReduce.debugLabel, 'stateReduce'); + }); + + test('should get debug info', () { + final testController = TestController(); + + expect(testController.stateReduce.debugInfo['value'].count, 0); + + testController.stateReduce.dispatch(IncrementAction()); + + expect(testController.stateReduce.debugInfo['value'].count, 1); + }); }); } diff --git a/packages/reactter/test/hooks/use_state_test.dart b/packages/reactter/test/hooks/use_state_test.dart index 3549de2c..b15a15b7 100644 --- a/packages/reactter/test/hooks/use_state_test.dart +++ b/packages/reactter/test/hooks/use_state_test.dart @@ -1,5 +1,5 @@ import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; import '../shareds/test_controllers.dart'; @@ -90,15 +90,69 @@ void main() { }, ); - testController.update( - () => testController.stateInt.update(() { - testController.stateInt.value = 3; - }), + testController.stateInt.update(() { + testController.stateInt.value = 3; + }); + + expectLater(testController.stateInt.value, 3); + + Rt.delete(); + }); + + test("should update manually", () { + final testController = Rt.create(() => TestController())!; + late bool didUpdate; + + testController.stateInt.value = 1; + + Rt.one(testController.stateInt, Lifecycle.didUpdate, (_, __) { + expect(testController.stateInt.value, 1); + didUpdate = true; + }); + + testController.stateInt.update(); + + expectLater(didUpdate, true); + + Rt.delete(); + }); + + test("should update manually with callback", () { + final testController = Rt.create(() => TestController())!; + + testController.stateInt.value = 1; + + testController.stateInt.update(() { + testController.stateInt.value += 1; + }); + + expect(testController.stateInt.value, 2); + + Rt.delete(); + }); + + test("should get debug label", () { + final testController = Rt.create(() => TestController())!; + + expect(testController.stateInt.debugLabel, 'stateInt'); + + Rt.delete(); + }); + + test("should get debug info", () { + final testController = Rt.create(() => TestController())!; + + expect( + testController.stateInt.debugInfo, + {'value': 0}, ); - await Future.microtask(() {}); + testController.stateInt.value = 1; - expectLater(testController.stateInt.value, 3); + expect( + testController.stateInt.debugInfo, + {'value': 1}, + ); Rt.delete(); }); diff --git a/packages/reactter/test/logger_test.dart b/packages/reactter/test/logger_test.dart new file mode 100644 index 00000000..138c0158 --- /dev/null +++ b/packages/reactter/test/logger_test.dart @@ -0,0 +1,253 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reactter/reactter.dart'; +import 'package:reactter/src/logger.dart'; + +class StateTest with RtState { + StateTest._(); + + factory StateTest() { + return Rt.registerState(() => StateTest._()); + } + + @override + String? get debugLabel => 'StateTest'; + + @override + Map get debugInfo => {}; +} + +void main() { + final List> logs = []; + + void output( + String message, { + required String name, + required int level, + StackTrace? stackTrace, + }) { + logs.add({ + 'message': message, + 'name': name, + 'level': level, + 'stackTrace': stackTrace, + }); + } + + List> initialized() { + try { + Rt.initializeLogger(name: 'Reactter Test', output: output); + } catch (e) { + expect(e, isInstanceOf()); + } + + expect(RtLogger.instance, isNotNull); + + return logs; + } + + group('Logger', () { + test('should be initialized', () { + initialized(); + }); + + test('should be log', () { + initialized(); + + final logger = RtLogger.instance; + + logger?.log('test'); + + expect(logs.last['name'], 'Reactter Test'); + expect(logs.last['level'], LogLevel.all); + expect(logs.last['message'], 'test'); + expect(logs.last['stackTrace'], isNotNull); + }); + + test('should be state log', () { + initialized(); + + final uState = UseState('test'); + + expect(logs.last['level'], LogLevel.finer); + expect(logs.last['message'], '${prettyFormat(uState)} created.'); + + final stateTest = StateTest(); + + expect(logs.last['level'], LogLevel.finer); + expect(logs.last['message'], '${prettyFormat(stateTest)} created.'); + + final instanceToBind = Symbol('test'); + + uState.bind(instanceToBind); + + expect(logs.last['level'], LogLevel.warning); + expect( + logs.last['message'], + startsWith( + 'The bound instance(${prettyFormat(instanceToBind)}) to state(${prettyFormat(uState)})', + ), + ); + + uState.unbind(); + + expect(logs.last['level'], LogLevel.finer); + expect( + logs.last['message'], + '${prettyFormat(uState)} unbound from ${prettyFormat(instanceToBind)}.', + ); + + uState.value = 'new value'; + + expect(logs.last['level'], LogLevel.finer); + expect(logs.last['message'], '${prettyFormat(uState)} updated.'); + + uState.dispose(); + + expect(logs.last['level'], LogLevel.finer); + expect(logs.last['message'], '${prettyFormat(uState)} disposed.'); + }); + + test('should be dependency log', () { + initialized(); + + final instance = StateTest(); + final id = 'test'; + + Rt.register(() => instance, id: id); + + expect(logs.last['level'], LogLevel.fine); + expect( + logs.last['message'], + '${prettyFormat(RtDependencyRef(id))} registered.', + ); + + final dependency = Rt.getDependencyRegisterByRef( + RtDependencyRef(id), + ); + + Rt.create(() => instance, id: id); + + expect(logs.last['level'], LogLevel.fine); + expect( + logs.last['message'], + '${prettyFormat(dependency)} created. Its instance: ${prettyFormat(instance)}.', + ); + + Rt.emit(dependency, Lifecycle.didMount, instance); + + expect(logs.last['level'], LogLevel.fine); + expect( + logs.last['message'], + '${prettyFormat(dependency)} mounted. Its instance: ${prettyFormat(instance)}.', + ); + + Rt.emit(dependency, Lifecycle.didUnmount, instance); + + expect(logs.last['level'], LogLevel.fine); + expect( + logs.last['message'], + '${prettyFormat(dependency)} unmounted. Its instance: ${prettyFormat(instance)}.', + ); + + instance.dispose(); + + Rt.destroy(id: id, onlyInstance: true); + + expect(logs.last['level'], LogLevel.fine); + expect( + logs.last['message'], + '${prettyFormat(dependency)} deleted. Its instance: ${prettyFormat(instance)}.', + ); + + Rt.unregister(id); + + expect(logs.last['level'], LogLevel.fine); + expect( + logs.last['message'], + '${prettyFormat(dependency)} unregistered.', + ); + + Rt.register(() => instance, id: id); + Rt.register(() => instance, id: id); + + expect(logs.last['level'], LogLevel.info); + expect( + logs.last['message'], + '${prettyFormat(dependency)} already registered.', + ); + + Rt.create(() => instance, id: id); + Rt.create(() => instance, id: id); + + expect(logs.last['level'], LogLevel.info); + expect( + logs.last['message'], + '${prettyFormat(dependency)} already created.', + ); + + Rt.delete(id); + Rt.delete(id); + + expect(logs.last['level'], LogLevel.info); + expect( + logs.last['message'], + '${prettyFormat(RtDependencyRef(id))} already deleted.', + ); + + Rt.unregister(id); + Rt.unregister(id); + + expect(logs.last['level'], LogLevel.info); + expect( + logs.last['message'], + '${prettyFormat(RtDependencyRef(id))} already unregistered.', + ); + + Rt.create(() => StateTest(), id: id, mode: DependencyMode.factory); + final dependencyFactoryRef = Rt.getDependencyRegisterByRef( + RtDependencyRef(id), + ); + Rt.delete(id); + + expect(logs.last['level'], LogLevel.info); + expect( + logs.last['message'], + "${prettyFormat(dependencyFactoryRef)}'s instance retained because is in factory mode.", + ); + + Rt.destroy(id: id); + Rt.create(() => StateTest(), id: id, mode: DependencyMode.singleton); + final dependencySingletonRef = Rt.getDependencyRegisterByRef( + RtDependencyRef(id), + ); + Rt.delete(id); + + expect(logs.last['level'], LogLevel.info); + expect( + logs.last['message'], + "${prettyFormat(dependencySingletonRef)} retained because is in singleton mode.", + ); + + Rt.destroy(id: id); + Rt.get(id); + + expect(logs.last['level'], LogLevel.warning); + expect( + logs.last['message'], + startsWith( + "${prettyFormat(RtDependencyRef(id))} builder was not registered previously."), + ); + + Rt.create(() => StateTest(), id: id); + Rt.unregister(id); + + expect(logs.last['level'], LogLevel.severe); + expect( + logs.last['message'], + startsWith( + "${prettyFormat(RtDependencyRef(id))} couldn't unregister", + ), + ); + }); + }); +} diff --git a/packages/reactter/test/memo_test.dart b/packages/reactter/test/memo_test.dart index fefa781a..d0935edb 100644 --- a/packages/reactter/test/memo_test.dart +++ b/packages/reactter/test/memo_test.dart @@ -1,7 +1,7 @@ import 'package:fake_async/fake_async.dart'; import 'package:reactter/reactter.dart'; -import 'package:reactter/src/memo/memo.dart'; -import 'package:test/test.dart'; + +import 'package:flutter_test/flutter_test.dart'; import 'shareds/test_controllers.dart'; @@ -103,7 +103,7 @@ void main() { test( "shouldn't memoize when an error Future occurs " - "using AsyncMemoSafe interceptor", () async { + "using MemoSafeAsyncInterceptor interceptor", () async { final memo = Memo, Args1>( (Args1 args) { return Future.error(args.arg1); @@ -131,7 +131,7 @@ void main() { fakeAsync((async) { final memo = Memo( (Args1 args) => args.arg1, - TemporaryCacheMemo( + MemoTemporaryCacheInterceptor( Duration(minutes: 1), ), ); @@ -165,11 +165,11 @@ void main() { final nInterceptors = 2; - final memoInterceptors = MemoInterceptors([ + final memoInterceptors = MultiMemoInterceptor([ FakeInterceptorForCoverage(), ...List.generate( nInterceptors, - (_) => MemoInterceptorWrapper( + (_) => MemoWrapperInterceptor( onInit: (memo, args) { nCallOnInit += 1; }, diff --git a/packages/reactter/test/obj_test.dart b/packages/reactter/test/obj_test.dart deleted file mode 100644 index bcd48e22..00000000 --- a/packages/reactter/test/obj_test.dart +++ /dev/null @@ -1,134 +0,0 @@ -// ignore_for_file: unnecessary_null_comparison, unrelated_type_equality_checks - -import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; - -import 'shareds/test_controllers.dart'; - -void main() { - group("Obj", () { - test("should be created by any type value", () { - final objBool = Obj(true); - expect(objBool, isA>()); - expect(objBool.value, true); - expect("$objBool", true.toString()); - - final objInt = Obj(1); - expect(objInt, isA>()); - expect(objInt.value, 1); - expect("$objInt", 1.toString()); - - final objDouble = Obj(1.2); - expect(objDouble, isA>()); - expect(objDouble.value, 1.2); - expect("$objDouble", 1.2.toString()); - - final objString = Obj("test"); - expect(objString, isA>()); - expect(objString.value, "test"); - - final objList = Obj(["test"]); - expect(objList, isA>>()); - expect(objList.value.first, "test"); - expect("$objList", ["test"].toString()); - - final objMap = Obj({"test": 1}); - expect(objMap, isA>>()); - expect(objMap["test"], 1); - expect("$objMap", {"test": 1}.toString()); - - final objAnother = Obj(TestClass("test")); - expect(objAnother, isA>()); - expect(objAnother.value.prop, "test"); - expect("$objAnother", TestClass("test").toString()); - - final objNull = Obj(null); - expect(objNull, isA>()); - expect(objNull.value, null); - expect("$objNull", null.toString()); - }); - - test("should set a new value", () { - final objBool = Obj(true); - objBool.value = false; - expect(objBool.value, false); - expect(objBool(true), true); - - final objInt = Obj(1); - objInt.value = 2; - expect(objInt.value, 2); - expect(objInt(3), 3); - - final objDouble = Obj(1.2); - objDouble.value = 2.3; - expect(objDouble.value, 2.3); - expect(objDouble(3.4), 3.4); - - final objString = Obj("test"); - objString.value = "other value"; - expect(objString.value, "other value"); - expect(objString("other value by call"), "other value by call"); - - final objList = Obj(["test"]); - objList.value = ["other value"]; - expect(objList.value.first, "other value"); - expect(objList(["other value by call"]).first, "other value by call"); - - final objMap = Obj({"test": 1}); - objMap.value = {"other": 2}; - expect(objMap["other"], 2); - expect(objMap({"other by call": 3})["other by call"], 3); - - final objAnother = Obj(TestClass("test")); - objAnother.value = TestClass("other instance"); - expect(objAnother.value.prop, "other instance"); - expect(objAnother(TestClass("other instance by call")).prop, - "other instance by call"); - - final objNull = Obj(null); - objNull.value = TestClass("not null"); - expect(objNull.value?.prop, "not null"); - expect(objNull(TestClass("not null by call"))?.prop, "not null by call"); - }); - - test("should be compared to another for its value", () { - final objBool = Obj(true); - expect(objBool == true, true); - expect(objBool == Obj(true), false); - - final objInt = Obj(1); - expect(objInt == 1, true); - expect(objInt == Obj(1), false); - - final objDouble = Obj(1.2); - expect(objDouble == 1.2, true); - expect(objDouble == Obj(1.2), false); - - final objString = Obj("test"); - expect(objString == "test", true); - expect(objString == Obj("test"), false); - - final objList = Obj(["test"]); - expect(objList == ["test"], false); - expect(objList == Obj(["test"]), false); - - final objMap = Obj({"test": 1}); - expect(objMap == {"test": 1}, false); - expect(objMap == Obj({"test": 1}), false); - - final objAnother = Obj(TestClass("test")); - expect(objAnother == TestClass("test"), false); - expect(objAnother == Obj(TestClass("test")), false); - - final objNull = Obj(null); - expect(objNull == null, false); - expect(objNull == Obj(null), false); - }); - - test("should be cast away nullability", () { - final objNull = Obj(true); - expect(objNull.value, true); - expect(objNull.notNull, isA>()); - }); - }); -} diff --git a/packages/reactter/test/shareds/test_controllers.dart b/packages/reactter/test/shareds/test_controllers.dart index bb70ac29..abc76a4f 100644 --- a/packages/reactter/test/shareds/test_controllers.dart +++ b/packages/reactter/test/shareds/test_controllers.dart @@ -75,29 +75,44 @@ TestStore _reducer(TestStore state, RtAction action) { } } -class TestController extends RtState { - final signalString = Signal("initial"); - final stateBool = UseState(false); - final stateString = UseState("initial"); - final stateInt = UseState(0); - final stateDouble = UseState(0.0); - final stateList = UseState([]); - final stateMap = UseState({}); - final stateClass = UseState(null); - final stateAsync = UseAsyncState("initial", _resolveStateAsync); - final stateAsyncWithArg = UseAsyncState.withArg( +class TestController { + final signalString = Signal("initial", debugLabel: "signalString"); + final stateBool = UseState(false, debugLabel: "stateBool"); + final stateString = UseState("initial", debugLabel: "stateString"); + final stateInt = UseState(0, debugLabel: "stateInt"); + final stateDouble = UseState(0.0, debugLabel: "stateDouble"); + final stateList = UseState([], debugLabel: "stateList"); + final stateMap = UseState({}, debugLabel: "stateMap"); + final stateClass = UseState(null, debugLabel: "stateClass"); + final stateAsync = UseAsyncState( + _resolveStateAsync, "initial", + debugLabel: "stateAsync", + ); + final stateAsyncWithArg = UseAsyncState.withArg( _resolveStateAsync, + "initial", + debugLabel: "stateAsyncWithArg", + ); + final stateAsyncWithError = UseAsyncState( + () async { + await Future.delayed(Duration(milliseconds: 1)); + throw Exception("has a error"); + }, + "initial", + debugLabel: "stateAsyncWithError", + ); + final stateReduce = UseReducer( + _reducer, + TestStore(count: 0), + debugLabel: "stateReduce", ); - final stateAsyncWithError = UseAsyncState("initial", () { - throw Exception("has a error"); - }); - final stateReduce = UseReducer(_reducer, TestStore(count: 0)); late final stateCompute = Rt.lazyState( () => UseCompute( () => (stateInt.value + stateDouble.value).clamp(5, 10), [stateInt, stateDouble], + debugLabel: "stateCompute", ), this, ); @@ -128,9 +143,15 @@ class TestController extends RtState { final inlineMemo = Memo.inline((Args? args) { return args?.arguments ?? []; }); + + // TestController() { + // assert(dependencyInjection == Rt); + // assert(stateManagement == Rt); + // assert(eventHandler == Rt); + // } } -class TestLifecycleController extends LifecycleObserver { +class TestLifecycleController with RtDependencyLifecycle { final stateString = UseState("initial"); final stateInt = UseState(0); diff --git a/packages/reactter/test/signal_test.dart b/packages/reactter/test/signal_test.dart index 9932e047..780e8012 100644 --- a/packages/reactter/test/signal_test.dart +++ b/packages/reactter/test/signal_test.dart @@ -1,16 +1,10 @@ import 'package:reactter/reactter.dart'; -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'shareds/test_controllers.dart'; void main() { group("Signal", () { - test("should be created by Obj", () { - final obj = Obj(true); - - expect(obj.toSignal, isA>()); - }); - test("should be cast away nullability", () { final signalNull = Signal(false); @@ -18,12 +12,6 @@ void main() { expect(signalNull.notNull, isA>()); }); - test("should be casted to Obj", () { - final signal = Signal(true); - - expect(signal.toObj, isA>()); - }); - test("should notifies when its value is changed", () { final signal = Signal("initial"); @@ -61,7 +49,7 @@ void main() { expectLater(didUpdateChecked, true); }); - test("should be refreshed", () { + test("should be notified manually", () { final signal = Signal("initial"); int count = 0; @@ -69,11 +57,11 @@ void main() { count += 1; }); - signal.refresh(); + signal.notify(); expectLater(count, 1); - signal.refresh(); + signal.notify(); expectLater(count, 1); }); @@ -110,10 +98,7 @@ void main() { test("should be able to used on instance with nested way", () { late final bool willUpdateChecked; late final bool didUpdateChecked; - - final test2Controller = Rt.create(() => Test2Controller())!; - final testController = test2Controller.testController.instance!; - + final testController = Rt.create(() => TestController())!; final signalString = testController.signalString; expect(signalString(), "initial"); @@ -139,7 +124,7 @@ void main() { }); test("should be able to bind to another instance", () { - final testController = Rt.create(() => SignalTestController())!; + final testController = Rt.create(() => TestController())!; final signalString = testController.signalString; expect(signalString(), "initial"); @@ -148,18 +133,40 @@ void main() { expect(signalString(), "change signal"); - Rt.destroy(); + Rt.destroy(); expect( - signalString("no throw a assertion error"), - "no throw a assertion error", + () { + signalString("throw a assertion error because it's been destroyed"); + }, + throwsA(isA()), ); }); - }); -} -class SignalTestController extends TestController { - SignalTestController() { - signalString.bind(TestController()); - } + test("should get debug label", () { + final signal = Signal("initial", debugLabel: "signal"); + + expect(signal.debugLabel, "signal"); + }); + + test("should get debug info", () { + final signal = Signal("initial"); + + expect(signal.debugInfo, {"value": "initial"}); + + signal("change value"); + + expect(signal.debugInfo, {"value": "change value"}); + }); + + test("should get value as String", () { + final signal = Signal("initial"); + + expect(signal.toString(), "initial"); + + signal("change value"); + + expect("$signal", "change value"); + }); + }); } diff --git a/packages/reactter_devtools_extension/.gitignore b/packages/reactter_devtools_extension/.gitignore new file mode 100644 index 00000000..29a3a501 --- /dev/null +++ b/packages/reactter_devtools_extension/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/reactter_devtools_extension/.metadata b/packages/reactter_devtools_extension/.metadata new file mode 100644 index 00000000..b5ef4875 --- /dev/null +++ b/packages/reactter_devtools_extension/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "ba393198430278b6595976de84fe170f553cc728" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: ba393198430278b6595976de84fe170f553cc728 + base_revision: ba393198430278b6595976de84fe170f553cc728 + - platform: web + create_revision: ba393198430278b6595976de84fe170f553cc728 + base_revision: ba393198430278b6595976de84fe170f553cc728 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/reactter_devtools_extension/README.md b/packages/reactter_devtools_extension/README.md new file mode 100644 index 00000000..61428ca0 --- /dev/null +++ b/packages/reactter_devtools_extension/README.md @@ -0,0 +1,7 @@ +This is the source of the Reactter's devtool. + +You can locally run it with: + +``` +flutter run -d chrome --dart-define=use_simulated_environment=true +``` diff --git a/packages/reactter_devtools_extension/analysis_options.yaml b/packages/reactter_devtools_extension/analysis_options.yaml new file mode 100644 index 00000000..0d290213 --- /dev/null +++ b/packages/reactter_devtools_extension/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/reactter_devtools_extension/devtools_options.yaml b/packages/reactter_devtools_extension/devtools_options.yaml new file mode 100644 index 00000000..995fadae --- /dev/null +++ b/packages/reactter_devtools_extension/devtools_options.yaml @@ -0,0 +1,4 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: + - reactter: true \ No newline at end of file diff --git a/packages/reactter_devtools_extension/flutter_01.png b/packages/reactter_devtools_extension/flutter_01.png new file mode 100644 index 00000000..c2929ce9 Binary files /dev/null and b/packages/reactter_devtools_extension/flutter_01.png differ diff --git a/packages/reactter_devtools_extension/lib/main.dart b/packages/reactter_devtools_extension/lib/main.dart new file mode 100644 index 00000000..681a08d7 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/main.dart @@ -0,0 +1,7 @@ +import 'package:devtools_extensions/devtools_extensions.dart'; +import 'package:flutter/material.dart'; +import 'package:reactter_devtools_extension/src/reactter_devtools_extension.dart'; + +void main() { + runApp(const DevToolsExtension(child: RtDevToolsExtension())); +} diff --git a/packages/reactter_devtools_extension/lib/src/bases/async_node.dart b/packages/reactter_devtools_extension/lib/src/bases/async_node.dart new file mode 100644 index 00000000..e847f65f --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/bases/async_node.dart @@ -0,0 +1,74 @@ +import 'dart:async'; + +import 'package:flutter_reactter/reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; +import 'package:vm_service/vm_service.dart'; + +abstract base class AsyncNode extends Node { + bool _isValueUpdating = false; + FutureOr? _futureLoadNode; + + InstanceRef? _lastInstanceRef; + InstanceRef _instanceRef; + InstanceRef get instanceRef => _instanceRef; + + final uNeedToLoadNode = UseState(true); + final uIsLoading = UseState(false); + + AsyncNode({ + required super.key, + required InstanceRef instanceRef, + }) : _instanceRef = instanceRef, + super(kind: instanceRef.kind!); + + Future getNodeInfo(); + + Future loadNodeInfo() async { + await Rt.batch(() async { + uInfo.value = await getNodeInfo(); + }); + } + + @override + Future loadNode() async { + if (_futureLoadNode != null) return _futureLoadNode; + + await (_futureLoadNode = Rt.batch(() async { + uIsLoading.value = true; + try { + await Future.wait([ + loadNodeInfo(), + super.loadNode(), + ]); + } catch (e) { + print(e); + } + _isValueUpdating = false; + uNeedToLoadNode.value = false; + uIsLoading.value = false; + })); + + if (_lastInstanceRef != null && _instanceRef != _lastInstanceRef) { + reloadNode(); + } + } + + Future updateInstanceRef(InstanceRef instanceRef) async { + _lastInstanceRef = instanceRef; + + if (_isValueUpdating) return; + + _isValueUpdating = true; + + if (_futureLoadNode != null) await _futureLoadNode; + + reloadNode(); + } + + void reloadNode() { + _instanceRef = _lastInstanceRef!; + _futureLoadNode = null; + uNeedToLoadNode.value = true; + } +} diff --git a/packages/reactter_devtools_extension/lib/src/bases/node.dart b/packages/reactter_devtools_extension/lib/src/bases/node.dart new file mode 100644 index 00000000..bc1f1337 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/bases/node.dart @@ -0,0 +1,39 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:devtools_app_shared/service.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/tree_node.dart'; +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; +import 'package:reactter_devtools_extension/src/utils/helper.dart'; + +abstract base class Node extends TreeNode { + final String key; + final String kind; + + final LinkedHashMap nodeRefs = LinkedHashMap(); + + final uInfo = UseState(null); + final uIsSelected = UseState(false); + final isAlive = Disposable(); + + Node({ + required this.key, + required this.kind, + }); + + @override + void dispose() { + isAlive.dispose(); + super.dispose(); + } + + Future> getDetails(); + + Future loadNode() async { + await Rt.batch(() async { + final nodes = await getDetails(); + addNodes(nodeRefs, nodes, addChild); + }); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/bases/node_info.dart b/packages/reactter_devtools_extension/lib/src/bases/node_info.dart new file mode 100644 index 00000000..5ce3d37f --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/bases/node_info.dart @@ -0,0 +1,37 @@ +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/constants.dart'; + +base class NodeInfo { + final Node node; + final NodeKind? nodeKind; + final String? type; + final String? identify; + final String? identityHashCode; + final String? value; + + const NodeInfo( + this.node, { + this.nodeKind, + this.type, + this.identify, + this.identityHashCode, + this.value, + }); + + NodeInfo copyWith({ + NodeKind? nodeKind, + String? type, + String? identify, + String? identityHashCode, + String? value, + }) { + return NodeInfo( + node, + nodeKind: nodeKind ?? this.nodeKind, + type: type ?? this.type, + identify: identify ?? this.identify, + identityHashCode: identityHashCode ?? this.identityHashCode, + value: value ?? this.value, + ); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/bases/tree_list.dart b/packages/reactter_devtools_extension/lib/src/bases/tree_list.dart new file mode 100644 index 00000000..cf1f7cff --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/bases/tree_list.dart @@ -0,0 +1,37 @@ +import 'dart:collection'; + +import 'package:flutter_reactter/reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/tree_node.dart'; + +base class TreeList> extends LinkedList with RtState { + final uMaxDepth = UseState(0); + + TreeList._(); + + factory TreeList() => Rt.registerState(() => TreeList._()); + + @override + void add(E entry) => update(() => super.add(entry)); + + @override + void addFirst(E entry) => update(() => super.addFirst(entry)); + + @override + void addAll(Iterable entries) => update(() => super.addAll(entries)); + + @override + bool remove(E entry) => entry.remove(); + + @override + void dispose() { + clear(); + super.dispose(); + } + + @override + void clear() { + super.clear(); + uMaxDepth.value = 0; + if (!isDisposed) notify(); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/bases/tree_node.dart b/packages/reactter_devtools_extension/lib/src/bases/tree_node.dart new file mode 100644 index 00000000..97a8af07 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/bases/tree_node.dart @@ -0,0 +1,194 @@ +import 'dart:collection'; +import 'package:flutter_reactter/reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/tree_list.dart'; + +abstract base class TreeNode> extends LinkedListEntry + with RtState { + final uChildren = UseState(LinkedHashSet()); + final uIsExpanded = UseState(false); + final uDepth = UseState(0); + + E? _parent; + E? get parent => _parent; + + E get lastDescendant { + if (!uIsExpanded.value) return this as E; + if (uChildren.value.isEmpty) return this as E; + + return uChildren.value.last.lastDescendant; + } + + int? getIndex() => list?.indexed.firstWhere((e) => e.$2 == this).$1; + + TreeNode() { + UseEffect(_onIsExpandedChanged, [uIsExpanded]); + UseEffect(() { + if (list is! TreeList) return; + + final rList = list as TreeList; + final nodeDepth = uDepth.value; + final listMaxDepth = rList.uMaxDepth.value; + + if (listMaxDepth >= nodeDepth) return; + + rList.uMaxDepth.value = nodeDepth; + }, [uDepth]); + + if (list != null) bind(list!); + } + + @override + void unlink() { + if (list == null) return; + + final rList = list; + super.unlink(); + if (rList is RtState) (rList as RtState).notify(); + } + + @override + void insertAfter(E entry) { + if (list == null) return; + + super.insertAfter(entry); + if (list is RtState) (list as RtState).notify(); + } + + @override + void insertBefore(E entry) { + if (list == null) return; + + super.insertBefore(entry); + if (list is RtState) (list as RtState).notify(); + } + + void replaceFor(E entry) { + Rt.batch(() { + if (parent != null) { + entry._toParent(parent); + } else { + insertAfter(entry); + } + + for (final child in [...uChildren.value]) { + entry.addChild(child); + } + + entry.uIsExpanded.update(() { + entry.uIsExpanded.value = uIsExpanded.value; + }); + + uChildren.update(() { + uChildren.value.clear(); + }); + + remove(); + }); + } + + void addChild(E entry) { + Rt.batch(() { + entry._toParent(this as E); + + uChildren.update(() { + uChildren.value.add(entry); + }); + }); + } + + bool remove() { + Rt.batch(() { + unlink(); + + for (final node in [...uChildren.value]) { + node.remove(); + } + + final parentIsDisposed = parent?.uChildren.isDisposed ?? true; + if (!parentIsDisposed) { + parent?.uChildren.update(() { + parent?.uChildren.value.remove(this); + }); + } + + if (!isDisposed) dispose(); + }); + + return list == null; + } + + void _toParent(E? parentToSet) { + if (parentToSet == _parent) return; + + _parent?.uChildren.value.remove(this); + _parent?.uChildren.notify(); + _parent = parentToSet; + + unbind(); + unlink(); + + if (_parent != null) bind(_parent!); + + if (_parent?.uIsExpanded.value ?? false) { + _parent?.lastDescendant.insertAfter(this as E); + } + + _recalculateDepth(); + uIsExpanded.notify(); + } + + void _recalculateDepth() { + uDepth.value = parent?.uDepth.value == null ? 0 : parent!.uDepth.value + 1; + + for (final child in [...uChildren.value]) { + child._recalculateDepth(); + } + } + + void _onIsExpandedChanged() { + Rt.batch(() { + if (uIsExpanded.value) { + _showChildren(); + } else { + _hideChildren(); + } + }); + } + + void _showChildren() { + if (list == null) return; + + Rt.batch(() { + E? prevChild; + + for (final node in [...uChildren.value]) { + node.unlink(); + + if (prevChild == null) { + insertAfter(node); + } else { + prevChild.lastDescendant.insertAfter(node); + } + + if (node.uIsExpanded.value) { + node._showChildren(); + } else { + node._hideChildren(); + } + + prevChild = node; + } + }); + } + + void _hideChildren() { + Rt.batch(() { + for (final node in uChildren.value) { + if (node.list == null) continue; + + node._hideChildren(); + node.unlink(); + } + }); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/constants.dart b/packages/reactter_devtools_extension/lib/src/constants.dart new file mode 100644 index 00000000..1ea66634 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/constants.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; + +const int kMaxValueLength = 150; +const double kNodeTileHeight = 24; + +enum NodeType { + dependency('dependency'), + state('state'), + instance('instance'); + + final String value; + + const NodeType(this.value); + + factory NodeType.fromString(String value) { + return NodeType.values.firstWhere( + (e) => e.value == value, + orElse: () => NodeType.instance, + ); + } +} + +abstract class NodeKindKey { + static const String dependency = 'dependency'; + static const String instance = 'instance'; + static const String state = 'state'; + static const String hook = 'hook'; + static const String signal = 'signal'; +} + +enum NodeKind { + dependency( + type: NodeType.dependency, + key: NodeKindKey.dependency, + label: 'Dependency', + abbr: 'D', + color: Colors.teal, + ), + instance( + type: NodeType.instance, + key: NodeKindKey.instance, + label: 'Instance', + abbr: 'I', + color: Colors.orange, + ), + state( + type: NodeType.state, + key: NodeKindKey.state, + label: 'State', + abbr: 'S', + color: Colors.blue, + ), + hook( + type: NodeType.state, + key: NodeKindKey.hook, + label: 'Hook', + abbr: 'H', + color: Colors.purple, + ), + signal( + type: NodeType.state, + key: NodeKindKey.signal, + label: 'Signal', + abbr: 'S', + color: Colors.deepPurpleAccent, + ); + + const NodeKind({ + required this.type, + required this.key, + required this.label, + required this.abbr, + required this.color, + }); + + final NodeType type; + final String key; + final String label; + final String abbr; + final Color color; + + static NodeKind? getKind(String kind) { + switch (kind) { + case NodeKindKey.dependency: + return dependency; + case NodeKindKey.instance: + return instance; + case NodeKindKey.state: + return state; + case NodeKindKey.hook: + return hook; + case NodeKindKey.signal: + return signal; + default: + return null; + } + } +} diff --git a/packages/reactter_devtools_extension/lib/src/controllers/node_details_controller.dart b/packages/reactter_devtools_extension/lib/src/controllers/node_details_controller.dart new file mode 100644 index 00000000..d82027fd --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/controllers/node_details_controller.dart @@ -0,0 +1,57 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:flutter_reactter/reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/bases/tree_list.dart'; +import 'package:reactter_devtools_extension/src/utils/helper.dart'; + +class NodeDetailsController { + bool _isMarked = false; + FutureOr? _futureLoadDetails; + final LinkedHashMap _nodeRefs = LinkedHashMap(); + + final uCurrentNode = UseState(null); + final detailNodeList = TreeList(); + + Future loadDetails(Node node) async { + if (_isMarked) return; + + if (_futureLoadDetails != null) { + _isMarked = true; + await _futureLoadDetails; + } + + await (_futureLoadDetails = Rt.batch(() async { + if (uCurrentNode.value != node) { + _nodeRefs.clear(); + detailNodeList.clear(); + } + + uCurrentNode.value = node; + + final nodes = await node.getDetails(); + + addNodes(_nodeRefs, nodes, (node) { + node.uIsExpanded.value = true; + detailNodeList.add(node); + }); + })); + + _futureLoadDetails = null; + _isMarked = false; + } + + void reloadDetails() async { + final node = uCurrentNode.value; + + if (node == null) return; + + await loadDetails(node); + } + + void resetState() { + uCurrentNode.value = null; + detailNodeList.clear(); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/controllers/nodes_controller.dart b/packages/reactter_devtools_extension/lib/src/controllers/nodes_controller.dart new file mode 100644 index 00000000..1fd41f9e --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/controllers/nodes_controller.dart @@ -0,0 +1,312 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:devtools_extensions/devtools_extensions.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/reactter.dart'; +import 'package:reactter_devtools_extension/src/controllers/node_details_controller.dart'; +import 'package:reactter_devtools_extension/src/nodes/instance/instance_info.dart'; +import 'package:reactter_devtools_extension/src/nodes/instance/instance_node.dart'; +import 'package:reactter_devtools_extension/src/constants.dart'; +import 'package:reactter_devtools_extension/src/nodes/dependency/dependency_info.dart'; +import 'package:reactter_devtools_extension/src/nodes/dependency/dependency_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/sentinel_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/slot_node.dart'; +import 'package:reactter_devtools_extension/src/bases/tree_list.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/nodes/state/state_info.dart'; +import 'package:reactter_devtools_extension/src/services/devtools_service.dart'; +import 'package:reactter_devtools_extension/src/nodes/state/state_node.dart'; +import 'package:vm_service/vm_service.dart'; + +class NodesController { + StreamSubscription? extEventSubscription; + final uNodeDetailsController = UseDependency(); + final devtoolsSevices = DevtoolsService(); + final nodesList = TreeList(); + final dependenciesList = TreeList(); + final uNodes = UseState(LinkedHashMap()); + + final nodesListViewKey = GlobalKey(); + final dependenciesListViewKey = GlobalKey(); + + final nodesListScrollControllerY = ScrollController(); + final dependencyListScrollControllerY = ScrollController(); + + NodeDetailsController get nodeDetailsController => + uNodeDetailsController.instance!; + Node? get uCurrentNode => nodeDetailsController.uCurrentNode.value; + String? get currentNodeKey => uCurrentNode?.key; + + NodesController() { + init(); + } + + Future init() async { + await getAllNodes(); + + final vmService = await serviceManager.onServiceAvailable; + + // Listen hot-restart for re-fetching all nodes + serviceManager.isolateManager.selectedIsolate.addListener(getAllNodes); + + extEventSubscription = vmService.onExtensionEvent.listen((event) { + if (!(event.extensionKind?.startsWith('ext.reactter.') ?? false)) { + return; + } + + final eventData = event.extensionData?.data ?? {}; + + switch (event.extensionKind) { + case 'ext.reactter.onStateCreated': + final Map state = eventData['state']; + addNodeByData(state); + break; + case 'ext.reactter.onStateBound': + final Map state = eventData['state']; + final Map instance = eventData['instance']; + addNodeByData(state); + addNodeByData(instance); + break; + case 'ext.reactter.onStateUnbound': + final String instanceKey = eventData['instanceKey']; + final bool isInstanceRemoved = eventData['isInstanceRemoved']; + if (isInstanceRemoved) removeNodeByKey(instanceKey); + break; + case 'ext.reactter.onStateUpdated': + final String stateKey = eventData['stateKey']; + if (currentNodeKey == stateKey) nodeDetailsController.reloadDetails(); + break; + case 'ext.reactter.onStateDisposed': + final String stateKey = eventData['stateKey']; + final bool isStateRemoved = eventData['isStateRemoved']; + if (isStateRemoved) removeNodeByKey(stateKey); + break; + case 'ext.reactter.onDependencyRegistered': + final Map dependency = eventData['dependency']; + addNodeByData(dependency); + break; + case 'ext.reactter.onDependencyCreated': + final Map dependency = eventData['dependency']; + final Map instance = eventData['instance']; + addNodeByData(dependency); + addNodeByData(instance); + break; + case 'ext.reactter.onDependencyDeleted': + final String instanceKey = eventData['instanceKey']; + final bool isInstanceRemoved = eventData['isInstanceRemoved']; + if (isInstanceRemoved) removeNodeByKey(instanceKey); + break; + case 'ext.reactter.onDependencyUnregistered': + final String dependencyKey = eventData['dependencyKey']; + removeNodeByKey(dependencyKey); + break; + } + }); + } + + Future getAllNodes() async { + resetState(); + final nodesInfo = await devtoolsSevices.getAllNodes(); + addNodes(nodesInfo); + } + + void resetState() { + nodesList.clear(); + dependenciesList.clear(); + uNodes.value.clear(); + nodeDetailsController.resetState(); + } + + void selectNodeByKey(String nodeKey) { + final node = uNodes.value[nodeKey]; + + if (node != null) selectNode(node); + + final index = node?.getIndex(); + + if (index == null) return; + + final offset = index * kNodeTileHeight; + final list = node?.list; + final isDependenciesList = list == dependenciesList; + final scrollController = isDependenciesList + ? dependencyListScrollControllerY + : nodesListScrollControllerY; + final scrollBottom = + scrollController.offset + scrollController.position.viewportDimension; + final nodeBottom = offset + kNodeTileHeight; + + if (offset > scrollController.offset && nodeBottom < scrollBottom) return; + + scrollController.animateTo( + offset, + duration: const Duration(milliseconds: 300), + curve: Curves.bounceIn, + ); + } + + Future addNodeByKey(String nodeKey, [Map fallback = const {}]) async { + final dataNode = await devtoolsSevices.getNodeBykey(nodeKey, fallback); + addNodeByData(dataNode); + } + + void addNodes(List dataNodes) { + Rt.batch(() { + for (final dataNode in dataNodes) { + addNodeByData(dataNode); + } + }); + } + + void addNodeByData(Map dataNode) { + final kind = dataNode['kind']; + final key = dataNode['key']; + final type = dataNode['type']; + final error = dataNode['error']; + final dependencyKey = dataNode['dependencyKey']; + + if (error is SentinelException) { + final nodePrev = uNodes.value[key]; + final node = nodePrev ?? SentinelNode(key: key); + + if (kind == 'dependency') { + addDependencyNode(node as DependencyNode); + } else { + addNode(node as InstanceNode); + } + + return; + } + + switch (kind) { + case NodeKindKey.dependency: + final nodePrev = uNodes.value[key]; + final node = + nodePrev is DependencyNode ? nodePrev : DependencyNode(key: key); + + node.uInfo.value = DependencyInfo( + node, + type: dataNode['type'], + mode: dataNode['mode'], + identify: dataNode['id'], + ); + node.uIsExpanded.value = true; + + addDependencyNode(node); + break; + case NodeKindKey.instance: + final nodePrev = uNodes.value[key]; + final node = + nodePrev is InstanceNode ? nodePrev : InstanceNode(key: key); + + node.uInfo.value = InstanceInfo( + node, + type: type, + identityHashCode: key, + dependencyKey: dependencyKey, + ); + node.uIsExpanded.value = true; + + addNode(node); + + break; + case NodeKindKey.state: + case NodeKindKey.hook: + case NodeKindKey.signal: + final nodePrev = uNodes.value[key]; + final node = nodePrev is StateNode ? nodePrev : StateNode(key: key); + + final debugLabel = dataNode['debugLabel']; + final boundInstanceKey = dataNode['boundInstanceKey']; + + node.uInfo.value = StateInfo( + node, + nodeKind: NodeKind.getKind(kind), + type: type, + identify: debugLabel, + identityHashCode: key, + boundInstanceKey: boundInstanceKey, + dependencyKey: dependencyKey, + ); + node.uIsExpanded.value = true; + + if (boundInstanceKey != null) { + final boundInstanceNodePrev = uNodes.value[boundInstanceKey]; + final boundInstanceNode = + boundInstanceNodePrev ?? SlotNode(key: boundInstanceKey); + + addNode(boundInstanceNode); + + boundInstanceNode.addChild(node); + } + + addNode(node); + + break; + } + } + + void addNode(Node node) { + final nodePrev = uNodes.value[node.key]; + + if (nodePrev == node) return; + + uNodes.value[node.key] = node; + uNodes.notify(); + + if (node is SlotNode) return; + + if (node.list == null) { + nodesList.add(node); + } + + nodePrev?.replaceFor(node); + } + + void addDependencyNode(DependencyNode node) { + uNodes.value[node.key] = node; + uNodes.notify(); + if (node.list == null) dependenciesList.add(node); + } + + void removeNodeByKey(String nodeKey) { + devtoolsSevices.disposeNodeByKey(nodeKey); + + if (currentNodeKey == nodeKey) { + nodeDetailsController.resetState(); + } + + uNodes + ..value[nodeKey]?.remove() + ..value.remove(nodeKey) + ..notify(); + } + + Future selectNode(Node node) async { + if (uCurrentNode != node) { + uCurrentNode?.uIsSelected.value = false; + node.uIsSelected.value = true; + } + + nodeDetailsController.loadDetails(node); + } +} + +class RtDevToolsInstanceException implements Exception { + RtDevToolsInstanceException(); + + @override + String toString() { + return ''' + RtDevTools is not available. + Make sure you have initialized RtDevTools before using this service. + You can do this by calling `Rt.initializeDebugging()` in your app entry point. eg: + + void main() { + Rt.initializeDebugging(); + runApp(MyApp()); + } + '''; + } +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/dart/closure_node.dart b/packages/reactter_devtools_extension/lib/src/nodes/dart/closure_node.dart new file mode 100644 index 00000000..b0ea9012 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/dart/closure_node.dart @@ -0,0 +1,101 @@ +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/async_node.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; +import 'package:reactter_devtools_extension/src/constants.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/key_value_node.dart'; +import 'package:reactter_devtools_extension/src/utils/extensions.dart'; +import 'package:vm_service/vm_service.dart'; + +final class ClosureNode extends AsyncNode { + Future? futureFuncRef; + + ClosureNode.$({ + required super.key, + required super.instanceRef, + }); + + factory ClosureNode({required String key, required InstanceRef instanceRef}) { + return Rt.registerState( + () => ClosureNode.$( + key: key, + instanceRef: instanceRef, + ), + ); + } + + Future getFuncRef() async { + final instance = await instanceRef.safeGetInstance(isAlive); + return instance?.closureFunction; + } + + @override + Future loadNode() async { + await super.loadNode(); + futureFuncRef = null; + } + + @override + Future> getDetails() async { + final funcRef = await (futureFuncRef ??= getFuncRef()); + + final name = funcRef?.name; + final location = funcRef?.location?.script?.uri; + final locationLine = funcRef?.location?.line; + final locationColumn = funcRef?.location?.column; + + return [ + if (name != null) + KeyValueNode( + key: 'name', + value: '"$name"', + kind: InstanceKind.kString, + ), + if (location != null) + KeyValueNode( + key: 'location', + value: '"$location"', + kind: InstanceKind.kString, + ), + if (locationLine != null) + KeyValueNode( + key: 'locationLine', + value: "$locationLine", + kind: InstanceKind.kInt, + ), + if (locationColumn != null) + KeyValueNode( + key: 'locationColumn', + value: '$locationColumn', + kind: InstanceKind.kInt, + ), + ]; + } + + @override + Future getNodeInfo() async { + final funcRef = await (futureFuncRef ??= getFuncRef()); + + final name = funcRef?.name; + final location = funcRef?.location?.script?.uri; + final locationLine = funcRef?.location?.line; + final locationColumn = funcRef?.location?.column; + final locationStr = "$location ${[ + locationLine, + locationColumn + ].where((e) => e != null).join(':')}"; + + final value = name != null + ? "Function($name) $locationStr" + : "Unknown - Cannot load value"; + + return NodeInfo( + this, + nodeKind: NodeKind.getKind(kind), + type: 'Function', + identify: name, + identityHashCode: locationStr, + value: value, + ); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/dart/iterable_node.dart b/packages/reactter_devtools_extension/lib/src/nodes/dart/iterable_node.dart new file mode 100644 index 00000000..ff0128df --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/dart/iterable_node.dart @@ -0,0 +1,72 @@ +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/async_node.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; +import 'package:reactter_devtools_extension/src/constants.dart'; +import 'package:reactter_devtools_extension/src/utils/extensions.dart'; +import 'package:vm_service/vm_service.dart'; + +final class IterableNode extends AsyncNode { + Future>? futureElementNodes; + + IterableNode.$({ + required super.key, + required super.instanceRef, + }); + + factory IterableNode({ + required String key, + required InstanceRef instanceRef, + }) { + return Rt.registerState( + () => IterableNode.$( + key: key, + instanceRef: instanceRef, + ), + ); + } + + Future> getElementNodes() async { + final instance = await instanceRef.safeGetInstance(isAlive); + final elements = instance?.elements?.cast() ?? []; + final elementNodes = []; + + for (var i = 0; i < elements.length; i++) { + final element = elements[i]; + final node = element.getNode(i.toString()); + elementNodes.add(node); + } + + return elementNodes; + } + + @override + Future loadNode() async { + await super.loadNode(); + futureElementNodes = null; + } + + @override + Future getNodeInfo() async { + final nodes = await (futureElementNodes ??= getElementNodes()); + final value = switch (kind) { + InstanceKind.kList => "List(length: ${nodes.length})", + InstanceKind.kSet => "Set(length: ${nodes.length})", + InstanceKind.kMap => "Map(length: ${nodes.length})", + _ => "Iterable(length: ${nodes.length})", + }; + + return NodeInfo( + this, + nodeKind: NodeKind.getKind(kind), + type: kind, + identify: "length: ${nodes.length}", + value: value, + ); + } + + @override + Future> getDetails() async { + return await (futureElementNodes ??= getElementNodes()); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/dart/key_value_node.dart b/packages/reactter_devtools_extension/lib/src/nodes/dart/key_value_node.dart new file mode 100644 index 00000000..bbeb809a --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/dart/key_value_node.dart @@ -0,0 +1,38 @@ +import 'package:flutter_reactter/reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; + +final class KeyValueNode extends Node { + String _value; + + String get value => _value; + set value(String value) { + if (_value == value) return; + + _value = value; + uInfo.value = NodeInfo(this, type: kind, value: _value); + } + + KeyValueNode.$({ + required super.key, + required super.kind, + required String value, + }) : _value = value { + uInfo.value = NodeInfo(this, type: kind, value: _value); + } + + factory KeyValueNode({ + required String key, + required String value, + required String kind, + }) { + return Rt.registerState( + () => KeyValueNode.$(key: key, value: value, kind: kind), + ); + } + + @override + Future> getDetails() async { + return []; + } +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/dart/map_node.dart b/packages/reactter_devtools_extension/lib/src/nodes/dart/map_node.dart new file mode 100644 index 00000000..cf9efd44 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/dart/map_node.dart @@ -0,0 +1,63 @@ +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/async_node.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; +import 'package:reactter_devtools_extension/src/constants.dart'; +import 'package:reactter_devtools_extension/src/utils/extensions.dart'; +import 'package:vm_service/vm_service.dart'; + +final class MapNode extends AsyncNode { + Future>? futureEntryNodes; + + MapNode.$({ + required super.key, + required super.instanceRef, + }); + + factory MapNode({required String key, required InstanceRef instanceRef}) { + return Rt.registerState( + () => MapNode.$(key: key, instanceRef: instanceRef), + ); + } + + Future> getEntryNodes() async { + final instance = await instanceRef.safeGetInstance(isAlive); + final associations = instance?.associations?.cast() ?? []; + final entryNodes = []; + + for (final entry in associations) { + final keyRef = entry.key as InstanceRef; + final keyStr = await keyRef.safeValue(isAlive); + final valueRef = entry.value as InstanceRef; + + entryNodes.add(valueRef.getNode(keyStr)); + } + + return entryNodes; + } + + @override + Future loadNode() async { + await super.loadNode(); + futureEntryNodes = null; + } + + @override + Future getNodeInfo() async { + final nodes = await (futureEntryNodes ??= getEntryNodes()); + final value = "Map(length: ${nodes.length})"; + + return NodeInfo( + this, + nodeKind: NodeKind.getKind(kind), + type: kind, + identify: "length: ${nodes.length}", + value: value, + ); + } + + @override + Future> getDetails() async { + return await (futureEntryNodes ??= getEntryNodes()); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/dart/plain_instance_node.dart b/packages/reactter_devtools_extension/lib/src/nodes/dart/plain_instance_node.dart new file mode 100644 index 00000000..7ae5ad94 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/dart/plain_instance_node.dart @@ -0,0 +1,121 @@ +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/async_node.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; +import 'package:reactter_devtools_extension/src/constants.dart'; +import 'package:reactter_devtools_extension/src/nodes/dependency/dependency_info.dart'; +import 'package:reactter_devtools_extension/src/nodes/instance/instance_info.dart'; +import 'package:reactter_devtools_extension/src/nodes/state/state_info.dart'; +import 'package:reactter_devtools_extension/src/services/eval_service.dart'; +import 'package:reactter_devtools_extension/src/utils/extensions.dart'; +import 'package:vm_service/vm_service.dart'; + +base class PlainInstanceNode extends AsyncNode { + PlainInstanceNode._({ + required super.key, + required super.instanceRef, + }); + + factory PlainInstanceNode({ + required String key, + required InstanceRef instanceRef, + }) { + return Rt.registerState( + () => PlainInstanceNode._( + key: key, + instanceRef: instanceRef, + ), + ); + } + + @override + Future getNodeInfo() async { + final eval = await EvalService.devtoolsEval; + final instanceInfo = await EvalService.evalsQueue.add( + () => eval.evalInstance( + 'RtDevTools.instance?.getPlainInstanceInfo(instance)', + isAlive: isAlive, + scope: {'instance': instanceRef.id!}, + ), + ); + + if (instanceInfo.kind != InstanceKind.kMap) return null; + + final instanceInfoMap = await instanceInfo.evalValue(isAlive, 2); + + if (instanceInfoMap is! Map) return null; + + final String kind = instanceInfoMap['kind']; + final String key = instanceInfoMap['key']; + final String type = instanceInfoMap['type']; + final String? id = instanceInfoMap['id']; + final String? debugLabel = instanceInfoMap['debugLabel']; + final String? value = instanceInfoMap['value']; + final String? identify = id ?? debugLabel ?? value; + final String formattedValue = + identify != null ? "$type($identify) #$key" : "$type #$key"; + final nodeKind = NodeKind.getKind(kind)!; + + switch (nodeKind) { + case NodeKind.dependency: + final mode = instanceInfoMap['mode']; + + return DependencyInfo( + this, + type: type, + identify: id, + mode: mode, + value: formattedValue, + ); + case NodeKind.instance: + final dependencyKey = instanceInfoMap['dependencyKey']; + + return InstanceInfo( + this, + type: type, + identityHashCode: key, + identify: value, + dependencyKey: dependencyKey, + value: formattedValue, + ); + case NodeKind.state: + case NodeKind.hook: + case NodeKind.signal: + return StateInfo( + this, + nodeKind: NodeKind.getKind(kind)!, + type: type, + identify: debugLabel, + identityHashCode: key, + value: formattedValue, + debugLabel: debugLabel, + ); + default: + return NodeInfo( + this, + nodeKind: NodeKind.getKind(kind), + type: type, + identify: value, + identityHashCode: key, + value: formattedValue, + ); + } + } + + @override + Future> getDetails() async { + final instance = await instanceRef.safeGetInstance(isAlive); + final fields = instance?.fields?.cast() ?? []; + final nodes = fields.map((field) { + if (field.value is! InstanceRef) return null; + + if (field.name.startsWith('_') || field.name.startsWith('\$')) { + return null; + } + + return (field.value as InstanceRef).getNode(field.name); + }).toList(); + + return nodes.whereType().toList(); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/dart/record_node.dart b/packages/reactter_devtools_extension/lib/src/nodes/dart/record_node.dart new file mode 100644 index 00000000..8c955669 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/dart/record_node.dart @@ -0,0 +1,65 @@ +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/async_node.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; +import 'package:reactter_devtools_extension/src/constants.dart'; +import 'package:reactter_devtools_extension/src/utils/extensions.dart'; +import 'package:vm_service/vm_service.dart'; + +final class RecordNode extends AsyncNode { + Future>? futureFieldNodes; + + RecordNode.$({ + required super.key, + required super.instanceRef, + }); + + factory RecordNode({required String key, required InstanceRef instanceRef}) { + return Rt.registerState( + () => RecordNode.$( + key: key, + instanceRef: instanceRef, + ), + ); + } + + Future> getFieldNodes() async { + final instance = await instanceRef.safeGetInstance(isAlive); + final fields = instance?.fields?.cast() ?? []; + final fieldNodes = []; + + for (final field in fields) { + final node = (field.value as InstanceRef).getNode("${field.name}"); + fieldNodes.add(node); + } + + return fieldNodes; + } + + @override + Future loadNode() async { + await super.loadNode(); + futureFieldNodes = null; + } + + @override + Future> getDetails() async { + return await (futureFieldNodes ??= getFieldNodes()); + } + + @override + Future getNodeInfo() async { + if (uNeedToLoadNode.value) return null; + + final nodes = await (futureFieldNodes ??= getFieldNodes()); + final value = "Record(length: ${nodes.length})"; + + return NodeInfo( + this, + nodeKind: NodeKind.getKind(kind), + type: kind, + identify: "length: ${nodes.length}", + value: value, + ); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/dependency/dependency_info.dart b/packages/reactter_devtools_extension/lib/src/nodes/dependency/dependency_info.dart new file mode 100644 index 00000000..846320a3 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/dependency/dependency_info.dart @@ -0,0 +1,15 @@ +import 'package:reactter_devtools_extension/src/constants.dart'; +import 'package:reactter_devtools_extension/src/nodes/instance/instance_info.dart'; + +final class DependencyInfo extends InstanceInfo { + final String? mode; + + DependencyInfo( + super.node, { + this.mode, + super.type, + super.identify, + super.identityHashCode, + super.value, + }) : super(nodeKind: NodeKind.dependency); +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/dependency/dependency_node.dart b/packages/reactter_devtools_extension/lib/src/nodes/dependency/dependency_node.dart new file mode 100644 index 00000000..a6f8ea01 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/dependency/dependency_node.dart @@ -0,0 +1,15 @@ +import 'package:flutter_reactter/reactter.dart'; +import 'package:reactter_devtools_extension/src/nodes/dependency/dependency_info.dart'; +import 'package:reactter_devtools_extension/src/nodes/instance/instance_node.dart'; + +final class DependencyNode extends InstanceNode { + final uIsLoading = UseState(false); + + DependencyNode.$({required super.key}) : super.$(); + + factory DependencyNode({required String key}) { + return Rt.registerState( + () => DependencyNode.$(key: key), + ); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/instance/instance_info.dart b/packages/reactter_devtools_extension/lib/src/nodes/instance/instance_info.dart new file mode 100644 index 00000000..e9218bee --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/instance/instance_info.dart @@ -0,0 +1,16 @@ +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; +import 'package:reactter_devtools_extension/src/constants.dart'; + +base class InstanceInfo extends NodeInfo { + final String? dependencyKey; + + InstanceInfo( + super.node, { + super.nodeKind = NodeKind.instance, + super.type, + super.identify, + super.identityHashCode, + super.value, + this.dependencyKey, + }); +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/instance/instance_node.dart b/packages/reactter_devtools_extension/lib/src/nodes/instance/instance_node.dart new file mode 100644 index 00000000..f564c6cb --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/instance/instance_node.dart @@ -0,0 +1,41 @@ +import 'package:flutter_reactter/reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; +import 'package:reactter_devtools_extension/src/nodes/instance/instance_info.dart'; +import 'package:reactter_devtools_extension/src/services/eval_service.dart'; +import 'package:reactter_devtools_extension/src/utils/extensions.dart'; +import 'package:vm_service/vm_service.dart'; + +base class InstanceNode extends Node { + InstanceNode.$({required super.key}) + : super(kind: InstanceKind.kPlainInstance); + + factory InstanceNode({required String key}) { + return Rt.registerState( + () => InstanceNode.$(key: key), + ); + } + + Future getDependency() async { + try { + final eval = await EvalService.devtoolsEval; + final dependencyKey = uInfo.value?.dependencyKey; + final dependencyRef = await EvalService.evalsQueue.add( + () => eval.safeEval( + 'RtDevTools.instance?.getDependencyRef("$dependencyKey")', + isAlive: isAlive, + ), + ); + return dependencyRef.getNode('dependency'); + } catch (e) { + print(e); + } + + return null; + } + + @override + Future>> getDetails() async => [ + await getDependency(), + ].whereType().toList(); +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/sentinel_node.dart b/packages/reactter_devtools_extension/lib/src/nodes/sentinel_node.dart new file mode 100644 index 00000000..150cd65a --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/sentinel_node.dart @@ -0,0 +1,25 @@ +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; + +final class SentinelNode extends Node { + SentinelNode._({required super.key}) : super(kind: 'sentinel'); + + factory SentinelNode({required String key}) { + return Rt.registerState( + () => SentinelNode._(key: key), + ); + } + + @override + Future loadNode() { + assert(false, 'SentinelNode should not be loaded'); + throw UnimplementedError(); + } + + @override + Future>> getDetails() { + assert(false, 'SentinelNode should not be expanded'); + throw UnimplementedError(); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/slot_node.dart b/packages/reactter_devtools_extension/lib/src/nodes/slot_node.dart new file mode 100644 index 00000000..5f00e100 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/slot_node.dart @@ -0,0 +1,24 @@ +import 'package:flutter_reactter/reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; + +final class SlotNode extends Node { + SlotNode._({required super.key}) : super(kind: 'slot'); + + factory SlotNode({required String key}) { + return Rt.registerState( + () => SlotNode._(key: key), + ); + } + + @override + Future loadNode() { + throw UnimplementedError(); + } + + @override + Future>> getDetails() { + // TODO: implement getDetails + throw UnimplementedError(); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/state/state_info.dart b/packages/reactter_devtools_extension/lib/src/nodes/state/state_info.dart new file mode 100644 index 00000000..ec05f0fe --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/state/state_info.dart @@ -0,0 +1,18 @@ +import 'package:reactter_devtools_extension/src/nodes/instance/instance_info.dart'; + +final class StateInfo extends InstanceInfo { + final String? debugLabel; + final String? boundInstanceKey; + + StateInfo( + super.node, { + super.nodeKind, + super.type, + super.identify, + super.identityHashCode, + super.value, + super.dependencyKey, + this.debugLabel, + this.boundInstanceKey, + }); +} diff --git a/packages/reactter_devtools_extension/lib/src/nodes/state/state_node.dart b/packages/reactter_devtools_extension/lib/src/nodes/state/state_node.dart new file mode 100644 index 00000000..305838c6 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/nodes/state/state_node.dart @@ -0,0 +1,57 @@ +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/nodes/instance/instance_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/state/state_info.dart'; +import 'package:reactter_devtools_extension/src/services/eval_service.dart'; +import 'package:reactter_devtools_extension/src/utils/extensions.dart'; + +final class StateNode extends InstanceNode { + final uIsLoading = UseState(false); + + StateNode.$({required super.key}) : super.$(); + + factory StateNode({required String key}) { + return Rt.registerState( + () => StateNode.$(key: key), + ); + } + + @override + Future> getDetails() async { + final instanceRefs = await Future.wait([ + getDependency(), + getBoundInstance(), + getDebugInfo(), + ]); + + return instanceRefs.whereType().toList(); + } + + Future getBoundInstance() async { + try { + final eval = await EvalService.devtoolsEval; + final boundInstanceRef = await eval.evalInstance( + 'RtDevTools.instance?.getBoundInstance("$key")', + isAlive: isAlive, + ); + return boundInstanceRef.getNode('boundInstance'); + } catch (e) { + print(e); + } + return null; + } + + Future getDebugInfo() async { + try { + final eval = await EvalService.devtoolsEval; + final debugInfoRef = await eval.evalInstance( + 'RtDevTools.instance?.getDebugInfo("$key")', + isAlive: isAlive, + ); + return debugInfoRef.getNode('debugInfo'); + } catch (e) { + print(e); + } + return null; + } +} diff --git a/packages/reactter_devtools_extension/lib/src/reactter_devtools_extension.dart b/packages/reactter_devtools_extension/lib/src/reactter_devtools_extension.dart new file mode 100644 index 00000000..796f2dd8 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/reactter_devtools_extension.dart @@ -0,0 +1,103 @@ +import 'package:devtools_app_shared/ui.dart'; +import 'package:flutter/material.dart' hide Split; +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/controllers/node_details_controller.dart'; +import 'package:reactter_devtools_extension/src/nodes/instance/instance_info.dart'; +import 'package:reactter_devtools_extension/src/widgets/dependencies_list.dart'; +import 'package:reactter_devtools_extension/src/widgets/detail_node_list.dart'; +import 'package:reactter_devtools_extension/src/widgets/instance_title.dart'; +import 'package:reactter_devtools_extension/src/widgets/nodes_list.dart'; +import 'controllers/nodes_controller.dart'; + +class RtDevToolsExtension extends StatelessWidget { + const RtDevToolsExtension({super.key}); + + @override + Widget build(BuildContext context) { + return RtMultiProvider( + [ + RtProvider(() => NodeDetailsController()), + RtProvider(() => NodesController()), + ], + builder: (context, child) { + return SplitPane( + axis: SplitPane.axisFor(context, 1.2), + initialFractions: const [0.55, 0.45], + children: [ + RoundedOutlinedBorder( + clip: true, + child: LayoutBuilder(builder: (context, constraints) { + return FlexSplitColumn( + totalHeight: constraints.maxHeight, + initialFractions: const [0.25, 0.75], + minSizes: const [0.0, 0.0], + headers: const [ + AreaPaneHeader( + roundedTopBorder: false, + includeTopBorder: false, + title: Text("Dependencies"), + ), + AreaPaneHeader( + rightPadding: 0.0, + roundedTopBorder: false, + title: Text("State/Instance Tree"), + ), + ], + children: const [ + DependenciesList(), + NodesList(), + ], + ); + }), + ), + RoundedOutlinedBorder( + clip: true, + child: Column( + children: [ + RtWatcher((context, watch) { + final nodeDetailsController = + context.use(); + final selectedNode = + watch(nodeDetailsController.uCurrentNode).value; + + if (selectedNode == null) { + return const AreaPaneHeader( + roundedTopBorder: false, + title: Text("Select a node for details"), + ); + } + + final nodeInfo = watch(selectedNode.uInfo).value; + + return AreaPaneHeader( + roundedTopBorder: false, + title: Row( + children: [ + const Text( + "Details of ", + ), + InstanceTitle( + identifyHashCode: selectedNode.key, + type: nodeInfo?.type, + nodeKind: nodeInfo?.nodeKind, + label: nodeInfo?.identify, + isDependency: nodeInfo is InstanceInfo + ? nodeInfo.dependencyKey != null + : false, + ), + ], + ), + ); + }), + const Expanded( + child: DetailNodeList(), + ), + ], + ), + ), + ], + ); + }, + ); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/services/devtools_service.dart b/packages/reactter_devtools_extension/lib/src/services/devtools_service.dart new file mode 100644 index 00000000..f8af4439 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/services/devtools_service.dart @@ -0,0 +1,70 @@ +import 'package:devtools_app_shared/service.dart' hide SentinelException; +import 'package:reactter_devtools_extension/src/services/eval_service.dart'; +import 'package:reactter_devtools_extension/src/utils/extensions.dart'; +import 'package:vm_service/vm_service.dart'; + +class DevtoolsService { + static final _nodesDisposables = {}; + + Future> getAllNodes({ + int page = 0, + int pageSize = 20, + }) async { + final eval = await EvalService.devtoolsEval; + final isAlive = Disposable(); + + final pageNodes = await EvalService.evalsQueue.add( + () => eval.evalInstance( + 'RtDevTools.instance?.getNodes($page, $pageSize)', + isAlive: isAlive, + ), + ); + + if (pageNodes.kind == InstanceKind.kNull) return []; + + final Map pageInfo = await pageNodes.evalValue(isAlive); + final totalPages = pageInfo['totalPages'] as int; + final dataNodes = (pageInfo['nodes'] as List).cast(); + + if (page >= totalPages - 1) return dataNodes; + + final nextDataNodes = await getAllNodes(page: page + 1, pageSize: pageSize); + + return dataNodes + nextDataNodes; + } + + Future getNodeBykey(String nodeKey, [Map fallback = const {}]) async { + try { + final eval = await EvalService.devtoolsEval; + final isAlive = + _nodesDisposables.putIfAbsent(nodeKey, () => Disposable()); + + final nodeInst = await EvalService.evalsQueue.add( + () => eval.evalInstance( + 'RtDevTools.instance?._nodesByKey["$nodeKey"]?.toJson()', + isAlive: isAlive, + ), + ); + + if (nodeInst.kind == InstanceKind.kNull) return {}; + + assert(nodeInst.kind == InstanceKind.kMap); + + return await nodeInst.evalValue(isAlive, null, true); + } catch (error) { + if (error is CancelledException) { + return {}; + } + + return { + ...fallback, + 'key': nodeKey, + 'error': error, + }; + } + } + + void disposeNodeByKey(String nodeKey) { + _nodesDisposables.remove(nodeKey)?.dispose(); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/services/eval_service.dart b/packages/reactter_devtools_extension/lib/src/services/eval_service.dart new file mode 100644 index 00000000..01385827 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/services/eval_service.dart @@ -0,0 +1,21 @@ +import 'package:devtools_app_shared/service.dart'; +import 'package:devtools_extensions/devtools_extensions.dart'; +import 'package:queue/queue.dart'; + +class EvalService { + static final evalsQueue = Queue(parallel: 5); + static final devtoolsEval = _getEvalOnDartLibrary( + 'package:reactter/src/devtools.dart', + ); + static final dartEval = _getEvalOnDartLibrary('dart:io'); +} + +Future _getEvalOnDartLibrary(String path) async { + final vmService = await serviceManager.onServiceAvailable; + + return EvalOnDartLibrary( + path, + vmService, + serviceManager: serviceManager, + ); +} diff --git a/packages/reactter_devtools_extension/lib/src/utils/color_palette.dart b/packages/reactter_devtools_extension/lib/src/utils/color_palette.dart new file mode 100644 index 00000000..884ad249 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/utils/color_palette.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:vm_service/vm_service.dart'; + +class ColorPalette { + final Color selected; + final Color key; + final Color type; + final Color label; + final Color identifyHashCode; + final Color nullValue; + final Color boolValue; + final Color stringValue; + final Color numberValue; + + const ColorPalette._({ + required this.selected, + required this.key, + required this.type, + required this.label, + required this.identifyHashCode, + required this.nullValue, + required this.boolValue, + required this.stringValue, + required this.numberValue, + }); + + static ColorPalette? dark; + static ColorPalette? light; + + factory ColorPalette._dark(BuildContext context) { + return ColorPalette._( + selected: Theme.of(context).primaryColorDark, + key: Theme.of(context).colorScheme.secondary, + type: Colors.amber, + label: Theme.of(context).colorScheme.primary, + identifyHashCode: Colors.grey, + nullValue: Colors.grey, + boolValue: Colors.lightBlue.shade300, + stringValue: Colors.green, + numberValue: Colors.orange, + ); + } + + factory ColorPalette._light(BuildContext context) { + return ColorPalette._( + selected: Theme.of(context).primaryColorLight, + key: Theme.of(context).colorScheme.secondary, + type: Colors.amber.shade800, + label: Theme.of(context).colorScheme.primary, + identifyHashCode: Colors.grey.shade800, + nullValue: Colors.lightBlue.shade700, + boolValue: Colors.purple.shade800, + stringValue: Colors.green.shade800, + numberValue: Colors.orange.shade800, + ); + } + + static ColorPalette of(BuildContext context) { + final theme = Theme.of(context); + + if (theme.brightness == Brightness.dark) { + return ColorPalette.dark ??= ColorPalette._dark(context); + } + + return ColorPalette.light ??= ColorPalette._light(context); + } + + static Color? getColorForNodeValue(BuildContext context, Node node) { + switch (node.kind) { + case InstanceKind.kNull: + return ColorPalette.of(context).nullValue; + case InstanceKind.kBool: + return ColorPalette.of(context).boolValue; + case InstanceKind.kDouble: + case InstanceKind.kInt: + return ColorPalette.of(context).numberValue; + case InstanceKind.kString: + return ColorPalette.of(context).stringValue; + default: + return null; + } + } +} diff --git a/packages/reactter_devtools_extension/lib/src/utils/extensions.dart b/packages/reactter_devtools_extension/lib/src/utils/extensions.dart new file mode 100644 index 00000000..aaad99e9 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/utils/extensions.dart @@ -0,0 +1,188 @@ +import 'package:devtools_app_shared/service.dart' hide SentinelException; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/closure_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/iterable_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/key_value_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/map_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/plain_instance_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/record_node.dart'; +import 'package:reactter_devtools_extension/src/services/eval_service.dart'; +import 'package:vm_service/vm_service.dart'; + +extension InstanceExt on Instance { + Future evalValue([ + Disposable? isAlive, + int? level, + throwOnError = false, + ]) async { + if (level != null && level == 0) return this; + + switch (kind) { + case InstanceKind.kNull: + return null; + case InstanceKind.kString: + return valueAsString; + case InstanceKind.kDouble: + return double.tryParse(valueAsString!); + case InstanceKind.kInt: + return int.tryParse(valueAsString!); + case InstanceKind.kBool: + return bool.tryParse(valueAsString!); + case InstanceKind.kMap: + final dataNode = {}; + + for (final entry in associations!) { + final InstanceRef keyRef = entry.key; + final InstanceRef valueRef = entry.value; + final key = await keyRef.evalValue(isAlive, null, throwOnError); + final value = await valueRef.evalValue( + isAlive, + level == null ? null : level - 1, + throwOnError, + ); + + dataNode[key] = value; + } + + return dataNode; + case InstanceKind.kList: + final list = elements!.cast(); + final listValues = []; + + for (final e in list) { + final value = await e.evalValue( + isAlive, + level == null ? null : level - 1, + ); + listValues.add(value); + } + + return listValues; + default: + return valueAsString; + } + } + + dynamic evalValueFirstLevel() { + switch (kind) { + case InstanceKind.kNull: + return null; + case InstanceKind.kString: + return valueAsString; + case InstanceKind.kDouble: + return double.tryParse(valueAsString!); + case InstanceKind.kInt: + return int.tryParse(valueAsString!); + case InstanceKind.kBool: + return bool.tryParse(valueAsString!); + default: + return this; + } + } + + String safeValue() { + switch (kind) { + case InstanceKind.kNull: + return 'null'; + case InstanceKind.kString: + return '"$valueAsString"'; + case InstanceKind.kMap: + return 'Map{...}(length: ${associations?.length})'; + case InstanceKind.kList: + return 'List[...](length: ${elements?.length})'; + case InstanceKind.kSet: + return 'Set{...}(length: ${elements?.length})'; + case InstanceKind.kRecord: + return 'Record(...)(length: ${fields?.length})'; + default: + return valueAsString ?? 'unknown'; + } + } +} + +extension InstanceRefExt on InstanceRef { + Node getNode(String key) { + return switch (kind) { + InstanceKind.kNull || + InstanceKind.kBool || + InstanceKind.kDouble || + InstanceKind.kInt => + KeyValueNode( + kind: kind!, + key: key, + value: '$valueAsString', + ), + InstanceKind.kString => KeyValueNode( + kind: kind!, + key: key, + value: '"$valueAsString"', + ), + InstanceKind.kMap => MapNode(key: key, instanceRef: this), + InstanceKind.kList => IterableNode( + key: key, + instanceRef: this, + ), + InstanceKind.kSet => IterableNode( + key: key, + instanceRef: this, + ), + InstanceKind.kRecord => RecordNode( + key: key, + instanceRef: this, + ), + InstanceKind.kClosure => ClosureNode( + key: key, + instanceRef: this, + ), + InstanceKind.kPlainInstance => PlainInstanceNode( + key: key, + instanceRef: this, + ), + _ => PlainInstanceNode(key: key, instanceRef: this), + }; + } + + Future safeGetInstance([ + Disposable? isAlive, + throwOnError = false, + ]) async { + try { + final eval = await EvalService.devtoolsEval; + final instance = await EvalService.evalsQueue.add( + () => eval.safeGetInstance(this, isAlive), + ); + + return instance; + } catch (e) { + if (!throwOnError) return null; + rethrow; + } + } + + Future evalValue([ + Disposable? isAlive, + int? level, + throwOnError = false, + ]) async { + if (level != null && level == 0) return this; + + try { + final instance = await safeGetInstance(isAlive, throwOnError); + return await instance?.evalValue(isAlive, level); + } catch (e) { + if (!throwOnError) return null; + + rethrow; + } + } + + Future evalValueFirstLevel([Disposable? isAlive]) async { + final instance = await safeGetInstance(isAlive); + return instance?.evalValueFirstLevel(); + } + + Future safeValue([Disposable? isAlive]) async { + final instance = await safeGetInstance(isAlive); + return instance?.safeValue() ?? 'unknown'; + } +} diff --git a/packages/reactter_devtools_extension/lib/src/utils/helper.dart b/packages/reactter_devtools_extension/lib/src/utils/helper.dart new file mode 100644 index 00000000..001534ba --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/utils/helper.dart @@ -0,0 +1,41 @@ +import 'dart:collection'; + +import 'package:reactter_devtools_extension/src/bases/async_node.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/key_value_node.dart'; + +void addNodes( + LinkedHashMap nodeRefs, + List nodes, + void Function(Node node) onAdd, +) { + final nodeToRemove = nodeRefs.keys.toSet(); + + for (final node in nodes) { + final nodeRef = nodeRefs[node.key]; + final isEqual = node.kind == nodeRef?.kind; + + if (isEqual) { + if (nodeRef is AsyncNode) { + nodeRef.updateInstanceRef((node as AsyncNode).instanceRef); + } else if (nodeRef is KeyValueNode) { + nodeRef.value = (node as KeyValueNode).value; + } + } else { + if (nodeRef != null) { + nodeRef.replaceFor(node); + } else { + onAdd(node); + } + + nodeRefs.putIfAbsent(node.key, () => node); + } + + nodeToRemove.remove(node.key); + } + + for (final key in nodeToRemove) { + final node = nodeRefs.remove(key); + node?.remove(); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/bidirectional_scroll_view.dart b/packages/reactter_devtools_extension/lib/src/widgets/bidirectional_scroll_view.dart new file mode 100644 index 00000000..dc90dfca --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/bidirectional_scroll_view.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:reactter_devtools_extension/src/widgets/offset_scrollbar.dart'; + +class BidirectionalScrollView extends StatelessWidget { + final double maxWidth; + final Widget Function(BuildContext, BoxConstraints) builder; + final ScrollController? scrollControllerX; + final ScrollController? scrollControllerY; + + const BidirectionalScrollView({ + super.key, + this.scrollControllerX, + this.scrollControllerY, + required this.maxWidth, + required this.builder, + }); + + @override + Widget build(BuildContext context) { + final scrollControllerX = this.scrollControllerX ?? ScrollController(); + final scrollControllerY = this.scrollControllerY ?? ScrollController(); + + return LayoutBuilder( + builder: (context, constraints) { + final viewportWidth = constraints.maxWidth; + + return Scrollbar( + controller: scrollControllerX, + thumbVisibility: true, + child: SingleChildScrollView( + controller: scrollControllerX, + scrollDirection: Axis.horizontal, + child: Material( + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: viewportWidth > maxWidth ? viewportWidth : maxWidth, + ), + child: OffsetScrollbar( + isAlwaysShown: true, + axis: Axis.vertical, + controller: scrollControllerY, + offsetController: scrollControllerX, + offsetControllerViewportDimension: viewportWidth, + child: builder(context, constraints), + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/dependencies_list.dart b/packages/reactter_devtools_extension/lib/src/widgets/dependencies_list.dart new file mode 100644 index 00000000..3a48d5af --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/dependencies_list.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/controllers/nodes_controller.dart'; +import 'package:reactter_devtools_extension/src/widgets/bidirectional_scroll_view.dart'; +import 'package:reactter_devtools_extension/src/widgets/dependency_tile.dart'; + +class DependenciesList extends StatelessWidget { + const DependenciesList({super.key}); + + @override + Widget build(BuildContext context) { + final focusNode = FocusNode(); + final nodesController = context.use(); + final scrollControllerY = nodesController.dependencyListScrollControllerY; + final listViewKey = nodesController.dependenciesListViewKey; + + return BidirectionalScrollView( + scrollControllerY: scrollControllerY, + maxWidth: 500, + builder: (context, constraints) { + return SelectableRegion( + focusNode: focusNode, + selectionControls: materialTextSelectionControls, + child: RtWatcher((context, watch) { + final dependenciesList = watch(nodesController.dependenciesList); + final length = dependenciesList.length; + + return ListView.custom( + key: listViewKey, + controller: scrollControllerY, + itemExtent: 24, + childrenDelegate: SliverChildBuilderDelegate( + (context, index) { + final node = dependenciesList.elementAt(index); + + return DependencyTile( + key: Key(node.key), + node: node, + onTap: () => nodesController.selectNode(node), + ); + }, + childCount: length, + ), + ); + }), + ); + }, + ); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/dependency_tile.dart b/packages/reactter_devtools_extension/lib/src/widgets/dependency_tile.dart new file mode 100644 index 00000000..1e81cef9 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/dependency_tile.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/nodes/instance/instance_info.dart'; +import 'package:reactter_devtools_extension/src/widgets/instance_title.dart'; +import 'package:reactter_devtools_extension/src/widgets/tile_builder.dart'; + +class DependencyTile extends StatelessWidget { + final Node node; + final void Function()? onTap; + + const DependencyTile({ + super.key, + required this.node, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return RtWatcher((context, watch) { + final isSelected = watch(node.uIsSelected).value; + + return TreeNodeTileBuilder( + treeNode: node, + title: NodeTileTitle(node: node), + isSelected: isSelected, + onTap: onTap, + ); + }); + } +} + +class NodeTileTitle extends StatelessWidget { + final Node node; + + const NodeTileTitle({ + super.key, + required this.node, + }); + + @override + Widget build(BuildContext context) { + return RtWatcher((context, watch) { + final nodeInfo = watch(node.uInfo).value; + + return InstanceTitle( + identifyHashCode: node.key, + nodeKind: nodeInfo?.nodeKind, + type: nodeInfo?.type, + label: nodeInfo?.identify, + isDependency: + nodeInfo is InstanceInfo ? nodeInfo.dependencyKey != null : false, + ); + }); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/detail_node_list.dart b/packages/reactter_devtools_extension/lib/src/widgets/detail_node_list.dart new file mode 100644 index 00000000..1fd977d7 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/detail_node_list.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/controllers/node_details_controller.dart'; +import 'package:reactter_devtools_extension/src/widgets/bidirectional_scroll_view.dart'; +import 'package:reactter_devtools_extension/src/widgets/detail_node_tile.dart'; + +class DetailNodeList extends StatelessWidget { + const DetailNodeList({super.key}); + + @override + Widget build(BuildContext context) { + final focusNode = FocusNode(); + final scrollControllerY = ScrollController(); + final nodeDetailsController = context.use(); + + return RtWatcher((context, watch) { + final maxDepth = + watch(nodeDetailsController.detailNodeList.uMaxDepth).value; + + return BidirectionalScrollView( + scrollControllerY: scrollControllerY, + maxWidth: 600 + (24.0 * maxDepth), + builder: (context, constraints) { + return SelectableRegion( + focusNode: focusNode, + selectionControls: materialTextSelectionControls, + child: RtWatcher((context, watch) { + final detailNodeList = + watch(nodeDetailsController.detailNodeList); + + final length = detailNodeList.length; + + return ListView.custom( + key: ObjectKey(detailNodeList), + controller: scrollControllerY, + itemExtent: 24, + childrenDelegate: SliverChildBuilderDelegate( + (context, index) { + final detailNode = detailNodeList.elementAt(index); + + return DetailNodeTile( + key: ObjectKey(detailNode), + node: detailNode, + ); + }, + childCount: length, + ), + ); + }), + ); + }, + ); + }); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/detail_node_tile.dart b/packages/reactter_devtools_extension/lib/src/widgets/detail_node_tile.dart new file mode 100644 index 00000000..6aa9baaa --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/detail_node_tile.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/async_node.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/utils/color_palette.dart'; +import 'package:reactter_devtools_extension/src/widgets/loading.dart'; +import 'package:reactter_devtools_extension/src/widgets/node_title.dart'; +import 'package:reactter_devtools_extension/src/widgets/tile_builder.dart'; + +class DetailNodeTile extends StatelessWidget { + final Node node; + + const DetailNodeTile({ + super.key, + required this.node, + }); + + @override + Widget build(BuildContext context) { + return TreeNodeTileBuilder( + treeNode: node, + title: Row( + children: [ + Text( + "${node.key}: ", + style: Theme.of(context) + .textTheme + .labelSmall + ?.copyWith(color: ColorPalette.of(context).key), + ), + RtWatcher((context, watch) { + if (node is AsyncNode) { + final asyncNode = node as AsyncNode; + + final isLoading = watch(asyncNode.uIsLoading).value; + if (isLoading) return const Loading(); + + final isNeedToLoadNode = watch(asyncNode.uNeedToLoadNode).value; + if (isNeedToLoadNode) asyncNode.loadNode(); + } + + return NodeTitle( + key: ObjectKey(node), + node: node, + ); + }), + ], + ), + isSelected: false, + onTap: () {}, + ); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/instance_icon.dart b/packages/reactter_devtools_extension/lib/src/widgets/instance_icon.dart new file mode 100644 index 00000000..6ab2580f --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/instance_icon.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:reactter_devtools_extension/src/constants.dart'; + +class InstanceIcon extends StatelessWidget { + final NodeKind? nodeKind; + final bool isDependency; + + const InstanceIcon({ + super.key, + required this.nodeKind, + this.isDependency = false, + }); + + @override + Widget build(BuildContext context) { + if (nodeKind == null) return const SizedBox(); + + return SizedBox.square( + dimension: 24, + child: Padding( + padding: const EdgeInsets.all(1), + child: Badge( + backgroundColor: isDependency ? Colors.teal : Colors.transparent, + child: Padding( + padding: const EdgeInsets.all(2.0), + child: Center( + child: CircleAvatar( + backgroundColor: nodeKind!.color, + child: Text( + nodeKind!.abbr, + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/instance_title.dart b/packages/reactter_devtools_extension/lib/src/widgets/instance_title.dart new file mode 100644 index 00000000..9c4ebb85 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/instance_title.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:reactter_devtools_extension/src/constants.dart'; +import 'package:reactter_devtools_extension/src/utils/color_palette.dart'; +import 'package:reactter_devtools_extension/src/widgets/instance_icon.dart'; + +class InstanceTitle extends StatelessWidget { + final String? identifyHashCode; + final String? type; + final NodeKind? nodeKind; + final String? label; + final bool isDependency; + final void Function()? onTapIcon; + + const InstanceTitle({ + super.key, + this.identifyHashCode, + this.type, + this.nodeKind, + this.label, + this.isDependency = false, + this.onTapIcon, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + if (nodeKind != null) + InkWell( + onTap: onTapIcon, + child: InstanceIcon( + nodeKind: nodeKind, + isDependency: isDependency, + ), + ), + RichText( + selectionRegistrar: SelectionContainer.maybeOf(context), + selectionColor: Theme.of(context).highlightColor, + text: TextSpan( + style: Theme.of(context).textTheme.labelSmall, + children: [ + TextSpan( + text: type, + style: Theme.of(context) + .textTheme + .labelSmall + ?.copyWith(color: ColorPalette.of(context).type), + ), + if (label != null) + TextSpan( + children: [ + const TextSpan(text: "("), + TextSpan( + text: label!, + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: ColorPalette.of(context).label, + ), + ), + const TextSpan(text: ")"), + ], + ), + if (identifyHashCode != null) + TextSpan( + text: " #$identifyHashCode", + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: ColorPalette.of(context).identifyHashCode, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/loading.dart b/packages/reactter_devtools_extension/lib/src/widgets/loading.dart new file mode 100644 index 00000000..7480d31b --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/loading.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class Loading extends StatelessWidget { + const Loading({super.key}); + + @override + Widget build(BuildContext context) { + return const SizedBox.square( + dimension: 18, + child: FittedBox( + fit: BoxFit.scaleDown, + child: CircularProgressIndicator(), + ), + ); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/node_tile.dart b/packages/reactter_devtools_extension/lib/src/widgets/node_tile.dart new file mode 100644 index 00000000..2196b8da --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/node_tile.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/nodes/instance/instance_info.dart'; +import 'package:reactter_devtools_extension/src/widgets/instance_title.dart'; +import 'package:reactter_devtools_extension/src/widgets/tile_builder.dart'; + +class NodeTile extends StatelessWidget { + final Node node; + final void Function()? onTap; + + const NodeTile({ + super.key, + required this.node, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return RtWatcher((context, watch) { + final isSelected = watch(node.uIsSelected).value; + + return TreeNodeTileBuilder( + treeNode: node, + title: NodeTileTitle(node: node), + isSelected: isSelected, + onTap: onTap, + ); + }); + } +} + +class NodeTileTitle extends StatelessWidget { + final Node node; + + const NodeTileTitle({ + super.key, + required this.node, + }); + + @override + Widget build(BuildContext context) { + return RtWatcher((context, watch) { + final nodeInfo = watch(node.uInfo).value; + final dependencyKey = + nodeInfo is InstanceInfo ? nodeInfo.dependencyKey : null; + + return InstanceTitle( + identifyHashCode: node.key, + nodeKind: nodeInfo?.nodeKind, + type: nodeInfo?.type, + label: nodeInfo?.identify, + isDependency: dependencyKey != null, + ); + }); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/node_title.dart b/packages/reactter_devtools_extension/lib/src/widgets/node_title.dart new file mode 100644 index 00000000..64e53064 --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/node_title.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/node.dart'; +import 'package:reactter_devtools_extension/src/bases/node_info.dart'; +import 'package:reactter_devtools_extension/src/controllers/nodes_controller.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/closure_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/iterable_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/key_value_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/map_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/plain_instance_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/dart/record_node.dart'; +import 'package:reactter_devtools_extension/src/nodes/instance/instance_info.dart'; +import 'package:reactter_devtools_extension/src/utils/color_palette.dart'; +import 'package:reactter_devtools_extension/src/widgets/instance_title.dart'; + +class NodeTitle extends StatelessWidget { + final Node node; + + const NodeTitle({super.key, required this.node}); + + @override + Widget build(BuildContext context) { + context.watch((_) => [node.uInfo]); + + final nodeInfo = node.uInfo.value; + final nodeKey = nodeInfo?.identityHashCode; + + if (nodeInfo == null) { + return Text( + '...', + style: Theme.of(context).textTheme.labelSmall, + ); + } + + Widget getInstanceTitle(NodeInfo? nodeInfo) { + final nodesController = context.use(); + final nodeOrigin = nodesController.uNodes.value[nodeKey]; + final nodeInfoOrigin = nodeOrigin?.uInfo.value; + final isDependency = nodeInfoOrigin is InstanceInfo && + nodeInfoOrigin.dependencyKey != null; + final onTapIcon = nodeKey != null && nodeOrigin != null + ? () => nodesController.selectNodeByKey(nodeKey) + : null; + + return InstanceTitle( + identifyHashCode: nodeKey, + type: nodeInfo?.type, + nodeKind: nodeInfo?.nodeKind, + label: nodeInfo?.identify, + isDependency: isDependency, + onTapIcon: onTapIcon, + ); + } + + return switch (node) { + KeyValueNode(uInfo: final nodeInfo) => Text( + '${nodeInfo.value?.value}', + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: ColorPalette.getColorForNodeValue(context, node), + ), + ), + PlainInstanceNode(uInfo: final nodeInfo) => + getInstanceTitle(nodeInfo.value), + MapNode(uInfo: final nodeInfo) || + IterableNode(uInfo: final nodeInfo) || + RecordNode(uInfo: final nodeInfo) || + ClosureNode(uInfo: final nodeInfo) || + Node(uInfo: final nodeInfo) => + InstanceTitle( + identifyHashCode: nodeKey, + type: nodeInfo.value?.type, + nodeKind: nodeInfo.value?.nodeKind, + label: nodeInfo.value?.identify, + ), + }; + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/nodes_list.dart b/packages/reactter_devtools_extension/lib/src/widgets/nodes_list.dart new file mode 100644 index 00000000..1f673bef --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/nodes_list.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/controllers/nodes_controller.dart'; +import 'package:reactter_devtools_extension/src/widgets/bidirectional_scroll_view.dart'; +import 'package:reactter_devtools_extension/src/widgets/node_tile.dart'; + +class NodesList extends StatelessWidget { + const NodesList({super.key}); + + @override + Widget build(BuildContext context) { + final focusNode = FocusNode(); + final nodesController = context.use(); + final listViewKey = nodesController.nodesListViewKey; + final scrollControllerY = nodesController.nodesListScrollControllerY; + + return RtWatcher((context, watch) { + final maxDepth = watch(nodesController.nodesList.uMaxDepth).value; + + return BidirectionalScrollView( + scrollControllerY: scrollControllerY, + maxWidth: 400 + (24.0 * maxDepth), + builder: (context, constraints) { + return SelectableRegion( + focusNode: focusNode, + selectionControls: materialTextSelectionControls, + child: RtWatcher((context, watch) { + final nodesList = watch(nodesController.nodesList); + final length = nodesList.length; + + return ListView.custom( + key: listViewKey, + controller: scrollControllerY, + itemExtent: 24, + childrenDelegate: SliverChildBuilderDelegate( + (context, index) { + final node = nodesList.elementAt(index); + + return NodeTile( + key: Key(node.key), + node: node, + onTap: () => nodesController.selectNode(node), + ); + }, + childCount: length, + ), + ); + }), + ); + }, + ); + }); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/offset_scrollbar.dart b/packages/reactter_devtools_extension/lib/src/widgets/offset_scrollbar.dart new file mode 100644 index 00000000..ec471eff --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/offset_scrollbar.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; + +/// Scrollbar that is offset by the amount specified by an [offsetController]. +/// +/// This makes it possible to create a [ListView] with both vertical and +/// horizontal scrollbars by wrapping the [ListView] in a +/// [SingleChildScrollView] that handles horizontal scrolling. The +/// [offsetController] is the offset of the parent [SingleChildScrollView] in +/// this example. +/// +/// This class could be optimized if performance was a concern using a +/// [CustomPainter] instead of an [AnimatedBuilder] so that the +/// [OffsetScrollbar] widget does not need to build on each change to the +/// [offsetController]. +class OffsetScrollbar extends StatefulWidget { + const OffsetScrollbar({ + super.key, + this.isAlwaysShown = false, + required this.axis, + required this.controller, + required this.offsetController, + required this.child, + required this.offsetControllerViewportDimension, + }); + + final bool isAlwaysShown; + final Axis axis; + final ScrollController controller; + final ScrollController offsetController; + final Widget child; + + /// The current viewport dimension of the offsetController may not be + /// available at build time as it is not updated until later so we require + /// that the known correct viewport dimension is passed into this class. + /// + /// This is a workaround because we use an AnimatedBuilder to listen for + /// changes to the offsetController rather than displaying the scrollbar at + /// paint time which would be more difficult. + final double offsetControllerViewportDimension; + + @override + State createState() => _OffsetScrollbarState(); +} + +class _OffsetScrollbarState extends State { + @override + Widget build(BuildContext context) { + if (!widget.offsetController.position.hasContentDimensions) { + SchedulerBinding.instance.addPostFrameCallback((timeStamp) { + if (widget.offsetController.position.hasViewportDimension && mounted) { + // TODO(jacobr): find a cleaner way to be notified that the + // offsetController now has a valid dimension. We would probably + // have to implement our own ScrollbarPainter instead of being able + // to use the existing Scrollbar widget. + setState(() {}); + } + }); + } + return AnimatedBuilder( + animation: widget.offsetController, + builder: (context, child) { + // Compute a delta to move the scrollbar from where it is by default to + // where it should be given the viewport dimension of the + // offsetController not the viewport that is the entire scroll extent + // of the offsetController because this controller is nested within the + // offset controller. + double delta = 0.0; + if (widget.offsetController.position.hasContentDimensions) { + delta = widget.offsetController.offset - + widget.offsetController.position.maxScrollExtent + + widget.offsetController.position.minScrollExtent; + if (widget.offsetController.position.hasViewportDimension) { + // TODO(jacobr): this is a bit of a hack. + // The viewport dimension from the offsetController may be one frame + // behind the true viewport dimension. We add this delta so the + // scrollbar always appears stuck to the side of the viewport. + delta += widget.offsetControllerViewportDimension - + widget.offsetController.position.viewportDimension; + } + } + final offset = widget.axis == Axis.vertical + ? Offset(delta, 0.0) + : Offset(0.0, delta); + return Transform.translate( + offset: offset, + child: Scrollbar( + thumbVisibility: widget.isAlwaysShown, + controller: widget.controller, + child: Transform.translate( + offset: -offset, + child: child, + ), + ), + ); + }, + child: widget.child, + ); + } +} diff --git a/packages/reactter_devtools_extension/lib/src/widgets/tile_builder.dart b/packages/reactter_devtools_extension/lib/src/widgets/tile_builder.dart new file mode 100644 index 00000000..2a04098a --- /dev/null +++ b/packages/reactter_devtools_extension/lib/src/widgets/tile_builder.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_reactter/flutter_reactter.dart'; +import 'package:reactter_devtools_extension/src/bases/tree_node.dart'; +import 'package:reactter_devtools_extension/src/utils/color_palette.dart'; + +class TreeNodeTileBuilder extends StatelessWidget { + final TreeNode treeNode; + final bool isSelected; + final Widget title; + final void Function()? onTap; + + const TreeNodeTileBuilder({ + super.key, + required this.treeNode, + required this.title, + this.isSelected = false, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return ListTile( + dense: true, + visualDensity: const VisualDensity(vertical: 0, horizontal: 0), + horizontalTitleGap: 0, + minVerticalPadding: 0, + minTileHeight: 24, + contentPadding: EdgeInsets.zero, + selected: isSelected, + selectedTileColor: ColorPalette.of(context).selected, + onTap: onTap, + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + RtWatcher((context, watch) { + final depth = watch(treeNode.uDepth).value; + + return Row( + children: List.generate( + depth.toInt(), + (index) => Container( + padding: const EdgeInsets.only( + left: 11, + ), + child: const VerticalDivider(width: 1), + ), + ), + ); + }), + TileExpandable(treeNode: treeNode), + title, + ], + ), + ); + } +} + +class TileExpandable extends StatelessWidget { + final TreeNode treeNode; + + const TileExpandable({super.key, required this.treeNode}); + + @override + Widget build(BuildContext context) { + return RtWatcher((context, watch) { + final children = watch(treeNode.uChildren).value; + + if (children.isEmpty) { + return Container( + width: 24, + ); + } + + final isExpanded = watch(treeNode.uIsExpanded).value; + + return Container( + padding: const EdgeInsets.only(left: 0), + child: InkWell( + child: Icon( + isExpanded ? Icons.keyboard_arrow_down : Icons.keyboard_arrow_right, + ), + onTap: () { + treeNode.uIsExpanded.value = !isExpanded; + }, + ), + ); + }); + } +} diff --git a/packages/reactter_devtools_extension/pubspec.yaml b/packages/reactter_devtools_extension/pubspec.yaml new file mode 100644 index 00000000..a8cd1ee7 --- /dev/null +++ b/packages/reactter_devtools_extension/pubspec.yaml @@ -0,0 +1,32 @@ +name: reactter_devtools_extension +descripqion: DevTools extension for Reactter +publish_to: "none" +version: 1.0.0+1 + +environment: + sdk: ">=3.3.1 <4.0.0" + +dependencies: + flutter: + sdk: flutter + devtools_extensions: ^0.1.1 + devtools_app_shared: ^0.1.1 + vm_service: ^14.2.5 + queue: ^3.1.0+2 + flutter_reactter: ^8.0.0-dev + # flutter_reactter: + # path: ../flutter_reactter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + +flutter: + uses-material-design: true + +scripts: + dev: flutter run -d chrome --dart-define=use_simulated_environment=true + build: + $before: flutter pub get + $script: dart run devtools_extensions build_and_copy --source=. --dest=../reactter/extension/devtools diff --git a/packages/reactter_devtools_extension/test/widget_test.dart b/packages/reactter_devtools_extension/test/widget_test.dart new file mode 100644 index 00000000..6f5ea248 --- /dev/null +++ b/packages/reactter_devtools_extension/test/widget_test.dart @@ -0,0 +1,13 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async {}); +} diff --git a/packages/reactter_devtools_extension/web/favicon.png b/packages/reactter_devtools_extension/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/packages/reactter_devtools_extension/web/favicon.png differ diff --git a/packages/reactter_devtools_extension/web/icons/Icon-192.png b/packages/reactter_devtools_extension/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/packages/reactter_devtools_extension/web/icons/Icon-192.png differ diff --git a/packages/reactter_devtools_extension/web/icons/Icon-512.png b/packages/reactter_devtools_extension/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/packages/reactter_devtools_extension/web/icons/Icon-512.png differ diff --git a/packages/reactter_devtools_extension/web/icons/Icon-maskable-192.png b/packages/reactter_devtools_extension/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/packages/reactter_devtools_extension/web/icons/Icon-maskable-192.png differ diff --git a/packages/reactter_devtools_extension/web/icons/Icon-maskable-512.png b/packages/reactter_devtools_extension/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/packages/reactter_devtools_extension/web/icons/Icon-maskable-512.png differ diff --git a/packages/reactter_devtools_extension/web/index.html b/packages/reactter_devtools_extension/web/index.html new file mode 100644 index 00000000..3b19c58e --- /dev/null +++ b/packages/reactter_devtools_extension/web/index.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + reactter_devtools_extension + + + + + + diff --git a/packages/reactter_devtools_extension/web/manifest.json b/packages/reactter_devtools_extension/web/manifest.json new file mode 100644 index 00000000..6d8d8428 --- /dev/null +++ b/packages/reactter_devtools_extension/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "reactter_devtools_extension", + "short_name": "reactter_devtools_extension", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/reactter_lint/README.md b/packages/reactter_lint/README.md index f97e948f..f8abd043 100644 --- a/packages/reactter_lint/README.md +++ b/packages/reactter_lint/README.md @@ -9,24 +9,29 @@ ____ [![Pub points](https://img.shields.io/pub/points/reactter_lint?color=196959&labelColor=23967F&logo=dart)](https://pub.dev/packages/reactter_lint/score) [![MIT License](https://img.shields.io/github/license/2devs-team/reactter?color=a85f00&labelColor=F08700&logoColor=fff&logo=Open%20Source%20Initiative)](https://github.com/2devs-team/reactter/blob/master/LICENSE) -`Reactter_lint` is an analytics tool for [Reactter](https://pub.dev/packages/reactter) that helps developers to encourage good coding practices and preventing frequent problems using the conventions Reactter. +`Reactter_lint` is an analytics tool for [Reactter](https://pub.dev/packages/reactter) that helps developers to encourage good coding practices and preventing frequent problems using the conventions Rt. -## Contents +### Contents -- [Contents](#contents) - [Quickstart](#quickstart) - [Lints](#lints) - - [hook\_late\_convention](#hook_late_convention) + - [rt\_hook\_name\_convention](#rt_hook_name_convention) - [Bad](#bad) - [Good](#good) - - [hook\_name\_convention](#hook_name_convention) + - [rt\_invalid\_hook\_position](#rt_invalid_hook_position) - [Bad](#bad-1) - [Good](#good-1) - - [invalid\_hook\_position](#invalid_hook_position) + - [rt\_invalid\_hook\_register](#rt_invalid_hook_register) - [Bad](#bad-2) - [Good](#good-2) - - [invalid\_hook\_register](#invalid_hook_register) + - [rt\_invalid\_state\_creation](#rt_invalid_state_creation) - [Bad](#bad-3) + - [God](#god) + - [rt\_no\_logic\_in\_create\_state](#rt_no_logic_in_create_state) + - [Bad](#bad-4) + - [God](#god-1) + - [rt\_state\_late\_convention](#rt_state_late_convention) + - [Bad](#bad-5) - [Good](#good-3) ## Quickstart @@ -53,38 +58,7 @@ dart run custom_lint ## Lints -### hook_late_convention - -The hook late must be attached an instance. - -#### Bad - -Cause: `ReactterHook` cannot be late without attaching an instance. - -```dart -class AppController { - final otherState = UseState(0); - late final stateLate = UseState(otherState.value); - ... -} -``` - -#### Good - -Fix: Use `Reactter.lazyState` for attaching an instance. - -```dart -class AppController { - final otherState = UseState(0); - late final stateLate = Reactter.lazyState( - () => UseState(otherState.value), - this, - ); - ... -} -``` - -### hook_name_convention +### rt_hook_name_convention The hook name should be prefixed with `use`. @@ -93,7 +67,7 @@ The hook name should be prefixed with `use`. Cause: The hook name is not prefixed with `use`. ```dart -class MyHook extends ReactterHook { +> class MyHook extends RtHook { ... } ``` @@ -103,12 +77,12 @@ class MyHook extends ReactterHook { Fix: Name hook using `use` preffix. ```dart -class UseMyHook extends ReactterHook { +class UseMyHook extends RtHook { ... } ``` -### invalid_hook_position +### rt_invalid_hook_position The hook must be defined after the hook register. @@ -117,9 +91,9 @@ The hook must be defined after the hook register. Cause: The hook cannot be defined before the hook register. ```dart -class UseMyHook extends ReactterHook { +class UseMyHook extends RtHook { final stateHook = UseState(0); - final $ = ReactterHook.$register; +> final $ = RtHook.$register; ... } ``` @@ -129,14 +103,14 @@ class UseMyHook extends ReactterHook { Fix: Define it after the hook register. ```dart -class UseMyHook extends ReactterHook { - final $ = ReactterHook.$register; +class UseMyHook extends RtHook { + final $ = RtHook.$register; final stateHook = UseState(0); ... } ``` -### invalid_hook_register +### rt_invalid_hook_register The hook register('$' field) must be final only. @@ -145,8 +119,8 @@ The hook register('$' field) must be final only. Cause: The hook register cannot be defined using getter. ```dart -class MyHook extends ReactterHook { - get $ => ReactterHook.$register; +class MyHook extends RtHook { +> get $ => RtHook.$register; ... } ``` @@ -154,8 +128,8 @@ class MyHook extends ReactterHook { Cause: The hook register cannot be defined using setter. ```dart -class UseMyHook extends ReactterHook { - set $(_) => ReactterHook.$register; +class UseMyHook extends RtHook { +> set $(_) => RtHook.$register; ... } ``` @@ -163,8 +137,8 @@ class UseMyHook extends ReactterHook { Cause: The hook register cannot be defined using `var` keyword. ```dart -class UseMyHook extends ReactterHook { - var $ = ReactterHook.$register; +class UseMyHook extends RtHook { +> var $ = RtHook.$register; ... } ``` @@ -172,8 +146,8 @@ class UseMyHook extends ReactterHook { Cause: The hook register cannot be defined using a type. ```dart -class UseMyHook extends ReactterHook { - Object $ = ReactterHook.$register; +class UseMyHook extends RtHook { +> Object $ = RtHook.$register; ... } ``` @@ -183,8 +157,110 @@ class UseMyHook extends ReactterHook { Fix: Define it using `final` keyword. ```dart -class UseMyHook extends ReactterHook { - final $ = ReactterHook.$register; +class UseMyHook extends RtHook { + final $ = RtHook.$register; + ... +} +``` + +### rt_invalid_state_creation + +The state must be create under the Reactter context. + +#### Bad + +Cause: The state cannot be created outside Reactter context. + +```dart +class MyState with RtState {...} + +> final myState = MyState(); +``` + +#### God + +Fix: Use `Rt.registerState` method for registering the state under the Reactter context. + +```dart +class MyState with RtState {...} + +final myState = Rt.registerState(() => MyState()); +``` + +### rt_no_logic_in_create_state + +Don't put logic in `registerState` method + +#### Bad + +Cause: The `registerState` method includes additional logic. + +```dart +> final myState = Rt.registerState(() { +> final inst = MyState(); +> inst.foo(); +> return inst; +> }); +``` + +```dart +> final myState = Rt.registerState(() { +> if (flag) return MyState(); +> +> return OtherState(); +> }); +``` + +#### God + +Fix: Try moving the logic out of `registerState` method. + +```dart +final myState = Rt.registerState(() => MyState()); +myState.foo(); +``` + +```dart +final myState = flag + ? Rt.registerState(() => MyState()) + : Rt.registerState(() => OtherState()); +``` + +### rt_state_late_convention + +The state late must be attached an instance. + +#### Bad + +Cause: `RtHook` cannot be late without attaching an instance. + +```dart +class AppController { + final stateA = UseState(0); + final stateB = UseState(0); +> late final stateLate = UseCompute( +> () => stateA.value + stateB.value, +> [stateA, stateB], +> ); + ... +} +``` + +#### Good + +Fix: Use `Rt.lazyState` for attaching an instance. + +```dart +class AppController { + final stateA = UseState(0); + final stateB = UseState(0); + late final stateLate = Rt.lazyState( + () => UseCompute( + () => stateA.value + stateB.value, + [stateA, stateB], + ), + this, + ); ... } ``` diff --git a/packages/reactter_lint/lib/reactter_lint.dart b/packages/reactter_lint/lib/reactter_lint.dart index 36016d67..d56e0d7a 100644 --- a/packages/reactter_lint/lib/reactter_lint.dart +++ b/packages/reactter_lint/lib/reactter_lint.dart @@ -1,9 +1,11 @@ // This is the entrypoint of our custom linter import 'package:custom_lint_builder/custom_lint_builder.dart'; -import 'package:reactter_lint/src/rules/hook_late_convention.dart'; -import 'package:reactter_lint/src/rules/hook_name_convention.dart'; -import 'package:reactter_lint/src/rules/invalid_hook_position.dart'; -import 'package:reactter_lint/src/rules/invalid_hook_register.dart'; +import 'package:reactter_lint/src/rules/rt_state_late_convention.dart'; +import 'package:reactter_lint/src/rules/rt_hook_name_convention.dart'; +import 'package:reactter_lint/src/rules/rt_invalid_hook_position.dart'; +import 'package:reactter_lint/src/rules/rt_invalid_hook_register.dart'; +import 'package:reactter_lint/src/rules/rt_no_logic_in_create_state.dart'; +import 'package:reactter_lint/src/rules/rt_invalid_state_creation.dart'; /// The function creates an instance of the _ReactterLinter class and returns it as a PluginBase object. PluginBase createPlugin() => _ReactterLinter(); @@ -13,10 +15,12 @@ class _ReactterLinter extends PluginBase { @override List getLintRules(CustomLintConfigs configs) { return const [ - HookLateConvention(), - HookNameConvention(), - InvalidHookPosition(), - InvalidHookRegister(), + RtStateLateConvention(), + RtHookNameConvention(), + RtInvalidHookPosition(), + RtInvalidHookRegister(), + RtNoLogicInRegisterState(), + RtInvalidStateCreation(), ]; } } diff --git a/packages/reactter_lint/lib/src/extensions.dart b/packages/reactter_lint/lib/src/extensions.dart index 08f1747a..0e1d754d 100644 --- a/packages/reactter_lint/lib/src/extensions.dart +++ b/packages/reactter_lint/lib/src/extensions.dart @@ -1,22 +1,41 @@ import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/src/dart/element/member.dart'; // ignore: implementation_imports -extension ElementExtension on Element? { +extension ElementExtension on Element { /// The `node` is a getter method defined as an extension on the `Element` class in /// Dart. It allows you to retrieve the AST (Abstract Syntax Tree) node associated with an element. AstNode? get node { - if (this == null) return null; + if (this.library == null) return null; - if (this?.library == null) return null; - - final parsedLibrary = this - ?.session - ?.getParsedLibraryByElement(this!.library!) as ParsedLibraryResult?; - final declaration = parsedLibrary?.getElementDeclaration(this!); + final parsedLibrary = this.session?.getParsedLibraryByElement(this.library!) + as ParsedLibraryResult?; + final declaration = parsedLibrary?.getElementDeclaration(this); return declaration?.node; } + + Element get canonicalElement { + var self = this; + if (self is PropertyAccessorElement) { + var variable = self.variable; + if (variable is FieldMember) { + // A field element defined in a parameterized type where the values of + // the type parameters are known. + // + // This concept should be invisible when comparing FieldElements, but a + // bug in the analyzer causes FieldElements to not evaluate as + // equivalent to equivalent FieldMembers. See + // https://github.com/dart-lang/sdk/issues/35343. + return variable.declaration; + } else { + return variable; + } + } else { + return self; + } + } } extension InterableExtension on Iterable { @@ -33,3 +52,72 @@ extension InterableExtension on Iterable { return null; } } + +extension BlockExtension on Block { + /// Returns the last statement of this block, or `null` if this is empty. + /// + /// If the last immediate statement of this block is a [Block], recurses into + /// it to find the last statement. + Statement? get lastStatement { + if (statements.isEmpty) { + return null; + } + var lastStatement = statements.last; + if (lastStatement is Block) { + return lastStatement.lastStatement; + } + return lastStatement; + } +} + +extension FunctionExpressionExtension on FunctionExpression { + Expression? get returnExpression { + return body.returnExpression; + } +} + +extension FunctionBodyExtension on FunctionBody { + Expression? get returnExpression { + if (this is BlockFunctionBody) { + var block = (this as BlockFunctionBody).block; + if (block.statements.isEmpty) return null; + var lastStatement = block.statements.last; + + if (lastStatement is ReturnStatement) { + return lastStatement.expression; + } + } + + if (this is ExpressionFunctionBody) { + return (this as ExpressionFunctionBody).expression; + } + + return null; + } +} + +/// Operations on iterables. +extension IterableExtensions on Iterable { + /// The first element of this iterator, or `null` if the iterable is empty. + T? get firstOrNull { + var iterator = this.iterator; + if (iterator.moveNext()) return iterator.current; + return null; + } + + /// The last element of this iterable, or `null` if the iterable is empty. + /// + /// This computation may not be efficient. + /// The last value is potentially found by iterating the entire iterable + /// and temporarily storing every value. + /// The process only iterates the iterable once. + /// If iterating more than once is not a problem, it may be more efficient + /// for some iterables to do: + /// ```dart + /// var lastOrNull = iterable.isEmpty ? null : iterable.last; + /// ``` + T? get lastOrNull { + if (isEmpty) return null; + return last; + } +} diff --git a/packages/reactter_lint/lib/src/helpers.dart b/packages/reactter_lint/lib/src/helpers.dart index 2f113257..3397fe32 100644 --- a/packages/reactter_lint/lib/src/helpers.dart +++ b/packages/reactter_lint/lib/src/helpers.dart @@ -1,12 +1,13 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:reactter_lint/src/consts.dart'; +import 'package:reactter_lint/src/extensions.dart'; /// The function checks if a given declaration is a hook register. /// /// Args: /// node (Declaration): The "node" parameter is a Declaration object. -bool isHookRegister(Declaration node) { +bool isRegisterDeclaration(Declaration node) { if (node is FieldDeclaration) { final name = node.fields.variables.first.name.toString(); return name == HOOK_REGISTER_VAR; @@ -31,3 +32,123 @@ Element? getElementFromDeclaration(Declaration node) { return node.declaredElement; } + +/// Returns whether the canonical elements of [element1] and [element2] are +/// equal. +bool canonicalElementsAreEqual(Element? element1, Element? element2) => + element1?.canonicalElement == element2?.canonicalElement; + +/// Returns whether the canonical elements from two nodes are equal. +/// +/// As in, [NullableAstNodeExtension.canonicalElement], the two nodes must be +/// [Expression]s in order to be compared (otherwise `false` is returned). +/// +/// The two nodes must both be a [SimpleIdentifier], [PrefixedIdentifier], or +/// [PropertyAccess] (otherwise `false` is returned). +/// +/// If the two nodes are PrefixedIdentifiers, or PropertyAccess nodes, then +/// `true` is returned only if their canonical elements are equal, in +/// addition to their prefixes' and targets' (respectfully) canonical +/// elements. +/// +/// There is an inherent assumption about pure getters. For example: +/// +/// A a1 = ... +/// A a2 = ... +/// a1.b.c; // statement 1 +/// a2.b.c; // statement 2 +/// a1.b.c; // statement 3 +/// +/// The canonical elements from statements 1 and 2 are different, because a1 +/// is not the same element as a2. The canonical elements from statements 1 +/// and 3 are considered to be equal, even though `A.b` may have side effects +/// which alter the returned value. +bool canonicalElementsFromIdentifiersAreEqual( + Expression? rawExpression1, Expression? rawExpression2) { + if (rawExpression1 == null || rawExpression2 == null) return false; + + var expression1 = rawExpression1.unParenthesized; + var expression2 = rawExpression2.unParenthesized; + + print("EXPRESSION 1: ${expression1.runtimeType}"); + print("EXPRESSION 2: ${expression2.runtimeType}"); + + if (expression1 is SimpleIdentifier) { + print("EXPRESSION SimpleIdentifier 1: $expression1"); + print("EXPRESSION SimpleIdentifier 2: $expression2"); + return expression2 is SimpleIdentifier && + canonicalElementsAreEqual(getWriteOrReadElement(expression1), + getWriteOrReadElement(expression2)); + } + + if (expression1 is PrefixedIdentifier) { + print("EXPRESSION PrefixedIdentifier 1: $expression1"); + print("EXPRESSION PrefixedIdentifier 2: $expression2"); + return expression2 is PrefixedIdentifier && + canonicalElementsAreEqual(expression1.prefix.staticElement, + expression2.prefix.staticElement) && + canonicalElementsAreEqual(getWriteOrReadElement(expression1.identifier), + getWriteOrReadElement(expression2.identifier)); + } + + if (expression1 is PropertyAccess && expression2 is PropertyAccess) { + print("EXPRESSION PropertyAccess 1: $expression1"); + print("EXPRESSION PropertyAccess 2: $expression2"); + var target1 = expression1.target; + var target2 = expression2.target; + return canonicalElementsFromIdentifiersAreEqual(target1, target2) && + canonicalElementsAreEqual( + getWriteOrReadElement(expression1.propertyName), + getWriteOrReadElement(expression2.propertyName)); + } + + if (expression1 is InstanceCreationExpression && + expression2 is InstanceCreationExpression) { + print( + "EXPRESSION InstanceCreationExpression 1: ${expression1.staticParameterElement}"); + print( + "EXPRESSION InstanceCreationExpression 2: ${expression2.staticParameterElement}"); + + return expression1 == expression2; + } + + return false; +} + +/// If the [node] is the finishing identifier of an assignment, return its +/// "writeElement", otherwise return its "staticElement", which might be +/// thought as the "readElement". +Element? getWriteOrReadElement(SimpleIdentifier node) { + var writeElement = _getWriteElement(node); + if (writeElement != null) { + return writeElement; + } + return node.staticElement; +} + +/// If the [node] is the target of a [CompoundAssignmentExpression], +/// return the corresponding "writeElement", which is the local variable, +/// the setter referenced with a [SimpleIdentifier] or a [PropertyAccess], +/// or the `[]=` operator. +Element? _getWriteElement(AstNode node) { + var parent = node.parent; + if (parent is AssignmentExpression && parent.leftHandSide == node) { + return parent.writeElement; + } + if (parent is PostfixExpression) { + return parent.writeElement; + } + if (parent is PrefixExpression) { + return parent.writeElement; + } + + if (parent is PrefixedIdentifier && parent.identifier == node) { + return _getWriteElement(parent); + } + + if (parent is PropertyAccess && parent.propertyName == node) { + return _getWriteElement(parent); + } + + return null; +} diff --git a/packages/reactter_lint/lib/src/rules/hook_late_convention.dart b/packages/reactter_lint/lib/src/rules/hook_late_convention.dart deleted file mode 100644 index 252587a8..00000000 --- a/packages/reactter_lint/lib/src/rules/hook_late_convention.dart +++ /dev/null @@ -1,124 +0,0 @@ -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/error/error.dart'; -import 'package:analyzer/error/listener.dart'; -import 'package:custom_lint_builder/custom_lint_builder.dart'; -import 'package:reactter_lint/src/extensions.dart'; -import 'package:reactter_lint/src/types.dart'; - -/// The `HookLateConvention` class is a DartLint rule that enforces a specific naming convention for -/// hooks. -class HookLateConvention extends DartLintRule { - const HookLateConvention() : super(code: _code); - - static const _code = LintCode( - name: "hook_late_convention", - errorSeverity: ErrorSeverity.WARNING, - problemMessage: "The '{0}' hook late must be attached an instance.", - correctionMessage: - "Try removing 'late' keyword or wrapping the hook using 'Rt.lazyState'.\n" - "Example: late final myHookLate = Rt.lazyState(() => UseState(0), this);", - ); - - @override - List getFixes() => [_HookLateFix()]; - - static whenIsInvalid({ - required CustomLintContext context, - required Function(Declaration node, Element element) onInvalid, - }) { - context.registry.addFieldDeclaration((node) { - final element = node.fields.variables.first.declaredElement; - - if (element == null) return; - - if (!reactterHookType.isAssignableFromType(element.type)) return; - - if (!element.isLate) return; - - bool checkIsUseLazy(MethodInvocation method) { - return method.target.toString() == "Rt" && - method.methodName.toString() == "lazyState"; - } - - bool checkIsVariableUseLazy(VariableDeclaration variable) { - return variable.childEntities.whereType().any( - checkIsUseLazy, - ); - } - - final isUseLazy = node.fields.childEntities - .whereType() - .any(checkIsVariableUseLazy); - - if (isUseLazy) return; - - onInvalid(node, element); - }); - } - - @override - void run( - CustomLintResolver resolver, - ErrorReporter reporter, - CustomLintContext context, - ) { - whenIsInvalid( - context: context, - onInvalid: (node, element) { - reporter.reportErrorForElement(code, element, [element.name!]); - }, - ); - } -} - -/// The `_HookLateFix` class is a Dart fix that provides a solution for a specific issue related to late -/// variables. -class _HookLateFix extends DartFix { - @override - void run( - CustomLintResolver resolver, - ChangeReporter reporter, - CustomLintContext context, - AnalysisError analysisError, - List others, - ) { - HookLateConvention.whenIsInvalid( - context: context, - onInvalid: (node, element) { - try { - if (node is! FieldDeclaration) return; - - bool checkIsInstanceCreationExpression(VariableDeclaration variable) { - return variable.childEntities - .whereType() - .isNotEmpty; - } - - final variable = node.fields.childEntities - .whereType() - .firstWhereOrNull(checkIsInstanceCreationExpression); - - final instanceCreationExpression = variable?.childEntities - .firstWhereOrNull((child) => child is InstanceCreationExpression); - - if (instanceCreationExpression == null) return; - - final changeBuilder = reporter.createChangeBuilder( - message: "Wrap with 'Rt.lazyState'.", - priority: 1, - ); - - changeBuilder.addDartFileEdit((builder) { - builder.addSimpleReplacement( - instanceCreationExpression.sourceRange, - "Rt.lazyState(() => $instanceCreationExpression, this)", - ); - }); - } catch (e) { - print(e); - } - }, - ); - } -} diff --git a/packages/reactter_lint/lib/src/rules/hook_name_convention.dart b/packages/reactter_lint/lib/src/rules/rt_hook_name_convention.dart similarity index 80% rename from packages/reactter_lint/lib/src/rules/hook_name_convention.dart rename to packages/reactter_lint/lib/src/rules/rt_hook_name_convention.dart index 4d9a021d..7a248811 100644 --- a/packages/reactter_lint/lib/src/rules/hook_name_convention.dart +++ b/packages/reactter_lint/lib/src/rules/rt_hook_name_convention.dart @@ -7,12 +7,11 @@ import 'package:custom_lint_builder/custom_lint_builder.dart'; import 'package:reactter_lint/src/consts.dart'; import 'package:reactter_lint/src/types.dart'; -/// The HookNameConvention class is a DartLintRule that enforces a specific naming convention for hooks. -class HookNameConvention extends DartLintRule { - const HookNameConvention() : super(code: _code); +class RtHookNameConvention extends DartLintRule { + const RtHookNameConvention() : super(code: _code); static const _code = LintCode( - name: "hook_name_convention", + name: "rt_hook_name_convention", errorSeverity: ErrorSeverity.WARNING, problemMessage: "The '{0}' hook name should be prefixed with '$HOOK_NAME_PREFIX'.", @@ -20,7 +19,7 @@ class HookNameConvention extends DartLintRule { ); @override - List getFixes() => [_HookNameFix()]; + List getFixes() => [_RtHookNameFix()]; static whenIsInvalid({ required CustomLintContext context, @@ -31,7 +30,7 @@ class HookNameConvention extends DartLintRule { if (declaredElement == null) return; - if (!reactterHookType.isAssignableFrom(declaredElement)) return; + if (!rtHookType.isAssignableFrom(declaredElement)) return; final name = declaredElement.name; @@ -66,8 +65,7 @@ class HookNameConvention extends DartLintRule { } } -/// The _HookNameFix class is a Dart fix that helps with fixing hook names. -class _HookNameFix extends DartFix { +class _RtHookNameFix extends DartFix { @override void run( CustomLintResolver resolver, @@ -76,12 +74,12 @@ class _HookNameFix extends DartFix { AnalysisError analysisError, List others, ) { - HookNameConvention.whenIsInvalid( + RtHookNameConvention.whenIsInvalid( context: context, onInvalid: (node, element) { try { final name = element.name!; - final nameUse = HookNameConvention.prefixUseName(name); + final nameUse = RtHookNameConvention.prefixUseName(name); final changeBuilder = reporter.createChangeBuilder( message: "Rename '$name' to '$nameUse'.", diff --git a/packages/reactter_lint/lib/src/rules/invalid_hook_position.dart b/packages/reactter_lint/lib/src/rules/rt_invalid_hook_position.dart similarity index 79% rename from packages/reactter_lint/lib/src/rules/invalid_hook_position.dart rename to packages/reactter_lint/lib/src/rules/rt_invalid_hook_position.dart index 769a54ab..85a23620 100644 --- a/packages/reactter_lint/lib/src/rules/invalid_hook_position.dart +++ b/packages/reactter_lint/lib/src/rules/rt_invalid_hook_position.dart @@ -8,13 +8,11 @@ import 'package:reactter_lint/src/extensions.dart'; import 'package:reactter_lint/src/helpers.dart'; import 'package:reactter_lint/src/types.dart'; -/// The InvalidHookPosition class is a DartLintRule that detects and reports invalid hook positions in -/// code. -class InvalidHookPosition extends DartLintRule { - const InvalidHookPosition() : super(code: _code); +class RtInvalidHookPosition extends DartLintRule { + const RtInvalidHookPosition() : super(code: _code); static const _code = LintCode( - name: "invalid_hook_position", + name: "rt_invalid_hook_position", errorSeverity: ErrorSeverity.WARNING, problemMessage: "The '{0}' must be defined after hook register('$HOOK_REGISTER_VAR').", @@ -31,9 +29,10 @@ class InvalidHookPosition extends DartLintRule { if (declaredElement == null) return; - if (!reactterHookType.isAssignableFrom(declaredElement)) return; + if (!rtHookType.isAssignableFrom(declaredElement)) return; - final hookRegisterNode = node.members.firstWhereOrNull(isHookRegister); + final hookRegisterNode = + node.members.firstWhereOrNull(isRegisterDeclaration); if (hookRegisterNode == null) return; @@ -42,7 +41,7 @@ class InvalidHookPosition extends DartLintRule { if (element == null) return false; - return reactterHookType.isAssignableFrom(element); + return rtHookType.isAssignableFrom(element); }).toList(); for (final hook in hooks) { diff --git a/packages/reactter_lint/lib/src/rules/invalid_hook_register.dart b/packages/reactter_lint/lib/src/rules/rt_invalid_hook_register.dart similarity index 87% rename from packages/reactter_lint/lib/src/rules/invalid_hook_register.dart rename to packages/reactter_lint/lib/src/rules/rt_invalid_hook_register.dart index b35c359d..0814ba86 100644 --- a/packages/reactter_lint/lib/src/rules/invalid_hook_register.dart +++ b/packages/reactter_lint/lib/src/rules/rt_invalid_hook_register.dart @@ -8,12 +8,11 @@ import 'package:reactter_lint/src/consts.dart'; import 'package:reactter_lint/src/helpers.dart'; import 'package:reactter_lint/src/types.dart'; -/// The InvalidHookRegister class is a DartLintRule that detects and reports invalid hook registrations. -class InvalidHookRegister extends DartLintRule { - const InvalidHookRegister() : super(code: _code); +class RtInvalidHookRegister extends DartLintRule { + const RtInvalidHookRegister() : super(code: _code); static const _code = LintCode( - name: "invalid_hook_register", + name: "rt_invalid_hook_register", errorSeverity: ErrorSeverity.ERROR, problemMessage: "The hook register('$HOOK_REGISTER_VAR' field) must be final only.", @@ -23,7 +22,7 @@ class InvalidHookRegister extends DartLintRule { ); @override - List getFixes() => [_HookRegisterFix()]; + List getFixes() => [_RtHookRegisterFix()]; static whenIsInvalid({ required CustomLintContext context, @@ -34,9 +33,8 @@ class InvalidHookRegister extends DartLintRule { if (declaredElement == null) return; - if (!reactterHookType.isAssignableFrom(declaredElement)) return; - - final hookRegisterNodes = node.members.where(isHookRegister); + if (!rtHookType.isAssignableFrom(declaredElement)) return; + final hookRegisterNodes = node.members.where(isRegisterDeclaration); if (hookRegisterNodes.isEmpty) return; @@ -73,9 +71,7 @@ class InvalidHookRegister extends DartLintRule { } } -/// The `_HookRegisterFix` class is a Dart fix that performs a specific action related to hook -/// registration. -class _HookRegisterFix extends DartFix { +class _RtHookRegisterFix extends DartFix { @override void run( CustomLintResolver resolver, @@ -84,7 +80,7 @@ class _HookRegisterFix extends DartFix { AnalysisError analysisError, List others, ) { - InvalidHookRegister.whenIsInvalid( + RtInvalidHookRegister.whenIsInvalid( context: context, onInvalid: (node, element) { try { diff --git a/packages/reactter_lint/lib/src/rules/rt_invalid_state_creation.dart b/packages/reactter_lint/lib/src/rules/rt_invalid_state_creation.dart new file mode 100644 index 00000000..5b986ad7 --- /dev/null +++ b/packages/reactter_lint/lib/src/rules/rt_invalid_state_creation.dart @@ -0,0 +1,136 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:reactter_lint/src/extensions.dart'; +import 'package:reactter_lint/src/types.dart'; + +class RtInvalidStateCreation extends DartLintRule { + const RtInvalidStateCreation() : super(code: _code); + + static const _code = LintCode( + name: "rt_invalid_state_creation", + errorSeverity: ErrorSeverity.WARNING, + problemMessage: + "The `{0}` state must be create under the Reactter context.", + correctionMessage: "Use `Rt.registerState` method.", + ); + + @override + List getFixes() => [_RtInvalidStateCreationFix()]; + + static whenIsInvalid({ + required LintCode code, + required CustomLintContext context, + required Function(InstanceCreationExpression node) onInvalid, + }) { + context.registry.addInstanceCreationExpression((node) { + final element = node.constructorName.staticElement; + + if (element == null || + element.isFactory || + rtHookType.isAssignableFromType(element.returnType) || + !rtStateType.isAssignableFromType(element.returnType) || + iAutoRegisterStateType.isAssignableFromType(element.returnType)) { + return; + } + + final methodInvocation = node.thisOrAncestorOfType(); + final targetType = methodInvocation?.realTarget?.staticType; + final methodName = methodInvocation?.methodName.staticElement; + final isRegisterState = targetType != null && + methodName != null && + rtInterface.isAssignableFromType(targetType) && + registerStateType.isAssignableFrom(methodName); + + if (methodInvocation == null || !isRegisterState) return onInvalid(node); + + final functionArg = methodInvocation.argumentList.arguments + .whereType() + .firstOrNull; + final returnFunctionArg = functionArg?.returnExpression; + + if (returnFunctionArg == null) return onInvalid(node); + + bool isEqualReturnExpression; + + if (returnFunctionArg is SimpleIdentifier) { + isEqualReturnExpression = returnFunctionArg.staticElement == element; + } else { + isEqualReturnExpression = + returnFunctionArg.unParenthesized == node.unParenthesized; + } + + if (!isEqualReturnExpression) return onInvalid(node); + }); + } + + bool isUsedRegisterStateMethod(Set nodes, FunctionBody body) { + final returnExpression = body.returnExpression; + print( + "RETURN EXPRESSION: $returnExpression, ${returnExpression.runtimeType}"); + if (returnExpression == null) return false; + + if (body is ExpressionFunctionBody) return true; + + if (returnExpression is SimpleIdentifier) { + final returnElement = returnExpression.staticElement; + final variableDeclaration = + nodes.whereType().firstOrNull; + final variableElement = variableDeclaration?.declaredElement; + + return returnElement == variableElement; + } + + return false; + } + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + whenIsInvalid( + code: code, + context: context, + onInvalid: (node) { + reporter.reportErrorForNode(code, node, [node.toString()]); + }, + ); + } +} + +class _RtInvalidStateCreationFix extends DartFix { + @override + void run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + AnalysisError analysisError, + List others, + ) { + RtInvalidStateCreation.whenIsInvalid( + code: RtInvalidStateCreation._code, + context: context, + onInvalid: (node) { + try { + final changeBuilder = reporter.createChangeBuilder( + message: + "Convert '${node.toString()}' to use `Rt.registerState` method.", + priority: 1, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + node.sourceRange, + "Rt.registerState(() => ${node.toString()})", + ); + }); + } catch (e) { + print(e); + } + }, + ); + } +} diff --git a/packages/reactter_lint/lib/src/rules/rt_no_logic_in_create_state.dart b/packages/reactter_lint/lib/src/rules/rt_no_logic_in_create_state.dart new file mode 100644 index 00000000..7fcbd0a0 --- /dev/null +++ b/packages/reactter_lint/lib/src/rules/rt_no_logic_in_create_state.dart @@ -0,0 +1,62 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:reactter_lint/src/extensions.dart'; +import 'package:reactter_lint/src/types.dart'; + +class RtNoLogicInRegisterState extends DartLintRule { + const RtNoLogicInRegisterState() : super(code: _code); + + static const _code = LintCode( + name: "rt_no_logic_in_create_state", + errorSeverity: ErrorSeverity.WARNING, + problemMessage: "Don't put logic in `registerState` method.", + correctionMessage: "Try moving the logic out of `registerState` method.", + ); + + static whenIsInvalid({ + required LintCode code, + required CustomLintContext context, + required Function(MethodInvocation node) onInvalid, + }) { + context.registry.addMethodInvocation((node) { + final targetType = node.realTarget?.staticType; + final methodName = node.methodName.staticElement; + + if (targetType == null || methodName == null) return; + if (!rtInterface.isAssignableFromType(targetType)) return; + if (!registerStateType.isAssignableFrom(methodName)) return; + final functionArg = _getFunctionFromArgument(node); + + if (functionArg == null || functionArg.body is! BlockFunctionBody) return; + + final functionBody = functionArg.body as BlockFunctionBody; + final statements = functionBody.block.statements; + + if (statements.length > 1) return onInvalid(node); + if (statements.firstOrNull is! ReturnStatement) return onInvalid(node); + }); + } + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + whenIsInvalid( + code: code, + context: context, + onInvalid: (node) { + reporter.reportErrorForNode(code, node, [node.toString()]); + }, + ); + } + + static FunctionExpression? _getFunctionFromArgument(MethodInvocation node) { + return node.argumentList.arguments + .whereType() + .firstOrNull; + } +} diff --git a/packages/reactter_lint/lib/src/rules/rt_state_late_convention.dart b/packages/reactter_lint/lib/src/rules/rt_state_late_convention.dart new file mode 100644 index 00000000..d7773ff9 --- /dev/null +++ b/packages/reactter_lint/lib/src/rules/rt_state_late_convention.dart @@ -0,0 +1,109 @@ +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/error/error.dart'; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +import 'package:reactter_lint/src/types.dart'; + +class RtStateLateConvention extends DartLintRule { + const RtStateLateConvention() : super(code: _code); + + static const _code = LintCode( + name: "rt_state_late_convention", + errorSeverity: ErrorSeverity.WARNING, + problemMessage: "The '{0}' state late must be attached an instance.", + correctionMessage: + "Try removing 'late' keyword or wrapping the state using 'Rt.lazyState'.\n" + "Example: late final myStateLate = Rt.lazyState(() => UseState(0), this);", + ); + + @override + List getFixes() => [_RtStateLateFix()]; + + static whenIsInvalid({ + required CustomLintContext context, + required Function(FieldDeclaration node) onInvalid, + }) { + context.registry.addFieldDeclaration((node) { + final variable = node.fields.variables.first; + final element = variable.declaredElement; + + if (element == null || + !element.isLate || + !rtStateType.isAssignableFromType(element.type)) { + return; + } + + final initializer = variable.initializer?.unParenthesized; + + if (initializer is! MethodInvocation) return onInvalid(node); + + final targetType = initializer.realTarget?.staticType; + final methodName = initializer.methodName.staticElement; + final isLazyState = targetType != null && + methodName != null && + rtInterface.isAssignableFromType(targetType) && + lazyStateType.isAssignableFrom(methodName); + + if (isLazyState) return; + + onInvalid(node); + }); + } + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + whenIsInvalid( + context: context, + onInvalid: (node) { + final variable = node.fields.variables.first; + final initializer = variable.initializer?.unParenthesized; + + if (initializer == null) return; + + reporter + .reportErrorForNode(code, initializer, [initializer.toString()]); + }, + ); + } +} + +class _RtStateLateFix extends DartFix { + @override + void run( + CustomLintResolver resolver, + ChangeReporter reporter, + CustomLintContext context, + AnalysisError analysisError, + List others, + ) { + RtStateLateConvention.whenIsInvalid( + context: context, + onInvalid: (node) { + try { + final variable = node.fields.variables.first; + final initializer = variable.initializer?.unParenthesized; + + if (initializer == null) return; + + final changeBuilder = reporter.createChangeBuilder( + message: "Wrap with 'Rt.lazyState'.", + priority: 1, + ); + + changeBuilder.addDartFileEdit((builder) { + builder.addSimpleReplacement( + initializer.sourceRange, + "Rt.lazyState(() => $initializer, this)", + ); + }); + } catch (e) { + print(e); + } + }, + ); + } +} diff --git a/packages/reactter_lint/lib/src/types.dart b/packages/reactter_lint/lib/src/types.dart index e8868407..59825db0 100644 --- a/packages/reactter_lint/lib/src/types.dart +++ b/packages/reactter_lint/lib/src/types.dart @@ -1,7 +1,17 @@ import 'package:custom_lint_builder/custom_lint_builder.dart'; -final reactterHookType = - TypeChecker.fromName('RtHook', packageName: 'reactter'); +final rtStateType = TypeChecker.fromName('RtState', packageName: 'reactter'); +final iAutoRegisterStateType = + TypeChecker.fromName('IAutoRegisterState', packageName: 'reactter'); +final rtHookType = TypeChecker.fromName('RtHook', packageName: 'reactter'); +final hookRegisterType = + TypeChecker.fromName('HookRegister', packageName: 'reactter'); -final hookRegister = - TypeChecker.fromName('_HookRegister', packageName: 'reactter'); +final reactterType = TypeChecker.fromPackage('reactter'); + +final rtInterface = + TypeChecker.fromName('RtInterface', packageName: 'reactter'); +final registerStateType = + TypeChecker.fromName('registerState', packageName: 'reactter'); +final lazyStateType = + TypeChecker.fromName('lazyState', packageName: 'reactter'); diff --git a/packages/reactter_lint/lib/src/visitors.dart b/packages/reactter_lint/lib/src/visitors.dart new file mode 100644 index 00000000..e69de29b diff --git a/packages/reactter_lint/pubspec.lock b/packages/reactter_lint/pubspec.lock deleted file mode 100644 index 96cfdcb7..00000000 --- a/packages/reactter_lint/pubspec.lock +++ /dev/null @@ -1,349 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a - url: "https://pub.dev" - source: hosted - version: "61.0.0" - analyzer: - dependency: "direct main" - description: - name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 - url: "https://pub.dev" - source: hosted - version: "5.13.0" - analyzer_plugin: - dependency: "direct main" - description: - name: analyzer_plugin - sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d - url: "https://pub.dev" - source: hosted - version: "0.11.2" - args: - dependency: transitive - description: - name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 - url: "https://pub.dev" - source: hosted - version: "2.4.2" - async: - dependency: transitive - description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.dev" - source: hosted - version: "2.11.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff - url: "https://pub.dev" - source: hosted - version: "2.0.3" - ci: - dependency: transitive - description: - name: ci - sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" - url: "https://pub.dev" - source: hosted - version: "0.1.0" - cli_util: - dependency: transitive - description: - name: cli_util - sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 - url: "https://pub.dev" - source: hosted - version: "0.4.1" - collection: - dependency: transitive - description: - name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a - url: "https://pub.dev" - source: hosted - version: "1.18.0" - convert: - dependency: transitive - description: - name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" - url: "https://pub.dev" - source: hosted - version: "3.1.1" - crypto: - dependency: transitive - description: - name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab - url: "https://pub.dev" - source: hosted - version: "3.0.3" - custom_lint: - dependency: transitive - description: - name: custom_lint - sha256: "22bd87a362f433ba6aae127a7bac2838645270737f3721b180916d7c5946cb5d" - url: "https://pub.dev" - source: hosted - version: "0.5.11" - custom_lint_builder: - dependency: "direct main" - description: - name: custom_lint_builder - sha256: "0d48e002438950f9582e574ef806b2bea5719d8d14c0f9f754fbad729bcf3b19" - url: "https://pub.dev" - source: hosted - version: "0.5.14" - custom_lint_core: - dependency: transitive - description: - name: custom_lint_core - sha256: "2952837953022de610dacb464f045594854ced6506ac7f76af28d4a6490e189b" - url: "https://pub.dev" - source: hosted - version: "0.5.14" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - file: - dependency: transitive - description: - name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - freezed_annotation: - dependency: transitive - description: - name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d - url: "https://pub.dev" - source: hosted - version: "2.4.1" - glob: - dependency: transitive - description: - name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - hotreloader: - dependency: transitive - description: - name: hotreloader - sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e - url: "https://pub.dev" - source: hosted - version: "4.2.0" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 - url: "https://pub.dev" - source: hosted - version: "4.8.1" - logging: - dependency: transitive - description: - name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.dev" - source: hosted - version: "0.12.16+1" - meta: - dependency: transitive - description: - name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" - url: "https://pub.dev" - source: hosted - version: "1.12.0" - package_config: - dependency: transitive - description: - name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - path: - dependency: transitive - description: - name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.dev" - source: hosted - version: "1.9.0" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 - url: "https://pub.dev" - source: hosted - version: "1.2.3" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" - url: "https://pub.dev" - source: hosted - version: "0.27.7" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.dev" - source: hosted - version: "1.10.0" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" - url: "https://pub.dev" - source: hosted - version: "1.11.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.dev" - source: hosted - version: "2.1.2" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.dev" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" - url: "https://pub.dev" - source: hosted - version: "0.7.0" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c - url: "https://pub.dev" - source: hosted - version: "1.3.2" - uuid: - dependency: transitive - description: - name: uuid - sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 - url: "https://pub.dev" - source: hosted - version: "4.3.3" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: e7d5ecd604e499358c5fe35ee828c0298a320d54455e791e9dcf73486bc8d9f0 - url: "https://pub.dev" - source: hosted - version: "14.1.0" - watcher: - dependency: transitive - description: - name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - yaml: - dependency: transitive - description: - name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" - url: "https://pub.dev" - source: hosted - version: "3.1.2" -sdks: - dart: ">=3.3.0 <4.0.0" diff --git a/screenshots/devtools.png b/screenshots/devtools.png new file mode 100644 index 00000000..2130726f Binary files /dev/null and b/screenshots/devtools.png differ diff --git a/screenshots/example_app.png b/screenshots/example_app.png new file mode 100644 index 00000000..588725e5 Binary files /dev/null and b/screenshots/example_app.png differ diff --git a/website/astro.config.mjs b/website/astro.config.mjs index eda4a7a7..4cf58e54 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -26,7 +26,6 @@ export default defineConfig({ src: "./public/logo.svg", }, customCss: ["./src/styles/custom.css"], - defaultLocale: "root", expressiveCode: { themes: [myTheme], }, @@ -39,6 +38,7 @@ export default defineConfig({ ThemeProvider: "./src/components/ThemeProvider.astro", ThemeSelect: "./src/components/ThemeSelect.astro", }, + defaultLocale: "root", locales: { root: { label: "English", @@ -46,6 +46,7 @@ export default defineConfig({ }, es: { label: "Español", + lang: "es", }, }, social: { @@ -72,6 +73,13 @@ export default defineConfig({ es: "Empezando", }, }, + { + label: "DevTools extension", + link: "/devtools_extension", + translations: { + es: "Extensión de DevTools", + }, + }, ], }, { @@ -84,51 +92,70 @@ export default defineConfig({ }, }, { - label: "Hooks", - translations: { - es: "Hooks", - }, - autogenerate: { - directory: "hooks", - }, - }, - { - label: "Classes", - translations: { - es: "Clases", - }, - autogenerate: { - directory: "classes", - }, - }, - { - label: "Widgets", - badge: "Flutter", + label: "Migration", translations: { - es: "Widgets", + es: "Migración", }, autogenerate: { - directory: "widgets", + directory: "migration", }, }, { - label: "Methods", + label: "API", translations: { - es: "Métodos", + es: "API", }, autogenerate: { - directory: "methods", - }, - }, - { - label: "Extensions", - badge: "Flutter", - translations: { - es: "Extensiones", - }, - autogenerate: { - directory: "extensions", + directory: "api", }, + items: [ + { + label: "Hooks", + translations: { + es: "Hooks", + }, + autogenerate: { + directory: "api/hooks", + }, + }, + { + label: "Classes", + translations: { + es: "Clases", + }, + autogenerate: { + directory: "api/classes", + }, + }, + { + label: "Methods", + translations: { + es: "Métodos", + }, + autogenerate: { + directory: "api/methods", + }, + }, + { + label: "Widgets", + badge: "Flutter", + translations: { + es: "Widgets", + }, + autogenerate: { + directory: "api/widgets", + }, + }, + { + label: "Extensions", + translations: { + es: "Extensiones", + }, + autogenerate: { + directory: "api/extensions", + }, + }, + ], }, { label: "Extra topics", diff --git a/website/public/devtools_extension/devtools.png b/website/public/devtools_extension/devtools.png new file mode 100644 index 00000000..2130726f Binary files /dev/null and b/website/public/devtools_extension/devtools.png differ diff --git a/website/public/devtools_extension/enable_button.png b/website/public/devtools_extension/enable_button.png new file mode 100644 index 00000000..7e908a92 Binary files /dev/null and b/website/public/devtools_extension/enable_button.png differ diff --git a/website/public/devtools_extension/extension_tab.png b/website/public/devtools_extension/extension_tab.png new file mode 100644 index 00000000..c86c5b72 Binary files /dev/null and b/website/public/devtools_extension/extension_tab.png differ diff --git a/website/public/devtools_extension/reactter_extension.png b/website/public/devtools_extension/reactter_extension.png new file mode 100644 index 00000000..221c207c Binary files /dev/null and b/website/public/devtools_extension/reactter_extension.png differ diff --git a/website/public/opengraph.png b/website/public/opengraph.png index cf2c5414..b0f78c16 100644 Binary files a/website/public/opengraph.png and b/website/public/opengraph.png differ diff --git a/website/src/components/Highlight/index.ts b/website/src/components/Highlight/index.ts index b37d04a8..bc97c5c4 100644 --- a/website/src/components/Highlight/index.ts +++ b/website/src/components/Highlight/index.ts @@ -2,5 +2,5 @@ export { default as HE } from "./HE.astro"; export { default as HK } from "./HK.astro"; export { default as HM } from "./HM.astro"; export { default as HS } from "./HS.astro"; -export { default as HN } from "./HN.astro"; +export { default as HN, default as HB } from "./HN.astro"; export { default as HT } from "./HT.astro"; diff --git a/website/src/components/SiteTitle.astro b/website/src/components/SiteTitle.astro index 6b4834f5..ec11a509 100644 --- a/website/src/components/SiteTitle.astro +++ b/website/src/components/SiteTitle.astro @@ -3,10 +3,10 @@ import { logos } from "virtual:starlight/user-images"; import config from "virtual:starlight/user-config"; import type { Props } from "@astrojs/starlight/props"; -const href = Astro.props.locale ?? "/"; +const { siteTitle, siteTitleHref } = Astro.props; --- - + { config.logo && logos.dark && ( <> @@ -36,7 +36,7 @@ const href = Astro.props.locale ?? "/"; "sr-only": config.logo?.replacesTitle, }} > - {config.title.en} + {siteTitle} diff --git a/website/src/content/docs/classes/args.mdx b/website/src/content/docs/api/classes/args.mdx similarity index 90% rename from website/src/content/docs/classes/args.mdx rename to website/src/content/docs/api/classes/args.mdx index 522970bd..c7901a32 100644 --- a/website/src/content/docs/classes/args.mdx +++ b/website/src/content/docs/api/classes/args.mdx @@ -2,7 +2,7 @@ title: Args description: Learn how to use generic arguments in Reactter. sidebar: - order: 3 + order: 10 --- import { HM, HT } from '@/components/Highlight'; @@ -18,7 +18,7 @@ It is usefult to use in the following cases: - **Function Argument Bundling**: When you need to pass multiple arguments to a function via a single argument. - **Argument Extraction**: When you required to get the arguments of a function as a list. - **Adaptation for Single-Argument Expectation**: In situations where a class or function anticipates a function with a single argument, but you need to provide it with a function that accepts multiple arguments, the [`ary`](#ary-function) method is very useful. -This adaptation is particularly valuable in contexts like employing the [`Memo`](/reactter/classes/memo) class or the [`UseAsyncState.withArg`](/reactter/hooks/use_async_state) hook, where compatibility hinges on transforming a multi-argument function into a single-argument one. +This adaptation is particularly valuable in contexts like employing the [`Memo`](/reactter/api/classes/memo) class or the [`UseAsyncState.withArg`](/reactter/api/hooks/use_async_state) hook, where compatibility hinges on transforming a multi-argument function into a single-argument one. ## Classes @@ -36,14 +36,16 @@ Reactter provides these generic arguments classes: In each of the methods it provides these properties and methods: -- `arguments`: A property to get the list of arguments. -- `arg1`: A property to get the first argument. -- `arg2`(`Args2`, `Args3`, `ArgsX2`, `ArgsX3` only): A property to get the second argument. -- `arg3`(`Args3`, `ArgsX3` only): A property to get the third argument. -- `toList()`: A method to get the list of arguments `T` type. +- **`arguments`**: A property to get the list of arguments. +- **`arg1`**: A property to get the first argument. +- **`arg2`**(`Args2`, `Args3`, `ArgsX2`, `ArgsX3` only): A property to get the second argument. +- **`arg3`**(`Args3`, `ArgsX3` only): A property to get the third argument. +- **`toList`**: A method to get the list of arguments `T` type. ## Usage +### Define Arguments + To use it, you can create an instance and provide the specified types of arguments, e.g.: ```dart @@ -81,7 +83,7 @@ void myFunction(ArgsX3 args) { myFunction(ArgsX3(1, 2, 3)); ``` -## Type compatibility +### Type compatibility The `Args` classes are compatible with each other, so you can pass an `Args` instance to a function that expects an `Args` instance with fewer arguments, e.g.: @@ -114,7 +116,7 @@ myFunction(Args2(1, "foo")); myFunction(Args3(1, "bar", false)); ``` -## Equality Comparation +### Equality Comparation The `Args` classes are comparable, so you can compare them with each other. Two Args instances are considered equal if they contain the same number of arguments and the values in each corresponding position match, e.g.: @@ -140,7 +142,7 @@ print(Args3(1, "foo", false) == Args([1, "foo", false, "bar"])); // false print(ArgsX3(3, "bar", false) == Args3("bar", 3, false)); // false ``` -## Ary Function +### Ary Function > The _**arity**_ or _**adicity**_ of a function is the number of arguments (i.e. inputs or parameters) it takes. diff --git a/website/src/content/docs/classes/memo.mdx b/website/src/content/docs/api/classes/memo.mdx similarity index 65% rename from website/src/content/docs/classes/memo.mdx rename to website/src/content/docs/api/classes/memo.mdx index a1ca812e..856afc3f 100644 --- a/website/src/content/docs/classes/memo.mdx +++ b/website/src/content/docs/api/classes/memo.mdx @@ -2,7 +2,7 @@ title: Memo description: Learn how to use memoization in Reactter. sidebar: - order: 2 + order: 9 --- import { HM, HT } from '@/components/Highlight'; @@ -21,30 +21,27 @@ Memo( `Memo` accepts these properties: -- `computeValue`: A method that takes an argument of type `A` and returns a value of `T` type. This is the core function that will be memoized. -- `interceptor`: A `MemoInterceptor` that allows you to intercept the memoization function calls and modify the memoization process. +- **`computeValue`**: A method that takes an argument of type `A` and returns a value of `T` type. This is the core function that will be memoized. +- **`interceptor`**: A `MemoInterceptor` that allows you to intercept the memoization function calls and modify the memoization process. Reactter providers some interceptors: - - `MemoSafeAsyncInterceptor`: prevents caching if the `Future` calculation function throws an error during execution. - - `MemoTemporaryCacheInterceptor`: removes memoized values from the cache after a specified duration. - - `MemoWrapperInterceptor`: Wraps a memoized function, enabling the definition of callbacks for initialization, successful completion, error handling, and finishing. - - `MemoMultiInterceptor`: allows multiple memoization interceptors to be used together. + - **`MemoSafeAsyncInterceptor`**: prevents caching if the `Future` calculation function throws an error during execution. + - **`MemoTemporaryCacheInterceptor`**: removes memoized values from the cache after a specified duration. + - **`MemoWrapperInterceptor`**: Wraps a memoized function, enabling the definition of callbacks for initialization, successful completion, error handling, and finishing. + - **`MultiMemoInterceptor`**: allows multiple memoization interceptors to be used together. ## Methods `Memo` provides the following methods that will help you manipulate the cache as you wish: -- `get`: Returns the cached value by argument passed. - - Syntax: +- **`get`**: Returns the cached value by argument passed. ```dart showLineNumbers=false T? get(A arg); ``` -- `remove`: Removes the cached value by argument passed. - - Syntax: +- **`remove`**: Removes the cached value by argument passed. ```dart showLineNumbers=false T? remove(A arg); ``` -- `clear`: Removes all cached data. - - Syntax: +- **`clear`**: Removes all cached data. ```dart showLineNumbers=false void clear(); ``` @@ -90,10 +87,10 @@ When the same number is passed to the `factorialMemo` function again, t :::note The `computeValue` of `Memo` accepts one argument only. If you want to add more arguments, you can supply it using the `Record`(if your proyect support) -or [`Args`](/reactter/classes/args)(A generic arguments provided by Reactter). +or [`Args`](/reactter/api/classes/args)(A generic arguments provided by Reactter). ::: :::note Use `Memo.inline` in case there is a typing conflict, -e.g. with the [`UseAsyncState`](/reactter/hooks/use_async_state) and [`UseCompute`](/reactter/hooks/use_compute) hooks which a `Function` type is required. +e.g. with the [`UseAsyncState`](/reactter/api/hooks/use_async_state) and [`UseCompute`](/reactter/api/hooks/use_compute) hooks which a `Function` type is required. ::: \ No newline at end of file diff --git a/website/src/content/docs/api/classes/rt_dependency_lifecycle.mdx b/website/src/content/docs/api/classes/rt_dependency_lifecycle.mdx new file mode 100644 index 00000000..f038ea24 --- /dev/null +++ b/website/src/content/docs/api/classes/rt_dependency_lifecycle.mdx @@ -0,0 +1,84 @@ +--- +title: RtDependencyLifecycle +description: The base class for all lifecycle observers in Reactter. +sidebar: + order: 6 +--- +import { HM, HT } from '@/components/Highlight'; +import CodeTabs from '@/components/CodeTabs.astro'; +import { Tabs, TabItem } from "@/components/Tabs"; +import ZappButton from "@/components/ZappButton.astro"; +import { Code } from "@astrojs/starlight/components"; +import { marks } from '@/examples/marks.ts' + +import counterControllerRtDependencyLifecycleCode from '@/examples/rt_dependency_lifecycle/lib/counter_controller.dart?raw'; +import counterRtDependencyLifecycleCode from '@/examples/rt_dependency_lifecycle/lib/counter.dart?raw'; +import counterViewRtDependencyLifecycleCode from '@/examples/rt_dependency_lifecycle/lib/counter_view.dart?raw'; +import counterMainRtDependencyLifecycleCode from '@/examples/rt_dependency_lifecycle/lib/main.dart?raw'; + +:::tip +This documentation assumes you've already read the [Lifecycle](/reactter/core_concepts/lifecycle/). +It's recommended read that first if you're new in Reactter. +::: + +`RtDependencyLifecycle` is a class that provides a way to observe the lifecycle of a dependency. + +## Syntax + +```dart showLineNumbers=false +abstract class RtDependencyLifecycle { + void onCreated() {} + void onWillMount() {} + void onDidMount() {} + void onWillUpdate(RtState state) {} + void onDidUpdate(RtState state) {} + void onWillUnmount() {} + void onDidUnmount() {} +} +``` + +## Methods + +- **`onCreated`**: Called when the dependency is created. +This is the first lifecycle method invoked. +- **`onWillMount`**(only in **flutter_reactter**): Called just before the dependency is mounted. +Useful for pre-mount setup. +- **`onDidMount`**(only in **flutter_reactter**): Called immediately after the dependency is mounted. +Ideal for post-mount initialization. +- **`onWillUpdate`**: Called before the dependency is updated. +The parameter is the state (RtState) that triggered the update. +Use this to react to state changes before they take effect. +- **`onDidUpdate`**: Called after the dependency is updated. +The parameter is the state (RtState) that triggered the update. +Use this to react to state changes after they take effect. +- **`onWillUnmount`**(only in **flutter_reactter**): Called just before the dependency is unmounted. +Useful for cleanup tasks. +- **`onDidUnmount`**(only in **flutter_reactter**): Called immediately after the dependency is unmounted. +Ideal for final cleanup or resource release. + +## Example + +Here's an example of a simple RtDependencyLifecycle implementation: + + + + + + + counter_controller.dart + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/website/src/content/docs/api/classes/rt_dependency_observer.mdx b/website/src/content/docs/api/classes/rt_dependency_observer.mdx new file mode 100644 index 00000000..680774d6 --- /dev/null +++ b/website/src/content/docs/api/classes/rt_dependency_observer.mdx @@ -0,0 +1,80 @@ +--- +title: RtDependencyObserver +description: The base class for observing and reacting to the lifecycle events of dependencies in Reactter, such as registration, creation, mounting, and deletion. +sidebar: + order: 5 +--- + +import { HE, HM, HT } from '@/components/Highlight'; + +`RtDependencyObserver` is a class that provides a way to observe the lifecycle of dependencies in Reactter. + +## Syntax + +```dart showLineNumbers=false + RtDependencyObserver({ + void onRegistered(DependencyRef dependency)?, + void onCreated(DependencyRef dependency, Object? instance)?, + void onMounted(DependencyRef dependency, Object? instance)?, + void onUnmounted(DependencyRef dependency, Object? instance)?, + void onDeleted(DependencyRef dependency, Object? instance)?, + void onUnregistered(DependencyRef dependency)?, + void onFailed(DependencyRef dependency, DependencyFail fail)?, + }); +``` + +The `RtDependencyObserver` class accepts these parameters: + +- **`onRegistered`**: *(optional)* Called when a `dependency` is registered. +- **`onCreated`**: *(optional)* Called when a `instance` of a `dependency` is created. +- **`onMounted`**: *(optional)* Called when the `instance` of a `dependency` is mounted. +- **`onUnmounted`**: *(optional)* Called when the `instance` of a `dependency` is unmounted. +- **`onDeleted`**: *(optional)* Called when the `instance` of a `dependency` is deleted. +- **`onUnregistered`**: *(optional)* Called when a `dependency` is unregistered. +- **`onFailed`**: *(optional)* Called when a `dependency` fails, indicating one of the following failures: + - **`DependencyFail.alreadyRegistered`**: The dependency is already registered. + - **`DependencyFail.alreadyCreated`**: An instance of dependency is already created. + - **`DependencyFail.alreadyDeleted`**: The instance of dependency is already deleted. + - **`DependencyFail.alreadyUnregistered`**: The dependency is already unregistered. + - **`DependencyFail.missingInstanceBuilder`**: The dependency was not registered previously. + - **`DependencyFail.builderRetained`**: The builder is retained because is in factory mode. + - **`DependencyFail.dependencyRetained`**: The builder and instance is retained because is in singleton mode. + - **`DependencyFail.cannotUnregisterActiveInstance`**: Cannot unregister the dependency because it has an active instance. + +## Usage + +To use `RtDependencyObserver`, you need to instantiate it, pass the callbacks you want to use, and then add it using the [`Rt.addObserver`](/reactter/api/methods/debugging_methods/#rtaddobserver) method, e.g.: + +```dart "RtDependencyObserver" "Rt.addObserver" +import 'package:reactter/reactter.dart'; + +void main() { + // Create a dependency observer with lifecycle callbacks + final dependencyObserver = RtDependencyObserver( + onRegistered: (dependency) { + print('Dependency($dependency) registered'); + }, + onCreated: (dependency, instance) { + print('Dependency($dependency) created with instance($instance)'); + }, + onMounted: (dependency, instance) { + print('Dependency($dependency) mounted with instance($instance)'); + }, + onUnmounted: (dependency, instance) { + print('Dependency($dependency) unmounted with instance($instance)'); + }, + onDeleted: (dependency, instance) { + print('Dependency($dependency) deleted with instance($instance)'); + }, + onUnregistered: (dependency) { + print('Dependency($dependency) unregistered'); + }, + onFailed: (dependency, fail) { + print('Dependency($dependency) failed with $fail'); + }, + ); + + // Add the dependency observer to Reactter's observer system + Rt.addObserver(dependencyObserver); +} +``` \ No newline at end of file diff --git a/website/src/content/docs/api/classes/rt_dependency_ref.mdx b/website/src/content/docs/api/classes/rt_dependency_ref.mdx new file mode 100644 index 00000000..fbd63961 --- /dev/null +++ b/website/src/content/docs/api/classes/rt_dependency_ref.mdx @@ -0,0 +1,57 @@ +--- +title: RtDependencyRef +description: Represents dependency managed by Reactter's dependency injection. +sidebar: + order: 4 +--- + +import { HE, HM, HT } from '@/components/Highlight'; +import CodeTabs from '@/components/CodeTabs.astro'; +import { Tabs, TabItem } from "@/components/Tabs"; +import ZappButton from "@/components/ZappButton.astro"; +import { Code } from "@astrojs/starlight/components"; +import { marks } from '@/examples/marks.ts' + +import counterControllerEventHandlerCode from '@/examples/lifecycle_event_handler/lib/counter_controller.dart?raw'; +import counterEventHandlerCode from '@/examples/lifecycle_event_handler/lib/counter.dart?raw'; +import counterViewEventHandlerCode from '@/examples/lifecycle_event_handler/lib/counter_view.dart?raw'; +import counterMainEventHandlerCode from '@/examples/lifecycle_event_handler/lib/main.dart?raw'; + +`RtDependencyRef` is a class that represents a reference to a dependency managed by Reactter's dependency injection system. + +## Syntax + +```dart showLineNumbers=false + RtDependencyRef([String? id]); +``` + +The `RtDependencyRef` class accepts this parameter: + +- **`id`**: *(optional)* A unique identifier for the dependency. + +## Usage + +To use `RtDependencyRef`, you need to instantiate it, pass the type of dependency you want to reference and use for event handling, e.g.: + + + + + + + counter_view.dart + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/website/src/content/docs/api/classes/rt_hooks.mdx b/website/src/content/docs/api/classes/rt_hooks.mdx new file mode 100644 index 00000000..53ea4443 --- /dev/null +++ b/website/src/content/docs/api/classes/rt_hooks.mdx @@ -0,0 +1,54 @@ +--- +title: RtHook +description: Aprende aceca de los Hooks en Reactter. +sidebar: + order: 3 +--- + +import { HM, HK, HT } from '@/components/Highlight'; +import StatePropertiesMethodsRef from '@/content/docs/shareds/state_properties_methods_ref.mdx'; +import CreatingCustomHook from '@/content/docs/shareds/creating_custom_hook.mdx'; +import UsingCustomHook from '@/content/docs/shareds/using_custom_hook.mdx'; + +:::tip +This documentation assumes you've already read the [hooks](/reactter/core_concepts/hooks/). +It's recommended read that first if you're new in Reactter. +::: + +`RtHook` is a class that provides a way to create a custom hook. + +## Syntax + +```dart showLineNumbers=false +abstract class RtHook with RtState { + static get $register => HookBindingZone(); + HookBindingZone get $; + void initHook() + void update([Function()? fnUpdate]) +} +``` + +## Properties & Methods + +- **`$register`**: A static getter to allow access to `HookBindingZone`. +It's responsible for registering a `RtHook` and attaching collected states to it. +- **`$`**: A required getter to access the `HookBindingZone` instance. +It must be defined has a `final` property on first line of the class for registering the hook and attach the states below it, like this: + + ```dart showLineNumbers=false + final $ = RtHook.$register; + ``` +- **`initHook`**: Called when the hook is initialized. +Useful for setting up state dependencies or performing side effects when the hook is first created. + + + +## Usage + +### Creating a custom hook + + + +### Using the hook + + \ No newline at end of file diff --git a/website/src/content/docs/api/classes/rt_state.mdx b/website/src/content/docs/api/classes/rt_state.mdx new file mode 100644 index 00000000..dbfb558c --- /dev/null +++ b/website/src/content/docs/api/classes/rt_state.mdx @@ -0,0 +1,158 @@ +--- +title: RtState +description: The base class for all states in Reactter. +sidebar: + order: 1 +--- + +import { HE, HN, HM, HT, HS } from '@/components/Highlight'; +import StatePropertiesMethods from '@/content/docs/shareds/state_properties_methods.mdx'; + +:::tip +This documentation assumes you've already read the [State Management](/reactter/core_concepts/state_management/). +It's recommended read that first if you're new in Reactter. +::: + +The `RtState` class is a foundational class in the Reactter framework, designed to manage state in a Dart application. +It provides a structured way to create, update, and dispose of state, ensuring that state changes are tracked and propagated efficiently. +This class is typically extended to create custom state that can be used within the Reactter ecosystem. + +## Syntax + +```dart showLineNumbers=false +abstract class RtState> { + String? get debugLabel; + Map get debugInfo; + void update(Function? fnUpdate) + void notify() + void bind(Object instance) + void unbind() + void dispose() +} +``` + +## Properties & Methods + + + +## Usage + +### Declaration + +To create a custom state, extend the `RtState` class and define the state properties and methods. +Register the state using [`Rt.registerState`](/reactter/api/methods/state_management_methods/#rtregisterstate) or as a dependency within the Reactter framework, e.g.: + +```dart collapse={13-20} mark={1,12} "RtState" "Rt.registerState" "Rt.on" "Lifecycle.didUpdate" /update(?=\u0028)/ +class MyState extends RtState { + int _value = 0; + int get value => _value; + set value(int n) { + if (n == _value) return; + update(() => _value = n); // set new value and notify observers + } + + MyState([int value = 0]) : _value = value; +} + +final state = Rt.registerState(() => MyState()); // Register state + +Rt.on( + state, + Lifecycle.didUpdate, + (_, __) => print('State updated: ${state.value}') +); + +state.value = 42; // Calls update internally +``` + +:::tip +You can also use the factory constructor to create a state, e.g.: + +```dart collapse={2-8, 15-21} mark={11-13} "RtState" "Rt.registerState" "Rt.on" "Lifecycle.didUpdate" /update(?=\u0028)/ +class MyState extends RtState { + int _value = 0; + int get value => _value; + set value(int n) { + if (n == _value) return; + update(() => _value = n); // set new value and notify observers + } + + MyState._([int value = 0]) : _value = value; + + factory MyState(int value) { + return Rt.registerState(() => MyState._(value)); // Register state + } +} + +Rt.on( + state, + Lifecycle.didUpdate, + (_, __) => print('State updated: ${state.value}') +); + +state.value = 42; // Calls update internally +``` + +This approach allows you to create a state directly without the need to call the `Rt.registerState` method explicitly. +::: + +### Updating the state + +To update the state, call the `update` method and provide a callback function that modifies the state, e.g.: + +```dart collapse={2-3, 8-13} mark={6, 22} "RtState" "Rt.registerState" "Rt.on" "Lifecycle.didUpdate" /update(?=\u0028)/ +class MyState extends RtState { + int _value = 0; + int get value => _value; + set value(int n) { + if (n == _value) return; + update(() => _value = n); // set new value and notify observers + } + + MyState._([int value = 0]) : _value = value; + + factory MyState(int value) { + return Rt.registerState(() => MyState._(value)); // Register state + } +} + +Rt.on( + state, + Lifecycle.didUpdate, + (_, __) => print('State updated: ${state.value}') +); + +state.value = 42; // Calls update internally +``` + +### Listening to changes + +When a state is updated, it emits the following lifecycle events: + +- `Lifecycle.willUpdate` event is triggered before the callback function of `update` method have been executed. +- `Lifecycle.didUpdate` event is triggered after the callback function of `update` method have been executed or after the `notify` method have been invoked. + +```dart collapse={2-3, 8-13} mark={16-20} "RtState" "Rt.registerState" "Rt.on" "Lifecycle.didUpdate" /update(?=\u0028)/ +class MyState extends RtState { + int _value = 0; + int get value => _value; + set value(int n) { + if (n == _value) return; + update(() => _value = n); // set new value and notify observers + } + + MyState._([int value = 0]) : _value = value; + + factory MyState(int value) { + return Rt.registerState(() => MyState._(value)); // Register state + } +} + +Rt.on( + state, + Lifecycle.didUpdate, + (_, __) => print('State updated: ${state.value}') +); + +state.value = 42; // Calls update internally +``` \ No newline at end of file diff --git a/website/src/content/docs/api/classes/rt_state_observer.mdx b/website/src/content/docs/api/classes/rt_state_observer.mdx new file mode 100644 index 00000000..769a3691 --- /dev/null +++ b/website/src/content/docs/api/classes/rt_state_observer.mdx @@ -0,0 +1,62 @@ +--- +title: RtStateObserver +description: The base class for observing and reacting to the lifecycle events of states in Reactter, such as creation, binding, updating, and disposal. +sidebar: + order: 2 +--- + +import { HM, HT } from '@/components/Highlight'; + +`RtStateObserver` is a class that provides a way to observe the lifecycle of states in Reactter. + +## Syntax + +```dart showLineNumbers=false + RtStateObserver({ + void onBound(RtState state, Object instance)?, + void onUnbound(RtState state, Object instance)?, + void onCreated(RtState state)?, + void onUpdated(RtState state)?, + void onDisposed(RtState state)?, + }); +``` + +The `RtStateObserver` class accepts these parameters: + +- **`onBound`**: *(optional)* Called when a `state` is bound to an `instance`. +- **`onUnbound`**: *(optional)* Called when a `state` is unbound from an `instance`. +- **`onCreated`**: *(optional)* Called when a `state` is initialized. +- **`onUpdated`**: *(optional)* Called when a `state` undergoes a change or update. +- **`onDisposed`**: *(optional)* Called when a `state` is disposed or cleaned up. + +## Usage + +To use `RtStateObserver`, you need to instantiate it, pass the callbacks you want to use and then add it using the [`Rt.addObserver`](/reactter/api/methods/debugging_methods/#rtaddobserver) method, e.g.: + +```dart "RtStateObserver" "Rt.addObserver" +import 'package:reactter/reactter.dart'; + +void main() { + // Create a state observer with lifecycle callbacks + final stateObserver = RtStateObserver( + onBound: (state, instance) { + print('State($state) bound to instance($instance)'); + }, + onUnbound: (state, instance) { + print('State($state) unbound from instance($instance)'); + }, + onCreated: (state) { + print('State($state) created'); + }, + onUpdated: (state) { + print('State($state) updated'); + }, + onDisposed: (state) { + print('State($state) disposed'); + }, + ); + + // Add the state observer to Reactter's observer system + Rt.addObserver(stateObserver); +} +``` diff --git a/website/src/content/docs/classes/signal.mdx b/website/src/content/docs/api/classes/signal.mdx similarity index 72% rename from website/src/content/docs/classes/signal.mdx rename to website/src/content/docs/api/classes/signal.mdx index 3b7f6c8a..f1283078 100644 --- a/website/src/content/docs/classes/signal.mdx +++ b/website/src/content/docs/api/classes/signal.mdx @@ -2,12 +2,12 @@ title: Signal description: The base class for all signals in Reactter. sidebar: - order: 1 + order: 7 --- import { HM, HT } from '@/components/Highlight'; import MDXRedry from '@/components/MDXRedry.astro'; -import StateMethodsLite from '@/content/docs/shareds/state_methods_lite.mdx'; +import StatePropertiesMethodsRef from '@/content/docs/shareds/state_properties_methods_ref.mdx'; import * as NoteLateState from '@/content/docs/shareds/note_late_state.mdx'; import * as NoteStateValueNotifies from '@/content/docs/shareds/note_state_value_notifies.mdx'; import * as ListeningChangesState from '@/content/docs/shareds/listening_changes_state.mdx'; @@ -20,16 +20,25 @@ import * as ListeningChangesState from '@/content/docs/shareds/listening_changes Signal(T initialValue); ``` -`Signal` accepts this property: +`Signal` accepts this parameter: -- `initialValue`: Initial value of `T` type that the it will hold. +- **`initialValue`**: Initial value of `T` type that the it will hold. ## Properties & Methods `Signal` provides the following properties and methods: -- `value`: A getter/setter that allows to read and write its state. - +- **`value`**: A getter/setter that allows to read and write its state. + +- Various properties and methods depending on the type of `T`. For example, if `T` is a `List`, you can use the methods and properties of the `List` class: + + ```dart showLineNumbers=false + final listSignal = Signal>([]); + listSignal.addAll([1, 2, 3]); + listSignal.removeAt(0); + listSignal.clear(); + ``` + ## Usage @@ -97,10 +106,10 @@ userSignal.update((user) { }); ``` -Use `refresh` method to force to notify changes. +Use `notify` method to force to notify changes. ```dart showLineNumbers=false -userSignal.refresh(); +userSignal.notify(); ``` ### Listening to changes @@ -111,5 +120,5 @@ userSignal.refresh(); }}/> :::note[With Flutter] -[`RtSignalWatcher`](/reactter/widgets/rt_signal_watcher) is a way to keep the widgets automatically updates, accessing the `value` of `Signal` reactively. +[`RtSignalWatcher`](/reactter/api/widgets/rt_signal_watcher) is a way to keep the widgets automatically updates, accessing the `value` of `Signal` reactively. ::: \ No newline at end of file diff --git a/website/src/content/docs/api/extensions/build_context_extensions.mdx b/website/src/content/docs/api/extensions/build_context_extensions.mdx new file mode 100644 index 00000000..9de81035 --- /dev/null +++ b/website/src/content/docs/api/extensions/build_context_extensions.mdx @@ -0,0 +1,289 @@ +--- +title: BuildContext Extensions +description: The extensions for the BuildContext class in Reactter. +sidebar: + order: 1 + badge: + text: Flutter +--- + +import { HM, HT, HN } from '@/components/Highlight'; +import MDXRedry from '@/components/MDXRedry.astro'; +import CodeTabs from '@/components/CodeTabs.astro'; +import { Tabs, TabItem } from "@/components/Tabs"; +import ZappButton from "@/components/ZappButton.astro"; +import { Code } from "@astrojs/starlight/components"; +import { marks } from '@/examples/marks.ts' + +import counterControllerCode from '@/examples/build_context/lib/counter_controller.dart?raw'; +import counterCode from '@/examples/build_context/lib/counter.dart?raw'; +import counterViewCode from '@/examples/build_context/lib/counter_view.dart?raw'; +import counterDivisibleCode from '@/examples/build_context_select/lib/counter_divisible.dart?raw'; +import mainCode from '@/examples/build_context/lib/main.dart?raw'; +import * as WatchVsSelectComparation from '@/content/docs/shareds/watch_vs_select_comparation.mdx'; + +The `BuildContext` class in Flutter provides information about the current widget in the tree. +Reactter extends the `BuildContext` class with additional methods to make it easier to work with dependencies. + +Reactter provides the following extensions for the `BuildContext` class: + +- [`BuildContext.use`](#buildcontextuse) +- [`BuildContext.watch`](#buildcontextwatch) +- [`BuildContext.watchId`](#buildcontextwatchid) +- [`BuildContext.select`](#buildcontextselect) + +## `BuildContext.use` + +`BuildContext.use` is a method that gets an instance of `T` dependency from the nearest ancestor provider ([`RtProvider`](/reactter/api/widgets/rt_provider) or [`RtComponent`](/reactter/api/widgets/rt_component)) of type `T`. + +### Syntax + +```dart showLineNumbers=false +T context.use([String? id]); +``` + +- `context`: The `BuildContext` object, which provides information about the current widget in the tree. +- `T`: The type of the dependency you want to access. +These are some points to consider: + - If the type is nullable, the method will return `null` if the dependency is not found. + - If the type is not nullable, the method will throw an exception if the dependency is not found. +- `id`: An optional identifier for the `T` dependency. If omitted, the dependency will be located by its type (`T`). + +### Usage + +This following example demonstrates how to use `BuildContext.use`: + + + + + + + counter.dart + + + + + counter_view.dart + + + + + + + + + + + + + +In this example, the `Counter` widget uses `BuildContext.use` to get the instance of `CounterController` from the nearest ancestor provider (located in `main.dart`). + +## `BuildContext.watch` + +`BuildContext.watch` is a method similar to [`BuildContext.use`](/reactter/api/extensions/build_context_use), but with an added functionality. +It not only retrieves the `T` dependency but also registers the current widget as a listener for any changes to the states set or otherwise any changes in the `T` dependency. +Consequently, whenever changes, the widget using `BuildContext.watch` is automatically notified and rebuilt. + +:::tip +You can optionally use `BuildContext.watch` with an `id`, but when you need to listen changes in a dependency's states using an `id`, it's better to use [`BuildContext.watchId`](/reactter/api/extensions/build_context_watch_id) for improved readability and clarity. + +```dart showLineNumbers=false +context.watch(null, 'myId'); +// is equivalent to +context.watchId('myId'); +``` +::: + +:::caution +To render using a computed value based on multiple states, use [`BuildContext.select`](/reactter/api/extensions/build_context_select) instead of `BuildContext.watch` or pre-compute the value using [`UseCompute`](/reactter/api/hooks/use_compute) to prevent unnecessary rebuilds. + + +::: + +### Syntax + +```dart showLineNumbers=false +T context.watch([ + List listenStates?(T instance), + String? id, +]); +``` + +- `context`: The `BuildContext` object, which provides information about the current widget in the tree. +- `T`: The type of the dependency you want to access. +These are some points to consider: + - If the type is nullable, the method will return `null` if the dependency is not found. + - If the type is not nullable, the method will throw an exception if the dependency is not found. +- `listenStates`: An optional function that returns a list of state (`RtState`) to listen for changes. If omitted, the method will listen for any changes in the `T` dependency. +- `id`: An optional identifier for the `T` dependency. If omitted, the dependency will be located by its type (`T`). + +### Usage + +This following example demonstrates how to use `BuildContext.watch`: + + + + + + + counter.dart + + + + + counter_view.dart + + + + + + + + + + + + + +In this example, we use `BuildContext.watch` to get the instance of `CounterController` from the nearest ancestor provider (located in `main.dart`) and listen for changes in the `count` state. + +## `BuildContext.watchId` + + +`BuildContext.watchId` is a method similar to [`BuildContext.watch`](/reactter/api/extensions/build_context_watch), but use `id` as first argument to locate the dependency. + +:::tip +This approach is useful when you have multiple dependencies of the same type and you want to access a specific one. + +While you can use [`BuildContext.watch`](/reactter/api/extensions/build_context_watch) with `id`, `BuildContext.watchId` is more readable and clear when an `id` is required. + +```dart showLineNumbers=false +context.watch(null, 'myId'); +// is equivalent to +context.watchId('myId'); +``` +::: + +### Syntax + +```dart showLineNumbers=false +T context.watchId( + String id, + [List listenStates?(T instance)], +); +``` + +- `context`: The `BuildContext` object, which provides information about the current widget in the tree. +- `T`: The type of the dependency you want to access. These are some points to consider: + - If the type is nullable, the method will return `null` if the dependency is not found. + - If the type is not nullable, the method will throw an exception if the dependency is not found. +- `id`: An identifier for the `T` dependency. The dependency will be located by its type (`T`) and the `id`. +- `listenStates`: An optional function that returns a list of state (`RtState`) to listen for changes. If omitted, the method will listen for any changes in the `T` dependency. + +### Usage + +This following example demonstrates how to use `BuildContext.watchId`: + + + + + + + counter.dart + + + + + counter_view.dart + + + + + + + + + + + + + +In this example, the `Counter` widget uses `BuildContext.watchId` to get the instance of `CounterController` by its `id` from the nearest ancestor provider (located in `main.dart`) and listen for changes in the `count` state. + +## `BuildContext.select` + +`BuildContext.select` is a method that computes a value whenever the selected states change and registers the current widget as a listener for these changes. +Consequently, each time the selected states change, the value is recalculated and if it is different from the previous one, the widget is automatically notified and rebuilt. + +:::tip +`BuildContext.select` is useful when you need to render using a computed value based on multiple states. +This aproach is more efficient than using [`BuildContext.watch`](/reactter/api/extensions/build_context_watch) because it avoids unnecessary rebuilds. + + +::: + +### Syntax + +```dart showLineNumbers=false +V context.select( + V computeValue(T instance, RtState select(RtState state)), + [String? id], +); +``` + +- `context`: The `BuildContext` object, which provides information about the current widget in the tree. +- `T`: The type of the dependency you want to select. It is used to locate the dependency from the nearest ancestor provider. +- `V`: The type of the value you want to compute. It is the return type of the `computeValue` function. +- `computeValue`: A function that computes the value based on the selected states. It takes two parameters: + - `instance`: The instance of the dependency of type `T`. + - `select`: A function that allows you to wrap the state (`RtState`) to be listened for changes and returns it. +- `id`: An optional identifier for the `T` dependency. If omitted, the dependency will be located by its type (`T`). + +:::tip +It's a good idea to name the select parameter to something more simple and shorter like `$` to make it easier to read and understand the `selector` function when you have multiple states to select. + +```dart showLineNumbers=false {2} +RtSelector( + selector: (inst, $) => $(inst.firstName).value + ' ' + $(inst.lastName).value, + builder: (context, inst, fullName, child) { + return Text("Full name: $fullName"); + }, +) +``` +::: + +### Usage + +This following example demonstrates how to use `BuildContext.select`: + + + + + + + counter_divisible.dart + + + + + counter_view.dart + + + + + + + + + + + + + + + + + +In this example, checks if the `count` state from `CounterController` is divisible by a specified number (`byNum`). +The `BuildContext.select` method is used to perform this check and rebuild the widget tree whenever the divisibility status changes. \ No newline at end of file diff --git a/website/src/content/docs/hooks/use_async_state.mdx b/website/src/content/docs/api/hooks/use_async_state.mdx similarity index 55% rename from website/src/content/docs/hooks/use_async_state.mdx rename to website/src/content/docs/api/hooks/use_async_state.mdx index 16e85878..31d2a700 100644 --- a/website/src/content/docs/hooks/use_async_state.mdx +++ b/website/src/content/docs/api/hooks/use_async_state.mdx @@ -4,9 +4,9 @@ description: UseAsyncState hook documentation. sidebar: order: 2 --- -import { HE, HK, HM, HT } from '@/components/Highlight'; +import { HB, HE, HK, HM, HT } from '@/components/Highlight'; import MDXRedry from '@/components/MDXRedry.astro'; -import StateMethodsLite from '@/content/docs/shareds/state_methods_lite.mdx'; +import StatePropertiesMethodsRef from '@/content/docs/shareds/state_properties_methods_ref.mdx'; import * as NoteLateState from '@/content/docs/shareds/note_late_state.mdx'; import * as NoteStateValueNotifies from '@/content/docs/shareds/note_state_value_notifies.mdx'; import * as ListeningChangesState from '@/content/docs/shareds/listening_changes_state.mdx'; @@ -17,58 +17,92 @@ import * as ListeningChangesState from '@/content/docs/shareds/listening_changes ```dart showLineNumbers=false UseAsyncState( - T initialValue, Future asyncFunction(), + T initialValue, + { String? debugLabel }, ); // UseAsyncState with arguments UseAsyncState.withArg( - T initialValue, Future asyncFunction(A arg), + T initialValue, + { String? debugLabel }, ); ``` -`UseAsyncState` accepts these arguments: +`UseAsyncState` accepts these parameters: -- `initialValue`: Initial value of `T` type that it will hold. -- `asyncFunction`: A function that returns a `Future` to update the state asynchronously. +- **`asyncFunction`**: A function that returns a `Future` to update the state asynchronously. This function is called by the `resolve` method and sets the `value` property. +- **`initialValue`**: Initial value of `T` type that it will hold. +- **`debugLabel`**: *(optional)* A label to identify the hook in the [DevTools extension](/reactter/devtools_extension). ## Properties & Methods -`UseAsyncState` provides the following properties and methods: - -- `value`: A getter that allows to read its state. -- `status`: A getter that allows to read its status. It can be: - - `UseAsyncStateStatus.standby`: Represents the standby status, indicating that the state is idle. - - `UseAsyncStateStatus.loading`: Denotes the loading status, indicating that an asynchronous operation is in progress. - - `UseAsyncStateStatus.done`: Indicates that the asynchronous operation has been successfully completed. - - `UseAsyncStateStatus.error`: Signifies that an error has occurred during the asynchronous operation. -- `error`: A getter that allows to get the error object when the `asyncFunction` fails. -- `resolve`: A method that updates the state asynchronously by calling the `asyncFunction` function. - - Syntax: +The `UseAsyncState` utility provides the following properties and methods to manage asynchronous states effectively: + +- **`value`**: + A getter to retrieve the current state of `T` type. + The `value` can either be the `initialValue` or the resolved value returned by the `asyncFunction`. +- **`status`**: + A getter to access the current status of the asynchronous state. The possible statuses are: + - **`UseAsyncStateStatus.idle`**: Indicates that the state is in stand by and no operation is in progress. + - **`UseAsyncStateStatus.loading`**: Indicates that an asynchronous operation is currently running. + - **`UseAsyncStateStatus.done`**: Indicates that the asynchronous operation has completed successfully. + - **`UseAsyncStateStatus.error`**: Indicates that an error occurred during the asynchronous operation. +- **`isLoading`**: +A boolean getter that returns `true` if the `asyncFunction` is currently in progress. +- **`isDone`**: + A boolean getter that returns `true` if the `asyncFunction` has successfully completed. +- **`isError`**: + A boolean getter that returns `true` if the `asyncFunction` has failed. +- **`error`**: + A getter that returns the error object if the `asyncFunction` has failed. +- **`future`**: + A getter that returns the `Future` of the asynchronous operation. +- **`uValue`**: + A reactive state object that provides the current value and notifies listeners when the value changes. +- **`uStatus`**: + A reactive state object that provides the current status and notifies listeners when the status changes. +- **`uIsLoading`**: + A reactive state object that provides a boolean indicating whether the `asyncFunction` is in progress, with notifications for changes. +- **`uIsDone`**: + A reactive state object that provides a boolean indicating whether the `asyncFunction` has completed, with notifications for changes. +- **`uIsError`**: + A reactive state object that provides a boolean indicating whether the `asyncFunction` has failed, with notifications for changes. +- **`uError`**: + A reactive state object that provides the error information and notifies listeners if an error occurs. +- **`resolve`**: + Executes the `asyncFunction` and updates the state asynchronously. + - **Syntax**: ```dart showLineNumbers=false FutureOr resolve(); - // for UseAsyncState.withArg + // For UseAsyncState.withArg FutureOr resolve(A arg); ``` -- `when`: A method that allows to computed a value depending on its status. - - Syntax: +- **`cancel`**: + Cancels the currently running asynchronous operation, if applicable. + - **Syntax**: + ```dart showLineNumbers=false + void cancel(); + ``` +- **`when`**: + Evaluates and returns a value based on the current state. Useful for rendering or handling state-specific logic. + - **Syntax**: ```dart showLineNumbers=false R? when({ - WhenValueReturn? standby, + WhenValueReturn? idle, WhenValueReturn? loading, WhenValueReturn? done, WhenErrorReturn? error, }); ``` - - Arguments: - - `standby`: A function that returns a value when the state is `UseAsyncStateStatus.standby`. - - `loading`: A function that returns a value when the state is `UseAsyncStateStatus.loading`. - - `done`: A function that returns a value when the state is `UseAsyncStateStatus.done`. - - `error`: A function that returns a value when the state is `UseAsyncStateStatus.error`. - - + - **parameters**: + - **`idle`**: A callback function invoked when the state is `UseAsyncStateStatus.idle`. + - **`loading`**: A callback function invoked when the state is `UseAsyncStateStatus.loading`. + - **`done`**: A callback function invoked when the state is `UseAsyncStateStatus.done`. + - **`error`**: A callback function invoked when the state is `UseAsyncStateStatus.error`. + ## Usage @@ -77,7 +111,7 @@ UseAsyncState.withArg( `UseAsyncState` can be initialized using the constructor class: ```dart showLineNumbers=false "UseAsyncState" "asyncFunction" -final uAsyncState = UseAsyncState('Initial value', asyncFunction); +final uAsyncState = UseAsyncState(asyncFunction, 'Initial value'); Future asyncFunction() async { await Future.delayed(Duration(seconds: 2)); @@ -92,7 +126,7 @@ class UserController { final String userId; late final uUser = Rt.lazyState( - () => UseAsyncState.withArg(null, this.getUser), + () => UseAsyncState.withArg(this.getUser, null), this, ); @@ -130,8 +164,8 @@ print("${uAsyncState.value}"); // Resolved value ```dart showLineNumbers=false "UseAsyncState.withArg" "asyncFunctionWithArg" final uAsyncStateWithArg = UseAsyncState.withArg( + asyncFunctionWithArg, 'Initial value', - asyncFunctionWithArg ); Future asyncFunctionWithArg(int arg) async { @@ -154,12 +188,12 @@ print("${uAsyncStateWithArg.value}"); // Resolved value with arg: 10 }}/> If you want to add more arguments, you can supply it using the `Record`(if your proyect support) -or [`Args`](/reactter/classes/args)(A generic arguments provided by Reactter), e.g.: +or [`Args`](/reactter/api/classes/args)(A generic arguments provided by Reactter), e.g.: ```dart showLineNumbers=false "UseAsyncState.withArg" "asyncFunctionWithArgs" ".resolve" ".value" final uAsyncStateWithArgs = UseAsyncState.withArg>( + asyncFunctionWithArgs, 'Initial value', - asyncFunctionWithArgs ); Future asyncFunctionWithArgs(ArgsX3 args) async { @@ -172,13 +206,12 @@ await uAsyncStateWithArgs.resolve(ArgsX3('arg1', 'arg2', 'arg3')); print("${uAsyncStateWithArgs.value}"); // Resolved value with args: arg1, arg2, arg3 ``` -### Using with Memo +### Caching the value -`UseAsyncState` does not cache the resolving `value`, meaning that it will resolve the `value` every time `resolve` is called, potentially impacting performance, especially if the `asyncFunction` is expensive. In this case, you should consider using [`Memo`](/reactter/classes/memo) to cache the resolving `value`, e.g.: +`UseAsyncState` doesn't cache the resolving `value` by default, meaning that it will resolve the `value` every time `resolve` is called, potentially impacting performance, especially if the `asyncFunction` is expensive. In this case, you should consider using [`Memo`](/reactter/api/classes/memo) to cache the resolving `value`, e.g.: ```dart "UseAsyncState" "Memo.inline" -final translateState = UseAsyncState.withArg>( - null, +final uTranslateState = UseAsyncState.withArg>( /// `Memo` stores the value resolved in cache, /// and retrieving that same value from the cache the next time /// it's needed instead of resolving it again. @@ -190,9 +223,12 @@ final translateState = UseAsyncState.withArg>( // this is fake code, which simulates a request to API return await api.translate(text, from, to); }, - AsyncMemoSafe(), // avoid to save in cache when throw a error + MemoSafeAsyncInterceptor(), // avoid to save in cache when throw a error ), + null, ); + +await uTranslateState.resolve(ArgsX3('Hello', 'en', 'es')); ``` ### Using `when` method @@ -201,7 +237,7 @@ final translateState = UseAsyncState.withArg>( ```dart showLineNumbers=false ".when" final result = uAsyncState.when( - standby: (value) => "Standby", + idle: (value) => "Standby", loading: (value) => "Loading", done: (value) => "Done", error: (error) => "Error: $error", @@ -220,7 +256,7 @@ Builder( ); return myController.uAsyncState.when( - standby: (value) => Text("Standby: $value"), + idle: (value) => Text("Standby: $value"), loading: (value) => CircularProgressIndicator(), done: (value) => Text("Done: $value"), error: (error) => Text("Error: $error"), @@ -242,10 +278,10 @@ uAsyncState.update((value) { }); ``` -Use `refresh` method to force to notify changes. +Use `notify` method to force to notify changes. -```dart showLineNumbers=false ".refresh" -uAsyncState.refresh(); +```dart showLineNumbers=false ".notify" +uAsyncState.notify(); ``` ### Listening to changes diff --git a/website/src/content/docs/hooks/use_compute.mdx b/website/src/content/docs/api/hooks/use_compute.mdx similarity index 82% rename from website/src/content/docs/hooks/use_compute.mdx rename to website/src/content/docs/api/hooks/use_compute.mdx index 09e9a10a..ca165a00 100644 --- a/website/src/content/docs/hooks/use_compute.mdx +++ b/website/src/content/docs/api/hooks/use_compute.mdx @@ -8,12 +8,13 @@ sidebar: import { HE, HM, HT } from '@/components/Highlight'; import { Code } from "@astrojs/starlight/components"; import MDXRedry from '@/components/MDXRedry.astro'; -import StateMethodsLite from '@/content/docs/shareds/state_methods_lite.mdx'; +import StatePropertiesMethodsRef from '@/content/docs/shareds/state_properties_methods_ref.mdx'; import * as NoteLateState from '@/content/docs/shareds/note_late_state.mdx'; import * as NoteStateValueNotifies from '@/content/docs/shareds/note_state_value_notifies.mdx'; import * as ListeningChangesState from '@/content/docs/shareds/listening_changes_state.mdx'; -`UseCompute` is a [hook](/reactter/core_concepts/hooks) that allows you to define a computation method (`computeValue`) to derive a value based on the current state of one or more dependencies. When any of these dependencies change, the hook recalculates the `value` and provides the updated result. +`UseCompute` is a [hook](/reactter/core_concepts/hooks) that allows you to define a computation method (`computeValue`) to derive a value based on the current state of one or more dependencies. +When any of these `dependencies` notify to change, the hook recalculates the `value` and provides the updated result. ## Syntax @@ -21,20 +22,22 @@ import * as ListeningChangesState from '@/content/docs/shareds/listening_changes UseCompute( T computeValue(), List dependencies, + { String? debugLabel }, ); ``` -`UseCompute` accepts these arguments: +`UseCompute` accepts these parameters: -- `computeValue`: A function that returns a value based on the current state of the dependencies. -- `dependencies`: A list of state(`RtState`, learn about it [here](/reactter/core_concepts/state_management/#state)) that the computation depends on. When any of these dependencies change, the `value` is recalculated. +- **`computeValue`**: A function that returns a value based on the current state of the dependencies. +- **`dependencies`**: A list of state(`RtState`, learn about it [here](/reactter/core_concepts/state_management/#state)) that the computation depends on. When any of these `dependencies` trigger `Lifecycle.didUpdate` event, the `value` is recalculated. +- **`debugLabel`**: *(optional)* A label to identify the hook in the [DevTools extension](/reactter/devtools_extension). ## Properties & Methods `UseCompute` provides the following properties and methods: -- `value`: A getter that allows you to read the computed value. - +- **`value`**: A getter that allows you to read the computed value. + ## Usage @@ -147,7 +150,7 @@ But when the calculated `value` is same as the previous `value`, it will not not ### Using with Memo -`UseCompute` does not cache the computed `value`, meaning that it will computing the `value` every time the `dependencies` is changed, potentially impacting performance, especially if the `computeValue` is expensive. In this case, you should consider using [`Memo`](/reactter/classes/memo) to cache the computed `value`, e.g.: +`UseCompute` does not cache the computed `value`, meaning that it will computing the `value` every time the `dependencies` is changed, potentially impacting performance, especially if the `computeValue` is expensive. In this case, you should consider using [`Memo`](/reactter/api/classes/memo) to cache the computed `value`, e.g.: ```dart showLineNumbers=false "UseCompute" "Memo" final addAB = Memo( diff --git a/website/src/content/docs/hooks/use_dependency.mdx b/website/src/content/docs/api/hooks/use_dependency.mdx similarity index 54% rename from website/src/content/docs/hooks/use_dependency.mdx rename to website/src/content/docs/api/hooks/use_dependency.mdx index e10bd5cf..57139387 100644 --- a/website/src/content/docs/hooks/use_dependency.mdx +++ b/website/src/content/docs/api/hooks/use_dependency.mdx @@ -5,116 +5,156 @@ sidebar: order: 6 --- +import CodeTabs from '@/components/CodeTabs.astro'; +import { Tabs, TabItem } from "@/components/Tabs"; import { HE, HM, HN, HS, HT } from '@/components/Highlight'; -import StateMethodsLite from '@/content/docs/shareds/state_methods_lite.mdx'; +import StatePropertiesMethodsRef from '@/content/docs/shareds/state_properties_methods_ref.mdx'; +import TipDependencyChecking from '@/content/docs/shareds/tip_dependency_checking.mdx'; :::tip This documentation assumes you've already read the [Dependency Injection](/reactter/core_concepts/dependency_injection). It's recommended read that first if you're new in Reactter. ::: -`UseDependency` is a [RtHook](https://pub.dev/documentation/reactter/latest/reactter/RtHook-class.html) that allows to manage a dependency. +`UseDependency` is a [hook](/reactter/core_concepts/hooks) that allows to manage a dependency. -## Constructors and factories +## Syntax -- `UseDependency`: Default constructor to get the `T` dependency. It's similar to using `Rt.find`. +- **`UseDependency`**: Default constructor to get the `T` dependency. It's similar to using `Rt.find`. ```dart showLineNumbers=false - UseDependency([String? id]); + UseDependency({ + String? id, + String? debugLabel + }); ``` -- `UseDependency.get`: Get a `T` dependency inmediately. It's similar to using `Rt.get`. +- **`UseDependency.get`**: Get a `T` dependency inmediately. It's similar to using `Rt.get`. ```dart showLineNumbers=false - UseDependency.get([String? id]); + UseDependency.get({ + String? id, + String? debugLabel + }); ``` -- `UseDependency.register`: Register a builder function to create a `T` dependency. It's similar to using `Rt.register`. +- **`UseDependency.register`**: Register a builder function to create a `T` dependency. It's similar to using `Rt.register`. ```dart showLineNumbers=false - UseDependency.register(T Function() builder, { - String? id, - DependencyMode mode = DependencyMode.builder, - }); + UseDependency.register( + T builder(), { + String? id, + DependencyMode mode = DependencyMode.builder, + String? debugLabel, + }, + ); ``` -- `UseDependency.create`: Registers, creates and gets a `T` dependency inmediately. It's similar to using `Rt.create`. +- **`UseDependency.create`**: Registers, creates and gets a `T` dependency inmediately. It's similar to using `Rt.create`. ```dart showLineNumbers=false - UseDependency.create(T Function() builder, { - String? id, - DependencyMode mode = DependencyMode.builder, - }); + UseDependency.create( + T builder(), { + String? id, + DependencyMode mode = DependencyMode.builder, + String? debugLabel, + }, + ); ``` -- `UseDependency.lazyBuilder`: Registers a builder function, to create a instance of the `T` dependency as [Builder](/reactter/core_concepts/dependency_injection/#builder) mode. It's similar to using `Rt.lazyBuilder`. +- **`UseDependency.lazyBuilder`**: Registers a builder function, to create a instance of the `T` dependency as [Builder](/reactter/core_concepts/dependency_injection/#builder) mode. It's similar to using `Rt.lazyBuilder`. ```dart showLineNumbers=false - UseDependency.lazyBuilder(T Function() builder, [String? id]); + UseDependency.lazyBuilder( + T builder(), { + String? id, + String? debugLabel + }, + ); ``` -- `UseDependency.lazyFactory`: Registers a builder function, to create a instance of the `T` dependency as [Factory](/reactter/core_concepts/dependency_injection/#factory) mode. It's similar to using `Rt.lazyFactory`. +- **`UseDependency.lazyFactory`**: Registers a builder function, to create a instance of the `T` dependency as [Factory](/reactter/core_concepts/dependency_injection/#factory) mode. It's similar to using `Rt.lazyFactory`. ```dart showLineNumbers=false - UseDependency.lazyFactory(T Function() builder, [String? id]); + UseDependency.lazyFactory( + T builder(), { + String? id, + String? debugLabel + }, + ); ``` -- `UseDependency.lazySingleton`: Registers a builder function, to create a instance of the `T` dependency as [Singleton](/reactter/core_concepts/dependency_injection/#singleton) mode. It's similar to using `Rt.lazySingleton`. +- **`UseDependency.lazySingleton`**: Registers a builder function, to create a instance of the `T` dependency as [Singleton](/reactter/core_concepts/dependency_injection/#singleton) mode. It's similar to using `Rt.lazySingleton`. ```dart showLineNumbers=false - UseDependency.lazySingleton(T Function() builder, [String? id]); + UseDependency.lazySingleton( + T builder(), { + String? id, + String? debugLabel + }, + ); ``` -- `UseDependency.builder`: Registers, creates and gets a `T` dependency inmediately as [Builder](/reactter/core_concepts/dependency_injection/#builder) mode. It's similar to using `Rt.builder`. +- **`UseDependency.builder`**: Registers, creates and gets a `T` dependency inmediately as [Builder](/reactter/core_concepts/dependency_injection/#builder) mode. It's similar to using `Rt.builder`. ```dart showLineNumbers=false - UseDependency.builder(T Function() builder, { - String? id, - DependencyMode mode = DependencyMode.builder, - }); + UseDependency.builder( + T builder(), { + String? id, + DependencyMode mode = DependencyMode.builder, + String? debugLabel, + }, + ); ``` -- `UseDependency.factory`: Registers, creates and gets a `T` dependency inmediately as [Factory](/reactter/core_concepts/dependency_injection/#factory) mode. It's similar to using `Rt.factory`. +- **`UseDependency.factory`**: Registers, creates and gets a `T` dependency inmediately as [Factory](/reactter/core_concepts/dependency_injection/#factory) mode. It's similar to using `Rt.factory`. ```dart showLineNumbers=false - UseDependency.factory(T Function() builder, { - String? id, - DependencyMode mode = DependencyMode.builder, - }); + UseDependency.factory( + T builder(), { + String? id, + DependencyMode mode = DependencyMode.builder, + String? debugLabel, + }, + ); ``` -- `UseDependency.singleton`: Registers, creates and gets a `T` dependency inmediately as [Singleton](/reactter/core_concepts/dependency_injection/#singleton) mode. It's similar to using `Rt.singleton`. +- **`UseDependency.singleton`**: Registers, creates and gets a `T` dependency inmediately as [Singleton](/reactter/core_concepts/dependency_injection/#singleton) mode. It's similar to using `Rt.singleton`. ```dart showLineNumbers=false - UseDependency.singleton(T Function() builder, { - String? id, - DependencyMode mode = DependencyMode.builder, - }); + UseDependency.singleton( + T builder(), { + String? id, + DependencyMode mode = DependencyMode.builder, + String? debugLabel, + }, + ); ``` -### Arguments +### Parameters -- `id`: An optional `String` value to identify the dependency of `T` type. -- `builder`: A function that returns an instance of the dependency of `T` type. -- `mode`: An optional `DependencyMode` value to define the dependency mode. Default value is `DependencyMode.builder`. -Learn more about it in [DependencyMode](/reactter/core_concepts/dependency_injection#dependency-modes). +- **`id`**: *(optional)* A `String` used to identify the dependency of type `T`. +- **`builder`**: A function that returns an instance of the dependency of type `T`. +- **`mode`**: *(optional)* A `DependencyMode` that defines the dependency mode. Defaults to `DependencyMode.builder`. + Learn more in [DependencyMode](/reactter/core_concepts/dependency_injection#dependency-modes). +- **`debugLabel`**: *(optional)* A `String` used to identify the hook in the [DevTools extension](/reactter/devtools_extension). :::note -Some constructors and factories don't have all the arguments. For example, -`builder` is not available in `UseDependency` and `UseDependency.get`, and `mode` is available only in `UseDependency.register` and `UseDependency.create`. +Some constructors and factories do not include all parameters. For example: +- **`builder`** is not available in `UseDependency` and `UseDependency.get`. +- **`mode`** is available only in `UseDependency.register` and `UseDependency.create`. ::: - ## Properties and methods -- `instance`: A getter property to get the dependency instance of `T` type. - +- **`instance`**: A getter property to get the dependency instance of `T` type. + ## Usage ### Finding a dependency -You can use the default constructor to obtain the dependency of `T` type, e.g.: +Use the default constructor to obtain the dependency of `T` type, e.g.: ```dart collapse={1-3, 5-9} "UseDependency" import 'package:reactter/reactter.dart'; @@ -131,36 +171,16 @@ class MyController { :::note The default constructor only will find the dependency by `T` type. Note that the `instance` property will be `null` if the dependency is not created already. -To register and create the dependency instance, using the `UseDependency.create` hook instead. Learn more about it in [Creating a dependency](/reactter/hooks/use_dependency#creating-a-dependency). +To register and create the dependency instance, using the `UseDependency.create` hook instead. Learn more about it in [Creating a dependency](/reactter/api/hooks/use_dependency#creating-a-dependency). -If only you need to retrieve the dependency instance, using the `UseDependency.get` hook instead. Learn more about it in [Getting a dependency](/reactter/hooks/use_dependency#getting-a-dependency). +If only you need to retrieve the dependency instance, using the `UseDependency.get` hook instead. Learn more about it in [Getting a dependency](/reactter/api/hooks/use_dependency#getting-a-dependency). ::: -:::tip -The `UseDependency` hook is ideal for checking if a dependency has been created and waiting for it to become available using `UseEffect` hook, e.g.: - -```dart title="my_controller.dart" "UseDependency" "UseEffect" -import 'package:reactter/reactter.dart'; - -class MyController { - final uDependency = UseDependency(); - - const MyController() { - UseEffect.runOnInit(() { - print( - uDependency.instance != null - ? "MyDependency is available" - : "MyDependency is not available yet." - ); - }, [uDependency]); - } -} -``` -::: + ### Identifying a dependency -You can use the `id` argument to identify the dependency. For example, to obtain a dependency by `uniqueId`: +Use the `id` parameter to identify the dependency. For example, to obtain a dependency by `uniqueId`: ```dart collapse={1-3, 5-9} "UseDependency" ins="uniqueId" import 'package:reactter/reactter.dart'; @@ -175,9 +195,13 @@ class MyController { ``` :::tip -The `id` argument is ideal for identifying dependencies, when you need to use the same dependency type in different places, e.g.: +The `id` parameter is ideal for identifying dependencies, when you need to use the same dependency type in different places, e.g.: -```dart title="my_controller.dart" "UseDependency.get" + + + + my_controller.dart +```dart "UseDependency.get" import 'package:reactter/reactter.dart'; class MyController { @@ -190,8 +214,10 @@ class MyController { } } ``` - -```dart title="my_app.dart" "RtMultiProvider" "RtProvider" + + + my_app.dart +```dart "RtMultiProvider" "RtProvider" import 'package:flutter_reactter/flutter_reactter.dart'; class MyApp extends StatelessWidget { @@ -210,11 +236,14 @@ class MyApp extends StatelessWidget { } } ``` + + + ::: ### Using the dependency -You can use the `instance` property to access the dependency. +Use the `instance` property to access the dependency. ```dart collapse={1-6, 8-9} "UseDependency" ".instance" import 'package:reactter/reactter.dart'; @@ -228,31 +257,11 @@ class MyController { } ``` -:::tip -If the dependency is not registered and created, the `instance` property will be `null`. - -To avoid this, you can use the `UseEffect` hook to get the dependency when it is created, e.g.: - -```dart title="my_controller.dart" "UseDependency" "UseEffect" -import 'package:reactter/reactter.dart'; - -class MyController { - final uDependency = UseDependency(); - - const MyController() { - UseEffect.runOnInit(() { - if (uDependency.instance != null) { - print("Dependency instance: ${uDependency.instance}"); - } - }, [uDependency]); - } -} -``` -::: + ### Getting a dependency -You can use the `UseDependency.get` hook to get the dependency instance, e.g.: +Ue the `UseDependency.get` hook to get the dependency instance, e.g.: ```dart collapse={1-3, 5-9} "UseDependency" "UseDependency.get" import 'package:reactter/reactter.dart'; @@ -269,13 +278,17 @@ class MyController { :::note `UseDependency.get` hook will get the dependency instance inmediately. If the dependency is not created yet, it will create it, but if the dependency is not registered, the `instance` property will be `null`. -To ensure that the dependency is created, you can using the `UseDependency.create` hook instead. Learn more about it in [Creating a dependency](/reactter/hooks/use_dependency#creating-a-dependency). +To ensure that the dependency is created, you can using the `UseDependency.create` hook instead. Learn more about it in [Creating a dependency](/reactter/api/hooks/use_dependency#creating-a-dependency). ::: :::tip The `UseDependency.get` hook is ideal for accessing a dependency, when you are sure that it is already registered before, e.g.: -```dart title="my_app.dart" "RtMultiProvider" "RtProvider" + + + + my_app.dart +```dart "RtMultiProvider" "RtProvider" import 'package:flutter_reactter/flutter_reactter.dart'; class MyApp extends StatelessWidget { @@ -293,8 +306,10 @@ class MyApp extends StatelessWidget { } } ``` - -```dart title="my_controller.dart" "UseDependency.get" + + + my_controller.dart +```dart "UseDependency.get" import 'package:flutter_reactter/flutter_reactter.dart'; class MyController { @@ -305,11 +320,14 @@ class MyController { } } ``` + + + ::: ### Registering a dependency -You can use the `UseDependency.register` hook to register a builder function to create the dependency instance, e.g.: +Use the `UseDependency.register` hook to register a builder function to create the dependency instance, e.g.: ```dart collapse={1-3, 5-9} "UseDependency.register" import 'package:reactter/reactter.dart'; @@ -341,13 +359,17 @@ class MyController { } ``` -Also, you can create the dependency instance inmediately using the `UseDependency.create` hook instead. Learn more about it in [Creating a dependency](/reactter/hooks/use_dependency#creating-a-dependency). +Also, you can create the dependency instance inmediately using the `UseDependency.create` hook instead. Learn more about it in [Creating a dependency](/reactter/api/hooks/use_dependency#creating-a-dependency). ::: :::tip The `UseDependency.register` hook is ideal for registering dependencies in a single location, e.g.: -```dart title="dependencies_registers.dart" "UseDependency.register" "UseDependency.register" + + + + dependencies_registers.dart +```dart "UseDependency.register" "UseDependency.register" import 'package:reactter/reactter.dart'; class DependenciesRegisters { @@ -358,8 +380,10 @@ class DependenciesRegisters { } } ``` - -```dart title="my_app.dart" "RtMultiProvider" "RtProvider" "DependenciesRegisters" + + + my_app.dart +```dart "RtMultiProvider" "RtProvider" "DependenciesRegisters" import 'package:flutter_reactter/flutter_reactter.dart'; class MyApp extends StatelessWidget { @@ -374,11 +398,14 @@ class MyApp extends StatelessWidget { } } ``` + + + ::: ### Creating a dependency -You can use the `UseDependency.create` hook to create a dependency instance inmediately, e.g.: +Use the `UseDependency.create` hook to create a dependency instance inmediately, e.g.: ```dart collapse={1-3, 5-9} "UseDependency.create" import 'package:reactter/reactter.dart'; @@ -398,7 +425,7 @@ The `UseDependency.create` hook is ideal for immediately creating the d ### Defining the dependency mode -You can use the `mode` argument to define the dependency mode. For example, to create a singleton dependency: +Use the `mode` parameter to define the [dependency mode](/reactter/core_concepts/dependency_injection#dependency-modes). For example, to create a singleton dependency: ```dart collapse={1-3, 8-12} "UseDependency.create" "DependencyMode.singleton" import 'package:reactter/reactter.dart'; @@ -416,12 +443,16 @@ class MyController { ``` :::note -The `mode` argument is available only in `UseDependency.register` and `UseDependency.create` hooks. +The `mode` parameter is available only in `UseDependency.register` and `UseDependency.create` hooks. If you need to define the dependency mode directly, use the `UseDependency.lazyBuilder`, `UseDependency.lazyFactory`, `UseDependency.lazySingleton` hooks for registering a dependency in its respective mode. -Likewise, use the `UseDependency.builder`, `UseDependency.factory` and `UseDependency.singleton` hooks instead, for creating a dependency in its respective mode. Learn more about it in [Dependency Modes](/reactter/core_concepts/dependency_injection#dependency-modes), e.g.: +Likewise, use the `UseDependency.builder`, `UseDependency.factory` and `UseDependency.singleton` hooks instead, for creating a dependency in its respective mode, e.g.: -```dart title="dependencies_registers.dart" "UseDependency.lazyFactory" "UseDependency.lazySingleton" "UseDependency.builder" + + + + my_controller.dart +```dart "UseDependency.lazyFactory" "UseDependency.lazySingleton" "UseDependency.builder" import 'package:reactter/reactter.dart'; class DependenciesRegisters { @@ -432,8 +463,10 @@ class DependenciesRegisters { } } ``` - -```dart title="my_app.dart" "RtMultiProvider" "RtProvider" "DependenciesRegisters" + + + my_app.dart +```dart "RtMultiProvider" "RtProvider" "DependenciesRegisters" import 'package:flutter_reactter/flutter_reactter.dart'; class MyApp extends StatelessWidget { @@ -448,8 +481,10 @@ class MyApp extends StatelessWidget { } } ``` - -```dart title="my_controller.dart" "UseDependency.get" "UseDependency.get" + + + my_controller.dart +```dart "UseDependency.get" "UseDependency.get" import 'package:reactter/reactter.dart'; class MyController { @@ -464,4 +499,7 @@ class MyController { } } ``` + + + ::: \ No newline at end of file diff --git a/website/src/content/docs/api/hooks/use_effect.mdx b/website/src/content/docs/api/hooks/use_effect.mdx new file mode 100644 index 00000000..92e04acc --- /dev/null +++ b/website/src/content/docs/api/hooks/use_effect.mdx @@ -0,0 +1,151 @@ +--- +title: UseEffect +description: UseEffect hook documentation. +sidebar: + order: 5 +--- + +import { HE, HN, HM, HT } from '@/components/Highlight'; +import StatePropertiesMethodsRef from '@/content/docs/shareds/state_properties_methods_ref.mdx'; +import * as ListeningChangesState from '@/content/docs/shareds/listening_changes_state.mdx'; + +`UseEffect` is a [hook](/reactter/core_concepts/hooks) that allows to manage side-effect, and its functionality is closely related to [Lifecycle](/reactter/core_concepts/lifecycle) of its `dependencies` or the bound **_instance_**. + +:::note +The bound _**instance**_(or dependency) is the instance of the class where the hook is used. +In this case, we will refer to the _**instance**_ as the _**dependency**_ which is managed by [dependency injection](/reactter/core_concepts/dependency_injection). +::: + +## Syntax + +```dart showLineNumbers=false +UseEffect( + void | Function callback(), + List dependencies, + { String? debugLabel }, +); + +// UseEffect run on init +UseEffect.runOnInit( + void | Function callback(), + List dependencies, + { String? debugLabel }, +); +``` + +`UseEffect` accepts these parameters: + +- **`callback`**: A function that performs side effects. This function is executed when the `dependencies` trigger `Lifecycle.didUpdate` event or the bound _**instance**_ trigger `Lifecycle.didMount` event. + If the `callback` returns a `Function`(considers as an _**effect cleanup**_), it will be called before the next effect is run or the bound _**instance**_ trigger `Lifecycle.willUnmount` event. + :::tip + The _**effect cleanup**_ is useful for cleaning up any resources or subscriptions that the effect may have created. + ::: + + :::note + The `callback` and _**effect cleanup**_ function may be called by [`Lifecycle`](/reactter/core_concepts/lifecycle) events, such as `Lifecycle.didMount` and `Lifecycle.willUnmount`. However, they work only if the instance is provided to the widget tree using the API of _**flutter_reactter**_ package. + ::: +- **`dependencies`**: A list of state(`RtState`, learn about it [here](/reactter/core_concepts/state_management/#state)) that the effect depends on. +- **`debugLabel`**: *(optional)* A label to identify the hook in the [DevTools extension](/reactter/devtools_extension). + +## Properties & Methods + +`UseEffect` provides the following properties and methods: + + + +## Usage + +### Declaration + +`UseEffect` can be initialized using the constructor class, e.g.: + +```dart collapse={1-6, 19-21} "UseEffect" "timer" /(uCount)(?!\u0060)/ +class MyCounter { + final uCount = UseState(0); + + const MyCounter() { + final timer = Timer.periodic(Duration(seconds: 1), (_) => uCount.value++); + + UseEffect(() { // Effect callback + // Executed by `Lifecycle.didUpdate` event of `uCount` state + // or `Lifecycle.didMount` event of `MyCounter` instance + print("Count: ${uCount.value}"); + + return () { // Effect cleanup + // Executed by `Lifecycle.willUpdate` event of `uCount` state + // or `Lifecycle.willUnmount` event of `MyCounter` instance + if (uCount.value == 10 && timer.isActive) { + timer.cancel(); + print("Counter stopped"); + } + }; + }, [uCount]); + } +} +``` + +In the example above, the `UseEffect` hook is used to print the `value` of the `uCount` state every second and stop the timer when the value reaches `10`. + +### Running it inmediately + +Sometimes you may want to execute the `UseEffect` immediately upon initialization. +You can use `UseEffect.runOnInit` or the `AutoDispatchEffect` mixin to archive this. +Here's an example: + +#### Using `UseEffect.runOnInit` + +```dart collapse={1-3, 14-14} "UseEffect.runOnInit" /(uCount)(?!\u0060)/ +final uCount = UseState(0); + +void main() { + UseEffect.runOnInit(() { // Effect callback + // Executed at the initiation and by `Lifecycle.didUpdate` event of `uCount` state. + print("Count: ${uCount.value}"); + Future.delayed(const Duration(seconds: 1), () => uCount.value++); + + return () { // Effect cleanup + // Executed by `Lifecycle.willUpdate` event of `uCount` state. + print("Cleanup executed"); + }; + }, [uCount]); +} +``` + +In the example above, the `UseEffect.runOnInit` hook is used to print the `value` of the `uCount` state inmediately and increment the value every second. + +:::tip +The `UseEffect.runOnInit` is useful when you want to execute the `callback` function inmediately without waiting for `Lifecycle.didMount` event of _**instance**_. +::: + +#### Using `AutoDispatchEffect` mixin. + +The `AutoDispatchEffect` mixin can be used to automatically execute the effect when the class is initialized. +This is particularly useful when you want to ensure that the side effect runs as soon as the instance is created. Here's an example: + +```dart collapse={1-3, 14-14} "AutoDispatchEffect" /(uCount)(?!\u0060)/ +class MyCounter with AutoDispatchEffect { + final uCount = UseState(0); + + MyCounter() { + final timer = Timer.periodic(Duration(seconds: 1), (_) => uCount.value++); + + UseEffect(() { // Effect callback + // Executed by `Lifecycle.didUpdate` event of `uCount` state + // or `Lifecycle.didMount` event of `MyCounter` instance + print("Count: ${uCount.value}"); + + return () { // Effect cleanup + // Executed by `Lifecycle.willUpdate` event of `uCount` state + // or `Lifecycle.willUnmount` event of `MyCounter` instance + if (uCount.value == 10 && timer.isActive) { + timer.cancel(); + print("Counter stopped"); + } + }; + }, [uCount]); + } +} +``` + +In this example, the `AutoDispatchEffect` mixin ensures that the `UseEffect` `callback` is executed immediately when the `MyCounter` instance is created. +This eliminates the need to manually call `UseEffect.runOnInit`. \ No newline at end of file diff --git a/website/src/content/docs/hooks/use_reducer.mdx b/website/src/content/docs/api/hooks/use_reducer.mdx similarity index 84% rename from website/src/content/docs/hooks/use_reducer.mdx rename to website/src/content/docs/api/hooks/use_reducer.mdx index 7fcd6bf0..48b79932 100644 --- a/website/src/content/docs/hooks/use_reducer.mdx +++ b/website/src/content/docs/api/hooks/use_reducer.mdx @@ -7,7 +7,7 @@ sidebar: import { HM, HT } from '@/components/Highlight'; import { Code } from "@astrojs/starlight/components"; import MDXRedry from '@/components/MDXRedry.astro'; -import StateMethodsLite from '@/content/docs/shareds/state_methods_lite.mdx'; +import StatePropertiesMethodsRef from '@/content/docs/shareds/state_properties_methods_ref.mdx'; import useReducerExampleCode from '@/examples/use_reducer/use_reducer_example.dart.code?raw'; import { useReducerExampleMark } from '@/examples/use_reducer/marks.ts'; import * as NoteLateState from '@/content/docs/shareds/note_late_state.mdx'; @@ -15,7 +15,7 @@ import * as NoteStateValueNotifies from '@/content/docs/shareds/note_state_value import * as ListeningChangesState from '@/content/docs/shareds/listening_changes_state.mdx'; `UseReducer` is a [hook](/reactter/core_concepts/hooks) that manages state using reducer function. -An alternative to [`UseState`](/reactter/hooks/use_state). +An alternative to [`UseState`](/reactter/api/hooks/use_state). :::tip `UseReducer` is usually preferable over `UseState` when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. @@ -27,25 +27,27 @@ An alternative to [`UseState`](/reactter/hooks/use_state). UseReducer( T reducer(T state, RtAction action), T initialValue, + { String? debugLabel }, ); ``` -`UseReducer` accepts these arguments: +`UseReducer` accepts these parameters: -- `reducer`: A function that takes the current `state` and an `action`, and returns a new state. -- `initialState`: Initial value of `T` type that it will hold. +- **`reducer`**: A function that takes the current `state` and an `action`, and returns a new state. +- **`initialState`**: Initial value of `T` type that it will hold. +- **`debugLabel`**: *(optional)* A label to identify the hook in the [DevTools extension](/reactter/devtools_extension). ## Properties & Methods `UseReducer` provides the following properties and methods: -- `value`: A getter that allows to read its state. -- `dispatch`: A method that allows to dispatch an action to update the state. - - Syntax: +- **`value`**: A getter that allows to read its state. +- **`dispatch`**: A method that allows to dispatch an action to update the state. + - **Syntax**: ```dart void dispatch(RtAction action); ``` - + ## Usage @@ -62,7 +64,7 @@ class UserController { final String userId; late final uUser = Rt.lazyState( - () => UseReducer.withArg(this.reducer, null), + () => UseReducer(this.reducer, null), this, ); @@ -159,10 +161,10 @@ uState.update((value) { }); ``` -Use `refresh` method to force to notify changes. +Use `notify` method to force to notify changes. -```dart showLineNumbers=false ".refresh" -userState.refresh(); +```dart showLineNumbers=false ".notify" +userState.notify(); ``` ### Listening to changes diff --git a/website/src/content/docs/hooks/use_state.mdx b/website/src/content/docs/api/hooks/use_state.mdx similarity index 78% rename from website/src/content/docs/hooks/use_state.mdx rename to website/src/content/docs/api/hooks/use_state.mdx index 9158c5f8..747a5a39 100644 --- a/website/src/content/docs/hooks/use_state.mdx +++ b/website/src/content/docs/api/hooks/use_state.mdx @@ -7,7 +7,7 @@ sidebar: import { Code } from "@astrojs/starlight/components"; import { HK, HM, HT } from '@/components/Highlight'; import MDXRedry from '@/components/MDXRedry.astro'; -import StateMethodsLite from '@/content/docs/shareds/state_methods_lite.mdx'; +import StatePropertiesMethodsRef from '@/content/docs/shareds/state_properties_methods_ref.mdx'; import * as NoteLateState from '@/content/docs/shareds/note_late_state.mdx'; import * as NoteStateValueNotifies from '@/content/docs/shareds/note_state_value_notifies.mdx'; import * as ListeningChangesState from '@/content/docs/shareds/listening_changes_state.mdx'; @@ -17,19 +17,20 @@ import * as ListeningChangesState from '@/content/docs/shareds/listening_changes ## Syntax ```dart showLineNumbers=false -UseState(T initialValue); +UseState(T initialValue, { String? debugLabel }); ``` -`UseState` accepts this argument: +`UseState` accepts these parameters: -- `initialValue`: Initial value of `T` type that it will hold. +- **`initialValue`**: Initial value of `T` type that it will hold. +- **`debugLabel`**: *(optional)* A label to identify the hook in the [DevTools extension](/reactter/devtools_extension). ## Properties & Methods `UseState` provides the following properties and methods: -- `value`: A getter/setter that allows to read and write its state. - +- **`value`**: A getter/setter that allows to read and write its state. + ## Usage @@ -82,10 +83,10 @@ uState.update((value) { }); ``` -Use `refresh` method to force to notify changes. +Use `notify` method to force to notify changes. -```dart showLineNumbers=false ".refresh" -userState.refresh(); +```dart showLineNumbers=false ".notify" +userState.notify(); ``` ### Listening to changes diff --git a/website/src/content/docs/api/methods/debugging_methods.mdx b/website/src/content/docs/api/methods/debugging_methods.mdx new file mode 100644 index 00000000..6fa6f2b3 --- /dev/null +++ b/website/src/content/docs/api/methods/debugging_methods.mdx @@ -0,0 +1,176 @@ +--- +title: Debugging Methods +description: Reactter debugging methods +sidebar: + order: 4 +--- + +import { HN, HM, HT, HS } from '@/components/Highlight'; + +Reactter provides a set of debugging methods to help you debug your states and dependencies in the finest detail. + +## `Rt.initializeDevTools` + +The `Rt.initializeDevTools` method initializes the [Reactter DevTools](http://localhost:4321/reactter/devtools_extension) for debugging purposes. +Only works in development mode. + +### Syntax + +```dart showLineNumbers=false +Rt.initializeDevTools(); +``` + +:::tip +This method should be called at the beginning of your application. +::: + +## `Rt.initializeLogger` + +The `Rt.initializeLogger` method initializes the Reactter logger for debugging purposes. Only works in development mode. + +### Syntax + +```dart showLineNumbers=false +Rt.initializeLogger({ + String name = 'REACTTER', + LogOutput output = dev.log, +}); + +typedef LogOutput = void Function( + String message, { + required String name, + required int level, + StackTrace? stackTrace, +}); +``` + +### Parameters + +- **`name`**: *(optional)* The name of the logger. Default is `'REACTTER'`. +- **`output`**: *(optional)* The output function(`LogOutput`) for the logger. Default is `dev.log`. + + The Parameters of the output function(`LogOutput`) are: + - **`message`**: The message to log. + - **`name`**: The name of the logger. + - **`level`**: The level of the log message. + The levels are(Copy from [package:logging](https://pub.dev/packages/logging)): + - `0`: Special key to turn on logging for all levels. + - `300`: Key for highly detailed tracing. + - `400`: Key for fairly detailed tracing. + - `500`: Key for tracing information. + - `700`: Key for static configuration messages. + - `800`: Key for informational messages. + - `900`: Key for potential problems. + - `1000`: Key for serious failures. + - `1200`: Key for extra debugging loudness. + - `2000`: Special key to turn off all logging. + - **`stackTrace`**: *(optional)* The stack trace of the log message. + +### Usage + +You can customize the logger output function to log messages in your preferred way, e.g.: + +```dart showLineNumbers=false +Rt.initializeLogger( + output: (String message, {required String name, required int level, StackTrace? stackTrace}) { + print('[$name] $message'); + }, +); +``` + +Or use another package like [logging](https://pub.dev/packages/logging) to log messages, e.g.: + +```dart showLineNumbers=false +void initializeLogger() { + final logger = Logger('REACTTER'); + final levelMapper = { for (final level in Level.LEVELS) level.value: level }; + + Rt.initializeLogger( + output: (String message, {required String name, required int level, StackTrace? stackTrace}) { + logger.log( + levelMapper[level] ?? Level.INFO, + message, + name: name, + stackTrace: stackTrace, + ) + }, + ); +} + +void main() { + initializeLogger(); + runApp(MyApp()); +} +``` + +## `Rt.addObserver` + +The `Rt.addObserver` method adds an observer to the Reactter store to listen to the state or dependency lifecycle. + +### Syntax + +```dart showLineNumbers=false +Rt.addObserver(IObserver observer); +``` + +### Parameters + +- **`observer`**: The observer to add like [`RtStateObserver`](/reactter/api/classes/rt_state_observer) or [`RtDependencyObserver`](/reactter/api/classes/rt_dependency_observer). + +### Usage + +You can add a [`RtStateObserver`](/reactter/api/classes/rt_state_observer) to listen to the state lifecycle, e.g.: + +```dart showLineNumbers=false +final stateObserver = RtStateObserver( + onChanged: (state) { + print('State changed: $state'); + }, +); + +Rt.addObserver(stateObserver); +``` + +Or add a [`RtDependencyObserver`](/reactter/api/classes/rt_dependency_observer) to listen to the dependency lifecycle, e.g.: + +```dart showLineNumbers=false +final dependencyObserver = RtDependencyObserver( + onCreated: (dependency, instance) { + print('Dependency created: $dependency'); + }, +); + +Rt.addObserver(dependencyObserver); +``` + +## `Rt.removeObserver` + +The `Rt.removeObserver` method removes an observer from the Reactter store. + +### Syntax + +```dart showLineNumbers=false +Rt.removeObserver(IObserver observer); +``` + +### Parameters + +- **`observer`**: The observer to remove. + +### Usage + +You can remove the observer added before, e.g.: + +```dart showLineNumbers=false collapse={1-8} +final stateObserver = RtStateObserver( + onChanged: (state) { + print('State changed: $state'); + }, +); + +Rt.addObserver(stateObserver); +// {...} +Rt.removeObserver(stateObserver); +``` + + diff --git a/website/src/content/docs/methods/dependency_injection_methods.mdx b/website/src/content/docs/api/methods/dependency_injection_methods.mdx similarity index 95% rename from website/src/content/docs/methods/dependency_injection_methods.mdx rename to website/src/content/docs/api/methods/dependency_injection_methods.mdx index 7e36c172..7d47c759 100644 --- a/website/src/content/docs/methods/dependency_injection_methods.mdx +++ b/website/src/content/docs/api/methods/dependency_injection_methods.mdx @@ -5,7 +5,7 @@ sidebar: order: 2 --- -import { HE, HK, HM, HN, HS, HT } from '@/components/Highlight'; +import { HB, HE, HK, HM, HN, HS, HT } from '@/components/Highlight'; import CodeTabs from '@/components/CodeTabs.astro'; import { Tabs, TabItem } from "@/components/Tabs"; import ZappButton from "@/components/ZappButton.astro"; @@ -71,7 +71,7 @@ These methods are used to create and manipulate dependencies in a more organized ## `Rt.register` The `Rt.register` method registers a new dependency. -It returns `true` if the dependency is registered successfully, otherwise return `false`. +It returns `true` if the dependency is registered successfully, otherwise return `false`. #### Syntax @@ -112,7 +112,7 @@ See the example below to understand how to use the `Rt.register` method ## `Rt.lazyBuilder` The `Rt.lazyBuilder` method registers a new dependency as [`DependencyMode.builder`](/reactter/core_concepts/dependency_injection/#builder). -It returns `true` if the dependency is registered successfully, otherwise return `false`. +It returns `true` if the dependency is registered successfully, otherwise return `false`. This method is similar to the `Rt.register` method but with the `DependencyMode.builder` mode only. @@ -152,7 +152,7 @@ See the example below to understand how to use the `Rt.lazyBuilder` met ## `Rt.lazyFactory` The `Rt.lazyFactory` method registers a new dependency as [`DependencyMode.factory`](/reactter/core_concepts/dependency_injection/#factory). -It returns `true` if the dependency is registered successfully, otherwise return `false`. +It returns `true` if the dependency is registered successfully, otherwise return `false`. This method is similar to the `Rt.register` method but with the `DependencyMode.factory` mode only. @@ -192,7 +192,7 @@ See the example below to understand how to use the `Rt.lazyFactory` met ## `Rt.lazySingleton` The `Rt.lazySingleton` method registers a new dependency as [`DependencyMode.singleton`](/reactter/core_concepts/dependency_injection/#singleton). -It returns `true` if the dependency is registered successfully, otherwise return `false`. +It returns `true` if the dependency is registered successfully, otherwise return `false`. This method is similar to the `Rt.register` method but with the `DependencyMode.singleton` mode. @@ -449,7 +449,7 @@ See the example below to understand how to use the `Rt.find` method in ## `Rt.exists` -The `Rt.exists` method checks if the dependency instance exists in Reactter. It returns `true` if the dependency exists, otherwise return `false`. +The `Rt.exists` method checks if the dependency instance exists in Reactter. It returns `true` if the dependency exists, otherwise return `false`. #### Syntax @@ -516,7 +516,7 @@ See the example below to understand how to use the `Rt.getDependencyMode``Rt.delete` The `Rt.delete` method removes the dependency instance and its registration from the Reactter based on the dependency mode. -It returns `true` if the dependency is successfully deleted, otherwise return `false`. +It returns `true` if the dependency is successfully deleted, otherwise return `false`. :::note The behavior of the `Rt.delete` method depends on the dependency mode: @@ -559,7 +559,7 @@ See the example below to understand how to use the `Rt.delete` method i ## `Rt.destroy` The `Rt.destroy` method forcibly removes the dependency instance and its registration from the Reactter. -It returns `true` if the dependency is successfully destroyed, otherwise return `false`. +It returns `true` if the dependency is successfully destroyed, otherwise return `false`. #### Syntax @@ -573,7 +573,7 @@ bool Rt.destroy({ #### Parameters - `id`: An optional identifier for the `T` dependency. If not provided, the default instance of the `T` dependency is used. -- `onlyDependency`: An optional parameter to delete the dependency instance only. The default value is `false`. +- `onlyDependency`: An optional parameter to delete the dependency instance only. The default value is `false`. #### Example @@ -597,7 +597,7 @@ See the example below to understand how to use the `Rt.destroy` method ## `Rt.unregister` The `Rt.unregister` method is used to unregister the dependency from the Reactter. -It returns `true` if the dependency is unregistered successfully, otherwise return `false`. +It returns `true` if the dependency is unregistered successfully, otherwise return `false`. #### Syntax @@ -631,7 +631,7 @@ See the example below to understand how to use the `Rt.unregister` meth ## `Rt.isActive` The `Rt.isActive` method is used to check if the dependency is registered in Reactter. -It returns `true` if the dependency is registered, otherwise return `false`. +It returns `true` if the dependency is registered, otherwise return `false`. #### Syntax diff --git a/website/src/content/docs/methods/event_handler_methods.mdx b/website/src/content/docs/api/methods/event_handler_methods.mdx similarity index 100% rename from website/src/content/docs/methods/event_handler_methods.mdx rename to website/src/content/docs/api/methods/event_handler_methods.mdx diff --git a/website/src/content/docs/methods/state_management_methods.mdx b/website/src/content/docs/api/methods/state_management_methods.mdx similarity index 77% rename from website/src/content/docs/methods/state_management_methods.mdx rename to website/src/content/docs/api/methods/state_management_methods.mdx index 024d689a..5c05fa20 100644 --- a/website/src/content/docs/methods/state_management_methods.mdx +++ b/website/src/content/docs/api/methods/state_management_methods.mdx @@ -63,6 +63,51 @@ void main() { In the example, the `uCount` state is lazily declared inside the `CountController` class using `Rt.lazyState`. It's accessed after creating the `CountController` instance, and when the instance is deleted, the state is automatically disposed, ensuring efficient resource management. + +## `Rt.registerState` + +`Rt.registerState` is a method that allows to register a [`RtState`](/reactter/api/classes/rt_state) instance. +All states created must be registered using this method for proper management and disposal. + +:::note +In most cases, you don't need to manually use the `Rt.registerState` method to create a state when using [`RtHook`](/reactter/api/classes/rt_hook) or [`Signal`](/reactter/api/classes/signal) classes, as they automatically handle the registration of the state instance. + +However, if you're creating a custom state using the [`RtState`](/reactter/api/classes/rt_state) class directly, you **must** use `Rt.registerState` to properly register the state instance. +::: + +#### Syntax + +```dart showLineNumbers=false +T Rt.registerState(T stateFn()); +``` + +#### Example + +In this example, the custom state using [`RtState`](/reactter/api/classes/rt_state) class is created and registered using `Rt.registerState` method. + +```dart collapse={13-20} mark={1,12} "RtState" "Rt.registerState" "Rt.on" "Lifecycle.didUpdate" /update(?=\u0028)/ +class MyState extends RtState { + int _value = 0; + int get value => _value; + set value(int n) { + if (n == _value) return; + update(() => _value = n); // set new value and notify observers + } + + MyState([int value = 0]) : _value = value; +} + +final state = Rt.registerState(() => MyState()); // Register state + +Rt.on( + state, + Lifecycle.didUpdate, + (_, __) => print('State updated: ${state.value}') +); + +state.value = 42; // Calls update internally +``` + ## `Rt.batch` The `Rt.batch` method allows multiple state changes to be grouped together, triggering them all at the end when the `callback` completes. This ensures that associated side effects occur only once, improving performance and reducing unnecessary re-renders. diff --git a/website/src/content/docs/classes/rt_component.mdx b/website/src/content/docs/api/widgets/rt_component.mdx similarity index 92% rename from website/src/content/docs/classes/rt_component.mdx rename to website/src/content/docs/api/widgets/rt_component.mdx index c437cb6c..033dd8cc 100644 --- a/website/src/content/docs/classes/rt_component.mdx +++ b/website/src/content/docs/api/widgets/rt_component.mdx @@ -2,11 +2,9 @@ title: RtComponent description: Learn how to use the RtComponent in Reactter. sidebar: - order: 4 - badge: - text: Flutter + order: 7 --- -import { HM, HT, HN } from '@/components/Highlight'; +import { HB, HM, HT } from '@/components/Highlight'; import CodeTabs from '@/components/CodeTabs.astro'; import { Tabs, TabItem } from "@/components/Tabs"; import ZappButton from "@/components/ZappButton.astro"; @@ -20,7 +18,7 @@ import counterViewCode from '@/examples/rt_component/lib/counter_view.dart?raw'; import mainCode from '@/examples/rt_component/lib/main.dart?raw'; The `RtComponent` is a `StatelessWidget` hat simplifies the creation of reactive widgets by utilizing the dependency injection system. -It provides features similar to [`RtProvider`](/reactter/widgets/rt_provider) and [`RtConsumer`](/reactter/widgets/rt_consumer) that are used to inject and consume dependencies. +It provides features similar to [`RtProvider`](/reactter/api/widgets/rt_provider) and [`RtConsumer`](/reactter/api/widgets/rt_consumer) that are used to inject and consume dependencies. :::tip The `RtComponent` widget is a more efficient way to create reactive widgets than using `RtProvider` and `RtConsumer` widgets separately. @@ -48,8 +46,8 @@ abstract class RtComponent extends StatelessWidget { It exposes the instance of the `T` dependency as argument of the function. If omitted, the `listenAll` getter is checked. - `listenAll`: A boolean value that determines whether to listen to all states(`RtState`) of the `T` dependency for rebuilds the widget tree defined in the `render` method. - For default, it is set to `false`. -- `render`: A required function that builds the widget tree based on the instance of the `T` dependency. It receives the following arguments: + For default, it is set to `false`. +- `render`: A required function that builds the widget tree based on the instance of the `T` dependency. It receives the following parameters: - `context`: The `BuildContext` of the `RtComponent` widget. - `inst`: The instance of the `T` dependency. diff --git a/website/src/content/docs/widgets/rt_consumer.mdx b/website/src/content/docs/api/widgets/rt_consumer.mdx similarity index 92% rename from website/src/content/docs/widgets/rt_consumer.mdx rename to website/src/content/docs/api/widgets/rt_consumer.mdx index 72fd4b0f..d0a6f4ad 100644 --- a/website/src/content/docs/widgets/rt_consumer.mdx +++ b/website/src/content/docs/api/widgets/rt_consumer.mdx @@ -4,7 +4,7 @@ description: Learn how to use the RtConsumer in Reactter. sidebar: order: 4 --- -import { HM, HN, HS, HT } from '@/components/Highlight'; +import { HB, HM, HS, HT } from '@/components/Highlight'; import CodeTabs from '@/components/CodeTabs.astro'; import { Tabs, TabItem } from "@/components/Tabs"; import ZappButton from "@/components/ZappButton.astro"; @@ -41,10 +41,10 @@ import counterChildCode from '@/examples/rt_consumer_child/lib/counter.dart?raw' import counterViewChildCode from '@/examples/rt_consumer_child/lib/counter_view.dart?raw'; import mainChildCode from '@/examples/rt_consumer_child/lib/main.dart?raw'; -The `RtConsumer` widget obtains the dependency provided by the closest [`RtProvider`](/reactter/widgets/rt_provider) widget and rebuilds itself whenever there are changes in the dependency or any defined states. +The `RtConsumer` widget obtains the dependency provided by the closest [`RtProvider`](/reactter/api/widgets/rt_provider) widget and rebuilds itself whenever there are changes in the dependency or any defined states. :::note -`RtConsumer` is similar to the [`BuildContext.use`](/reactter/extensions/builder_context_use) and [`BuildContext.watch`](/reactter/extensions/builder_context_watch) functions, but it is designed to be used within the widget tree. +`RtConsumer` is similar to the [`BuildContext.use`](/reactter/api/extensions/builder_context_use) and [`BuildContext.watch`](/reactter/api/extensions/builder_context_watch) functions, but it is designed to be used within the widget tree. ::: ## Syntax @@ -74,13 +74,13 @@ RtConsumer({ - `child`: An optional `Widget` that remains static while the widget tree is rebuilt. It is passed to the `builder` function if it is defined. - `listenAll`: A boolean that determines whether to listen to all states provided by the `T` dependency. - If set to `true`, the `listenStates` function is ignored. - The default value is `false`. + If set to `true`, the `listenStates` function is ignored. + The default value is `false`. - `listenStates`: An optional function that returns a list of state(`RtState`) to listen to. It takes the instance of the `T` dependency as an argument. If omitted, the `listenAll` property is checked. - `builder`: A function that rebuilds a widget depending on the `RtConsumer`. - It receives the following arguments: + It receives the following parameters: - `context`: The `BuildContext` of the `RtConsumer` widget. - `instance`: The instance of `T` dependency provided by the closest `RtProvider` widget. - `child`: The `child` widget passed to `RtConsumer`. @@ -126,12 +126,12 @@ Consequently, it does not rebuild whenever there are changes in the `count` stat :::note For specific state listening, you can use the `listenStates` property(see the [Listening to specific states](#listening-to-specific-states) section). -Alternatively, you can set the `listenAll` property to `true` to listen to all states provided by the `CounterController` dependency(see the [Listening to all states](#listening-to-all-states) section). +Alternatively, you can set the `listenAll` property to `true` to listen to all states provided by the `CounterController` dependency(see the [Listening to all states](#listening-to-all-states) section). ::: ### Listening to all state -To listen to all states, set the `listenAll` property to `true`. This ensures that `RtConsumer` rebuilds whenever any state in the dependency changes. +To listen to all states, set the `listenAll` property to `true`. This ensures that `RtConsumer` rebuilds whenever any state in the dependency changes. :::caution Using the `listenAll` property indiscriminately is not recommended, especially when some states are not directly involved in rendering the widget subtree. @@ -237,7 +237,7 @@ Here's an example of how to use it: counter.dart - + @@ -279,7 +279,7 @@ Here's an example of how to use it: counter.dart - + diff --git a/website/src/content/docs/widgets/rt_multi_provider.mdx b/website/src/content/docs/api/widgets/rt_multi_provider.mdx similarity index 98% rename from website/src/content/docs/widgets/rt_multi_provider.mdx rename to website/src/content/docs/api/widgets/rt_multi_provider.mdx index 61384a67..2961dde9 100644 --- a/website/src/content/docs/widgets/rt_multi_provider.mdx +++ b/website/src/content/docs/api/widgets/rt_multi_provider.mdx @@ -43,7 +43,7 @@ RtMultiProvider( - `child`: An optional `Widget` that remains static while the widget tree is rebuilt. It is passed to the `builder` function if it is defined. - `builder`: An optional function which builds a widget depending on the `RtMultiProvider`. If it not defined, the `child` widget is returned. -It receives the following arguments: +It receives the following parameters: - `context`: The `BuildContext` of the widget. A handle to the location of `RtMultiProvider` in the widget tree. - `child`: The `child` widget passed to the `RtMultiProvider` widget. @@ -57,7 +57,7 @@ In the following example, we have a simple counter application that uses the main.dart - + diff --git a/website/src/content/docs/widgets/rt_provider.mdx b/website/src/content/docs/api/widgets/rt_provider.mdx similarity index 99% rename from website/src/content/docs/widgets/rt_provider.mdx rename to website/src/content/docs/api/widgets/rt_provider.mdx index 0e4e050a..4d8f9c4b 100644 --- a/website/src/content/docs/widgets/rt_provider.mdx +++ b/website/src/content/docs/api/widgets/rt_provider.mdx @@ -119,7 +119,7 @@ The `RtProvider` class has three constructors, each catering to differe It is passed to the `builder` function if it is defined. - `builder`: An optional function which builds a widget based on the dependency. If it not defined, the `child` widget is returned. -It receives the following arguments: +It receives the following parameters: - `context`: The `BuildContext` of the widget. A handle to the location of `RtProvider` in the widget tree. - `instance`: The instance of the `T` dependency. It is available only to default constructor. - `child`: The `child` widget passed to the `RtProviders` widget. diff --git a/website/src/content/docs/widgets/rt_scope.mdx b/website/src/content/docs/api/widgets/rt_scope.mdx similarity index 79% rename from website/src/content/docs/widgets/rt_scope.mdx rename to website/src/content/docs/api/widgets/rt_scope.mdx index 5cb0179c..a8df65fe 100644 --- a/website/src/content/docs/widgets/rt_scope.mdx +++ b/website/src/content/docs/api/widgets/rt_scope.mdx @@ -14,7 +14,7 @@ import { marks } from '@/examples/marks.ts' import counterViewCode from '@/examples/rt_scope/lib/counter_view.dart?raw'; import mainCode from '@/examples/rt_scope/lib/main.dart?raw'; -The `RtScope` widget serves as an entry point for Reactter, allowing you to use any consumer(such as [`RtConsumer`](/reactter/widgets/rt_consumer), [`RtSelector`](/reactter/widgets/rt_selector), [`RtComponent`](/reactter/widgets/rt_component), [`BuildContext.watch`](/reactter/extensions/builder_context_watch), [`BuildContext.select`](/reactter/extensions/builder_context_select)) to observe state changes within the subtree of the widget it wraps, without the need to explicitly define dependencies. +The `RtScope` widget serves as an entry point for Reactter, allowing you to use any consumer(such as [`RtConsumer`](/reactter/api/widgets/rt_consumer), [`RtSelector`](/reactter/api/widgets/rt_selector), [`RtComponent`](/reactter/api/widgets/rt_component), [`BuildContext.watch`](/reactter/api/extensions/builder_context_watch), [`BuildContext.select`](/reactter/api/extensions/builder_context_select)) to observe state changes within the subtree of the widget it wraps, without the need to explicitly define dependencies. :::caution The `RtScope` widget is optional, but if you use any consumers without defining dependencies and their ancestor widgets are not wrapped by it, a `RtScopeNotFoundException` error will be thrown. Therefore, make sure this widget is correctly placed in the widget tree hierarchy. diff --git a/website/src/content/docs/widgets/rt_selector.mdx b/website/src/content/docs/api/widgets/rt_selector.mdx similarity index 96% rename from website/src/content/docs/widgets/rt_selector.mdx rename to website/src/content/docs/api/widgets/rt_selector.mdx index 02176da8..2b1c1190 100644 --- a/website/src/content/docs/widgets/rt_selector.mdx +++ b/website/src/content/docs/api/widgets/rt_selector.mdx @@ -30,8 +30,8 @@ import counterDivisibleIdCode from '@/examples/rt_selector_id/lib/counter_divisi import counterViewIdCode from '@/examples/rt_selector_id/lib/counter_view.dart?raw'; import mainIdCode from '@/examples/rt_selector_id/lib/main.dart?raw'; -The `RtSelector` widget is a similarly to [`RtConsumer`](/reactter/widgets/rt_consumer). -It obtains the dependency provided by the closest [`RtProvider`](/reactter/widgets/rt_provider) widget and allows you to select specific states to compute a value. +The `RtSelector` widget is a similarly to [`RtConsumer`](/reactter/api/widgets/rt_consumer). +It obtains the dependency provided by the closest [`RtProvider`](/reactter/api/widgets/rt_provider) widget and allows you to select specific states to compute a value. This value will trigger a rebuild of the widget tree whenever it changes. ## Syntax @@ -61,11 +61,11 @@ RtSelector({ - `child`: An optional `Widget` that remains static while the widget tree is rebuilt. If defined, it is passed to the `builder` function. - `selector`: A function that computes a value `V` from one or more states and listens for changes to rebuild the widget tree when the value computed changes. - It receives the following arguments: + It receives the following parameters: - `instance`: The instance of `T` dependency provided by the closest `RtProvider` widget. - `select`: A function that allows you to wrap the state(`RtState`) to be listened for changes and returns it. - `builder`: A function that rebuilds a widget depending on the `RtSelector`. -It receives the following arguments: +It receives the following parameters: - `context`: The `BuildContext` of the `RtSelector` widget. - `instance`: The instance of `T` dependency provided by the closest `RtProvider` widget. - `value`: The selected value computed `V` by the `selector` function. @@ -90,7 +90,7 @@ RtSelector( To use `RtSelector`, wrap it around the widget that you want to rebuild when the selected value changes. -Here’s an example of how to use it: +Here's an example of how to use it: diff --git a/website/src/content/docs/widgets/rt_signal_watcher.mdx b/website/src/content/docs/api/widgets/rt_signal_watcher.mdx similarity index 85% rename from website/src/content/docs/widgets/rt_signal_watcher.mdx rename to website/src/content/docs/api/widgets/rt_signal_watcher.mdx index 4e499d68..79a76cb0 100644 --- a/website/src/content/docs/widgets/rt_signal_watcher.mdx +++ b/website/src/content/docs/api/widgets/rt_signal_watcher.mdx @@ -23,7 +23,7 @@ It acts as a listener for all `Signal` instances within its subtree, tr It is not recommended to use `RtSignalWatcher` with a widget tree where it may contain a considerable number of arbitrary unchecked signals. Because `RtSignalWatcher` will listen any signals in the widget tree, regardless of its level, this could result in needless rebuilds that negatively affect performance. -For more granular control and performance optimization, consider using [`RtWatcher`](/reactter/widgets/rt_watcher), [`RtConsumer`](/reactter/widgets/rt_consumer), [`RtSelector`](/reactter/widgets/rt_selector), [`BuildContext.watch`](/reactter/extensions/builder_context_watch), [`BuildContext.select`](/reactter/extensions/builder_context_select) instead to listen for specific states and rebuild parts of the widget tree that require it. +For more granular control and performance optimization, consider using [`RtWatcher`](/reactter/api/widgets/rt_watcher), [`RtConsumer`](/reactter/api/widgets/rt_consumer), [`RtSelector`](/reactter/api/widgets/rt_selector), [`BuildContext.watch`](/reactter/api/extensions/builder_context_watch), [`BuildContext.select`](/reactter/api/extensions/builder_context_select) instead to listen for specific states and rebuild parts of the widget tree that require it. ::: ## Syntax @@ -42,7 +42,7 @@ RtSignalWatcher({ - `child`: An optional `Widget` that remains static while the widget tree is rebuilt. It is passed to the `builder` function if it is defined. - `builder`: A required function that builds a widget tree based on the current state of the signals. - It receives the following arguments: + It receives the following parameters: - `context`: The `BuildContext` of the `RtSignalWatcher` widget. - `child`: The `child` widget passed to `RtSignalWatcher`. diff --git a/website/src/content/docs/widgets/rt_watcher.mdx b/website/src/content/docs/api/widgets/rt_watcher.mdx similarity index 99% rename from website/src/content/docs/widgets/rt_watcher.mdx rename to website/src/content/docs/api/widgets/rt_watcher.mdx index dd7e87ae..fa5b375c 100644 --- a/website/src/content/docs/widgets/rt_watcher.mdx +++ b/website/src/content/docs/api/widgets/rt_watcher.mdx @@ -2,7 +2,7 @@ title: RtWatcher description: Learn how to use the RtWatcher in Reactter. sidebar: - order: 7 + order: 6 --- import { HM, HT, HN } from "@/components/Highlight"; diff --git a/website/src/content/docs/core_concepts/dependency_injection.mdx b/website/src/content/docs/core_concepts/dependency_injection.mdx index ba30f43a..4e1989d8 100644 --- a/website/src/content/docs/core_concepts/dependency_injection.mdx +++ b/website/src/content/docs/core_concepts/dependency_injection.mdx @@ -1,108 +1,127 @@ --- -title: Dependency Injection -description: Learn how to manage dependencies in Reactter. +title: Dependency injection +description: Learn about the dependency injection system in Reactter. sidebar: order: 2 --- -import { HE, HM, HN, HT } from '@/components/Highlight'; +import { HE, HM, HN, HS, HT } from '@/components/Highlight'; import CodeTabs from '@/components/CodeTabs.astro'; import { Tabs, TabItem } from "@/components/Tabs"; import { Code } from "@astrojs/starlight/components"; import { marks } from '@/examples/marks.ts' -With Reactter, managing objects becomes straightforward. You can create, delete, and access desired objects from a single centralized location, accessible from anywhere in your code, all thanks to Reactter's robust dependency injection system. +Dependency injection is a design pattern that simplifies the management of an object's dependencies. -Dependency injection offers several benefits. +With Reactter, managing objects becomes easy. You can create, delete, and access desired objects from a single centralized place, accessible from anywhere in your code, all thanks to Reactter's solid dependency injection system. -- **Inversion of Control**: It adheres to the principle of inversion of control, where the responsibility for object creation and management is delegated to Reactter. This results in improved code _modularity_, _reusability_, and _testability_. -- **Simplified Code**: By offloading the responsibility of creating dependencies from individual classes, dependency injection simplifies code, allowing classes to focus more on their core functionality. +Dependency injection offers several benefits. Some of the most notable are: + +- **Decoupling**: Dependency injection decouples classes from their dependencies, making it easier to modify and reuse code. +- **Inversion of control**: It adheres to the principle of inversion of control, where the responsibility of creating and managing objects is delegated to Reactter. +This results in better _modularity_, _reusability_, and _testability_ of the code. +- **Simplified code**: By delegating the responsibility of creating dependencies from individual classes, dependency injection simplifies the code, allowing classes to focus more on their core functionality. ## API Reactter provides the following dependencies injection mechanisms: - Hooks - - [`UseDependency`](/reactter/hooks/UseDependency) + - [`UseDependency`](/reactter/api/hooks/UseDependency) - Methods - - [`Rt.register`](/reactter/methods/dependency_injection_methods/#rtregister) - - [`Rt.lazyBuilder`](/reactter/methods/dependency_injection_methods/#rtlazy_builder) - - [`Rt.lazyFactory`](/reactter/methods/dependency_injection_methods/#rtlazy_factory) - - [`Rt.lazySingleton`](/reactter/methods/dependency_injection_methods/#rtlazy_singleton) - - [`Rt.create`](/reactter/methods/dependency_injection_methods/#rtcreate) - - [`Rt.builder`](/reactter/methods/dependency_injection_methods/#rtbuilder) - - [`Rt.factory`](/reactter/methods/dependency_injection_methods/#rtfactory) - - [`Rt.singleton`](/reactter/methods/dependency_injection_methods/#rtsingleton) - - [`Rt.get`](/reactter/methods/dependency_injection_methods/#rtget) - - [`Rt.find`](/reactter/methods/dependency_injection_methods/#rtfind) - - [`Rt.exists`](/reactter/methods/dependency_injection_methods/#rtexists) - - [`Rt.getDependencyMode`](/reactter/methods/dependency_injection_methods/#rtget_dependency_mode) - - [`Rt.delete`](/reactter/methods/dependency_injection_methods/#rtdelete) - - [`Rt.destroy`](/reactter/methods/dependency_injection_methods/#rtdestroy) - - [`Rt.unregister`](/reactter/methods/dependency_injection_methods/#rtunregister) - - [`Rt.isActive`](/reactter/methods/dependency_injection_methods/#rtis_active) - + - [`Rt.register`](/reactter/api/methods/dependency_injection_methods/#rtregister) + - [`Rt.lazyBuilder`](/reactter/api/methods/dependency_injection_methods/#rtlazy_builder) + - [`Rt.lazyFactory`](/reactter/api/methods/dependency_injection_methods/#rtlazy_factory) + - [`Rt.lazySingleton`](/reactter/api/methods/dependency_injection_methods/#rtlazy_singleton) + - [`Rt.create`](/reactter/api/methods/dependency_injection_methods/#rtcreate) + - [`Rt.builder`](/reactter/api/methods/dependency_injection_methods/#rtbuilder) + - [`Rt.factory`](/reactter/api/methods/dependency_injection_methods/#rtfactory) + - [`Rt.singleton`](/reactter/api/methods/dependency_injection_methods/#rtsingleton) + - [`Rt.get`](/reactter/api/methods/dependency_injection_methods/#rtget) + - [`Rt.find`](/reactter/api/methods/dependency_injection_methods/#rtfind) + - [`Rt.exists`](/reactter/api/methods/dependency_injection_methods/#rtexists) + - [`Rt.isActive`](/reactter/api/methods/dependency_injection_methods/#rtis_active) + - [`Rt.getDependencyMode`](/reactter/api/methods/dependency_injection_methods/#rtget_dependency_mode) + - [`Rt.delete`](/reactter/api/methods/dependency_injection_methods/#rtdelete) + - [`Rt.destroy`](/reactter/api/methods/dependency_injection_methods/#rtdestroy) + - [`Rt.unregister`](/reactter/api/methods/dependency_injection_methods/#rtunregister) :::tip[With Flutter] -If you are using Flutter, go to [Rendering Control](/reactter/core_concepts/rendering_control) to learn how it manage dependencies through Widgets and BuildContext extension methods. +If you are using Flutter, go to [rendering control](/reactter/core_concepts/rendering_control) to learn how it manage dependencies through `Widget` and `BuildContext` extension methods. ::: -## How it works +## How It Works -Reactter manages the dependencies through a centralized mechanism. -This core component serves as a central repository for registering, resolving, and providing dependencies across the app. -To comprehend this mechanism thoroughly, let's break down the process into five stages: +Reactter manages dependencies through a centralized mechanism that acts as a main repository responsible for registering, resolving, and providing dependencies throughout the application. +To understand how this system works in its entirety, we will break down the process into the following stages: -1. **Registration**: This stage involves registering the dependency into Reactter's context with the specified `id` and `mode` params. +1. **Registration**: This stage involves registering the dependency within the Reactter context using a specific type, a `builder` function, an `id`, and a dependency `mode`. - For this, you can use the following methods: - - [`Rt.register`](/reactter/methods/dependency_injection_methods/#rtregister) - - [`Rt.lazyBuilder`](/reactter/methods/dependency_injection_methods/#rtlazy_builder)(registers with `builder` mode) - - [`Rt.lazyFactory`](/reactter/methods/dependency_injection_methods/#rtlazy_factory)(registers with `factory` mode) - - [`Rt.lazySingleton`](/reactter/methods/dependency_injection_methods/#rtlazy_singleton)(registers with `singleton` mode) + To perform this registration, you can use the following methods: + - [`Rt.register`](/reactter/en/api/methods/dependency_injection_methods/#rtregister) + - [`Rt.lazyBuilder`](/reactter/en/api/methods/dependency_injection_methods/#rtlazy_builder) + - [`Rt.lazyFactory`](/reactter/en/api/methods/dependency_injection_methods/#rtlazy_factory) + - [`Rt.lazySingleton`](/reactter/en/api/methods/dependency_injection_methods/#rtlazy_singleton) - The `Lifecycle.registered` event is emitted. + During registration, the event `Lifecycle.registered` is triggered. -2. **Resolving**: When there is a request for getting a dependency, Reactter gets it according to `id` and the `mode` through the registry. -If the dependency with/without `id` is not yet created, Reactter initializes it based on registry(this condition doesn't apply to `find` method). +2. **Resolution**: When a dependency is requested, Reactter creates an instance of the dependency from the registered `builder` function, according to the provided type and `id`. For this, you can use the following methods: - - [`Rt.get`](/reactter/methods/dependency_injection_methods/#rtget) - - [`Rt.find`](/reactter/methods/dependency_injection_methods/#rtfind)(doesn't create the dependency) - - [`Rt.create`](/reactter/methods/dependency_injection_methods/#rtcreate)(registers if not found in registry) - - [`Rt.builder`](/reactter/methods/dependency_injection_methods/#rtbuilder)(registers with `builder` mode if not found in registry) - - [`Rt.factory`](/reactter/methods/dependency_injection_methods/#rtfactory)(registers with `factory` mode if not found in registry) - - [`Rt.singleton`](/reactter/methods/dependency_injection_methods/#rtsingleton)(registers with `singleton` mode if not found in registry) - - The following events are fired(only if the dependency instance is created): + - [`Rt.get`](/reactter/en/api/methods/dependency_injection_methods/#rtget) + - [`Rt.create`](/reactter/en/api/methods/dependency_injection_methods/#rtcreate) + - [`Rt.builder`](/reactter/en/api/methods/dependency_injection_methods/#rtbuilder) + - [`Rt.factory`](/reactter/en/api/methods/dependency_injection_methods/#rtfactory) + - [`Rt.singleton`](/reactter/en/api/methods/dependency_injection_methods/#rtsingleton) + + :::note + All of the above methods, except `Rt.get`, in addition to instantiating the dependency, may also register it if it hasn't been registered previously. + ::: + + If a new dependency instance is created, the following events will be emitted: - `Lifecycle.created` - - `Lifecycle.willMount`(_flutter_reactter_ only) - - `Lifecycle.didMount`(_flutter_reactter_ only) + - `Lifecycle.willMount` (only in _flutter_reactter_) + - `Lifecycle.didMount` (only in _flutter_reactter_) -3. **Usage**: The dependency is then used across the app as needed. +3. **Usage**: Once the dependency is resolved, its instance can be used anywhere within the application. - Some of these events may occur: + To access the dependency or check its state, you can use the following methods: + - [`Rt.find`](/reactter/en/api/methods/dependency_injection_methods/#rtfind) + - [`Rt.get`](/reactter/en/api/methods/dependency_injection_methods/#rtget) + - [`Rt.exists`](/reactter/en/api/methods/dependency_injection_methods/#rtexists) + - [`Rt.isActive`](/reactter/en/api/methods/dependency_injection_methods/#rtis_active) + - [`Rt.getDependencyMode`](/reactter/en/api/methods/dependency_injection_methods/#rtget_dependency_mode) + + If the dependency's state is updated, the following events will be emitted: - `Lifecycle.willUpdate` - `Lifecycle.didUpdate` -4. **Deleting**: When the dependency with/without `id` is no longer required, Reactter deletes it. +4. **Deletion**: In this stage, Reactter removes the dependency instance based on the provided type and `id`. - For this, you can use the following methods: - - [`Rt.delete`](/reactter/methods/dependency_injection_methods/#rtdelete) - - [`Rt.destroy`](/reactter/methods/dependency_injection_methods/#rtdestroy)(delete & unregister) + To do this, you can use the following methods: + - [`Rt.delete`](/reactter/en/api/methods/dependency_injection_methods/#rtdelete) + - [`Rt.destroy`](/reactter/en/api/methods/dependency_injection_methods/#rtdestroy) + + :::note + Depending on the dependency `mode`, the dependency instance may not be deleted, or the registration may also be removed. + ::: - The following events are fired: - - `Lifecycle.willUnmount`(_flutter_reactter_ only) - - `Lifecycle.didUnmount`(_flutter_reactter_ only) + During deletion, the following events will be emitted: + - `Lifecycle.willUnmount` (only in _flutter_reactter_) + - `Lifecycle.didUnmount` (only in _flutter_reactter_) - `Lifecycle.deleted` -5. **Unregistration**: When the dependency with/without `id` is no longer required and depending on `mode`, Reactter unregisters it. +5. **Unregistration**: In this stage, Reactter removes the dependency's registration based on the provided type and `id`. - For this, you can use the following methods: - - [`Rt.unregister`](/reactter/methods/dependency_injection_methods/#rtunregister) - - [`Rt.delete`](/reactter/methods/dependency_injection_methods/#rtdelete) - - [`Rt.destroy`](/reactter/methods/dependency_injection_methods/#rtdestroy) + To unregister the dependency, you can use the following methods: + - [`Rt.unregister`](/reactter/en/api/methods/dependency_injection_methods/#rtunregister) + - [`Rt.delete`](/reactter/en/api/methods/dependency_injection_methods/#rtdelete) + - [`Rt.destroy`](/reactter/en/api/methods/dependency_injection_methods/#rtdestroy) + + :::note + Depending on the dependency `mode`, the dependency registration may not be removed. + ::: - The `Lifecycle.unregistered` event is emitted. + When the dependency registration is removed, the event `Lifecycle.unregistered` will be emitted. :::note `id` and `mode` are optional parameters. @@ -113,7 +132,7 @@ If `mode` is not provided, Reactter will use the default mode, which is `Dep A **dependency** in Reactter is referred to as an **instance** of specific type. -The scope of the registered dependency is global. This indicates that using the [shortcuts to manage dependency](/reactter/shortcuts/dependency) or [`UseDependency`](/reactter/hooks/UseDependency) will allow you to access them from anywhere in the project. +The scope of the registered dependency is global. This indicates that using the [shortcuts to manage dependency](/reactter/shortcuts/dependency) or [`UseDependency`](/reactter/api/hooks/UseDependency) will allow you to access them from anywhere in the project. ::: :::tip @@ -122,89 +141,88 @@ The scope of the registered dependency is global. This indicates that using the ### Example -To understand it better, we will return to the countdown example seen from the [State Management](/reactter/core_concepts/state_management/#example) page, -but now using the dependency injection: +To better understand this, let's go back the countdown example from the [state manager page](/reactter/en/core_concepts/state_management/#example), but now using dependency injection: Countdown())!; - // Start the countdown - await countdown.run(); -} + import 'package:reactter/reactter.dart'; + import 'countdown.dart'; + + void main() async { + // Create an instance of the 'Countdown' class + final countdown = Rt.create(() => Countdown())!; + // Start the countdown + await countdown.run(); + } `} lang="dart" mark={marks} /> (() => Counter(10)); - - // Get the instance of the \`Counter\` class - Counter get counter => uCounter.instance; - - /// Start the countdown - Future run() { - // Listen to the \`didUpdate\` event of the \`counter\` instance - // and print the current \`value\` of \`count\` each time it changes - Rt.on( - counter, - Lifecycle.didUpdate, - (_, __) => print('Count: \${counter.count}'), - ); - - // Create a timer that decrements the \`count\` state by 1 - // every second until it reaches 0 - return Timer.periodic(Duration(seconds: 1), _countdown); - } + import 'package:reactter/reactter.dart'; + import 'counter.dart'; + + /// A class representing a countdown + class Countdown { + // Create an instance of the 'Counter' class using the 'UseDependency' hook + // with an initial value of 10 + final uCounter = UseDependency.create(() => Counter(10)); + + // Get the 'Counter' instance + Counter get counter => uCounter.instance; + + /// Start the countdown + Future run() { + // Listen for the 'didUpdate' event on the 'counter' instance + // and print the current value of 'count' + Rt.on( + counter, + Lifecycle.didUpdate, + (_, __) => print('Count: \${counter.count}'), + ); + + // Create a timer that calls the '_countdown' function + // every second + return Timer.periodic(Duration(seconds: 1), _countdown); + } - // Decrement the \`count\` state by 1 each time the timer ticks - // and delete the \`Counter\` instance when the count value reaches 0 - void _countdown(Timer timer) { - counter.decrement(); + // Decrement the 'count' state by 1 every timer cycle + // and delete the 'Counter' instance when the value reaches 0 + void _countdown(Timer timer) { + counter.decrement(); - if (counter.count == 0) { - timer.cancel(); - Rt.delete(); + if (counter.count == 0) { + timer.cancel(); + Rt.delete(); + } } } -} `} lang="dart" mark={marks} /> _count; + /// A class representing a counter that holds the 'count' state + class Counter { + final Signal _count; - int get count => _count.value; + int get count => _count.value; - const Counter(int initialValue) : _count = Signal(initialValue); + const Counter(int initialValue) : _count = Signal(initialValue); - void decrement() => _count.value -= 1; -} + void decrement() => _count.value -= 1; + } `} lang="dart" mark={marks} /> -In this example, we have create a countdown of `10` seconds, and when it reaches `0`, the `Counter` instance is deleted. -But we will make a small tweak to change the countdown behavior. +In this example, we've created a countdown from `10` seconds, and when it reaches `0`, the `Counter` instance is deleted. +But if we want to use the `Counter` instance elsewhere in the code, we can do it like this: ```dart title="main.dart" ins={3, 6-7} collapse={8-11} "Rt.register" "Rt.create" /Counter(?!\u0060)/ /Countdown(?!\u0060)/ /(countdown) =/ "countdown.run" import 'package:reactter/reactter.dart'; @@ -212,20 +230,20 @@ import 'countdown.dart'; import 'counter.dart'; void main() async { - // Register the `Counter` class with an initial value of 20 + // Register the 'Counter' class with an initial value of 20 Rt.register(() => Counter(20)); - // Create an instance of the `Countdown` class + // Create an instance of the 'Countdown' class final countdown = Rt.create(() => Countdown())!; // Start the countdown await countdown.run(); } ``` -Now, the countdown will start from `20` and when it reaches `0`, the `Counter` instance is deleted. -What happens is that the `Counter` instance is registered with an initial value of `20`, -and when the `Countdown` instance is created, it uses the `Counter` instance registered. +Now, the countdown will start from `20`, and when it reaches `0`, the instance of `Counter` will be deleted. +What happens is that the instance of `Counter` is registered with an initial value of `20`, +and when the instance of `Countdown` is created, it uses the registered instance of `Counter`. -Ok, but what if we want to use the `Counter` instance in another part of the code? Let's look: +Ok, But what if we want to use the instance of `Counter` elsewhere in the code? Let's see: ```dart title="main.dart" ins={12-15} collapse={6-11} "Rt.register" "Rt.create" "Rt.get" /counter(?!\\.)/ "counter?.count" /Counter(?!\u0060)(?! )/ /Countdown(?!\u0060)/ /(countdown) =/ "countdown.run" import 'package:reactter/reactter.dart'; @@ -241,15 +259,16 @@ void main() async { await countdown.run(); // Get the instance of the `Counter` class final counter = Rt.get(); - // Try to print the current count value + // Try to print the current `count` value print('Count: ${counter?.count ?? 'Counter instance not found'}'); } ``` -In this case, the countdown will work as before, but when trying to get the `Counter` instance to print its value, -the ouput will be _“Counter instance not found”_. -This occurs because `Counter` was registered as `DependencyMode.builder`(the default mode), -so when it was deleted at the end of the countdown its registration was also deleted. -If we want to get the `Counter` instance to print its value, we need to register using the `DependencyMode.singleton` mode, looking like this: +In this case, the countdown will work as before, but when trying to get the instance of `Counter` to print its value, +the output will be 'Counter instance not found'. +This happens because `Counter` was registered as `DependencyMode.builder` (the default mode), +so when it is deleted at the end of the countdown, its registration is also removed. + +If we want to obtain the instance of Counter to print its value, we need to register it using the DependencyMode.singleton mode, as shown below: ```dart title="main.dart" {7} collapse={8-15} "Rt.register" "Rt.create" "Rt.get" "DependencyMode.singleton" /counter(?!\\.)/ "counter?.count" /Counter(?!\u0060)(?! )/ /Countdown(?!\u0060)/ /(countdown) =/ "countdown.run" import 'package:reactter/reactter.dart'; @@ -265,13 +284,11 @@ void main() async { await countdown.run(); // Get the instance of the `Counter` class final counter = Rt.get(); - // Try to print the current count value + // Try to print the current `count` value print('Count: ${counter?.count ?? 'Counter instance not found'}'); } ``` -Let's now delve into the **modes** of dependency registration. - ## Dependency Modes The **mode** with which a dependency is registered determines how it is managed by Reactter. There are three modes: @@ -282,63 +299,54 @@ The **mode** with which a dependency is registered determines how it is managed ### Builder -Builder is a ways to manage a dependency, -which registers a builder function and creates the instance, -unless it has already done so. +The Builder mode is a way to manage a dependency by registering a `builder` function and creating an instance only if it hasn't been created previously. -In builder mode, when the dependency tree no longer needs it, -it is completely deleted, -including unregistration (deleting the builder function). +In this mode, when the dependency tree no longer needs it, the instance is completely removed, including the registration and the `builder` function. -Reactter identifies the builder mode as +Reactter identifies the Builder mode as `DependencyMode.builder` -and it's using for default. +and uses it by default. :::note - **Builder** uses less RAM than [Factory](#factory) and [Singleton](#singleton), - but it consumes more CPU than the other modes. + Builder uses less RAM than [Factory](#factory) and [Singleton](#singleton), + but consumes more CPU than the other modes. ::: ### Factory -Factory is a ways to manage a dependency, -which registers a builder function only once -and creates the instance if not already done. +The Factory mode is a way to manage a dependency in which a `builder` function is registered, and a new instance is created every time it is requested. -In factory mode, when the dependency tree no longer needs it, -the instance is deleted and the builder function is kept in the register. +In this mode, when the dependency tree no longer uses it, the instance is removed, but the `builder` function remains registered. -Reactter identifies the factory mode as +Reactter identifies the Factory mode as `DependencyMode.factory` -and to active it,set it in the `mode` argument of [`Rt.register`](/reactter/methods/dependency_injection_methods/#rtregister) and [`Rt.create`](/reactter/methods/dependency_injection_methods/#rtcreate), -or use [`Rt.lazyFactory`](/reactter/methods/dependency_injection_methods/#rtlazy_factory), [`Rt.factory`](/reactter/methods/dependency_injection_methods/#rtfactory). +and to activate it, set the `mode` in the argument of [`Rt.register`](/reactter/api/methods/dependency_injection_methods/#rtregister) and [`Rt.create`](/reactter/api/methods/dependency_injection_methods/#rtcreate), +or use [`Rt.lazyFactory`](/reactter/api/methods/dependency_injection_methods/#rtlazy_factory), [`Rt.factory`](/reactter/api/methods/dependency_injection_methods/#rtfactory). :::note - **Factory** uses more RAM than [Builder](#builder) + Factory uses more RAM than [Builder](#builder), but not more than [Singleton](#singleton), - and consumes more CPU than [Singleton](#singleton) + and consumes more CPU than [Singleton](#singleton), but not more than [Builder](#builder). ::: ### Singleton -Singleton is a ways to manage a dependency, -which registers a builder function and creates the instance only once. +The Singleton mode is a way to manage a dependency by registering a `builder` function and ensuring that the instance is created only once. -The singleton mode preserves the instance and its states, -even if the dependency tree stops using it. +When using the singleton mode, the dependency instance and its states remain active, even if the dependency tree no longer uses it. +This also includes the creation function, unless it is explicitly removed. -Reactter identifies the singleton mode as +:::tip + Use [`Rt.destroy`](/reactter/api/methods/dependency_injection_methods/#rtdestroy) if you want to delete both the instance and the registration of a dependency in singleton mode. +::: + +Reactter identifies the Singleton mode as `DependencyMode.singleton` -and to active it, -set it in the `mode` argument of [`Rt.register`](/reactter/methods/dependency_injection_methods/#rtregister) and [`Rt.create`](/reactter/methods/dependency_injection_methods/#rtcreate), -or use [`Rt.lazySingleton`](/reactter/methods/dependency_injection_methods/#rtlazy_singleton), [`Rt.singleton`](/reactter/methods/dependency_injection_methods/#rtsingleton). +and to activate it, set the `mode` in the argument of [`Rt.register`](/reactter/api/methods/dependency_injection_methods/#rtregister) and [`Rt.create`](/reactter/api/methods/dependency_injection_methods/#rtcreate), +or use [`Rt.lazySingleton`](/reactter/api/methods/dependency_injection_methods/#rtlazy_singleton), [`Rt.singleton`](/reactter/api/methods/dependency_injection_methods/#rtsingleton). :::note - **Singleton** consumes less CPU than [Builder](#builder) and [Factory](#factory), + Singleton consumes less CPU than [Builder](#builder) and [Factory](#factory), but uses more RAM than the other modes. ::: - -:::note - Use [`Rt.destroy`](/reactter/methods/dependency_injection_methods/#rtdestroy) if you want to force destroy the instance and its register. -::: diff --git a/website/src/content/docs/core_concepts/event_handler.mdx b/website/src/content/docs/core_concepts/event_handler.mdx index 7dab9d74..6765c782 100644 --- a/website/src/content/docs/core_concepts/event_handler.mdx +++ b/website/src/content/docs/core_concepts/event_handler.mdx @@ -1,10 +1,10 @@ --- -title: "Event Handler" -description: "The base class for all event handlers in Reactter." +title: Event handler +description: Learn about the event handler system in Reactter. sidebar: order: 3 --- -import { HE, HN, HM, HT } from '@/components/Highlight'; +import { HE, HK, HN, HM, HT } from '@/components/Highlight'; In Reactter, event handler plays a pivotal role in facilitating seamless communication and coordination between various components within the application. @@ -16,23 +16,26 @@ fostering a cohesive ecosystem where different parts of the application can inte Reactter offers the following event handler mechanisms: - Hooks - - [`UseEffect`](/reactter/hooks/UseEffect) + - [`UseEffect`](/reactter/api/hooks/UseEffect) - Methods - - [`Rt.on`](/reactter/methods/event_handler_methods/#rton) - - [`Rt.one`](/reactter/methods/event_handler_methods/#rtone) - - [`Rt.emit`](/reactter/methods/event_handler_methods/#rtemit) - - [`Rt.off`](/reactter/methods/event_handler_methods/#rtoff) - - [`Rt.offAll`](/reactter/methods/event_handler_methods/#rtoffall) + - [`Rt.on`](/reactter/api/methods/event_handler_methods/#rton) + - [`Rt.one`](/reactter/api/methods/event_handler_methods/#rtone) + - [`Rt.emit`](/reactter/api/methods/event_handler_methods/#rtemit) + - [`Rt.off`](/reactter/api/methods/event_handler_methods/#rtoff) + - [`Rt.offAll`](/reactter/api/methods/event_handler_methods/#rtoffall) ## How it works -Event handler in Reactter is based on a few fundamental concepts: +The event handler in Reactter is based on some fundamental concepts: -- **Event**: Is a enum that represents a specific action or occurrence in the application. -- **Instance**: Is an object that emits and listens to events. -- **Listener**: Is a function that is executed when an event is emitted. +- **Event**: It is an `enum` that represents a specific action or occurrence associated with a particular instance. +It defines the type of interaction or change that can happen. +- **Instance**: It is an `Object` used to identify the entity that triggers the event and to emit the corresponding events. +It acts as the emitter that connects the event to the action. +- **Action**: It is a `Function` that executes in response to an emitted event. +It contains the logic needed to manage the event and define the desired behavior. -Understanding these concepts is crucial for effectively managing event-driven interactions in Reactter apps. +Understanding these concepts is crucial for efficiently managing event-driven interactions in Reactter applications. ### Example @@ -55,7 +58,7 @@ void main() async { ); // Create a timer that decrements the `value` of `count` - // by 1 every second until it reaches 0 + // by 1 each time second until it reaches 0 await Timer.periodic(Duration(seconds: 1), countdown); } @@ -70,17 +73,11 @@ void countdown(Timer timer) { } ``` -In this example, the line 10 to 14, we see that the `Rt.on` method is used to subscribe to the `Lifecycle.didUpdate` **event** of the `count` **instance**. -Whenever the `count` state changes, the **listener** function is invoked, printing the current `value` of the `count` state. +In this example, we see that the `Rt.on` method is used to subscribe to the **event** `Lifecycle.didUpdate` of the `count` **instance** from line 10 to 14. +Whenever the count state changes, the listener function is invoked, printing the current value of the count state. -Here, we can't see the emitter, because it's encapsulated within the `Signal` class, and it's called when the `value` of the `count` state changes. -This mechanism is made possible by the underlying state management system. - -:::tip - Learn about [Lifecycle](/reactter/core_concepts/lifecycles). -::: - -Now, we do a small tweak to add an emitter: +Here, we cannot see the emitting statement(`emit`) because it is encapsulated inside the `Signal` class and is called when the `value` of the `count` state changes. +To see how an event is emitted, let's make a small adjustment to add an emitter: ```dart title="main.dart" ins={4,18-24,38-39} /count(?!down)(?!\u0060)/ "count.value" "Rt.on" "Lifecycle.didUpdate" "Rt.emit" /(CustomEvent) / /(CustomEvent(\.countdownFinished))(?!\u0060)/ import 'dart:async'; @@ -109,7 +106,7 @@ void main() async { ); // Create a timer that decrements the `value` of `count` - // by 1 every second until it reaches 0 + // by 1 each time second until it reaches 0 await Timer.periodic(Duration(seconds: 1), countdown); } diff --git a/website/src/content/docs/core_concepts/hooks.mdx b/website/src/content/docs/core_concepts/hooks.mdx index 7820c3ed..af76f1fe 100644 --- a/website/src/content/docs/core_concepts/hooks.mdx +++ b/website/src/content/docs/core_concepts/hooks.mdx @@ -4,6 +4,7 @@ description: Learn how to create a custom hook in Reactter. sidebar: order: 6 --- + import { HE, HM, HN, HT } from '@/components/Highlight'; import CodeTabs from '@/components/CodeTabs.astro'; import { Tabs, TabItem } from "@/components/Tabs"; @@ -11,7 +12,9 @@ import ZappButton from "@/components/ZappButton.astro"; import { Code } from "@astrojs/starlight/components"; import { marks } from '@/examples/marks.ts' -import StateMethodsLite from '@/content/docs/shareds/state_methods_lite.mdx'; +import StatePropertiesMethodsRef from '@/content/docs/shareds/state_properties_methods_ref.mdx'; +import CreatingCustomHook from '@/content/docs/shareds/creating_custom_hook.mdx'; +import UsingCustomHook from '@/content/docs/shareds/using_custom_hook.mdx'; import MainCode from '@/examples/custom_hook/lib/main.dart?raw'; import MyCustomFormCode from '@/examples/custom_hook/lib/my_custom_form.dart?raw'; @@ -22,29 +25,29 @@ Hooks are classes with the ability to use states and manage side effects. They a ## API -Reactter provider some hooks to manage the state and side effects of the application, which are: +Reactter provides the following hooks: -- [`UseState`](/reactter/hooks/use_state) -- [`UseAsyncState`](/reactter/hooks/use_async_state) -- [`UseReducer`](/reactter/hooks/use_reducer) -- [`UseCompute`](/reactter/hooks/use_compute) -- [`UseEffect`](/reactter/hooks/use_effect) -- [`UseDependency`](/reactter/hooks/use_instance) +- [`UseState`](/reactter/api/hooks/use_state) +- [`UseAsyncState`](/reactter/api/hooks/use_async_state) +- [`UseReducer`](/reactter/api/hooks/use_reducer) +- [`UseCompute`](/reactter/api/hooks/use_compute) +- [`UseEffect`](/reactter/api/hooks/use_effect) +- [`UseDependency`](/reactter/api/hooks/use_instance) ## How it works -Hooks in Reactter are classes that extend `RtHook` and follow a special naming convention with the `Use` prefix. The `RtHook` class is responsible for binding the hook to other hooks and states, and managing the lifecycle of the hook. +Hooks in Reactter are classes that extend [`RtHook`](/reactter/api/classes/rt_hook) and follow a special naming convention with the `Use` prefix. The `RtHook` class is responsible for binding the hook to other hooks and states, and managing the lifecycle of the hook. -Hooks in Reactter are essentially stateful entities because `RtHook` inherits from `RtState` this inheritance allows hooks to manage both state and lifecycle methods efficiently. +Hooks in Reactter are essentially stateful entities because [`RtHook`](/reactter/api/classes/rt_hook) inherits from `RtState` this inheritance allows hooks to manage both state and lifecycle methods efficiently. To manage these aspects, Hooks provide the following: - + ## Custom hook -Reactter provides a way to create _**Custom Hooks**_ to encapsulate logic that can be reused across the application. +Reactter provides a way to create _**custom hooks**_ to encapsulate logic that can be reused across the application. -There are several advantages to using _**Custom Hooks**_: +There are several advantages to using _**custom hooks**_: - **Reusability**: to use the same hook again and again, without the need to write it twice. - **Clean Code**: extracting part of code into a hook will provide a cleaner codebase. @@ -52,63 +55,8 @@ There are several advantages to using _**Custom Hooks**_: ### Creating a custom hook -To create a _**Custom hook**_, you need to create a class that extends `RtHook` and follow the naming convention with the `Use` prefix. - -:::note -`RtHook` is a class that inherits from `RtState`. -It provides a set of methods to manage the state and lifecycle of the hook. -Learn more about [State methods](/reactter/core_concepts/state_management/#state-methods). -::: - -Here's an example of a _**Custom Hook**_ that manages the state of a text input: - - - -:::caution[Attention!!] -To create a _**Custom Hook**_(`RtHook`), you need to register it by adding the following line: - -```dart showLineNumbers=false -final $ = RtHook.$register; -``` - -It's important to note that the states and hooks defined above this line are not linked to the hook. -To avoid this, it's recommended to place this line as the first line in the class body. -::: - -As shown in the example above, we can utilize other hooks such as [`UseEffect`](/reactter/hooks/use_effect) to monitor changes in the text input's controller and ensure it is disposed when the hook is destroyed. - -The `update` method is used to set the internal `_value` to the current text in the controller, which keeps the state synchronized with the input. -This methods is a part of the `RtHook` class that allows you to update the state of the hook. - -### Using a custom hook - -You can then call that _**Custom Hook**_ from anywhere in the code and get access to its shared logic: - - - - - - - my_controller.dart - - - - - my_custom_form.dart - - - - - - - - - - - - + -In the example above, the form captures first and last name inputs, combines them into a full name, and displays the result. -`MyController` uses `UseTextInput` hook for capturing the first and last name. -The `fullName` state is defined using [`UseCompute`](/reactter/hooks/use_compute) to compute the full name based on the values of `firstNameInput` and `lastNameInput`. This ensures the full name is automatically updated whenever either input changes. +### Using the hook + diff --git a/website/src/content/docs/core_concepts/lifecycle.mdx b/website/src/content/docs/core_concepts/lifecycle.mdx index 91595f54..94082e23 100644 --- a/website/src/content/docs/core_concepts/lifecycle.mdx +++ b/website/src/content/docs/core_concepts/lifecycle.mdx @@ -1,9 +1,10 @@ --- title: Lifecycle -description: The lifecycle of a component in React. +description: Learn about the lifecycle in Reactter. sidebar: order: 5 --- + import { HE, HM, HT, HS } from '@/components/Highlight'; import CodeTabs from '@/components/CodeTabs.astro'; import { Tabs, TabItem } from "@/components/Tabs"; @@ -18,10 +19,10 @@ import counterEventHandlerCode from '@/examples/lifecycle_event_handler/lib/coun import counterViewEventHandlerCode from '@/examples/lifecycle_event_handler/lib/counter_view.dart?raw'; import counterMainEventHandlerCode from '@/examples/lifecycle_event_handler/lib/main.dart?raw'; -import counterControllerLifecycleObserverCode from '@/examples/lifecycle_observer/lib/counter_controller.dart?raw'; -import counterLifecycleObserverCode from '@/examples/lifecycle_observer/lib/counter.dart?raw'; -import counterViewLifecycleObserverCode from '@/examples/lifecycle_observer/lib/counter_view.dart?raw'; -import counterMainLifecycleObserverCode from '@/examples/lifecycle_observer/lib/main.dart?raw'; +import counterControllerRtDependencyLifecycleCode from '@/examples/rt_dependency_lifecycle/lib/counter_controller.dart?raw'; +import counterRtDependencyLifecycleCode from '@/examples/rt_dependency_lifecycle/lib/counter.dart?raw'; +import counterViewRtDependencyLifecycleCode from '@/examples/rt_dependency_lifecycle/lib/counter_view.dart?raw'; +import counterMainRtDependencyLifecycleCode from '@/examples/rt_dependency_lifecycle/lib/main.dart?raw'; import counterControllerLifecycleUseEffectCode from '@/examples/lifecycle_use_effect/lib/counter_controller.dart?raw'; import counterLifecycleUseEffectCode from '@/examples/lifecycle_use_effect/lib/counter.dart?raw'; @@ -47,7 +48,7 @@ Let's explore the lifecycle events: ## Using Event Handler -You can listen to the lifecycle events of a dependency using `Rt.on` or `Rt.one` method of the event handler. e.g: +You can listen to the lifecycle events of an instance (like a dependency or state) using [`Rt.on`](/reactter/api/methods/event_handler_methods/#rton) or [`Rt.one`](/reactter/api/methods/event_handler_methods/#rtone) method of the event handler. e.g: @@ -76,44 +77,43 @@ You can listen to the lifecycle events of a dependency using `Rt.on` or :::note - The `RtDependency` is a generic class that takes the type of the dependency and optionally pass an `id`. - This class is used to identify the dependency in the event handler. - It ideal to use the `RtDependency` class when the dependency is not initialize yet. + The [`RtDependencyRef`](/reactter/api/classes/rt_dependency) is a generic class used to identify the dependency in the event handler. + It ideal to use the [`RtDependencyRef`](/reactter/api/classes/rt_dependency_ref) class when the dependency is not initialize yet. ::: -## Using LifecycleObserver +## Using RtDependencyLifecycle -Extend your instances with `LifecycleObserver` +Extend your instances with [`RtDependencyLifecycle`](/reactter/api/classes/) and use its methods to observe the lifecycle events. e.g: - + counter_controller.dart - + - + - + - + ## Using UseEffect -The [`UseEffect`](/reactter/hooks/use_effect) hook can be used to listen to the lifecycle events of a dependency. e.g: +The [`UseEffect`](/reactter/api/hooks/use_effect) hook can be used to listen to the lifecycle events of a dependency or state. e.g: diff --git a/website/src/content/docs/core_concepts/rendering_control.mdx b/website/src/content/docs/core_concepts/rendering_control.mdx index 9820dd62..f4d472e6 100644 --- a/website/src/content/docs/core_concepts/rendering_control.mdx +++ b/website/src/content/docs/core_concepts/rendering_control.mdx @@ -1,6 +1,6 @@ --- -title: Rendering Control -description: Learn how to control the rendering of components in Reactter. +title: Rendering control +description: Learn how to control the rendering in Reactter. sidebar: order: 4 badge: @@ -18,28 +18,27 @@ import counterCode from '@/examples/counter/lib/counter.dart?raw'; import counterViewCode from '@/examples/counter/lib/counter_view.dart?raw'; import counterMainCode from '@/examples/counter/lib/main.dart?raw'; -In Flutter, efficient rendering control is essential for crafting high-performance, responsive, and scalable applications. -Reactter provides a way to easily control the rendering of components in the widget tree behavior effortlessly, using the _**flutter_reactter**_ package. +In Flutter, efficient rendering control is crucial for building high-performance, responsive, and scalable applications. +The **_flutter_reactter_** package offers a straightforward way to manage widget tree rendering behavior, enabling to optimize performance and fine-tune their app's responsiveness with ease. ## API -This package provides a collection of classes, widgets and some methods: +This package provides the following rendering control mechanisms: -- Classes - - [`RtComponent`](/reactter/widgets/rt_component) - Widgets - - [`RtScope`](/reactter/widgets/rt_scope) - - [`RtProvider`](/reactter/widgets/rt_provider) - - [`RtProviders`](/reactter/widgets/rt_providers) - - [`RtConsumer`](/reactter/widgets/rt_consumer) - - [`RtSelector`](/reactter/widgets/rt_selector) - - [`RtWatcher`](/reactter/widgets/rt_watcher) - - [`RtSignalWatcher`](/reactter/widgets/rt_signal_watcher) + - [`RtScope`](/reactter/api/widgets/rt_scope) + - [`RtProvider`](/reactter/api/widgets/rt_provider) + - [`RtProviders`](/reactter/api/widgets/rt_providers) + - [`RtConsumer`](/reactter/api/widgets/rt_consumer) + - [`RtSelector`](/reactter/api/widgets/rt_selector) + - [`RtWatcher`](/reactter/api/widgets/rt_watcher) + - [`RtComponent`](/reactter/api/widgets/rt_component) + - [`RtSignalWatcher`](/reactter/api/widgets/rt_signal_watcher) - Extensions - - [`BuilderContext.use`](/reactter/extensions/builder_context_use) - - [`BuilderContext.watch`](/reactter/extensions/builder_context_watch) - - [`BuilderContext.watchId`](/reactter/extensions/builder_context_watch_id) - - [`BuilderContext.select`](/reactter/extensions/builder_context_select) + - [`BuilderContext.use`](/reactter/api/extensions/builder_context_use) + - [`BuilderContext.watch`](/reactter/api/extensions/builder_context_watch) + - [`BuilderContext.watchId`](/reactter/api/extensions/builder_context_watch_id) + - [`BuilderContext.select`](/reactter/api/extensions/builder_context_select) ## How it works @@ -77,16 +76,15 @@ Let's create a simple counter app using Reactter to demonstrate how to control t +Now, when you run the app, you'll see a counter application with two buttons to increment and decrement the `count` value. -Now, when you run the app, you will see a counter app with two buttons to increment and decrement the `count` value. +In this scenario, only the `Text` widget will rebuild when the `count` value changes, while the `CounterView` widget will not rebuild entirely. +This happens because the `RtConsumer` widget observes the `count` property and triggers a rebuild of the widget tree only when the `count` value changes. -In this scenario, only the `Text` widget will be rebuilt when the `count` value changes, not the entire `CounterView` widget. -This is because the `RtConsumer` widget observes the `count` property and triggers the rebuilding of the widget tree when the `count` value changes. - -In example, we used the `RtConsumer` widget to observe the `count` property of the `counterController` instance, +In previous example, we used the `RtConsumer` widget to observe the `count` property of the `counterController` instance, but we can do the same functionality using the `watch` method of the `BuildContext` class. -Here's how we can refactor the code to use the `watch` method along with a `Builder` widget to achieve the same outcome: +Next, we'll refactor the code to use the `watch` method along with the `Builder` widget to achieve the same result: ```dart title="counter_view.dart" startLineNumber={23} del={1-5} ins={6-13} "RtConsumer" "context.watch" // Observe the `count` property of the `counterController` @@ -107,9 +105,8 @@ Here's how we can refactor the code to use the `watch` method along wit ), ``` -Although the `watch` method can be directly employed within the builder method of the `RtProvider` widget, +Although the `watch` method can be directly employed within the `builder` method of the `RtProvider` widget, it's advisable to utilize it alongside a `Builder` widget to prevent unnecessary rebuilds of the widget tree. This practice leverages the `BuildContext` scope, offering a more granular approach to rendering control within the widget tree. For more advanced use cases, you can employ other Reactter's widgets and `BuildContext` methods to further refine the rendering control of the widget tree. -By embracing these strategies, you can optimize the performance and efficiency of your Flutter applications while ensuring a seamless user experience. diff --git a/website/src/content/docs/core_concepts/state_management.mdx b/website/src/content/docs/core_concepts/state_management.mdx index ff04b9c4..8f29285d 100644 --- a/website/src/content/docs/core_concepts/state_management.mdx +++ b/website/src/content/docs/core_concepts/state_management.mdx @@ -1,30 +1,37 @@ --- -title: State Management -description: Reactter provides a simple and efficient way to manage the state of your application. +title: State management +description: Learn about the state management system in Reactter and how it works. sidebar: order: 1 --- + import { HE, HM, HT } from '@/components/Highlight'; import StateMethods from '@/content/docs/shareds/state_methods.mdx'; -State management is a critical aspect of any application. -It allows you to manage the state of your application, and facilitates seamless tracking and handling of changes to it. +State management is a critical aspect of any application, as it is responsible for controlling and maintaining data consistency over time. +It facilitates updating and tracking changes in the state, ensuring a smooth user experience and stable application operation. + +At Reactter, we understand the importance of state management, which is why we have designed a state management system that is efficient, reactive, and easy to use. + +To continue, we will show you the mechanisms that Reactter offers for state management and how they work. ## API Reactter provides a variety of mechanisms for state management, including classes, hooks, and methods: - Classes - - [`Signal`](/reactter/classes/signal) + - [`Signal`](/reactter/api/classes/signal) + - [`RtState`](/reactter/api/classes/rt_state) + - [`RtHook`](/reactter/api/classes/rt_hook) - Hooks - - [`UseState`](/reactter/hooks/use_state) - - [`UseAsyncState`](/reactter/hooks/use_async_state) - - [`UseReducer`](/reactter/hooks/use_reducer) - - [`UseCompute`](/reactter/hooks/use_compute) + - [`UseState`](/reactter/api/hooks/use_state) + - [`UseAsyncState`](/reactter/api/hooks/use_async_state) + - [`UseReducer`](/reactter/api/hooks/use_reducer) + - [`UseCompute`](/reactter/api/hooks/use_compute) - Methods - - [`Rt.lazyState`](/reactter/methods/state_management_methods/#rtlazy_state) - - [`Rt.batch`](/reactter/methods/state_management_methods/#rtbatch) - - [`Rt.untracked`](/reactter/methods/state_management_methods/#rtuntracked) + - [`Rt.lazyState`](/reactter/api/methods/state_management_methods/#rtlazy_state) + - [`Rt.batch`](/reactter/api/methods/state_management_methods/#rtbatch) + - [`Rt.untracked`](/reactter/api/methods/state_management_methods/#rtuntracked) :::tip Learn about [Hooks](/reactter/core_concepts/hooks). @@ -39,20 +46,22 @@ To dive into the concept, let's start by exploring what constitutes a state in R ### State -All state in Reactter are classes that inherit `RtState`, +All states in Reactter are classes that inherit [`RtState`](/reactter/api/classes/rt_state), which encapsulates the data and behavior of a particular state, and provides a way to notify observers when the state changes. -Reactter offers two fundamental approaches for creating states: [`Signal`](/reactter/classes/signal) and [`Hooks`](/reactter/core_concepts/hooks). +:::note +Reactter offers three fundamental approaches for creating states: [`RtState`](/reactter/api/classes/rt_state), [`Signal`](/reactter/api/classes/signal) and [`Hooks`](/reactter/core_concepts/hooks). +::: ### State methods -`RtState` class provides some methods for managing states, which are: +[`RtState`](/reactter/api/classes/rt_state) class provides some methods for managing states. Let's know about them: ### Example -Let's see an example of how a `Signal` state is used and what happens under the hood. +Let's see an countdown example using `Signal` and understand what happens under the hood. ```dart title="main.dart" /(Signal)(?!\u0060)/ /count(?!down)(?!\u0060)/ "count.value" "Rt.on" "Lifecycle.didUpdate" import 'dart:async'; @@ -62,7 +71,7 @@ import 'package:reactter/reactter.dart'; final count = Signal(10); void main() async { - // Listen to the `didUpdate` event of the `count` state + // Liste n to the `didUpdate` event of the `count` state // and print the `value` of `count` each time it changes Rt.on( count, @@ -70,8 +79,7 @@ void main() async { (_, __) => print('Count: $count') ); - // Create a timer that decrements the `value` of `count` - // by 1 every second until it reaches 0 + // Create a timer that invoked the `countdown` function every second await Timer.periodic(Duration(seconds: 1), countdown); } @@ -86,27 +94,41 @@ void countdown(Timer timer) { } ``` -During the process, as the `value` of `count` changes and triggers the `Lifecycle.didUpdate` event, -internally within the `Signal` class, the `update` method is invoked to notify its listeners(in line 11 of the code below), as follows: +Now let's see what the `Signal` class contains and how the `count` state is updated in the example above. -```dart title="signal.dart" "RtState" "_value" "update" -class Signal extends RtState[...] { +```dart title="signal.dart" "RtContextMixin" "Rt.registerState" "RtState" "_value" "update" +class Signal with RtState { + // State value T _value; - Signal(this._value); - T get value => _value; set value(T val) { if (_value == val) return; + // Notify listeners that the state has changed, + // triggering the `Lifecycle.willUpdate` and `Lifecycle.didUpdate` events in order. update((_) => _value = val); } + // Private constructor, only a `Signal` instance can be created through the factory. + Signal._(this._value); + + factory Signal(T value) { + // Register a new state in the Reactter context + return Rt.registerState( + () => Signal._(value), + ); + } [...] } ``` +During the process, as the `value` of `count` changes, the `Lifecycle.didUpdate` event is triggered, which is fired by the `update` method (`signal.dart`, line 12). +This event is listened to by the `Rt.on` method (`main.dart`, line 10), which prints the `value` of `count`. + +This occurs thanks to the reactivity of Reactter, which is responsible for notifying listeners by emitting events related to the **_lifecycle_** of the state. + :::tip Learn about [Lifecycle](/reactter/core_concepts/lifecycle). ::: \ No newline at end of file diff --git a/website/src/content/docs/devtools_extension.mdx b/website/src/content/docs/devtools_extension.mdx new file mode 100644 index 00000000..bd6782e9 --- /dev/null +++ b/website/src/content/docs/devtools_extension.mdx @@ -0,0 +1,48 @@ +--- +title: DevTools extension +description: Reactter DevTools Extension +--- +import { HM } from '@/components/Highlight'; + +Now you can integrate Reactter into the DevTools tooling suite as an extension to inspect states and dependencies in the finest detail. + +![Reactter DevTools extension](/public/devtools_extension/devtools.png) + +## Enable the extension + +:::note +Ensure that you have the Reactter [installed](/reactter/getting_started#instalaci%C3%B3n) in your project before enabling the extension. +::: + +To install the Reactter DevTools extension, you need to follow these steps: + +- Open the DevTools in your browser. +Visit the [official documentation](https://docs.flutter.dev/tools/devtools#start) to learn how to open the DevTools in your browser. + +- Go to the Extensions tab. +![Extension tab](/public/devtools_extension/extension_tab.png) + +- Click on the "Enable" button on the Reactter DevTools extension. +![Enable button](/public/devtools_extension/enable_button.png) + +Once you have enabled the Reactter DevTools extension, you can start using it here. + +![Reactter DevTools extension tab](/public/devtools_extension/reactter_extension.png) + +## Usage + +To use the Reactter DevTools extension, it is necessary to call `Rt.initializeDevTools` in your main file. + +```dart title="main.dart" +import 'package:reactter/reactter.dart'; + +void main() { + Rt.initializeDevtools(); + runApp(MyApp()); +} +``` + +:::tip +Use `debugLabel` and `debugInfo` property in your states and dependencies for better visualization in the DevTools extension. +::: + diff --git a/website/src/content/docs/es/core_concepts/dependency_injection.mdx b/website/src/content/docs/es/core_concepts/dependency_injection.mdx new file mode 100644 index 00000000..1aad247a --- /dev/null +++ b/website/src/content/docs/es/core_concepts/dependency_injection.mdx @@ -0,0 +1,355 @@ +--- +title: Inyección de dependencias +description: Aprende sobre la inyección de dependencias en Reactter. +sidebar: + order: 2 +--- +import { HE, HM, HN, HS, HT } from '@/components/Highlight'; +import CodeTabs from '@/components/CodeTabs.astro'; +import { Tabs, TabItem } from "@/components/Tabs"; +import { Code } from "@astrojs/starlight/components"; +import { marks } from '@/examples/marks.ts' + +La inyección de dependencias es un patrón de diseño que facilita la gestión de las dependencias de un objeto. + +Con Reactter, la gestión de objetos se vuelve sencilla. +Puedes crear, eliminar y acceder a los objetos deseados desde un único lugar centralizado, accesible desde cualquier parte de tu código, todo gracias al sólido sistema de inyección de dependencias de Reactter. + +La inyección de dependencias ofrece varios beneficios. Algunos de los más destacados son: + +- **Desacoplamiento**: La inyección de dependencias desacopla las clases de sus dependencias, lo que facilita la modificación y la reutilización del código. +- **Inversión de control**: Se adhiere al principio de inversión de control, donde la responsabilidad de la creación y gestión de objetos se delega a Reactter. +Esto resulta en una mejor _modularidad_, _reutilización_ y _testabilidad_ del código. +- **Código simplificado**: Al delegar la responsabilidad de crear dependencias desde las clases individuales, la inyección de dependencias simplifica el código, permitiendo que las clases se centren más en su funcionalidad principal. + +## API + +Reactter proporciona los siguientes mecanismos de inyección de dependencias: + +- Hooks + - [`UseDependency`](/reactter/es/api/hooks/UseDependency) +- Métodos + - [`Rt.register`](/reactter/es/api/methods/dependency_injection_methods/#rtregister) + - [`Rt.lazyBuilder`](/reactter/es/api/methods/dependency_injection_methods/#rtlazy_builder) + - [`Rt.lazyFactory`](/reactter/es/api/methods/dependency_injection_methods/#rtlazy_factory) + - [`Rt.lazySingleton`](/reactter/es/api/methods/dependency_injection_methods/#rtlazy_singleton) + - [`Rt.create`](/reactter/es/api/methods/dependency_injection_methods/#rtcreate) + - [`Rt.builder`](/reactter/es/api/methods/dependency_injection_methods/#rtbuilder) + - [`Rt.factory`](/reactter/es/api/methods/dependency_injection_methods/#rtfactory) + - [`Rt.singleton`](/reactter/es/api/methods/dependency_injection_methods/#rtsingleton) + - [`Rt.get`](/reactter/es/api/methods/dependency_injection_methods/#rtget) + - [`Rt.find`](/reactter/es/api/methods/dependency_injection_methods/#rtfind) + - [`Rt.exists`](/reactter/es/api/methods/dependency_injection_methods/#rtexists) + - [`Rt.isActive`](/reactter/es/api/methods/dependency_injection_methods/#rtis_active) + - [`Rt.getDependencyMode`](/reactter/es/api/methods/dependency_injection_methods/#rtget_dependency_mode) + - [`Rt.delete`](/reactter/es/api/methods/dependency_injection_methods/#rtdelete) + - [`Rt.destroy`](/reactter/es/api/methods/dependency_injection_methods/#rtdestroy) + - [`Rt.unregister`](/reactter/es/api/methods/dependency_injection_methods/#rtunregister) + +:::tip[Con Flutter] +Sí estás utilizando Flutter, consulta acerca del [control del renderizado](/reactter/es/core_concepts/rendering_control) para aprender a gestionar las dependencias a través de los `Widget` y los métodos extendibles del `BuildContext`. +::: + +## ¿Cómo funciona? + +Reactter gestiona las dependencias a través de un mecanismo centralizado que actua como un repositorio principal encargado de registrar, resolver y suministrar dependencias en toda la aplicación. +Para comprender cómo funciona este sistema en su totalidad, desglosaremos el proceso en las siguientes etapas: + +1. **Registro**: Esta etapa implica registrar la dependencia en el contexto de Reactter utilizando un tipo específico, una función de creación (`builder`), un identificador (`id`) y un modo de dependencia (`mode`). + + Para realizar este registro, puedes utilizar los siguientes métodos: + - [`Rt.register`](/reactter/es/api/methods/dependency_injection_methods/#rtregister) + - [`Rt.lazyBuilder`](/reactter/es/api/methods/dependency_injection_methods/#rtlazy_builder) + - [`Rt.lazyFactory`](/reactter/es/api/methods/dependency_injection_methods/#rtlazy_factory) + - [`Rt.lazySingleton`](/reactter/es/api/methods/dependency_injection_methods/#rtlazy_singleton) + + Durante el registro se emite el evento `Lifecycle.registered`. + +2. **Resolución**: Cuando se solicita una dependencia, Reactter crea una instancia de la dependencia a partir de la funcion de creación (`builder`) registrada, según el tipo e identificador (`id`) proporcionados. + + Para ello, puedes utilizar los siguientes métodos: + - [`Rt.get`](/reactter/es/api/methods/dependency_injection_methods/#rtget) + - [`Rt.create`](/reactter/es/api/methods/dependency_injection_methods/#rtcreate) + - [`Rt.builder`](/reactter/es/api/methods/dependency_injection_methods/#rtbuilder) + - [`Rt.factory`](/reactter/es/api/methods/dependency_injection_methods/#rtfactory) + - [`Rt.singleton`](/reactter/es/api/methods/dependency_injection_methods/#rtsingleton) + + :::note + Todos los anteriores métodos, excepto `Rt.get`, ademas de instanciar la dependencia, pueden registrarla si no ha sido registrada previamente. + ::: + + Si se crea una nueva instancia de la dependencia, se emitirán los siguientes eventos: + - `Lifecycle.created` + - `Lifecycle.willMount` (solo en _flutter_reactter_) + - `Lifecycle.didMount` (solo en _flutter_reactter_) + +3. **Uso**: Una vez resuelta la dependencia, su instancia puede ser utilizada en cualquier parte de la aplicación. + + Para poder acceder a la dependencia o comprobar su estado, puedes utilizar los siguientes métodos: + - [`Rt.find`](/reactter/es/api/methods/dependency_injection_methods/#rtfind) + - [`Rt.get`](/reactter/es/api/methods/dependency_injection_methods/#rtget) + - [`Rt.exists`](/reactter/es/api/methods/dependency_injection_methods/#rtexists) + - [`Rt.isActive`](/reactter/es/api/methods/dependency_injection_methods/#rtis_active) + - [`Rt.getDependencyMode`](/reactter/es/api/methods/dependency_injection_methods/#rtget_dependency_mode) + + Si algún estado de la dependencia se actualiza, se emitirán los siguientes eventos: + - `Lifecycle.willUpdate` + - `Lifecycle.didUpdate` + +4. **Eliminación**: En esta etapa, Reactter elimina la instancia de la dependencia según el tipo e identificador (`id`) proporcionados. + + Para ello, puedes utilizar los siguientes métodos: + - [`Rt.delete`](/reactter/es/api/methods/dependency_injection_methods/#rtdelete) + - [`Rt.destroy`](/reactter/es/api/methods/dependency_injection_methods/#rtdestroy) + + :::note + Según el modo de la dependencia (`mode`), es posible que no se elimine la instancia de la dependencia o, en su caso, que también se elimine su registro. + ::: + + Durante la eliminación se emiten los siguientes eventos: + - `Lifecycle.willUnmount` (solo en _flutter_reactter_) + - `Lifecycle.didUnmount` (solo en _flutter_reactter_) + - `Lifecycle.deleted` + +5. **Desregistro**: En esta etapa, Reactter elimina el registro de la dependencia del tipo y el identificador (`id`) proporcionados. + + Para desregistrar la dependencia, puedes utilizar los siguientes métodos: + - [`Rt.unregister`](/reactter/es/api/methods/dependency_injection_methods/#rtunregister) + - [`Rt.delete`](/reactter/es/api/methods/dependency_injection_methods/#rtdelete) + - [`Rt.destroy`](/reactter/es/api/methods/dependency_injection_methods/#rtdestroy) + + :::note + Según el modo de la dependencia (`mode`), es posible que no se elimine el registro de la dependencia. + ::: + + Cuando se elimina el registro de la dependencia, se emitirá el evento `Lifecycle.unregistered`. + +:::note +`id` y `mode` son parámetros opcionales. + +Si no se proporciona el `id`, Reactter resolverá la dependencia en función del tipo. + +Si no se proporciona el `mode`, Reactter utilizará el modo predeterminado, que es `DependencyMode.builder`. + +Una **dependencia** en Reactter se refiere a una **instancia** de un tipo específico. + +El ámbito de la dependencia registrada es global. Esto indica que al utilizar los [atajos para gestionar dependencias](/reactter/es/shortcuts/dependency) o el [`UseDependency`](/reactter/es/api/hooks/UseDependency), podrás acceder a ellas desde cualquier parte del proyecto. +::: + +:::tip + Aprende sobre el [Ciclo de vida](/reactter/es/core_concepts/lifecycle). +::: + +### Ejemplo + +Para entenderlo mejor, retomaremos el ejemplo de la cuenta regresiva visto desde la página del [gestor de estados](/reactter/es/core_concepts/state_management/#ejemplo), +pero ahora utilizando la inyección de dependencias: + + + + + Countdown())!; + // Inicia la cuenta regresiva + await countdown.run(); + } + `} lang="dart" mark={marks} /> + + + + (() => Counter(10)); + + // Obtiene la instancia de \`Counter\` + Counter get counter => uCounter.instance; + + /// Inicia la cuenta regresiva + Future run() { + // Escucha el evento \`didUpdate\` de la instancia \`counter\` + // e imprime el valor actual de \`count\` + Rt.on( + counter, + Lifecycle.didUpdate, + (_, __) => print('Count: \${counter.count}'), + ); + + // Crea un temporizador que invoca la función \`_countdown\` + // cada segundo + return Timer.periodic(Duration(seconds: 1), _countdown); + } + + // Decrementa el estado \`count\` en 1 cada ciclo del temporizador + // y elimina la instancia de \`Counter\` cuando el valor llega a 0 + void _countdown(Timer timer) { + counter.decrement(); + + if (counter.count == 0) { + timer.cancel(); + Rt.delete(); + } + } + } + `} lang="dart" mark={marks} /> + + + + _count; + + int get count => _count.value; + + const Counter(int initialValue) : _count = Signal(initialValue); + + void decrement() => _count.value -= 1; + } + `} lang="dart" mark={marks} /> + + + + +En este ejemplo, hemos creado una cuenta regresiva de `10` segundos, y cuando llega a `0`, la instancia de `Counter` es eliminada. +Pero si queremos utilizar la instancia de `Counter` en otra parte del código, podemos hacerlo de la siguiente manera: + +```dart title="main.dart" ins={3, 6-7} collapse={8-11} "Rt.register" "Rt.create" /Counter(?!\u0060)/ /Countdown(?!\u0060)/ /(countdown) =/ "countdown.run" +import 'package:reactter/reactter.dart'; +import 'countdown.dart'; +import 'counter.dart'; + +void main() async { + // Registra la clase `Counter` con un valor inicial de 20 + Rt.register(() => Counter(20)); + // Crea una instancia de la clase `Countdown` + final countdown = Rt.create(() => Countdown())!; + // Inicia la cuenta regresiva + await countdown.run(); +} +``` + +Ahora, la cuenta regresiva iniciará desde `20` y cuando llegue a `0`, la instancia de `Counter` será eliminada. +Lo que sucede es que la instancia de `Counter` es registrada con un valor inicial de `20`, +y cuando se crea la instancia de `Countdown`, utiliza la instancia de `Counter` registrada. + +Pero, ¿qué pasa si queremos utilizar la instancia de `Counter` en otra parte del código? Veamos: + +```dart title="main.dart" ins={12-15} collapse={6-11} "Rt.register" "Rt.create" "Rt.get" /counter(?!\\.)/ "counter?.count" /Counter(?!\u0060)(?! )/ /Countdown(?!\u0060)/ /(countdown) =/ "countdown.run" +import 'package:reactter/reactter.dart'; +import 'countdown.dart'; +import 'counter.dart'; + +void main() async { + // Registra la clase `Counter` con un valor inicial de 20 + Rt.register(() => Counter(20)); + // Crea una instancia de la clase `Countdown` + final countdown = Rt.create(() => Countdown())!; + // Inicia la cuenta regresiva + await countdown.run(); + // Obtiene la instancia de la clase `Counter` + final counter = Rt.get(); + // Intenta imprimir el valor actual de `count` + print('Count: ${counter?.count ?? 'Counter instance not found'}'); +} +``` + +En este caso, la cuenta regresiva funcionará como antes, pero al intentar obtener la instancia de `Counter` para imprimir su valor, +la salida será 'Counter instance not found'. +Esto ocurre porque `Counter` fue registrado como `DependencyMode.builder` (modo predeterminado), +por lo que al ser eliminada al final de la cuenta regresiva, su registro también se elimina. + +Si queremos obtener la instancia de `Counter` para imprimir su valor, necesitamos registrarla utilizando el modo `DependencyMode.singleton`, quedando de la siguiente manera: + +```dart title="main.dart" {7} collapse={8-15} "Rt.register" "Rt.create" "Rt.get" "DependencyMode.singleton" /counter(?!\\.)/ "counter?.count" /Counter(?!\u0060)(?! )/ /Countdown(?!\u0060)/ /(countdown) =/ "countdown.run" +import 'package:reactter/reactter.dart'; +import 'countdown.dart'; +import 'counter.dart'; + +void main() async { + // Registra la clase `Counter` con un valor inicial de 20 + Rt.register(() => Counter(20), mode: DependencyMode.singleton); + // Crea una instancia de la clase `Countdown` + final countdown = Rt.create(() => Countdown())!; + // Inicia la cuenta regresiva + await countdown.run(); + // Obtiene la instancia de la clase `Counter` + final counter = Rt.get(); + // Intenta imprimir el valor actual de la cuenta + print('Count: ${counter?.count ?? 'Counter instance not found'}'); +} +``` + +## Modos de dependencia + +El `mode` con el que se registra una dependencia determina cómo es gestionada por Reactter. Existe tres modos de dependencia: + +- [Builder](#builder) +- [Factory](#factory) +- [Singleton](#singleton) + +### Builder + +El modo Builder es una forma de gestionar una dependencia que registra una función de creación (`builder`) y crea una instancia solo si no ha sido creada previamente. + +En este modo, cuando el árbol de dependencias deja de necesitarla, se elimina por completo, incluyendo el registro y la función de creación (`builder`). + +Reactter identifica el modo builder como +`DependencyMode.builder` +y lo utiliza por defecto. + +:::note + Builder utiliza menos RAM que [Factory](#factory) y [Singleton](#singleton), + pero consume más CPU que los otros modos. +::: + +### Factory + +El modo Factory es una forma de gestionar una dependencia en la que se registra una función de creación (`builder`) y se crea una nueva instancia cada vez que se solicita. + +En este modo, cuando el árbol de dependencias deja de utilizarla, la instancia se elimina, pero la función de creación (`builder`) permanece registrada. + +Reactter identifica el modo factory como +`DependencyMode.factory` +y para activarlo, establezca el `mode` en el argumento de [`Rt.register`](/reactter/es/api/methods/dependency_injection_methods/#rtregister) y [`Rt.create`](/reactter/es/api/methods/dependency_injection_methods/#rtcreate), +o utilice [`Rt.lazyFactory`](/reactter/es/api/methods/dependency_injection_methods/#rtlazy_factory), [`Rt.factory`](/reactter/es/api/methods/dependency_injection_methods/#rtfactory). + +:::note + Factory utiliza más RAM que [Builder](#builder), + pero no más que [Singleton](#singleton), + y consume más CPU que [Singleton](#singleton), + pero no más que [Builder](#builder). +::: + +### Singleton + +El modo Singleton es una forma de gestionar una dependencia que registra una función de creación (`builder`) y garantiza que la instancia se cree solo una vez. + +Cuando se utiliza el modo singleton, la instancia de la dependencia y sus estados se mantienen activos, incluso si el árbol de dependencias ya no la utiliza. +Esto incluye también la función de creación, a menos que se elimine explícitamente. + +:::tip + Utilice [`Rt.detroy`](/reactter/es/api/methods/dependency_injection_methods/#rtdestroy) si deseas eliminar tanto la instancia como el registro de una dependencia en modo singleton. +::: + +Reactter identifica el modo singleton como +`DependencyMode.singleton` +y para activarlo, establezca el `mode` en el argumento de [`Rt.register`](/reactter/es/api/methods/dependency_injection_methods/#rtregister) y [`Rt.create`](/reactter/es/api/methods/dependency_injection_methods/#rtcreate), +o utilice [`Rt.lazySingleton`](/reactter/es/api/methods/dependency_injection_methods/#rtlazy_singleton), [`Rt.singleton`](/reactter/es/api/methods/dependency_injection_methods/#rtsingleton). + +:::note + Singleton consume menos CPU que [Builder](#builder) y [Factory](#factory), + pero utiliza más RAM que los otros modos. +::: diff --git a/website/src/content/docs/es/core_concepts/event_handler.mdx b/website/src/content/docs/es/core_concepts/event_handler.mdx new file mode 100644 index 00000000..a5700c54 --- /dev/null +++ b/website/src/content/docs/es/core_concepts/event_handler.mdx @@ -0,0 +1,130 @@ +--- +title: Manejador de eventos +description: Aprende sobre el sistema de manejo de eventos en Reactter. +sidebar: + order: 3 +--- +import { HE, HK, HN, HM, HT } from '@/components/Highlight'; + +En Reactter, el manejador de eventos juega un papel fundamental en facilitar la comunicación +y coordinación sin problemas entre varios componentes dentro de la aplicación. +Diseñado para garantizar una gestión de estados eficiente e inyección de dependencias, +fomentando un ecosistema cohesivo donde diferentes partes de la aplicación pueden interactuar armoniosamente. + +## API + +Reactter ofrece los siguientes mecanismos de manejador de eventos: + +- Hooks + - [`UseEffect`](/reactter/es/api/hooks/UseEffect) +- Metodos + - [`Rt.on`](/reactter/es/api/methods/event_handler_methods/#rton) + - [`Rt.one`](/reactter/es/api/methods/event_handler_methods/#rtone) + - [`Rt.emit`](/reactter/es/api/methods/event_handler_methods/#rtemit) + - [`Rt.off`](/reactter/es/api/methods/event_handler_methods/#rtoff) + - [`Rt.offAll`](/reactter/es/api/methods/event_handler_methods/#rtoffall) + +## ¿Cómo funciona? + +El manejador de eventos en Reactter se basa en algunos conceptos fundamentales: + +- **Evento**: Es un enumerador (`enum`) que representa una acción o suceso específico asociado a una instancia en particular. +Define el tipo de interacción o cambio que puede ocurrir. +- **Instancia**: Es un objeto (`Object`) utilizado para identificar la entidad que origina el evento y para emitir los eventos correspondientes. +Actúa como el emisor que conecta el evento con la acción. +- **Acción**: Es una función (`Function`) que se ejecuta en respuesta a un evento emitido. +Contiene la lógica necesaria para gestionar el evento y definir el comportamiento deseado. + +Entender estos conceptos es crucial para gestionar eficazmente las interacciones basadas en eventos en las aplicaciones de Reactter. + +### Ejemplo + +Para ilustrar esto, tomemos un ejemplo de cuenta regresiva visto en la página de [Gestión de estados](/reactter/es/core_concepts/state_management/#ejemplo): + +```dart title="main.dart" {10-14} /count(?!down)(?!\u0060)/ "count.value" "Rt.on" "Lifecycle.didUpdate" +import 'dart:async'; +import 'package:reactter/reactter.dart'; + +// Crea un estado reactivo llamado `count` utilizando la clase `Signal` +final count = Signal(10); + +void main() async { + // Escucha el evento `didUpdate` del estado `count` + // e imprime `value` de `count` con cada actualización + Rt.on( + count, + Lifecycle.didUpdate, + (_, __) => print('Count: $count') + ); + + // Crea un temporizador que decrementa el `value` de `count` + // en 1 cada segundo hasta que llegue a 0 + await Timer.periodic(Duration(seconds: 1), countdown); +} + +// Decrementa `value` de `count` en 1 cada ciclo del temporizador +// y cancela el `timer` cuando `value` de `count` llegue a 0 +void countdown(Timer timer) { + count.value -= 1; + + if (count.value == 0) { + timer.cancel(); + } +} +``` + +En este ejemplo, desde la línea 10 a la 14, vemos que el método `Rt.on` se utiliza para suscribirse al **evento** `Lifecycle.didUpdate` de la **instancia** `count`. +Cada que cambie el estado de `count`, se invoca la función (**acción**), que imprime el valor (`value`) actual del estado de `count`. + +Aquí, no podemos ver la sentencia que emite (`emit`) porque se encuentra encapsulada dentro de la clase `Signal`, y se llama cuando el `value` del estado `count` cambia. +Para ver cómo se emite un evento, hagamos un pequeño ajuste para agregar un emisor: + +```dart title="main.dart" ins={4,18-24,38-39} /count(?!down)(?!\u0060)/ "count.value" "Rt.on" "Lifecycle.didUpdate" "Rt.emit" /(CustomEvent) / /(CustomEvent(\.countdownFinished))(?!\u0060)/ +import 'dart:async'; +import 'package:reactter/reactter.dart'; + +enum CustomEvent { countdownFinished } + +// Crea un estado reactivo llamado `count` utilizando la clase `Signal` +final count = Signal(10); + +void main() async { + // Escucha el evento `didUpdate` del estado `count` + // e imprime `value` de `count` con cada actualización + Rt.on( + count, + Lifecycle.didUpdate, + (_, __) => print('Count: $count') + ); + + // Escucha el evento `countdownFinished` del estado `count` + // e imprime un mensaje cuando la cuenta regresiva finaliza + Rt.on( + count, + CustomEvent.countdownFinished, + (_, __) => print('Countdown finished!') + ); + + // Crea un temporizador que decrementa el `value` de `count` + // en 1 cada segundo hasta que llegue a 0 + await Timer.periodic(Duration(seconds: 1), countdown); +} + + +// Decrementa `value` de `count` en 1 cada ciclo del temporizador +// y cancela el `timer` cuando `value` de `count` llegue a 0 +void countdown(Timer timer) { + count.value -= 1; + + if (count.value == 0) { + timer.cancel(); + // Emite el evento `countdownFinished` cuando la cuenta regresiva finaliza + Rt.emit(count, CustomEvent.countdownFinished); + } +} +``` +Hemos añadido un nuevo **evento** llamado `CustomEvent.countdownFinished` y una nueva function(**accion**) que imprime un mensaje cuando la cuenta regresiva finaliza. +Cuando la cuenta regresiva llega a `0`, la **instancia** `count` emite el **evento** `CustomEvent.countdownFinished`, y la función(**accion**) se invoca, imprimiendo el mensaje. + +Este ejemplo demuestra cómo el sistema de manejador de eventos en Reactter facilita la comunicación sin problemas entre diferentes partes de la aplicación, +facilitando una coordinación e interacción eficientes. diff --git a/website/src/content/docs/es/core_concepts/hooks.mdx b/website/src/content/docs/es/core_concepts/hooks.mdx new file mode 100644 index 00000000..0384fa70 --- /dev/null +++ b/website/src/content/docs/es/core_concepts/hooks.mdx @@ -0,0 +1,112 @@ +--- +title: Hooks +description: Aprende aceca de los Hooks en Reactter. +sidebar: + order: 6 +--- + +import { HE, HM, HN, HT } from '@/components/Highlight'; +import CodeTabs from '@/components/CodeTabs.astro'; +import { Tabs, TabItem } from "@/components/Tabs"; +import ZappButton from "@/components/ZappButton.astro"; +import { Code } from "@astrojs/starlight/components"; +import { marks } from '@/examples/marks.ts' + +import StatePropertiesMethodsRef from '@/content/docs/es/shareds/state_properties_methods_ref.mdx'; + +import MainCode from '@/examples/custom_hook/lib/main.dart?raw'; +import MyCustomFormCode from '@/examples/custom_hook/lib/my_custom_form.dart?raw'; +import MyControllerCode from '@/examples/custom_hook/lib/my_controller.dart?raw'; +import UseTextInputCode from '@/examples/custom_hook/lib/use_text_input.dart?raw'; + +Los hooks son clases con la capacidad de usar estados y gestionar efectos secundarios. +Son un concepto fundamental en Reactter y se utilizan para encapsular la lógica que se puede reutilizar en cualquier parte de la aplicación. + +## API + +Reactter provee los siguientes hooks: + +- [`UseState`](/reactter/es/api/hooks/use_state) +- [`UseAsyncState`](/reactter/es/api/hooks/use_async_state) +- [`UseReducer`](/reactter/es/api/hooks/use_reducer) +- [`UseCompute`](/reactter/es/api/hooks/use_compute) +- [`UseEffect`](/reactter/es/api/hooks/use_effect) +- [`UseDependency`](/reactter/es/api/hooks/use_instance) + +## ¿Cómo funciona? + +Los hooks en Reactter son clases que extienden de [`RtHook`](/reactter/es/api/classes/rt_hook) es responsable de vincular el hook a otros hooks y estados, y de gestionar el ciclo de vida del hook. + +Los hooks en Reactter son esencialmente entidades con estado porque [`RtHook`](/reactter/es/api/classes/rt_hook) hereda de `RtState` esta herencia permite a los hooks gestionar tanto el estado como los métodos del ciclo de vida de manera eficiente. + +Para gestionar estos aspectos, los Hooks proporcionan lo siguiente: + + + +## Hook personalizado + +Reactter proporciona una forma de crear _**hooks personalizados**_ para encapsular la lógica que se puede reutilizar en cualquier parte de la aplicación. + +Hay varias ventajas en usar _**hoks personalizados**_: + +- **Reusabilidad**: para usar el mismo hook una y otra vez, sin necesidad de escribirlo dos veces. +- **Código limpio**: extraer parte del código en un hook proporcionará una base de código más limpia. +- **Mantenibilidad**: más fácil de mantener. si necesita cambiar la lógica del hook, solo necesita cambiarla una vez. + +### Crear un hook personalizado + +Para crear un _**hook personalizado**_, debes crear una clase que extienda de [`RtHook`](/reactter/es/api/classes/rt_hook) y seguir la convención de nomenclatura con el prefijo `Use`. + +Aquí tienes un ejemplo de un _**hook personalizado**_ que gestiona el estado de un campo de texto: + + + +:::caution[¡Atención!] +Para crear un _**hook**_, debes registrarlo agregando la siguiente línea: + +```dart showLineNumbers=false +final $ = RtHook.$register; +``` + +Es importante tener en cuenta que los estados y hooks definidos por encima de esta línea no estarán vinculados al hook. +Para evitar esto, se recomienda colocar esta línea como la primera línea en el cuerpo de la clase. +::: + +Como se muestra en el ejemplo anterior, podemos utilizar otros hooks como [`UseEffect`](/reactter/es/api/hooks/use_effect) para monitorear los cambios en el controlador del campo de texto y asegurarnos de que se elimine cuando el hook se destruya. + +El método `update` se utiliza para establecer el `_value` interno en el texto actual del controlador, lo que mantiene el estado sincronizado con el campo de texto. +Este método es parte de la clase `RtHook` que te permite actualizar el estado del hook. + +### Usar un hook personalizado + +Los _**hook personalizados**_ pueden ser llamados desde cualquier parte del código y acceder a su lógica compartida, por ejemplo: + + + + + + + my_controller.dart + + + + + my_custom_form.dart + + + + + + + + + + + + + +En el ejemplo anterior, el formulario recopila los datos ingresados en los campos de nombre y apellido, los combina en un nombre completo y muestra el resultado. +`MyController` utiliza el hook `UseTextInput` (hook personalizado creado previamente) para capturar los valores de nombre y apellido. + +El estado `fullName` se define mediante [`UseCompute`](/reactter/es/api/hooks/use_compute), lo que permite calcular el nombre completo en función de los valores de `firstNameInput` y `lastNameInput`. +Esto garantiza que el nombre completo se actualice automáticamente cada vez que se modifiquen las entradas \ No newline at end of file diff --git a/website/src/content/docs/es/core_concepts/lifecycle.mdx b/website/src/content/docs/es/core_concepts/lifecycle.mdx new file mode 100644 index 00000000..0dbf6edb --- /dev/null +++ b/website/src/content/docs/es/core_concepts/lifecycle.mdx @@ -0,0 +1,141 @@ +--- +title: Cliclo de vida +description: Aprende sobre el ciclo de vida en Reactter. +sidebar: + order: 5 +--- + +import { HE, HM, HT, HS } from '@/components/Highlight'; +import CodeTabs from '@/components/CodeTabs.astro'; +import { Tabs, TabItem } from "@/components/Tabs"; +import ZappButton from "@/components/ZappButton.astro"; +import { Code } from "@astrojs/starlight/components"; +import { marks } from '@/examples/marks.ts' + +import TipLifecycleExample from '@/content/docs/es/shareds/tip_lifecycle_example.mdx'; + +import counterControllerEventHandlerCode from '@/examples/lifecycle_event_handler/lib/counter_controller.dart?raw'; +import counterEventHandlerCode from '@/examples/lifecycle_event_handler/lib/counter.dart?raw'; +import counterViewEventHandlerCode from '@/examples/lifecycle_event_handler/lib/counter_view.dart?raw'; +import counterMainEventHandlerCode from '@/examples/lifecycle_event_handler/lib/main.dart?raw'; + +import counterControllerRtDependencyLifecycleCode from '@/examples/rt_dependency_lifecycle/lib/counter_controller.dart?raw'; +import counterRtDependencyLifecycleCode from '@/examples/rt_dependency_lifecycle/lib/counter.dart?raw'; +import counterViewRtDependencyLifecycleCode from '@/examples/rt_dependency_lifecycle/lib/counter_view.dart?raw'; +import counterMainRtDependencyLifecycleCode from '@/examples/rt_dependency_lifecycle/lib/main.dart?raw'; + +import counterControllerLifecycleUseEffectCode from '@/examples/lifecycle_use_effect/lib/counter_controller.dart?raw'; +import counterLifecycleUseEffectCode from '@/examples/lifecycle_use_effect/lib/counter.dart?raw'; +import counterViewLifecycleUseEffectCode from '@/examples/lifecycle_use_effect/lib/counter_view.dart?raw'; +import counterMainLifecycleUseEffectCode from '@/examples/lifecycle_use_effect/lib/main.dart?raw'; + + +En Reactter, tanto los estados como la dependencia (gestionada por la [inyección de dependencia](/reactter/es/core_concepts/dependency_injection)) contienen diferentes etapas, +también conocidas como ciclo de vida (`Lifecycle`). +El ciclo de vida implica eventos emitidos a través del [manejador de eventos](/reactter/es/core_concepts/event_handler). + +Estos son los eventos del ciclo de vida: + +- `Lifecycle.registered`: se activa cuando la dependencia ha sido registrada. +- `Lifecycle.created`: se activa cuando la instancia de la dependencia ha sido creada. +- `Lifecycle.willMount` (exclusivo del paquete _**flutter_reactter**_): se activa cuando la dependencia va a ser montada en el árbol de widgets. +- `Lifecycle.didMount` (exclusivo del paquete _**flutter_reactter**_): se activa después de que la dependencia se haya montado con éxito en el árbol de widgets. +- `Lifecycle.willUpdate`: se activa en cualquier momento en que el estado o la dependencia estén a punto de ser actualizados. El parámetro de evento es un `RtState`. +- `Lifecycle.didUpdate`: se activa en cualquier momento en que el estado o la dependencia ha sido actualizado. El parámetro de evento es un `RtState`. +- `Lifecycle.willUnmount` (exclusivo del paquete _**flutter_reactter**_): se activa cuando la dependencia está a punto de ser desmontada del árbol de widgets. +- `Lifecycle.didUnmount` (exclusivo del paquete _**flutter_reactter**_): se activa cuando la dependencia ha sido desmontada con éxito del árbol de widgets. +- `Lifecycle.deleted`: se activa cuando la instancia de la dependencia ha sido eliminada. +- `Lifecycle.unregistered`: se activa cuando la dependencia ya no está registrada. + +## Usando el manejador de eventos + +Puedes escuchar los eventos del ciclo de vida de una instancia (como una dependencia o un estado) usando el método [`Rt.on`](/reactter/es/api/methods/event_handler_methods/#rton) o [`Rt.one`](/reactter/es/api/methods/event_handler_methods/#rtone) del manejador de eventos. Por ejemplo: + + + + + + + + + counter_view.dart + + + + + counter_controller.dart + + + + + + + + + + + + + +:::note + El [`RtDependencyRef`](/reactter/es/api/classes/rt_dependency_ref) es una clase genérica que se utiliza para identificar la dependencia en el manejador de eventos. + Es ideal usar la clase [`RtDependencyRef`](/reactter/es/api/classes/rt_dependency_ref) cuando la dependencia aún no se ha inicializado. +::: + +## Usando RtDependencyLifecycle + +Extiende tus instancias con [`RtDependencyLifecycle`](/reactter/es/api/classes/lifecycle_observer) para escuchar los eventos del ciclo de vida de una dependencia o estado. Por ejemplo: + + + + + + + + + counter_controller.dart + + + + + + + + + + + + + + + + + +## Usando UseEffect + +El hook [`UseEffect`](/reactter/es/api/hooks/use_effect) se puede usar para escuchar los eventos del ciclo de vida de una dependencia o estado. Por ejemplo: + + + + + + + + + counter_controller.dart + + + + + + + + + + + + + + + + diff --git a/website/src/content/docs/es/core_concepts/rendering_control.mdx b/website/src/content/docs/es/core_concepts/rendering_control.mdx new file mode 100644 index 00000000..bb0e8c90 --- /dev/null +++ b/website/src/content/docs/es/core_concepts/rendering_control.mdx @@ -0,0 +1,114 @@ +--- +title: Control del renderizado +description: Aprende cómo controlar el renderizado en Reactter. +sidebar: + order: 4 + badge: + text: Flutter +--- +import { HE, HM, HT } from '@/components/Highlight'; +import CodeTabs from '@/components/CodeTabs.astro'; +import { Tabs, TabItem } from "@/components/Tabs"; +import ZappButton from "@/components/ZappButton.astro"; +import { Code } from "@astrojs/starlight/components"; +import { marks } from '@/examples/marks.ts' + +import counterControllerCode from '@/examples/counter/lib/counter_controller.dart?raw'; +import counterCode from '@/examples/counter/lib/counter.dart?raw'; +import counterViewCode from '@/examples/counter/lib/counter_view.dart?raw'; +import counterMainCode from '@/examples/counter/lib/main.dart?raw'; + +En Flutter, el control eficiente del renderizado es crucial para crear aplicaciones de alto rendimiento, receptivas y escalables. +El paquete **_flutter_reactter_** ofrece una manera sencilla de gestionar el comportamiento del renderizado en el árbol de widgets, permitiendo optimizar el rendimiento y ajustar la capacidad de respuesta de la aplicación con facilidad. + +## API + +Este paquete proporciona los siguientes mecanismos de control del renderizado: + +- Widgets + - [`RtScope`](/reactter/api/widgets/rt_scope) + - [`RtProvider`](/reactter/api/widgets/rt_provider) + - [`RtProviders`](/reactter/api/widgets/rt_providers) + - [`RtConsumer`](/reactter/api/widgets/rt_consumer) + - [`RtSelector`](/reactter/api/widgets/rt_selector) + - [`RtWatcher`](/reactter/api/widgets/rt_watcher) + - [`RtComponent`](/reactter/api/widgets/rt_component) + - [`RtSignalWatcher`](/reactter/api/widgets/rt_signal_watcher) +- Extensiones + - [`BuilderContext.use`](/reactter/api/extensions/builder_context_use) + - [`BuilderContext.watch`](/reactter/api/extensions/builder_context_watch) + - [`BuilderContext.watchId`](/reactter/api/extensions/builder_context_watch_id) + - [`BuilderContext.select`](/reactter/api/extensions/builder_context_select) + +## How it works + +El control del renderizado en Reactter se basa en dos conceptos fundamentales de Flutter: + +- **InheritedWidget**: Este mecanismo poderoso comparte eficientemente datos en todo el árbol de widgets. +Reactter extiende esta capacidad con el widget `RtProvider`, que almacena dependencias utilizando el sistema de inyección de dependencias. +Esto permite que los widgets descendientes accedan a estas dependencias según sea necesario. + +- **Extensiones de BuildContext**: Estos métodos facilitan el acceso a dependencias y el control del renderizado dentro del árbol de widgets. +Los widgets de Reactter como `RtConsumer`, `RtSelector` y `RtComponent` utilizan estos métodos para observar dependencias o estados. +Cuando la dependencia o cualquier estado observado cambia, estos widgets activan rápidamente la reconstrucción del árbol de widgets para reflejar el estado actualizado. + +### Ejemplo + +Veamos cómo crear una aplicación de contador simple usando Reactter para demostrar cómo controlar el renderizado del árbol de widgets en Flutter. + + + + + + + + + + + + + + + + + + + + + + + +Ahora, cuando ejecutes la aplicación, verás una aplicación de contador con dos botones para incrementar y decrementar el valor de `count`. + +En este caso, solo el widget `Text` se reconstruirá cuando el valor de `count` cambie, mientras que el widget `CounterView` no se reconstruirá por completo. +Esto se debe a que el widget `RtConsumer` observa la propiedad `count` y activa la reconstrucción del árbol de widgets únicamente cuando el valor de `count` cambia. + +En el ejemplo anterior, utilizamos el widget `RtConsumer` para observar la propiedad `count` de la instancia `counterController`, +pero podemos lograr la misma funcionalidad utilizando el método `watch` de la clase `BuildContext`. + +A continuación, mostramos cómo modificar el código para emplear el método `watch` junto al widget `Builder` y lograr el mismo resultado: + +```dart title="counter_view.dart" startLineNumber={23} del={1-5} ins={6-13} "RtConsumer" "context.watch" + // Observe the `count` property of the `counterController` + // and rebuild the widget tree when the `count` value changes + RtConsumer( + listenStates: (counterController) => [counterController.count], + builder: (context, counterController, child) { + Builder( + builder: (context) { + // Observe the `count` property of the `counterController` + // and rebuild the widget tree when the `count` value changes + final counterController = context.watch( + (counterController) => [counterController.count], + ); + + return Text("${counterController.count}"); + }, + ), +``` + +Mientras que el método `watch` puede emplearse directamente dentro del método `builder` del widget `RtProvider`, +es aconsejable utilizarlo junto a un widget `Builder` para evitar reconstrucciones innecesarias del árbol de widgets. +Esta práctica aprovecha el alcance de `BuildContext`, ofreciendo un enfoque más granular para controlar el renderizado dentro del árbol de widgets. + +Para casos de uso más avanzados, puedes emplear otros widgets de Reactter y métodos de `BuildContext` para poder refinar aún más el control del renderizado del árbol de widgets. diff --git a/website/src/content/docs/es/core_concepts/state_management.mdx b/website/src/content/docs/es/core_concepts/state_management.mdx new file mode 100644 index 00000000..f0d73f44 --- /dev/null +++ b/website/src/content/docs/es/core_concepts/state_management.mdx @@ -0,0 +1,129 @@ +--- +title: Gestor de estados +description: Aprende sobre el sistema de gestión de estados en Reactter y cómo funciona. +sidebar: + order: 1 +--- +import { HE, HM, HT } from '@/components/Highlight'; +import StateMethods from '@/content/docs/es/shareds/state_methods.mdx'; + +La gestión de estados es un aspecto crítico de cualquier aplicación, ya que es la encargada de controlar y mantener la coherencia de los datos a lo largo del tiempo. +Facilita la actualización y el seguimiento de los cambios en el estado, asegurando una experiencia de usuario fluida y un funcionamiento estable de la aplicación. + +En Reactter sabemos la importancia de la gestión de estados, por lo que hemos diseñado un sistema de gestión de estados que es eficiente, reactivo y fácil de usar. + +A continuación, te mostramos los mecanimos que Reactter ofrece para la gestión de estados y aprenderás cómo funcionan. + +## API + +Reactter proporciona una gran variedad de mecanismos para la gestión de estados, incluyendo clases, hooks y métodos: + +- Clases + - [`Signal`](/reactter/es/api/classes/signal) + - [`RtState`](/reactter/es/api/classes/RtState) +- Hooks + - [`UseState`](/reactter/es/api/hooks/use_state) + - [`UseAsyncState`](/reactter/es/api/hooks/use_async_state) + - [`UseReducer`](/reactter/es/api/hooks/use_reducer) + - [`UseCompute`](/reactter/es/api/hooks/use_compute) +- Metodos + - [`Rt.lazyState`](/reactter/es/api/methods/state_management_methods/#rtlazy_state) + - [`Rt.batch`](/reactter/es/api/methods/state_management_methods/#rtbatch) + - [`Rt.untracked`](/reactter/es/api/methods/state_management_methods/#rtuntracked) + +:::tip + Aprende sobre [Hooks](/reactter/es/core_concepts/hooks). +::: + +## ¿Cómo funciona? + +El sistema de gestión de estados de Reactter se basa en el concepto de _reactividad_. +Contrario a la noción predominante de que implementar programación reactiva en Dart puede ser desafiante, +Reactter simplifica en gran medida este proceso. +Para adentrarnos en el concepto, comencemos explorando qué constituye un estado en Reactter. + +### Estado + +Todos los estados en Reactter son clases que heredan de `RtState`, +la cual encapsula los datos y el comportamiento de un estado particular, y proporciona una forma de notificar a los observadores cuando el estado cambia + +### Metodos del estado + +La clase [`RtState`](/reactter/api/classes/rt_state) proporciona algunos métodos para la gestión de estados. Conozcámoslos: + + + +### Ejemplo + +Veamos un ejemplo de una cuenta regresiva utilizando `Signal` y desentrañaremos lo qué sucede bajo el capó. + +```dart title="main.dart" /(Signal)(?!\u0060)/ /count(?!down)(?!\u0060)/ "count.value" "Rt.on" "Lifecycle.didUpdate" +import 'dart:async'; +import 'package:reactter/reactter.dart'; + +// Crea un estado reactivo llamado `count` utilizando la clase `Signal` +final count = Signal(10); + +void main() async { + // Escucha el evento `didUpdate` del estado `count` + // e imprime `value` de `count` con cada actualización + Rt.on( + count, + Lifecycle.didUpdate, + (_, __) => print('Count: $count'), + ); + + // Crea un temporizador que invoca la función `countdown` cada segundo + await Timer.periodic(Duration(seconds: 1), countdown); +} + +// Decrementa `value` de `count` en 1 cada ciclo del temporizador +// y cancela el `timer` cuando `value` de `count` llegue a 0 +void countdown(Timer timer) { + count.value -= 1; + + if (count.value == 0) { + timer.cancel(); + } +} +``` + +Ahora veamos que contiene la clase `Signal` y cómo se actualiza el estado `count` en el ejemplo anterior + +```dart title="signal.dart" "Rt.registerState" "RtState" "_value" "update" +class Signal with RtState { + // Valor del estado + T _value; + + // Constructor privada, solo se puede crear una instancia de `Signal` a través del factory. + Signal._(this._value); + + factory Signal(T value) { + // Se registra un nuevo estado en el contexto de Reactter + return Rt.registerState( + () => Signal._(value), + ); + } + + T get value => _value; + + set value(T val) { + if (_value == val) return; + + // Notifica a los oyentes que el estado ha cambiado, + // disparando los eventos `Lifecycle.willUpdate` y `Lifecycle.didUpdate` en orden. + update((_) => _value = val); + } + + [...] +} +``` + +Durante el proceso, a medida que el `value` de `count` cambia, se desencadena el evento `Lifecycle.didUpdate`, el cual es disparado por el método `update` (`signal.dart`, linea 22). +Este evento es escuchado por el método `Rt.on` (`main.dart`, linea 10), que imprime el `value` de `count`. + +Esto ocurre gracias a la reactividad de Reactter, que es encargada de notificar a los oyentes mediante la emisión de eventos relacionados con el **_ciclo de vida_** del estado. + +:::tip + Aprende sobre el [Ciclo de vida](/reactter/es/core_concepts/lifecycle). +::: \ No newline at end of file diff --git a/website/src/content/docs/es/extra_topics/binding_state_to_dependency.mdx b/website/src/content/docs/es/extra_topics/binding_state_to_dependency.mdx new file mode 100644 index 00000000..6e319a24 --- /dev/null +++ b/website/src/content/docs/es/extra_topics/binding_state_to_dependency.mdx @@ -0,0 +1,156 @@ +--- +title: Vinculación del estado a la dependencia +description: Aprende a vincular un estado a una dependencia en Reactter para una gestión de estado más eficiente y reactiva. +--- + +import { Code } from "@astrojs/starlight/components"; +import { HE, HK, HM, HN, HS, HT } from '@/components/Highlight'; + +:::tip +Este guía asume que ya has leído sobre [Inyección de dependencias](/reactter/es/core_concepts/dependency_injection) y [Gestión del estado](/reactter/es/core_concepts/state_management). +Se recomienda leer eso primero si eres nuevo en Reactter. +::: + +Un estado de Reactter([`RtState`](/reactter/es/core_concepts/state_management/#state)) como [`Signal`](/reactter/es/api/classes/signal) o cualquier [`Hooks`](/reactter/es/core_concepts/hooks) puede ser vinculado a la dependencia, permitiendo que el estado sea manipulado directamente desde la dependencia y notificar a sus oyentes sobre cualquier cambio. +Además, asegura que el estado se elimine automáticamente cuando la dependencia ya no sea necesaria. + +Al integrar el estado directamente dentro de las dependencias, se beneficia de un **código más limpio** y **mantenible**. +El manejo automático por parte de Reactter significa **menos código repetitivo** y **menos errores* relacionados con la gestión manual del estado, lo que conduce a un proceso de desarrollo más eficiente. +Este enfoque **simplifica** la sincronización entre el estado y su dependencia asociada, **mejorando la capacidad de respuesta** y la **fiabilidad** general de su aplicación. + +### Vinculación automática + +Para que esto suceda automáticamente, el estado debe declararse como propiedad o dentro del constructor de la dependencia. +Cuando se hace esto, Reactter se encarga automáticamente de vincular el estado a la dependencia, asegurando una gestión y reactividad del estado sin problemas, por ejemplo: + +```dart title="count_controller.dart" "UseState" "UseEffect" +import "package:reactter/reactter.dart"; + +class CountController { + // Estado declarado como propiedad + final uCount = UseState(0); + + CountController() { + // Estado declarado dentro del constructor + UseEffect(() { + print("Count: ${uCount.value}"); + }, [uCount]); + } +} +``` + +En el ejemplo anterior, el estado `uCount` se declara como propiedad de la clase `CountController` y el hook `UseEffect` se utiliza dentro del constructor para reaccionar a los cambios en el estado `uCount`, imprimiendo su valor cada vez que cambia. +Esto vincula automáticamente el estado `uCount` y el hook `UseEffect` a la instancia de `CountController`, demostrando cómo Reactter maneja la vinculación y reactividad de forma transparente. + +:::caution +Si el estado se declara de forma perezosa (por ejemplo, utilizando la palabra clave `late`), no se vinculará automáticamente a la dependencia. +En tales casos, debes usar el método `Rt.lazyState` para vincular el estado a la dependencia(Ver [vinculación perezoso](#vinculación-perezosa)). +::: + +### Vinculación perezosa + +Cuando un estado se declara de forma perezosa, no se vincula automáticamente a la dependencia. +En tales casos, puedes usar el método [`Rt.lazyState`](/reactter/es/api/methods/state_management_methods/#rtlazy_state) para vincular el estado a la dependencia, por ejemplo: + +```dart title="count_controller.dart" "UseState" "Rt.lazyState" +import "package:reactter/reactter.dart"; + +class CountController { + final int initialCount; + + late final uCount = Rt.lazyState( + () => UseState(this.initialCount), + this, + ); + + CountController([this.initialCount = 0]) { + UseEffect(() { + print("Count: ${uCount.value}"); + }, [uCount]); + } +} +``` + +En el ejemplo anterior, el estado `uCount` se declara de forma perezosa utilizando la palabra clave `late`. +Para vincular el estado a la instancia de `CountController`, se utiliza el método [`Rt.lazyState`](/reactter/es/api/methods/state_management_methods/#rtlazy_state), pasando la función de creación del estado y la instancia de la dependencia como argumentos. +Esto asegura que cuando se accede a `uCount`, se vinculará automáticamente a la instancia de `CountController`. + +### Vinculación manual + +Si bien la vinculación automática simplifica la gestión del estado, puede haber escenarios en los que necesites vincular manualmente el estado a una dependencia. +La vinculación manual proporciona un mayor control sobre cómo y cuándo se asocia el estado con la dependencia. + +Para vincular manualmente un estado a una dependencia, debes vincular explícitamente el estado dentro de la dependencia utilizando el método `bind` del estado, por ejemplo: + +```dart title="count_controller.dart" "UseState" "bind" +class CountController { + late final uCount = UseState(this.initialCount); + + final int initialCount; + + CountController([this.initialCount = 0]) { + count.bind(this); + } +} +``` + +En el ejemplo anterior, el estado `uCount` se declara de forma perezosa utilizando la palabra clave `late`. +Para vincular el estado a la instancia de `CountController`, se utiliza el método `bind` del estado, pasando la instancia de la dependencia como argumento. +Esto asegura que el estado `uCount` esté asociado a la instancia de `CountController`, lo que permite que la dependencia actualice reactivamente en función de los cambios en el estado. + +:::note +La [vinculación manual](#vinculación-manual) y [vinculación perezosa](#vinculación-perezosa) tienen propositos diferentes. +La vinculación manual es útil cuando se necesita un control preciso sobre el proceso de vinculación de estados, mientras que la **vinculación perezosa** es adecuada cuando se desea que el estado se inicialice sólo cuando se accede a él por primera vez. +Elige el método adecuado en función de tus necesidades específicas. +::: + +### Desvinculación automática + +Cuando se elimina una dependencia, el estado asociado se elimina automáticamente. +Este mecanismo de desvinculación automática simplifica la gestión de estados y reduce el riesgo de fugas de memoria o desperdicio de recursos. + +En el ejemplo siguiente, el estado `uCount` se elimina automáticamente cuando se borra la instancia `CountController`, lo que garantiza que los recursos se liberan de forma eficiente: + +```dart title="main.dart" "Rt.create" "Rt.delete" +import "./count_controller.dart"; + +void main() { + final controller = Rt.create(() => CountController(10)); + controller.uCount.value += 2; // Count: 12 + Rt.delete(); + controller.uCount.value += 3; // Error: "Can't update when it's been disposed" +} +``` + +### Desvinculación manual + +En algunos casos, puede que necesites desvincular manualmente un estado de una dependencia. +La desvinculación manual proporciona un mayor control sobre cuándo se libera el estado y puede ser útil en escenarios en los que se desea desvincular el estado de forma temporal o permanente. + +Para desvincular manualmente un estado de una dependencia, puedes utilizar el método `unbind` del estado, por ejemplo: + +```dart title="count_controller.dart" "UseState" "unbind" +class CountController { + late final uCount = UseState(this.initialCount); + + final int initialCount; + + CountController([this.initialCount = 0]) { + count.bind(this); + } + + void dispose() { + count.unbind(this); + } +} +``` + +:::caution +Aunque la desvinculación manual proporciona un mayor control, un uso inadecuado puede provocar problemas de gestión de memoria y aumentar el riesgo de errores. +Se recomienda utilizar la [desvinculación automática](#desvinculación-automática) siempre que sea posible, ya que simplifica el proceso y reduce la probabilidad de introducir fugas de memoria o de no liberar los recursos correctamente. + +La desvinculación manual debe utilizarse con precaución y sólo cuando sea absolutamente necesario. +Además, los desarrolladores deben asegurarse de realizar un seguimiento del proceso de desvinculación para evitar dejar estados no utilizados en memoria, lo que podría afectar negativamente al rendimiento y la utilización de recursos. +::: + + diff --git a/website/src/content/docs/es/extra_topics/updating_objects_in_state.mdx b/website/src/content/docs/es/extra_topics/updating_objects_in_state.mdx new file mode 100644 index 00000000..5fc8cd48 --- /dev/null +++ b/website/src/content/docs/es/extra_topics/updating_objects_in_state.mdx @@ -0,0 +1,169 @@ +--- +title: Actualizar objectos en estado +description: Aprende a actualizar objectos en estado de Reactter. +--- +import { HM, HN, HS, HT } from '@/components/Highlight'; + +Un estado de Reactter(`RtState`) como `Signal`, `UseState`, `UseAsyncState`, `UseReducer` y `UseCompute` pueden contener cualquier tipo de valor de Dart, incluidos objetos. +Sin embargo, no debes modificar los objetos contenidos en el estado de Reactter directamente. + +En esta guía, aprenderás cómo actualizar objetos de forma segura y efectiva en un estado Reactter. + +## ¿Qué es una mutación? + +Puedes almacenar cualquier tipo de valor de Dart en el estado. + +```dart showLineNumbers=false +final count = Signal(0); +``` + +Hasta ahora has estado trabajando con números, cadenas y booleanos. +Estos tipos de valores de Dart son _**inmutables**_, lo que significa que no se pueden cambiar o son _**de solo lectura**_. +Puedes desencadenar una nueva representación para reemplazar un valor: + +```dart showLineNumbers=false +count.value = 2; +``` + +El estado `count` cambió de `0` a `2`, pero el número `0` en sí mismo no cambió. +No es posible realizar cambios en los valores primitivos incorporados como números, cadenas y booleanos en Dart. + +Ahora considera un objeto en un estado: + +```dart showLineNumbers=false collapse={1-7} +class User { + String name; + int age; + + User({required this.name, required this.age}); +} + +final user = Signal(User(name: "Jane Doe", age: 25)); +``` + +Tecnicamente, es posible cambiar el contenido del objeto en sí. Esto se llama una **mutación**: + +```dart showLineNumbers=false +user.value.name = "John Doe"; +``` + +Sin embargo, aunque los objetos en el estado de Reactter son técnicamente mutables, debes tratarlos como si fueran inmutables, como números, booleanos y cadenas. +En lugar de mutarlos, siempre debes reemplazarlos. + +## Tratar el estado como de solo lectura + +En otras palabras, debes tratar cualquier objeto de Dart que pongas en el estado como de solo lectura. + +El siguiente ejemplo mantiene un objeto en el estado para representar al usuario. +El nombre del usuario se cambia de `"Jane Doe"` a `"John Doe"` cuando haces clic en el botón, +pero el nombre del usuario sigue siendo el mismo. + +```dart title="main.dart" "Signal" "User" "RtSignalWatcher" "user.value.name" "user.value.age" "user" mark={31} +import 'package:flutter/material.dart'; +import 'package:reactter/reactter.dart'; + +void main() { + runApp(MyApp()); +} + +class User { + String name; + int age; + + User({required this.name, required this.age}); +} + +final user = Signal(User(name: "Jane Doe", age: 25)); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: Text("Inmutable state example")), + body: Center( + child: RtSignalWatcher((){ + return Column( + children: [ + Text('Name: ${user.value.name}'), + Text('Age: ${user.value.age}'), + ElevatedButton( + onPressed: () { + user.value.name = "John Doe"; + }, + child: Text("Change Name"), + ), + ], + ); + }), + ), + ), + ); + } +} +``` + +El problema está en este fragmento de código. + +```dart startLineNumber=24 +user.value.name = "John Doe"; +``` + +Este código modifica el objeto asignado a un nuevo `name` cuando se hace clic en el botón +pero no desencadena un nuevo renderizado porque el objeto en sí no ha cambiado. +La propiedad `name` del objeto ha cambiado, pero el objeto en sí no lo ha hecho. +Y Reactter no sabe que el objeto ha cambiado porque sigue siendo el mismo objeto. +Aunque la mutación del estado puede funcionar en algunos casos, no lo recomendamos. +Debes tratar el valor de estado al que tienes acceso como de solo lectura. + +Para desencadenar un nuevo renderizado en este caso, crea un nuevo objeto y pásalo a la función de configuración del estado: + +```dart startLineNumber=24 +user.value = User(name: "John Doe", age: user.value.age); +``` + +Cunado `value` se establece en un nuevo objeto, Reactter sabe que el estado ha cambiado y desencadena un nuevo renderizado. + +## Copiar objetos + +En el ejemplo anterior, creaste un nuevo objeto con el mismo `age` y un `name` diferente. +Pero ¿qué pasa si quieres cambiar el `age` y mantener el `name` igual?. También puedes hacerlo: + +```dart showLineNumbers=false +user.value = User(name: user.value.name, age: 30); +``` + +Esto es un patrón común cuando trabajas con objetos en el estado. +Sin embargo, crear un nuevo objeto cada vez que quieras cambiar una propiedad puede ser tedioso. +Para simplificar este proceso, puedes agregar el siguiente método(`copyWith`) a la clase `User`: + +```dart collapse={2-6} ins="final" ins={7-13} mark={2-3} mark="copyWith" +class User { + final String name; + final int age; + + const User({required this.name, required this.age}); + + User copyWith({String? name, int? age}) { + return User( + name: name ?? this.name, + age: age ?? this.age, + ); + } +} +``` + +Este método crea un nuevo objeto con las mismas propiedades que el objeto original, excepto por las propiedades que especifiques. +Puedes usar este método para crear un nuevo objeto con el mismo `name` y un `age` diferente: + +```dart showLineNumbers=false ".copyWith" +user.value = user.value.copyWith(age: 30); +``` + +## Resumen + +- Trata los objetos en el estado como de solo lectura. +- Cuando necesites actualizar un objeto, crea uno nuevo o haz una copia del objeto existente, y luego establece el estado para usar este nuevo o copiado objeto. +- Evita mutar objetos en el estado directamente. +- Crea un nuevo objeto y pásalo a la función de configuración del estado para desencadenar un nuevo renderizado. +- Usa el método `copyWith` para crear un nuevo objeto con las mismas propiedades que el objeto original, excepto por las propiedades que especifiques. \ No newline at end of file diff --git a/website/src/content/docs/es/getting_started.mdx b/website/src/content/docs/es/getting_started.mdx new file mode 100644 index 00000000..a06bb3d2 --- /dev/null +++ b/website/src/content/docs/es/getting_started.mdx @@ -0,0 +1,153 @@ +--- +title: Empezando +description: Una guía paso a paso sobre la instalación y el uso de Reactter. +--- + +import { Code } from "@astrojs/starlight/components"; + +import reactterVersion from "@/data/reactter_version"; +import flutterReactterVersion from "@/data/flutter_reactter_version"; +import { Tabs, TabItem } from "@/components/Tabs"; + + +export const reactterPubspecCode = ` +dependencies: + reactter: ${reactterVersion ? `^${reactterVersion}` : "# add version here"} +`; + +Antes de nada, debes saber que Reactter se distribuye en dos paquetes: + +

    +
  • + + Reactter + + : El paquete central que contiene las funcionalidades básicas de Reactter. Se recomienda utilizar este paquete si está desarrollando una aplicación Dart. +
  • +
  • + + Flutter Reactter + + : Este paquete incluye el paquete principal y funciones adicionales de Flutter. Se recomienda utilizar este paquete si está desarrollando una aplicación Flutter. +
  • +
+ +
+ +## Instalación + +Para seguir esta guía, necesitarás un proyecto Dart/Flutter existente. +Si aún no tienes uno, puedes crear una app Dart aquí o una app Flutter aquí. + +Una vez configurado el proyecto, proceda a instalar el paquete utilizando uno de los siguientes métodos: + +- [Instalación automatizada](#instalación-automatizada) +- [Instalación manual](#instalación-manual) + +### Instalación automatizada + +Añada el paquete a su proyecto ejecutando el siguiente comando: + + + + ```shell showLineNumbers=false + dart pub add reactter + ``` + + + ```shell showLineNumbers=false + flutter pub add flutter_reactter + ``` + + + +### Instalación manual + +Agrega el paquete a su proyecto añadiendo la siguiente línea a su archivo `pubspec.yaml`: + + + + + + + + + + +Y ejecute el siguiente comando para instalar el paquete: + + + + ```shell showLineNumbers=false +dart pub get + ``` + + + ```shell showLineNumbers=false +flutter pub get + ``` + + + +## Configuraciones adicionales + +Este paso es opcional pero recomendable. +Utilice el paquete [![Reactter Lint](https://img.shields.io/pub/v/reactter_lint?color=1d7fac&labelColor=29b6f6&label=reactter_lint&logo=dart)](https://pub.dev/packages/reactter_lint) para aplicar las mejores prácticas y convenciones en su proyecto:: + +```shell showLineNumbers=false +dart pub add reactter_lint +``` + +Además, si estás desarrollando con Visual Studio Code, es una buena idea utilizar [Reactter Snippets](https://marketplace.visualstudio.com/items?itemName=CarLeonDev.reacttersnippets) para mejorar la productividad. + +## Uso + +Para utilizar Reactter, debe importar el paquete en su archivo Dart donde lo requieras: + + + + ```dart +import 'package:reactter/reactter.dart'; + ``` + + + ```dart +import 'package:flutter_reactter/flutter_reactter.dart'; + ``` + + + +Listo! No tienes que preocuparte por la configuración, Reactter ya esta listo para usarse en su proyecto. + +
+ +### ¿Qué sigue? + +Si eres nuevo en Reactter, puedes empezar leyendo la sección de _**Conceptos básicos**_. +Esto te ayudará a entender los conceptos básicos de Reactter y cómo utilizarlos en tu proyecto. + +En esta sección, los temas que aprenderá son: + +- [Gestor de estados](/reactter/es/core_concepts/state_management) +- [Inyección de dependencia](/reactter/es/core_concepts/dependency_injection) +- [Manejador de eventos](/reactter/es/core_concepts/event_handler) +- [Control del renderizado](/reactter/es/core_concepts/rendering_control) +- [Ciclo de vida](/reactter/es/core_concepts/lifecycle) +- [Hooks](/reactter/es/core_concepts/hooks) + diff --git a/website/src/content/docs/es/index.mdx b/website/src/content/docs/es/index.mdx new file mode 100644 index 00000000..0787e8e5 --- /dev/null +++ b/website/src/content/docs/es/index.mdx @@ -0,0 +1,84 @@ +--- +title: Reactter +description: Un ligero, potente y reactivo Gestor de Estados, Inyección de Dependencia y Manejador de Eventos para Dart/Flutter. +template: splash +hero: + title:

Reactter

+ tagline: +

+ Un paquete + ligero, + potente y + reactivo +

+

+ Gestor de Estados, + Inyección de Dependencias y + Manejador de Eventos para + Dart/Flutter. +

+ image: + html: + actions: + - text: Empezar + link: /reactter/es/getting_started/ + icon: rocket + variant: primary + - text: Explore en pub.dev + link: https://pub.dev/packages/reactter + icon: external + attrs: + target: _blank + footer: TEST +--- + +import { CardGrid } from "@astrojs/starlight/components"; +import Card from "@/components/Card.astro"; + +
+ ## Pruébalo +

Experimente todo el potencial de Reactter probando en Zapp.

+ + +:::tip[¡El poder está en tus manos!] + [Empieza instalando](/reactter/es/getting_started) Reactter en tus proyectos y desbloquea todo su potencial. +::: \ No newline at end of file diff --git a/website/src/content/docs/es/shareds/state_methods.mdx b/website/src/content/docs/es/shareds/state_methods.mdx new file mode 100644 index 00000000..cc7d87b1 --- /dev/null +++ b/website/src/content/docs/es/shareds/state_methods.mdx @@ -0,0 +1,24 @@ +--- +title: Metodos del estado +--- +import { HE, HM, HT } from '@/components/Highlight'; + +- **`update`**: Ejecuta una función callback y notifica a sus observadores que el estado ha cambiado. +Cuando se invoca, emite dos eventos [lifecycle](/reactter/es/core_concepts/lifecycle) para señalar la transición de estado: + - `Lifecycle.willUpdate` se emite primero, indicando la actualización inminente. + - `Lifecycle.didUpdate` se emite una vez que el proceso de actualización se ha completado. +- **`notify`**: Fuerza al estado a notificar a sus observadores. +A diferencia de `update`, sólo emite el evento `Lifecycle.didUpdate`, ya que no implica ningún paso previo a la notificación. +- **`bind`**: Establece una conexión entre el estado y una instancia específica. +Esta conexión permite a la instancia actualizarse de forma reactiva en función de los cambios en el estado. +Al vincular el estado, la instancia es notificado de los cambios en el estado y puede reflejar adecuadamente esos cambios en su comportamiento. +- **`unbind`**: Libera la conexión entre el estado y la instancia. +Al desvincularse, la instancia dejará de recibir actualizaciones del estado. +Esto puede ser útil cuando una instancia ya no está utilizando activamente el estado o cuando necesita desvincularse del estado temporal o permanentemente. +- **`dispose`**: Es responsable de limpiar el estado y cualquier observador o recurso asociado. +Disponer del estado garantiza que se libere correctamente y que ya no consuma memoria o recursos de procesamiento innecesariamente. + +:::note +Mientras que los métodos `bind`, `unbind` y `dispose` se utilizan normalmente para gestionar el [ciclo de vida](/reactter/es/core_concepts/lifecycle) de un estado, +Reactter maneja esto automáticamente, cuando el estado es declarado dentro de una instancia que existe dentro del contexto de Reactter vía [dependecy injection](/reactter/es/core_concepts/dependency_injection/), por lo que no necesitas preocuparte por ello(Apendas más acerca de la [vinculación del estado a la dependencia](/reactter/es/extra_topics/binding_state_to_dependency/)). +::: \ No newline at end of file diff --git a/website/src/content/docs/es/shareds/state_properties_methods.mdx b/website/src/content/docs/es/shareds/state_properties_methods.mdx new file mode 100644 index 00000000..f078664f --- /dev/null +++ b/website/src/content/docs/es/shareds/state_properties_methods.mdx @@ -0,0 +1,10 @@ +--- +title: Properties & Methods of RtState +--- + +import { HT } from '@/components/Highlight'; +import StateMethods from '@/content/docs/es/shareds/state_methods.mdx'; + +- **`debugLabel`**: Una cadena que representa la etiqueta del estado para propósitos de depuración. +- **`debugInfo`**: Un `Map` que contiene información de depuración sobre el estado. + diff --git a/website/src/content/docs/es/shareds/state_properties_methods_ref.mdx b/website/src/content/docs/es/shareds/state_properties_methods_ref.mdx new file mode 100644 index 00000000..54e0f6fc --- /dev/null +++ b/website/src/content/docs/es/shareds/state_properties_methods_ref.mdx @@ -0,0 +1,11 @@ +--- +title: Properties & Methods of RtState +--- + +import { HT } from '@/components/Highlight'; +import StatePropertiesMethods from '@/content/docs/es/shareds/state_properties_methods.mdx'; + +
+ Propiedades y métodos heradados de [`RtState`](/reactter/api/classes/rt_state) + +
\ No newline at end of file diff --git a/website/src/content/docs/es/shareds/tip_lifecycle_example.mdx b/website/src/content/docs/es/shareds/tip_lifecycle_example.mdx new file mode 100644 index 00000000..c3f73bac --- /dev/null +++ b/website/src/content/docs/es/shareds/tip_lifecycle_example.mdx @@ -0,0 +1,8 @@ +--- +title: Consejo sobre el ejemplo del ciclo de vida +--- + +:::tip + Este ejemplo se toma de la página de [control de renderizado](http://localhost:4321/reactter/es/core_concepts/rendering_control/#example). + Se recomienda leer esa página primero antes de continuar. +::: \ No newline at end of file diff --git a/website/src/content/docs/extensions/build_context_select.mdx b/website/src/content/docs/extensions/build_context_select.mdx deleted file mode 100644 index 761d070f..00000000 --- a/website/src/content/docs/extensions/build_context_select.mdx +++ /dev/null @@ -1,97 +0,0 @@ ---- -title: "BuildContext.select" -description: "Learn how to select a value in a BuildContext in Reactter." -sidebar: - order: 4 ---- - -import { HM, HT, HN } from '@/components/Highlight'; -import MDXRedry from '@/components/MDXRedry.astro'; -import CodeTabs from '@/components/CodeTabs.astro'; -import { Tabs, TabItem } from "@/components/Tabs"; -import ZappButton from "@/components/ZappButton.astro"; -import { Code } from "@astrojs/starlight/components"; -import { marks } from '@/examples/marks.ts' - -import * as WatchVsSelectComparation from '@/content/docs/shareds/watch_vs_select_comparation.mdx'; -import counterControllerCode from '@/examples/build_context_select/lib/counter_controller.dart?raw'; -import counterCode from '@/examples/build_context_select/lib/counter.dart?raw'; -import counterDivisibleCode from '@/examples/build_context_select/lib/counter_divisible.dart?raw'; -import counterViewCode from '@/examples/build_context_select/lib/counter_view.dart?raw'; -import mainCode from '@/examples/build_context_select/lib/main.dart?raw'; - -`BuildContext.select` is a method that computes a value whenever the selected states change and registers the current widget as a listener for these changes. -Consequently, each time the selected states change, the value is recalculated and if it is different from the previous one, the widget is automatically notified and rebuilt. - -:::tip - -`BuildContext.select` is useful when you need to render using a computed value based on multiple states. -This aproach is more efficient than using [`BuildContext.watch`](/reactter/extensions/build_context_watch) because it avoids unnecessary rebuilds. - - -::: - -## Syntax & description - -```dart showLineNumbers=false -V context.select( - V computeValue(T instance, RtState select(RtState state)), - [String? id], -); -``` - -- `context`: The `BuildContext` object, which provides information about the current widget in the tree. -- `T`: The type of the dependency you want to select. It is used to locate the dependency from the nearest ancestor provider. -- `V`: The type of the value you want to compute. It is the return type of the `computeValue` function. -- `computeValue`: A function that computes the value based on the selected states. It takes two arguments: - - `instance`: The instance of the dependency of type `T`. - - `select`: A function that allows you to wrap the state(`RtState`) to be listened for changes and returns it. -- `id`: An optional identifier for the `T` dependency. If omitted, the dependency will be located by its type (`T`). - -:::tip -It's a good idea to name the select parameter to something more simple and shorter like `$` to make it easier to read and understand the `selector` function when you have multiple states to select. - -```dart showLineNumbers=false {2} -RtSelector( - selector: (inst, $) => $(inst.firstName).value + ' ' + $(inst.lastName).value, - builder: (context, inst, fullName, child) { - return Text("Full name: $fullName"); - }, -) -``` -::: - -## Usage - -This following example demonstrates how to use `BuildContext.select`: - - - - - - - counter_divisible.dart - - - - - counter_view.dart - - - - - - - - - - - - - - - - - -In this example, checks if the `count` state from `CounterController` is divisible by a specified number (`byNum`). -The `BuildContext.select` method is used to perform this check and rebuild the widget tree whenever the divisibility status changes. \ No newline at end of file diff --git a/website/src/content/docs/extensions/build_context_use.mdx b/website/src/content/docs/extensions/build_context_use.mdx deleted file mode 100644 index 79afe310..00000000 --- a/website/src/content/docs/extensions/build_context_use.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: BuildContext.use -description: Learn how to use a value in a BuildContext in Reactter. -sidebar: - order: 1 ---- - -import { HM, HT, HN } from '@/components/Highlight'; -import CodeTabs from '@/components/CodeTabs.astro'; -import { Tabs, TabItem } from "@/components/Tabs"; -import ZappButton from "@/components/ZappButton.astro"; -import { Code } from "@astrojs/starlight/components"; -import { marks } from '@/examples/marks.ts' - -import counterControllerCode from '@/examples/build_context/lib/counter_controller.dart?raw'; -import counterCode from '@/examples/build_context/lib/counter.dart?raw'; -import counterViewCode from '@/examples/build_context/lib/counter_view.dart?raw'; -import mainCode from '@/examples/build_context/lib/main.dart?raw'; - -`BuildContext.use` is a method that gets an instance of `T` dependency from the nearest ancestor provider([`RtProvider`](/reactter/widgets/rt_provider) or [`RtComponent`](/reactter/widgets/rt_component)) of type `T`. - -## Syntax & description - -```dart showLineNumbers=false -T context.use([String? id]); -``` - -- `context`: The `BuildContext` object, which provides information about the current widget in the tree. -- `T`: The type of the dependency you want to access. -These are some points to consider: - - If the type is nullable, the method will return `null` if the dependency is not found. - - If the type is not nullable, the method will throw an exception if the dependency is not found. -- `id`: An optional identifier for the `T` dependency. If omitted, the dependency will be located by its type(`T`). - -## Usage - -This following example demonstrates how to use `BuildContext.use`: - - - - - - - counter.dart - - - - - counter_view.dart - - - - - - - - - - - - - -In this example, the `Counter` widget uses `BuildContext.use` to get the instance of `CounterController` from the nearest ancestor provider(located in `main.dart`). \ No newline at end of file diff --git a/website/src/content/docs/extensions/build_context_watch.mdx b/website/src/content/docs/extensions/build_context_watch.mdx deleted file mode 100644 index 33ab745b..00000000 --- a/website/src/content/docs/extensions/build_context_watch.mdx +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: BuildContext.watch -description: Learn how to watch a value in a BuildContext in Reactter. -sidebar: - order: 2 ---- - -import { HM, HT, HN } from '@/components/Highlight'; -import MDXRedry from '@/components/MDXRedry.astro'; -import CodeTabs from '@/components/CodeTabs.astro'; -import { Tabs, TabItem } from "@/components/Tabs"; -import ZappButton from "@/components/ZappButton.astro"; -import { Code } from "@astrojs/starlight/components"; -import { marks } from '@/examples/marks.ts' - -import * as WatchVsSelectComparation from '@/content/docs/shareds/watch_vs_select_comparation.mdx'; -import counterControllerCode from '@/examples/build_context/lib/counter_controller.dart?raw'; -import counterCode from '@/examples/build_context/lib/counter.dart?raw'; -import counterViewCode from '@/examples/build_context/lib/counter_view.dart?raw'; -import mainCode from '@/examples/build_context/lib/main.dart?raw'; - -`BuildContext.watch` is a method similar to [`BuildContext.use`](/reactter/extensions/build_context_use), but with an added functionality. -It not only retrieves the `T` dependency but also registers the current widget as a listener for any changes to the states set or otherwise any changes in the `T` dependency. -Consequently, whenever changes, the widget using `BuildContext.watch` is automatically notified and rebuilt. - -:::tip -You can optionally use `BuildContext.watch` with an `id`, but when you need to listen changes in a dependency's states using an `id`, it's better to use [`BuildContext.watchId`](/reactter/extensions/build_context_watch_id) for improved readability and clarity. - -```dart showLineNumbers=false -context.watch(null, 'myId'); -// is equivalent to -context.watchId('myId'); -``` -::: - -:::caution -To render using a computed value based on multiple states, use [`BuildContext.select`](/reactter/extensions/build_context_select) instead of `BuildContext.watch` or pre-compute the value using [`UseCompute`](/reactter/hooks/use_compute) to prevent unnecessary rebuilds. - - -::: - - -## Syntax & description - -```dart showLineNumbers=false -T context.watch([ - List listenStates?(T instance), - String? id, -]); -``` - -- `context`: The `BuildContext` object, which provides information about the current widget in the tree. -- `T`: The type of the dependency you want to access. -These are some points to consider: - - If the type is nullable, the method will return `null` if the dependency is not found. - - If the type is not nullable, the method will throw an exception if the dependency is not found. -- `listenStates`: An optional function that returns a list of state(`RtState`) to listen for changes. If omitted, the method will listen for any changes in the `T` dependency. -- `id`: An optional identifier for the `T` dependency. If omitted, the dependency will be located by its type(`T`). - -## Usage - -This following example demonstrates how to use `BuildContext.watch`: - - - - - - - counter.dart - - - - - counter_view.dart - - - - - - - - - - - - - -In this example, we use `BuildContext.watch` to get the instance of `CounterController` from the nearest ancestor provider(located in `main.dart`) and listen for changes in the `count` state. diff --git a/website/src/content/docs/extensions/build_context_watch_id.mdx b/website/src/content/docs/extensions/build_context_watch_id.mdx deleted file mode 100644 index 94881280..00000000 --- a/website/src/content/docs/extensions/build_context_watch_id.mdx +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: BuildContext.watchId -description: Learn how to watch using id in a BuildContext in Reactter. -sidebar: - order: 3 ---- - -import { HM, HT, HN } from '@/components/Highlight'; -import CodeTabs from '@/components/CodeTabs.astro'; -import { Tabs, TabItem } from "@/components/Tabs"; -import ZappButton from "@/components/ZappButton.astro"; -import { Code } from "@astrojs/starlight/components"; -import { marks } from '@/examples/marks.ts' - -import counterControllerCode from '@/examples/build_context/lib/counter_controller.dart?raw'; -import counterCode from '@/examples/build_context/lib/counter.dart?raw'; -import counterViewCode from '@/examples/build_context/lib/counter_view.dart?raw'; -import mainCode from '@/examples/build_context/lib/main.dart?raw'; - -`BuildContext.watchId` is a method similar to [`BuildContext.watch`](/reactter/extensions/build_context_watch), but use `id` as first argument to locate the dependency. - -:::tip -This approach is useful when you have multiple dependencies of the same type and you want to access a specific one. - -While you can use [`BuildContext.watch`](/reactter/extensions/build_context_watch) with `id`, `BuildContext.watchId` is more readable and clear when an `id` is required. - -```dart showLineNumbers=false -context.watch(null, 'myId'); -// is equivalent to -context.watchId('myId'); -``` -::: - -## Syntax & description - -```dart showLineNumbers=false -T context.watchId( - String id, - [List listenStates?(T instance)], -); -``` - -- `context`: The `BuildContext` object, which provides information about the current widget in the tree. -- `T`: The type of the dependency you want to access. These are some points to consider: - - If the type is nullable, the method will return `null` if the dependency is not found. - - If the type is not nullable, the method will throw an exception if the dependency is not found. -- `id`: An identifier for the `T` dependency. The dependency will be located by its type(`T`) and the `id`. -- `listenStates`: An optional function that returns a list of state(`RtState`) to listen for changes. If omitted, the method will listen for any changes in the `T` dependency. - -## Usage - -This following example demonstrates how to use `BuildContext.watchId`: - - - - - - - counter.dart - - - - - counter_view.dart - - - - - - - - - - - - - -In this example, the `Counter` widget uses `BuildContext.watchId` to get the instance of `CounterController` by its `id` from the nearest ancestor provider(located in `main.dart`) and listen for changes in the `count` state. diff --git a/website/src/content/docs/extra_topics/_renaming_classes.mdx b/website/src/content/docs/extra_topics/_renaming_classes.mdx deleted file mode 100644 index 13ca9617..00000000 --- a/website/src/content/docs/extra_topics/_renaming_classes.mdx +++ /dev/null @@ -1,96 +0,0 @@ ---- -title: Renaming Classes -description: Renaming Classes in Reactter. ---- - -import { Code } from "@astrojs/starlight/components"; -import { HE, HK, HM, HN, HS, HT } from '@/components/Highlight'; -import { Tabs, TabItem } from "@/components/Tabs"; - -:::caution -This practices is not recommended. Renaming classes can cause any the following issues: -- It can make your codebase harder to understand and maintain. -- It can make it harder to find information about the class you are looking for. -- It can cause conflicts with other packages that depend on the original name. - -It's recommended to do it only when you have a good reason to do so and you understand the potential consequences. -::: - -Reactter classes are generally prefixed with `Reactter` to avoid conflicts with other packages, so you can easily identify them. This may seem verbose and redundant, but you can rename them to shorter names or other aliases if you prefer. - -To rename a class, use `typedef` to create an alias for the class. -Here is an example of renaming classes to prefix them with `Rt` instead of `Reactter`: - - - - ```dart title="reactter.dart" - import 'package:reactter/reactter.dart'; - - /// Alias for [Reactter] - /// ignore: non_constant_identifier_names - final Rt = Reactter; - - /// Alias for [ReactterState] - typedef RtState = ReactterState; - - /// Alias for [ReactterHook] - typedef RtHook = ReactterHook; - - /// Alias for [ReactterDependency] - typedef RtDependency = ReactterDependency; - - /// Alias for [ReactterAction] - typedef RtAction = ReactterAction; - - /// Alias for [ReactterActionCallable] - typedef RtActionCallable = ReactterActionCallable; - ``` - - - ```dart title="flutter_reactter.dart" - import 'package:flutter_reactter/flutter_reactter.dart'; - - /// Alias for [Reactter] - /// ignore: non_constant_identifier_names - final Rt = Reactter; - - /// Alias for [ReactterState] - typedef RtState = ReactterState; - - /// Alias for [ReactterHook] - typedef RtHook = ReactterHook; - - /// Alias for [ReactterDependency] - typedef RtDependency = ReactterDependency; - - /// Alias for [ReactterAction] - typedef RtAction = ReactterAction; - - /// Alias for [ReactterActionCallable] - typedef RtActionCallable = ReactterActionCallable; - - /// Alias for [ReactterComponent] - typedef RtComponent = ReactterComponent; - - /// Alias for [ReactterScope] - typedef RtScope = ReactterScope; - - /// Alias for [ReactterProvider] - typedef RtProvider = ReactterProvider; - - /// Alias for [ReactterProviders] - typedef RtProviders = ReactterProviders; - - /// Alias for [ReactterConsumer] - typedef RtConsumer = ReactterConsumer; - - /// Alias for [ReactterSelector] - typedef RtSelector = ReactterSelector; - - /// Alias for [ReactterWatcher] - typedef RtWatcher = ReactterWatcher; - ``` - - - - diff --git a/website/src/content/docs/extra_topics/binding_state_to_dependency.mdx b/website/src/content/docs/extra_topics/binding_state_to_dependency.mdx index 29d976fe..1d213dd1 100644 --- a/website/src/content/docs/extra_topics/binding_state_to_dependency.mdx +++ b/website/src/content/docs/extra_topics/binding_state_to_dependency.mdx @@ -11,10 +11,12 @@ This guies assumes you've already read the [Dependency Injection](/reactter/core It's recommended read that first if you're new in Reactter. ::: -A state([`RtState`](/reactter/core_concepts/state_management/#state) like [`Signal`](/reactter/classes/signal) or any [`Hooks`](/reactter/core_concepts/hooks)) can be bound to the dependency, allowing the state to be manipulated directly from the dependency and to notify its listeners about any changes. +A Reactter state([`RtState`](/reactter/core_concepts/state_management/#state)) such as [`Signal`](/reactter/api/classes/signal) or any [`Hooks`](/reactter/core_concepts/hooks) can be bound to the dependency, allowing the state to be manipulated directly from the dependency and to notify its listeners about any changes. Additionally, it ensures that the state is automatically disposed of when the dependency is no longer needed. -By integrating state directly within dependencies, you benefit from _**cleaner**_ and more _**maintainable**_ code. The automatic handling by Reactter means _**less boilerplate**_ and _**fewer errors**_ related to manual state management, leading to a more efficient development process. This approach _**simplifies**_ the synchronization between state and its associated dependency, enhancing the _**overall responsiveness**_ and _**reliability**_ of your application. +By integrating state directly within dependencies, you benefit from _**cleaner**_ and more _**maintainable**_ code. +The automatic handling by Reactter means _**less boilerplate**_ and _**fewer errors**_ related to manual state management, leading to a more efficient development process. +This approach _**simplifies**_ the synchronization between state and its associated dependency, enhancing the _**overall responsiveness**_ and _**reliability**_ of your application. ### Automatic binding @@ -37,28 +39,18 @@ class CountController { } ``` -In the example above, the `uCount` state is declared as a property of the `CountController` class and the `UseEffect` hook is used within the constructor to react to changes in the `uCount` state, printing its value whenever it changes. This automatically binds the `uCount` state and `UseEffect` hook to the `CountController` instance, demonstrates how Reactter handles the binding and reactivity seamlessly. - -```dart title="main.dart" "Rt.create" "Rt.delete" -import "./count_controller.dart"; - -void main() { - final controller = Rt.create(() => CountController(10)); - controller.uCount.value += 2; // Count: 12 - Rt.delete(); - controller.uCount.value += 3; // Error: "Can't update when it's been disposed" -} -``` - -In the example above, the `uCount` state is accessed after creating the `CountController` instance, demonstrating that the state is bound to the dependency and can be manipulated directly from the instance. When the `CountController` instance is deleted, the state is automatically disposed of, ensuring that resources are released efficiently. +In the example above, the `uCount` state is declared as a property of the `CountController` class and the `UseEffect` hook is used within the constructor to react to changes in the `uCount` state, printing its value whenever it changes. +This automatically binds the `uCount` state and `UseEffect` hook to the `CountController` instance, demonstrates how Reactter handles the binding and reactivity seamlessly. :::caution -If the state is declared lazily (e.g., using `late` keyword), it will not be automatically bound to the dependency. In such cases, you need to use the `Rt.lazyState` method to bind the state to the dependency(See [Lazy state binding](#lazy-state-binding)). +If the state is declared lazily (e.g., using `late` keyword), it will not be automatically bound to the dependency. +In such cases, you need to use the `Rt.lazyState` method to bind the state to the dependency(See [lazy binding](#lazy-binding)). ::: -### Lazy state binding +### Lazy binding -When a state is declared lazily, it is not automatically bound to the dependency. In such cases, you can use the [`Rt.lazyState`](/reactter/methods/state_management_methods/#rtlazystate) method to bind the state to the dependency, e.g.: +When a state is declared lazily, it is not automatically bound to the dependency. +In such cases, you can use the [`Rt.lazyState`](/reactter/api/methods/state_management_methods/#rtlazystate) method to bind the state to the dependency, e.g.: ```dart title="count_controller.dart" "UseState" "Rt.lazyState" import "package:reactter/reactter.dart"; @@ -79,24 +71,14 @@ class CountController { } ``` -In the example above, the `uCount` state is declared lazily using the `late` keyword. To bind the state to the `CountController` instance, the [`Rt.lazyState`](/reactter/methods/state_management_methods/#rtlazystate) method is used, passing the state creation function and the dependency instance as arguments. This ensures that when `uCount` is accessed, it will be automatically bound to the `CountController` instance, e.g.: - -```dart title="main.dart" "Rt.create" "Rt.delete" -import "./count_controller.dart"; - -void main() { - final controller = Rt.create(() => CountController(10)); - controller.uCount.value += 2; // Count: 12 - Rt.delete(); - controller.uCount.value += 3; // Error: "Can't update when it's been disposed" -} -``` - -In the example above, the `uCount` state is accessed after creating the `CountController` instance, demonstrating that the state is bound to the dependency and can be manipulated directly from the instance. When the `CountController` instance is deleted, the state is automatically disposed of, ensuring that resources are released efficiently. +In the example above, the `uCount` state is declared lazily using the `late` keyword. +To bind the state to the `CountController` instance, the [`Rt.lazyState`](/reactter/api/methods/state_management_methods/#rtlazystate) method is used, passing the state creation function and the dependency instance as arguments. +This ensures that when `uCount` is accessed, it will be automatically bound to the `CountController` instance. ### Manual binding -While automatic binding simplifies state management, there may be scenarios where you need to manually bind the state to a dependency. Manual binding provides greater control over how and when the state is associated with the dependency. +While automatic binding simplifies state management, there may be scenarios where you need to manually bind the state to a dependency. +Manual binding provides greater control over how and when the state is associated with the dependency. To manually bind a state to a dependency, you need to explicitly link the state within the dependency using the `bind` method of the state, e.g.: @@ -112,7 +94,22 @@ class CountController { } ``` -In the example above, the `uCount` state is declared lazily using the `late` keyword. To manually bind the state to the `CountController` instance, the `bind` method is called within the constructor, passing the dependency instance as an argument. This ensures that the `uCount` state is associated with the `CountController` instance, e.g.: +In the example above, the `uCount` state is declared lazily using the `late` keyword. +To manually bind the state to the `CountController` instance, the `bind` method is called within the constructor, passing the dependency instance as an argument. +This ensures that the `uCount` state is associated with the `CountController` instance, e.g.: + +:::note +[Manual binding](#manual-binding) and [lazy state binding](#lazy-state-binding) serve different purposes. +**Manual binding** is useful when you need precise control over the state binding process, while **lazy binding** is suitable when you want the state to be initialized only when it is first accessed. +Choose the appropriate approach based on your specific requirements. +::: + +### Automatic unbinding + +When a dependency is deleted, the associated state is automatically disposed of. +This automatic unbinding mechanism simplifies state management and reduces the risk of memory leaks or resource wastage. + +In the example below, the `uCount` state is automatically disposed of when the `CountController` instance is deleted, ensuring that resources are released efficiently: ```dart title="main.dart" "Rt.create" "Rt.delete" import "./count_controller.dart"; @@ -125,9 +122,35 @@ void main() { } ``` -In the example above, the `uCount` state is accessed after creating the `CountController` instance, demonstrating that the state is bound to the dependency and can be manipulated directly from the instance. When the `CountController` instance is deleted, the state is automatically disposed of, ensuring that resources are released efficiently. +### Manual unbinding -:::note -[Manual binding](#manual-binding) and [lazy state binding](#lazy-state-binding) serve different purposes. **Manual binding** is useful when you need precise control over the state binding process, while **Lazy state binding** is suitable when you want the state to be initialized only when it is first accessed. Choose the appropriate approach based on your specific requirements. +In some cases, you may need to manually unbind a state from a dependency. +Manual unbinding provides greater control over when the state is released and can be useful in scenarios where you want to detach the state temporarily or permanently. + +To manually unbind a state from a dependency, you can use the `unbind` method of the state, e.g.: + +```dart title="count_controller.dart" "UseState" "unbind" +class CountController { + late final uCount = UseState(this.initialCount); + + final int initialCount; + + CountController([this.initialCount = 0]) { + count.bind(this); + } + + void dispose() { + count.unbind(this); + } +} +``` + +:::caution +While manual unbinding provides greater control, improper use can lead to memory management issues and increase the risk of errors. +It is recommended to use [automatic unbinding](#automatic-unbinding) whenever possible, as it simplifies the process and reduces the likelihood of introducing memory leaks or failing to release resources properly. + +Manual unbinding should be used cautiously and only when absolutely necessary. +Additionally, developers must ensure that they keep track of the unbinding process to avoid leaving unused states in memory, which could negatively affect performance and resource utilization. ::: + diff --git a/website/src/content/docs/extra_topics/updating_objects_in_state.mdx b/website/src/content/docs/extra_topics/updating_objects_in_state.mdx index b8633e97..1a0f2201 100644 --- a/website/src/content/docs/extra_topics/updating_objects_in_state.mdx +++ b/website/src/content/docs/extra_topics/updating_objects_in_state.mdx @@ -4,10 +4,10 @@ description: Updating objects in state --- import { HM, HN, HS, HT } from '@/components/Highlight'; -States(`RtState`) like `Signal`, `UseState`, `UseAsyncState`, `UseReducer` and `UseCompute` can hold any type of Dart value, including objects. -However, you should not modify objects held in the Reactter state directly, -when you need to update an object, create a new one or make a copy of the existing object, -and then set the state to use this new or copied object. +A Reactter state(`RtState`) such as `Signal`, `UseState`, `UseAsyncState`, `UseReducer` and `UseCompute` can hold any type of Dart value, including objects. +However, you should never modify objects held in a Reactter state directly. + +In this guide, you'll learn how to safely and effectively update objects in a Reactter state. ## What is a mutation? @@ -41,7 +41,7 @@ class User { final user = Signal(User(name: "Jane Doe", age: 25)); ``` -Technically, it is possible to change the contents of the object itself. This is called a mutation: +Technically, it is possible to change the contents of the object itself. This is called a **mutation**: ```dart showLineNumbers=false user.value.name = "John Doe"; @@ -54,7 +54,7 @@ Instead of mutating them, you should always replace them. In other words, you should treat any Dart object that you put into state as read-only. -This example holds an object in state to represent the user. +The following example holds an object in state to represent the user. The user's name is changed from `"Jane Doe"` to `"John Doe"` when you click the button. But the user's name stays the same. @@ -135,7 +135,7 @@ user.value = User(name: user.value.name, age: 30); This is a common pattern when working with objects in state. However, creating a new object every time you want to change a property can be cumbersome. -To simplify this process, you can add the following method to the `User` class: +To simplify this process, you can add the following method(`copyWith`) to the `User` class: ```dart collapse={2-6} ins="final" ins={7-13} mark={2-3} mark="copyWith" class User { diff --git a/website/src/content/docs/getting_started.mdx b/website/src/content/docs/getting_started.mdx index 4cbdf33d..1ebada3c 100644 --- a/website/src/content/docs/getting_started.mdx +++ b/website/src/content/docs/getting_started.mdx @@ -40,8 +40,8 @@ Before anything, you need to be aware that Reactter is distributed on two packag ## Installation -To follow this guide, you’ll need an existing Dart/Flutter project. -If you don't get one yet, head to here. +To follow this guide, you'll need an existing Dart/Flutter project. +If you don't get one yet, you can create a Dart app here or a Flutter app here. Once you have your project set up, proceed to install the package using one of the following methods: @@ -95,7 +95,7 @@ dependencies: -After adding the package to your `pubspec.yaml` file, run the following command to install the package: +And run the following command to install the package: @@ -137,15 +137,7 @@ import 'package:flutter_reactter/flutter_reactter.dart'; -That's it! You are now ready to use Reactter in your Dart/Flutter project. - -{/* -:::note -Reactter classes are generally prefixed with `Reactter` to avoid conflicts with other packages, so you can easily identify them. -This may seem verbose and redundant, but you can rename them to shorter names or other aliases if you prefer. -Check the [Renaming clases](/reactter/extra_topics/renaming_classes) section for more information. -::: -*/} +You're done! You don't have to worry about configuration, Reactter is ready to use in your project.
@@ -155,10 +147,10 @@ If you are new to Reactter, you can start by reading the _**Core Concepts**_ sec This will help you understand the basic concepts of Reactter and how to use them in your project. In this section, the topics you will learn about are: -- [State Management](/reactter/core_concepts/state_management) -- [Dependency Injection](/reactter/core_concepts/dependency_injection) -- [Event Handler](/reactter/core_concepts/event_handler) -- [Rendering Control](/reactter/core_concepts/rendering_control) +- [State management](/reactter/core_concepts/state_management) +- [Dependency injection](/reactter/core_concepts/dependency_injection) +- [Event handler](/reactter/core_concepts/event_handler) +- [Rendering control](/reactter/core_concepts/rendering_control) - [Lifecycle](/reactter/core_concepts/lifecycle) - [Hooks](/reactter/core_concepts/hooks) diff --git a/website/src/content/docs/hooks/use_effect.mdx b/website/src/content/docs/hooks/use_effect.mdx deleted file mode 100644 index f467fc2c..00000000 --- a/website/src/content/docs/hooks/use_effect.mdx +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: UseEffect -description: UseEffect hook documentation. -sidebar: - order: 5 ---- - -import { HE, HN, HM, HT } from '@/components/Highlight'; -import StateMethodsLite from '@/content/docs/shareds/state_methods_lite.mdx'; -import * as ListeningChangesState from '@/content/docs/shareds/listening_changes_state.mdx'; - -`UseEffect` is a [RtHook](https://pub.dev/documentation/reactter/latest/reactter/RtHook-class.html) that allows to manage side-effect, and its functionality is closely related to [Lifecycle](/reactter/core_concepts/lifecycle) of the _**instance**_ and its dependencies. - -:::note -The _**instance**_(or dependency) is the instance of the class where the hook is used. -In this case, we will refer to the _**instance**_ as the _**dependency**_ which is managed by [dependency injection](/reactter/core_concepts/dependency_injection). -::: - -## Syntax - -```dart showLineNumbers=false -UseEffect( - void | Function callback(), - List dependencies, -); - -// UseEffect run on init -UseEffect.runOnInit( - void | Function callback(), - List dependencies, -); -``` - -`UseEffect` accepts these arguments: - -- `callback`: A function that performs side effects. This function is executed when the `dependencies` argument changes or the _**instance**_ trigger `Lifecycle.didMount` event. - If the `callback` returns a `Function`(considers as an _**effect cleanup**_), it will be called before the next effect is run or the _**instance**_ trigger `Lifecycle.willUnmount` event. - :::tip - The _**effect cleanup**_ is useful for cleaning up any resources or subscriptions that the effect may have created. - ::: - - :::note - The `callback` and _**effect cleanup**_ function may be called by [`Lifecycle`](/reactter/core_concepts/lifecycle) events, such as `Lifecycle.didMount` and `Lifecycle.willUnmount`. However, they work only if the instance is provided to the widget tree using the API of _**flutter_reactter**_ package. - ::: -- `dependencies`: A list of state(`RtState`, learn about it [here](/reactter/core_concepts/state_management/#state)) that the effect depends on. - -## Properties & Methods - -`UseEffect` provides the following properties and methods: - - - -## Usage - -### Declaration - -`UseEffect` can be initialized using the constructor class, e.g.: - -```dart collapse={1-6, 19-21} "UseEffect" "timer" /(uCount)(?!\u0060)/ -class MyCounter { - final uCount = UseState(0); - - const MyCounter() { - final timer = Timer.periodic(Duration(seconds: 1), (_) => uCount.value++); - - UseEffect(() { - // Execute by `uCount` state changed or 'Lifecycle.didMount' event - print("Count: ${uCount.value}"); - - return () { - // Effect cleanup - Execute before `uCount` state changed or 'Lifecycle.willUnmount' event - if (uCount.value == 10 && timer.isActive) { - timer.cancel(); - print("Counter stopped"); - } - }; - }, [uCount]); - } -} -``` - -In the example above, the `UseEffect` hook is used to print the `value` of the `uCount` state every second and stop the timer when the value reaches `10`. - -### Running it inmediately - -Sometime you may want to execute inmediately the `UseEffect` is initialized, you can use `UseEffect.runOnInit` to do that, e.g.: - -```dart collapse={1-3, 14-14} "UseEffect.runOnInit" /(uCount)(?!\u0060)/ -final uCount = UseState(0); - -void main() { - UseEffect.runOnInit(() { - // Execute by `uCount` state changed or 'Lifecycle.didMount' event - print("Count: ${uCount.value}"); - Future.delayed(const Duration(seconds: 1), () => uCount.value++); - - return () { - // Effect cleanup - Execute before `uCount` state changed or 'Lifecycle.willUnmount' event - print("Cleanup executed"); - }; - }, [uCount]); -} -``` - -In the example above, the `UseEffect.runOnInit` hook is used to print the `value` of the `uCount` state inmediately and increment the value every second. - -:::tip -The `UseEffect.runOnInit` is useful when you want to execute the `callback` function inmediately without waiting for `Lifecycle.didMount` event of _**instance**_. -::: \ No newline at end of file diff --git a/website/src/content/docs/index.mdx b/website/src/content/docs/index.mdx index 4c8ab979..6db1a6e9 100644 --- a/website/src/content/docs/index.mdx +++ b/website/src/content/docs/index.mdx @@ -14,7 +14,7 @@ hero:

State Management, Dependency Injection and - Event Handler for + Event Handler package for Dart/Flutter.

image: @@ -61,7 +61,7 @@ import Card from "@/components/Card.astro"; relying on any external dependencies. - Share states and logic across your app using [Dependency + Share states and logic across your app via [Dependency Injection](/reactter/core_concepts/dependency_injection) and [Custom Hooks](/reactter/core_concepts/hooks/#custom-hook). diff --git a/website/src/content/docs/migration/v7.3.0_to_v8.0.0.mdx b/website/src/content/docs/migration/v7.3.0_to_v8.0.0.mdx new file mode 100644 index 00000000..a17a8517 --- /dev/null +++ b/website/src/content/docs/migration/v7.3.0_to_v8.0.0.mdx @@ -0,0 +1,256 @@ +--- +title: v7.3.0 to v8.0.0 +description: Migration Guide from v7.3.0 to v8.0.0 +--- +import { HE, HM, HT } from '@/components/Highlight'; + +This guide will help you migrate your project from Reactter **v7.3.0** to **v8.0.0**. +The new version introduces several **enhancements**, **breaking changes**, and **fixes** to improve stability, performance, and developer experience. +Below, we'll walk you through the necessary steps to update your codebase. + +By following this guide, you should be able to successfully migrate your project to Reactter v8.0.0. +If you encounter any issues, refer to the [official documentation](https://pub.dev/documentation/reactter/8.0.0) or report them as an [issue on Github](https://github.com/2devs-team/reactter/issues). + +## 1. Overview of Changes + +### Enhancements + +- Add Reactter devtools extension. +- Add [`Rt.addObserver`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtInterface/addObserver.html) and [`Rt.removeObserver`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtInterface/removeObserver.html) methods to manage the observers. +- Add [`RtStateObserver`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtStateObserver-class.html) class to monitor the lifecycle of state(`onStateCreated`, `onStateBound`, `onStateUnbound`, `onStateUpdated` and `onStateDisposed`). +- Add [`RtDependencyObserver`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtDependencyObserver-class.html) class to monitor the lifecycle of dependency(`onDependencyRegistered`, `onDependencyCreated`, `onDependencyMounted`, `onDependencyUnmounted`, `onDependencyDeleted`, `onDependencyUnregistered` and `onDependencyFailed`). +- Add [`Rt.initializeLogger`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtLoggerExt/initializeLogger.html) to initialize the Reactter logger. +- Add [`Rt.initializeDevTools`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtDevToolsExt/initializeDevTools.html) to initialize the devtools for observing the states and dependencies. +- Add [`debugLabel`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtState/debugLabel.html) and [`debugInfo`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtState/debugInfo.html) to states and hooks to get info for debugging. +- Add [`RtContextMixin`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtContextMixin-mixin.html) mixin to provide access to the `Rt` instance. +- Add [`RtState`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtState-class.html) abstract class to implement the base logic of the state. +- Add [`Rt.registerState`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtInterface/registerState.html) method to create a new state. +- Add [`Rt.getRefAt`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtInterface/getRefAt.html) to get the reference of dependency created. +- Add [`initHook`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtHook/initHook.html) method to `RtHook` to call when hook is created. +- Add [`cancel`](https://pub.dev/documentation/reactter/8.0.0/reactter/UseAsyncState/cancel.html) method to `UseAsyncState` to cancel current method. +- Update [`batch`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtInterface/batch.html) and [`untracked`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtInterface/untracked.html) methods to support asynchronous callbacks. +- Enhance event handling and notifier logic for improved stability and performance. +- Enhance state management and error handling. +- Add dependency `mode` to [`RtComponent`](https://pub.dev/documentation/flutter_reactter/8.0.0/flutter_reactter/RtComponent-class.html). +- Enhance dependency management to ensure to re-build when is detected changes while building. + +### Breakings + +- Remove [`Reactter`](https://pub.dev/documentation/reactter/7.3.0/reactter/Reactter.html), use [`Rt`](https://pub.dev/documentation/reactter/8.0.0/reactter/Rt.html) instead. +- Remove [`ReactterStateImpl`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterStateImpl.html) and [`RtStateImpl`](https://pub.dev/documentation/reactter/7.3.0/reactter/RtStateImpl-class.html), use [`RtState`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtState-class.html) instead. +- Replace [`UseAsyncStateStatus.standby`](https://pub.dev/documentation/reactter/7.3.0/reactter/UseAsyncStateStatus.html) to [`UseAsyncStateStatus.idle`](https://pub.dev/documentation/reactter/8.0.0/reactter/UseAsyncStateStatus.html). +- Rename [`DispatchEffect`](https://pub.dev/documentation/reactter/7.3.0/reactter/DispatchEffect-class.html) to [`AutoDispatchEffect`](https://pub.dev/documentation/reactter/8.0.0/reactter/DispatchEffect-class.html). +- Move `asyncFunction` to the first parameter of [`UseAsyncState`](https://pub.dev/documentation/reactter/8.0.0/reactter/UseAsyncState-class.html) and [`UseAsyncState.withArg`](https://pub.dev/documentation/reactter/8.0.0/reactter/UseAsyncState/withArg.html). +- Remove [`Rt.isRegistered`](https://pub.dev/documentation/reactter/7.3.0/reactter/RtInterface/isRegistered.html), use [`Rt.isActive`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtInterface/isActive.html) instead. +- Remove [`Rt.getInstanceManageMode`](https://pub.dev/documentation/reactter/7.3.0/reactter/RtInterface/getInstanceManageMode.html), use [`Rt.getDependencyMode`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtInterface/getDependencyMode.html) instead. +- Remove [`InstanceManageMode`](https://pub.dev/documentation/reactter/7.3.0/reactter/InstanceManageMode.html), use [`DependencyMode`](https://pub.dev/documentation/reactter/8.0.0/reactter/DependencyMode.html) instead. +- Remove [`Lifecycle.initialized`](https://pub.dev/documentation/reactter/7.3.0/reactter/Lifecycle.html), use [`Lifecycle.created`](https://pub.dev/documentation/reactter/8.0.0/reactter/Lifecycle.html) instead. +- Remove [`Lifecycle.destroyed`](https://pub.dev/documentation/reactter/7.3.0/reactter/Lifecycle.html), use [`Lifecycle.deleted`](https://pub.dev/documentation/reactter/8.0.0/reactter/Lifecycle.html) instead. +- Replace [`LifecycleObserver`](https://pub.dev/documentation/reactter/7.3.0/reactter/LifecycleObserver-class.html) to [`RtDependencyLifecycle`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtDependencyLifecycle-class.html). +- Remove [`LifecycleObserver.onInitialized`](https://pub.dev/documentation/reactter/7.3.0/reactter/LifecycleObserver/onInitialized.html), use [`RtDependencyLifecycle.onCreated`](https://pub.dev/documentation/reactter/8.0.0//reactter/RtDependencyLifecycle/onCreated.html) instead. +- Remove [`ReactterInstance`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterInstance.html) and [`ReactterDependency`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterDependency.html), use [`RtDependencyRef`](https://pub.dev/documentation/reactter/8.0.0//reactter/RtDependencyRef-class.html) instead. +- Remove [`ReactterHook`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterHook.html), use [`RtHook`](https://pub.dev/documentation/reactter/8.0.0//reactter/RtHook-class.html) instead. +- Remove [`ReactterInterface`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterInterface.html), use [`RtInterface`](https://pub.dev/documentation/reactter/8.0.0//reactter/RtInterface-class.html) instead. +- Remove [`RtState.refresh`](https://pub.dev/documentation/reactter/7.3.0/reactter/RtState/refresh.html), use [`RtState.notify`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtState/notify.html) instead. +- Remove [`UseInstance`](https://pub.dev/documentation/reactter/7.3.0/reactter/UseInstance-class.html), use [`UseDependency`](https://pub.dev/documentation/reactter/8.0.0/reactter/UseDependency-class.html) instead. +- Remove [`ReactterAction`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterAction.html), use [`RtAction`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtAction-class.html) instead. +- Remove [`ReactterActionCallable`](https://pub.dev/documentation/reactter/7.3.0/reactter/ReactterActionCallable.html), use [`RtActionCallable`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtActionCallable-class.html) instead. +- Remove [`MemoInterceptors`](https://pub.dev/documentation/reactter/7.3.0/reactter/MemoInterceptors.html), use [`MultiMemoInterceptor`](https://pub.dev/documentation/reactter/8.0.0/reactter/MultiMemoInterceptor-class.html) instead. +- Remove [`AsyncMemoSafe`](https://pub.dev/documentation/reactter/7.3.0/reactter/AsyncMemoSafe.html), use [`MemoSafeAsyncInterceptor`](https://pub.dev/documentation/reactter/8.0.0/reactter/MemoSafeAsyncInterceptor-class.html) instead. +- Remove [`TemporaryCacheMemo`](https://pub.dev/documentation/reactter/7.3.0/reactter/TemporaryCacheMemo.html), use [`MemoTemporaryCacheInterceptor`](https://pub.dev/documentation/reactter/8.0.0/reactter/MemoTemporaryCacheInterceptor-class.html) instead. +- Remove [`MemoInterceptorWrapper`](https://pub.dev/documentation/reactter/7.3.0/reactter/MemoInterceptorWrapper.html), use [`MemoWrapperInterceptor`](https://pub.dev/documentation/reactter/8.0.0/reactter/MemoWrapperInterceptor-class.html) instead. +- Remove [`Obj`](https://pub.dev/documentation/reactter/7.3.0/reactter/Obj-class.html). +- Remove [`init`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/RtProvider/init.html) property from `RtProvider`, use [`RtProvider.init`](https://pub.dev/documentation/flutter_reactter/8.0.0/flutter_reactter/RtProvider/RtProvider.init.html) instead. +- Remove [`ReactterComponent`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/ReactterComponent-class.html), use [`RtComponent`](https://pub.dev/documentation/flutter_reactter/8.0.0/flutter_reactter/RtComponent-class.html) instead. +- Remove [`ReactterConsumer`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/ReactterConsumer-class.html), use [`RtConsumer`](https://pub.dev/documentation/flutter_reactter/8.0.0/flutter_reactter/RtConsumer-class.html) instead. +- Remove [`ReactterProvider`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/ReactterProvider-class.html), use [`RtProvider`](https://pub.dev/documentation/flutter_reactter/8.0.0/flutter_reactter/RtProvider-class.html) instead. +- Remove [`ReactterProviders`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/ReactterProviders-class.html), use [`RtMultiProvider`](https://pub.dev/documentation/flutter_reactter/8.0.0/flutter_reactter/RtMultiProvider-class.html) instead. +- Remove [`ReactterScope`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/ReactterScope-class.html), use [`RtScope`](https://pub.dev/documentation/flutter_reactter/8.0.0/flutter_reactter/RtScope-class.html) instead. +- Remove [`ReactterWatcher`](https://pub.dev/documentation/flutter_reactter/7.3.1/flutter_reactter/ReactterWatcher-class.html), use [`RtSignalWatcher`](https://pub.dev/documentation/flutter_reactter/8.0.0/flutter_reactter/RtSignalWatcher-class.html) instead. +- Remove [`ReactterDependencyNotFoundException`](https://pub.dev/documentation/reactter/7.3.0/flutter_reactter/ReactterDependencyNotFoundException-class.html), use [`RtDependencyNotFoundException`](https://pub.dev/documentation/reactter/8.0.0/flutter_reactter/RtDependencyNotFoundException-class.html) instead. +- Remove [`ReactterScopeNotFoundException`](https://pub.dev/documentation/reactter/7.3.0/flutter_reactter/ReactterScopeNotFoundException-class.html), use [`RtScopeNotFoundException`](https://pub.dev/documentation/reactter/8.0.0/flutter_reactter/RtScopeNotFoundException-class.html) instead. + +### Fixes + +- Fix bug in [`UseEffect`](https://pub.dev/documentation/reactter/8.0.0/reactter/UseEffect-class.html) causing dependencies to not be unwatched. +- Fix to show the correct dependency mode in the logger of the [`Rt.register`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtInterface/register.html) method. +- Fix nested [`Rt.batch`](https://pub.dev/documentation/reactter/8.0.0/reactter/RtInterface/batch.html) method to work correctly. +- Add error handling listeners and continue to next listeners in [`Notifier`](https://pub.dev/documentation/reactter/8.0.0/reactter/Notifier-class.html) class. +- Fix to propagate state param to `boundInstance`. +- Remove listeners correctly from single-use listeners and handle null cases. + +## 2. Implement the changes + +- Replace all occurrences of `Reactter` with `Rt`. + + ```dart showLineNumbers=false del={1,3} del="Reactter" ins={2,4} ins="Rt" + Reactter.create(...); + Rt.create(...); + ReactterProvider(...); + RtProvider(...); + ``` + +- If you're using `ReactterStateImpl` or `RtStateImpl`, migrate to `RtState`. + + ```dart showLineNumbers=false del={1} del="ReactterStateImpl" ins={2} ins="RtState" + class MyState extends ReactterStateImpl { + class MyState extends RtState { + // Implementation + } + ``` + +- Replace `UseAsyncStateStatus.standby` with `UseAsyncStateStatus.idle`. + + ```dart showLineNumbers=false del={1} del="standby" ins={2} ins="idle" + UseAsyncStateStatus.standby; + UseAsyncStateStatus.idle; + ``` + +- Replace `DispatchEffect` with `AutoDispatchEffect`. + + ```dart showLineNumbers=false del={1} del="DispatchEffect" ins={2} ins="AutoDispatchEffect" + class MyClass with DispatchEffect { + class MyClass with AutoDispatchEffect { + // Implementation + } + ``` + +- Move `asyncFunction` to the first parameter of `UseAsyncState` and `UseAsyncState.withArg`. + + ```dart showLineNumbers=false del={1,3} del="initialValue, asyncFunction" ins={2,4} ins="asyncFunction, initialValue" + final uAsyncState = UseAsyncState(initialValue, asyncFunction); + final uAsyncState = UseAsyncState(asyncFunction, initialValue); + final uAsyncStateWithArg = UseAsyncState.withArg(initialValue, asyncFunction); + final uAsyncStateWithArg = UseAsyncState.withArg(asyncFunction, initialValue); + ``` + +- Replace `Rt.isRegistered` with `Rt.isActive`. + + ```dart showLineNumbers=false del={1} del="isRegistered" ins={2} ins="isActive" + Rt.isRegistered(instance); + Rt.isActive(instance); + ``` + +- Replace `Rt.getInstanceManageMode` with `Rt.getDependencyMode`. + + ```dart showLineNumbers=false del={1} del="getInstanceManageMode" ins={2} ins="getDependencyMode" + Rt.getInstanceManageMode(instance); + Rt.getDependencyMode(instance); + ``` + +- Replace `RtState.refresh` with `RtState.notify`. + + ```dart showLineNumbers=false del={1} del="refresh" ins={2} ins="notify" + state.refresh(); + state.notify(); + ``` + +- Replace `UseInstance` with `UseDependency`. + + ```dart showLineNumbers=false del={1} del="UseInstance" ins={2} ins="UseDependency" + final uDependency = UseInstance(); + final uDependency = UseDependency(); + ``` + +- Replace deprecated `Lifecycle` enum values with their new counterparts. + + ```dart showLineNumbers=false del={1,3,5} del="initialized" del="destroyed" del="onInitialized" ins={2,4,6} ins="created" ins="deleted" ins="onCreated" + Lifecycle.initialized; + Lifecycle.created; + Lifecycle.destroyed; + Lifecycle.deleted; + ``` +- Replace `LifecycleObserver` with `RtDependencyLifecycle`. + + ```dart showLineNumbers=false del={1} del="LifecycleObserver" ins={2} ins="RtDependencyLifecycle" + class MyClass extends LifecycleObserver { + class MyClass extends RtDependencyLifecycle { + // Implementation + } + ``` + +- Replace `LifecycleObserver.onInitialized` with `RtDependencyLifecycle.onCreated`. + + ```dart showLineNumbers=false del={1,2} del="LifecycleObserver" del="onInitialized" ins={3,4} ins="RtDependencyLifecycle" ins="onCreated" + class MyClass extends LifecycleObserver { + void onInitialized() { + class MyClass extends RtDependencyLifecycle { + void onCreated() { + // Implementation + } + } + ``` + +- Replace `MemoInterceptors` with `MultiMemoInterceptor`. + + ```dart showLineNumbers=false del={3} del="MemoInterceptors" ins={4} ins="MultiMemoInterceptor" + final memo = Memo( + computeValue, + MemoInterceptors([ + MultiMemoInterceptor([ + // Interceptors + ]), + ); + ``` + +- Replace `AsyncMemoSafe` with `MemoSafeAsyncInterceptor`. + + ```dart showLineNumbers=false del={3} del="AsyncMemoSafe" ins={4} ins="MemoSafeAsyncInterceptor" + final memo = Memo( + computeValue, + AsyncMemoSafe(), + MemoSafeAsyncInterceptor(), + ); + ``` + +- Replace `TemporaryCacheMemo` with `MemoTemporaryCacheInterceptor`. + + ```dart showLineNumbers=false del={3} del="TemporaryCacheMemo" ins={4} ins="MemoTemporaryCacheInterceptor" + final memo = Memo( + computeValue, + TemporaryCacheMemo(...), + MemoTemporaryCacheInterceptor(...), + ); + ``` + +- Replace `MemoInterceptorWrapper` with `MemoWrapperInterceptor`. + + ```dart showLineNumbers=false del={3} del="MemoInterceptorWrapper" ins={4} ins="MemoWrapperInterceptor" + final memo = Memo( + computeValue, + MemoInterceptorWrapper(...), + MemoWrapperInterceptor(...), + ); + ``` + +- Replace `init` property with `RtProvider.init` constructor. + + ```dart showLineNumbers=false del={1,2} del="init: true," ins={3} ins="RtProvider.init" + RtProvider( + init: true, + RtProvider.init( + ... + ); + + ``` + +- Replace `ReactterProviders` or `RtProviders` with `RtMultiProvider`. + + ```dart showLineNumbers=false del={1} del="RtProviders" ins={2} ins="RtMultiProvider" + RtProviders(...) + RtMultiProvider(...) + ``` + +- Replace `ReactterWatcher` with `RtSignalWatcher`. + + ```dart showLineNumbers=false del={1} del="ReactterWatcher" ins={2} ins="RtSignalWatcher" + ReactterWatcher(...) + RtSignalWatcher(...) + ``` + + +## **3. Verify Your Code** +After making the above changes, thoroughly test your application to ensure everything works as expected. Pay special attention to: +- State and dependency lifecycles. +- Event handling and notifier logic. +- Debugging and logging outputs. \ No newline at end of file diff --git a/website/src/content/docs/overview.mdx b/website/src/content/docs/overview.mdx index 9dd5448c..99c399fe 100644 --- a/website/src/content/docs/overview.mdx +++ b/website/src/content/docs/overview.mdx @@ -2,54 +2,49 @@ title: Overview description: Know about Reactter, why it was created and its features. --- -Reactter started as an experiment to redesign what a state management solution should be. -The goal was to create a state management solution that was **reactive**, **_simple_**, **_fast_**, and **_powerful_**. The result was Reactter. +import { HT } from '@/components/Highlight'; -Reactter is a dart package that stands out as an **_lightweight_**, **_powerful_**, and _**reactive**_ **state management**, **dependency injection** and **event handler** solution. +State management is the backbone of any Flutter application, but as apps grow in complexity, so do the challenges of maintaining clean, scalable, and efficient code. +While Flutter offers built-in tools like `StatefulWidget`, they often fall short when it comes to managing global state or handling advanced use cases. +This has led to a wide variety of state management packages, each with its own strengths, but also its own trade-offs. -:::note -Reactter is inspired by the ReactJS library, taking some concepts and ideas from it and adapt to Dart and Flutter. -The name **Reactter** is a combination of the words (**_Reac_**)t and Flu(**_tter_**). -::: +**Reactter** was born out of a desire to create something different, a state management solution that doesn't just solve problems but elevates the entire development experience. +Inspired by the simplicity and reactivity of ReactJS, Reactter brings a fresh, modern approach to Dart and Flutter development. +It's not just another state management package, it's a powerful tool designed to make your app faster, your code cleaner, and your life as a developer easier. -## Motivation +What sets Reactter apart? While other packages focus solely on state management, Reactter goes further by seamlessly integrating **dependency injection**, **event handling**, and **rendering control** into a single, cohesive framework. +It's lightweight yet powerful, simple yet flexible, and designed to work seamlessly with any architecture or pattern. +Whether you're building a small project or a large-scale application, Reactter gives you the tools to write less boilerplate, improve readability, and maintain granular control over your app's behavior. -State management is fundamental to apps, because it determines how information is stored, updated and shared within app. -While there are several state management solutions available, often these solutions don't provide a full integration with equally important concepts, such as dependency injection and event handler. - -You might think, _"But we already have packages for it"_ or _"I can develop it myself"_ -While that's true, handling these aspects separately can limit the development experience and result in critical aspects being overlooked: - -- **Scalability**: Without proper integration between these 3 concepts, the app may have difficulty scaling efficiently as it grows in complexity and size. -- **Flexibility**: An integrated architecture becomes easier to adapt and extend the app to meet new requirements or incorporate new features without major overhauls. -- **Maintainability**: Disjointed these 3 concepts lead to a fragmented codebase, which makes it harder to understand, debug, and modify. -- **Performance**: Poor integration between these 3 concepts, the app may become less responsive and inefficient in using system resources. -This can lead to slower execution times, increased memory usage, and a suboptimal user experience. - -Reactter addresses these challenges by offering a comprehensive framework that integrates state management, dependency injection, and event handling. This integration provides a smoother, more consistent development experience and ensures that applications are robust, efficient, and maintainable. +But Reactter isn't just about features, it's about philosophy. +We believe that state management should be intuitive, not intimidating. +That's why Reactter is built with a *developer-first* mindset, offering a syntax that's easy to learn, a workflow that's easy to debug, and a design that's easy to love. +With Reactter, you're not just managing state, you're unlocking the full potential of your applications. ## Features Reactter offers the following features: -- **Fine-grained reactivity** (Signals, Hooks). -- **Lightweight** and **fast** -- **No boilerplate** (no recurring code) -- **Easy to use** -- **Highly reusable** (Custom hooks, Dependency injection) -- **100% Dart**(Compatible with latest version of Dart) -- **Fully testable** (100% coverage) -- **No code generation** -- **0 configuration** -- **0 dependencies** -- **Rendering control** (using `flutter_reactter` package) -- **Not opinionated** (You can use it with any architecture or pattern. Do whatever you want!!) +- ⚡️ Engineered for **speed**. +- 🪶 Super **lightweight**. +- 👓 **Simple syntax**, easy to learn. +- ✂️ **Reduce boilerplate code** significantly. +- 👁️ Improve **code readability**. +- 🚀 **Granular reactivity** using [state](/reactter/core_concepts/state_management/#state) and [hooks](/reactter/core_concepts/hooks). +- 🧩 **Highly reusable** states and logic via [custom hooks](/reactter/core_concepts/hooks/#custom-hook) and [dependency injection](/reactter/core_concepts/dependency_injection/). +- 🎮 Total [**rendering control**](/reactter/core_concepts/rendering_control). +- ✅ **Highly testable** with 100% code coverage. +- 🐞 **Fully debuggable** using the [Reactter DevTools extension](/reactter/devtools_extension). +- 💧 **Not opinionated**. Use it with any architecture or pattern. +- 🪄 **Zero dependencies**, **zero configuration** and **no code generation**. +- 💙 **Compatible with Dart and Flutter**, supporting the latest Dart version. ## Try Reactter online Experience the full potential of Reactter by trying it online on Zapp. + -:::tip[The power is in your hand!!] - [Getting](/reactter/guides/getting_started) Reactter into your projects and unlocking its full potential. +:::tip[Unlock the full potential of Reactter!] + [Get started](/reactter/guides/getting_started) and transform your Dart/Flutter development experience with Reactter. ::: \ No newline at end of file diff --git a/website/src/content/docs/shareds/creating_custom_hook.mdx b/website/src/content/docs/shareds/creating_custom_hook.mdx new file mode 100644 index 00000000..05fbaca8 --- /dev/null +++ b/website/src/content/docs/shareds/creating_custom_hook.mdx @@ -0,0 +1,30 @@ +--- +title: Creating a custom hook +--- + +import { HT, HM } from '@/components/Highlight'; +import { Code } from "@astrojs/starlight/components"; +import { marks } from '@/examples/marks.ts' +import UseTextInputCode from '@/examples/custom_hook/lib/use_text_input.dart?raw'; + +To create a [custom hook](/reactter/core_concepts/hooks/#custom-hook), you need to create a class that extends [`RtHook`](/reactter/api/classes/rt_hook) and follow the naming convention with the `Use` prefix. + +Here's an example of a [custom hook](/reactter/core_concepts/hooks/#custom-hook) that manages the state of a text input: + + + +:::caution[Attention!!] +To create a _**hook**_, you need to register it by adding the following line: + +```dart showLineNumbers=false +final $ = RtHook.$register; +``` + +It's important to note that the states and hooks defined above this line are not linked to the hook. +To avoid this, it's recommended to place this line as the first line in the class body. +::: + +As shown in the example above, we can utilize other hooks such as [`UseEffect`](/reactter/api/hooks/use_effect) to monitor changes in the text input's controller and ensure it is disposed when the hook is destroyed. + +The `update` method is used to set the internal `_value` to the current text in the controller, which keeps the state synchronized with the input. +This methods is a part of the `RtHook` class that allows you to update the state of the hook. diff --git a/website/src/content/docs/shareds/listening_changes_state.mdx b/website/src/content/docs/shareds/listening_changes_state.mdx index ac23e6cc..6059404f 100644 --- a/website/src/content/docs/shareds/listening_changes_state.mdx +++ b/website/src/content/docs/shareds/listening_changes_state.mdx @@ -8,8 +8,8 @@ import { HE, HM, HT } from '@/components/Highlight'; When `value` has changed, the `[[stateName]]` will emit the following events(learn about it [here](/reactter/core_concepts/lifecycle/)): -- `Lifecycle.willUpdate` event is triggered before the `value` change or `update`, `refresh` methods have been invoked. -- `Lifecycle.didUpdate` event is triggered after the `value` change or `update`, `refresh` methods have been invoked. +- `Lifecycle.willUpdate` event is triggered before the change in `value` or `update` method have been invoked. +- `Lifecycle.didUpdate` event is triggered after the change in `value` or after `update` or `notify` methods have been invoked. Example of listening to changes: diff --git a/website/src/content/docs/shareds/state_methods.mdx b/website/src/content/docs/shareds/state_methods.mdx index 0fe8d64a..b86b948a 100644 --- a/website/src/content/docs/shareds/state_methods.mdx +++ b/website/src/content/docs/shareds/state_methods.mdx @@ -3,21 +3,22 @@ title: State Methods --- import { HE, HM, HT } from '@/components/Highlight'; -- `update`: Executes a callback function and notify its listeners that the state has changed. -When it is invoked, it emits two events to signal the state transition: -`Lifecycle.willUpdate` is emitted first, indicating the impending update, followed by `Lifecycle.didUpdate` once the update process is complete. -- `refresh`: Forces the state to notify its listeners that it has changed. +- **`update`**: Executes a callback function and notify its observers that the state has changed. +When it is invoked, it emits two [lifecycle](/reactter/core_concepts/lifecycle) events to signal the state transition: + - `Lifecycle.willUpdate` is emitted first, indicating the impending update. + - `Lifecycle.didUpdate` is emitted once the update process is complete. +- **`notify`**: Forces the state to notify its observers. Unlike `update`, it emits only the `Lifecycle.didUpdate` event, as it doesn't involve any preparatory steps before the notification. -- `bind`: Establishes a connection between the state and a specific instance. +- **`bind`**: Establishes a connection between the state and a specific instance. This connection allows the instance to reactively update based on changes to the state. By binding the state, the instance becomes aware of changes to the state and can appropriately reflect those changes in its behavior. -- `unbind`: Releases the connection between the state and the instance. +- **`unbind`**: Releases the connection between the state and the instance. When unbinding, the instance will no longer receive updates from the state. This can be useful when an instance is no longer actively using the state or when it needs to detach from the state temporarily or permanently. -- `dispose`: Is responsible for cleaning up the state and any associated listeners or resources. +- **`dispose`**: Is responsible for cleaning up the state and any associated observers or resources. Disposing of the state ensures that it is properly released and no longer consumes memory or processing resources unnecessarily. :::note -The `bind`, `unbind` and `dispose` methods are normally used to manage the [Lifecycle](/reactter/core_concepts/lifecycle) of a state. -However, Reactter handles this automatically, when the state is declared inside an instance that exists within Reactter's context via the [dependecy injection](/reactter/core_concepts/dependency_injection/), so you don't need to worry about it(Learn more about [Binding State to Dependency](/reactter/extra_topics/binding_state_to_dependency/)). +While the `bind`, `unbind` and `dispose` methods are normally used to manage the [lifecycle](/reactter/core_concepts/lifecycle) of a state, +Reactter handles this automatically, when the state is declared inside an instance that exists within Reactter's context via the [dependecy injection](/reactter/core_concepts/dependency_injection/), so you don't need to worry about it(Learn more about [Binding State to Dependency](/reactter/extra_topics/binding_state_to_dependency/)). ::: \ No newline at end of file diff --git a/website/src/content/docs/shareds/state_methods_lite.mdx b/website/src/content/docs/shareds/state_methods_lite.mdx deleted file mode 100644 index 3da103ef..00000000 --- a/website/src/content/docs/shareds/state_methods_lite.mdx +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: State Methods ---- -import { HM, HT } from '@/components/Highlight'; - -- Methods inherited from `RtState`(Learn more [here](/reactter/core_concepts/state_management/#state-methods)): - - `update`: A method to notify changes after run a set of instructions. - - `refresh`: A method to force to notify changes. - - *`bind`: A method to bind an instance to it. - - *`unbind`: A method to unbind an instance to it. - - *`dispose`: A method to remove all listeners and free resources. - - :::note - \* These methods are unnecessary when is declared within a dependency(class registered via the [dependecy injection](/reactter/core_concepts/dependency_injection)). - ::: \ No newline at end of file diff --git a/website/src/content/docs/shareds/state_properties_methods.mdx b/website/src/content/docs/shareds/state_properties_methods.mdx new file mode 100644 index 00000000..43a4d11d --- /dev/null +++ b/website/src/content/docs/shareds/state_properties_methods.mdx @@ -0,0 +1,10 @@ +--- +title: Properties & Methods of RtState +--- + +import { HE, HM, HN, HT } from '@/components/Highlight'; +import StateMethods from '@/content/docs/shareds/state_methods.mdx'; + +- **`debugLabel`**: A string that represents the label of the state object for debugging purposes. +- **`debugInfo`**: A map that contains debug information about the state object. + diff --git a/website/src/content/docs/shareds/state_properties_methods_ref.mdx b/website/src/content/docs/shareds/state_properties_methods_ref.mdx new file mode 100644 index 00000000..52b33c5b --- /dev/null +++ b/website/src/content/docs/shareds/state_properties_methods_ref.mdx @@ -0,0 +1,11 @@ +--- +title: Properties & Methods of RtState +--- + +import { HT } from '@/components/Highlight'; +import StatePropertiesMethods from '@/content/docs/shareds/state_properties_methods.mdx'; + +
+ Properties and methods inherited from [`RtState`](/reactter/api/classes/rt_state) + +
\ No newline at end of file diff --git a/website/src/content/docs/shareds/tip_dependency_checking.mdx b/website/src/content/docs/shareds/tip_dependency_checking.mdx new file mode 100644 index 00000000..b917216f --- /dev/null +++ b/website/src/content/docs/shareds/tip_dependency_checking.mdx @@ -0,0 +1,29 @@ +--- +title: Tip about the UseDependency hook +--- +import { HN, HT } from '@/components/Highlight'; + +:::tip + +If the dependency is not registered and created, the `instance` property will be `null`. + +To avoid this, you can use the [`UseEffect`](/reactter/api/hooks/use_effect) hook to get the dependency when it is created, e.g.: + +```dart title="my_controller.dart" "UseDependency" "UseEffect" +import 'package:reactter/reactter.dart'; + +class MyController { + final uDependency = UseDependency(); + + const MyController() { + UseEffect.runOnInit(() { + print( + uDependency.instance != null + ? "MyDependency is available" + : "MyDependency is not available yet." + ); + }, [uDependency]); + } +} +``` +::: \ No newline at end of file diff --git a/website/src/content/docs/shareds/tip_lifecycle_example.mdx b/website/src/content/docs/shareds/tip_lifecycle_example.mdx index 96c25955..725592df 100644 --- a/website/src/content/docs/shareds/tip_lifecycle_example.mdx +++ b/website/src/content/docs/shareds/tip_lifecycle_example.mdx @@ -3,6 +3,6 @@ title: Tip about the lifecycle example --- :::tip - This example is taken from [Rendering Control](http://localhost:4321/reactter/core_concepts/rendering_control/#example) page. + This example is taken from [rendering control](http://localhost:4321/reactter/core_concepts/rendering_control/#example) page. It's recommended to read that page first before proceeding. ::: \ No newline at end of file diff --git a/website/src/content/docs/shareds/using_custom_hook.mdx b/website/src/content/docs/shareds/using_custom_hook.mdx new file mode 100644 index 00000000..2a3a2515 --- /dev/null +++ b/website/src/content/docs/shareds/using_custom_hook.mdx @@ -0,0 +1,47 @@ +--- +title: Using the hook +--- + +import { HT, HM } from '@/components/Highlight'; +import CodeTabs from '@/components/CodeTabs.astro'; +import { Tabs, TabItem } from "@/components/Tabs"; +import ZappButton from "@/components/ZappButton.astro"; +import { Code } from "@astrojs/starlight/components"; +import { marks } from '@/examples/marks.ts' + +import MainCode from '@/examples/custom_hook/lib/main.dart?raw'; +import MyCustomFormCode from '@/examples/custom_hook/lib/my_custom_form.dart?raw'; +import MyControllerCode from '@/examples/custom_hook/lib/my_controller.dart?raw'; +import UseTextInputCode from '@/examples/custom_hook/lib/use_text_input.dart?raw'; + +You can then call that [custom hook](/reactter/core_concepts/hooks/#custom-hook) from anywhere in the code and get access to its shared logic: + + + + + + + my_controller.dart + + + + + my_custom_form.dart + + + + + + + + + + + + + +In the previous example, the form captures the input from the name and surname fields, combines them into a full name, and displays the result. +The `MyController` component uses the `UseTextInput` hook (_**custom hook**_ created previously) to manage the name and lastname inputs. + +The `fullName` state is defined using [`UseCompute`](/reactter/api/hooks/use_compute), which calculates the full name based on the values of `firstNameInput` and `lastNameInput`. +This ensures that the full name updates automatically whenever either input changes. \ No newline at end of file diff --git a/website/src/examples/build_context/pubspec.yaml b/website/src/examples/build_context/pubspec.yaml index 647e0d7f..e6bf5209 100644 --- a/website/src/examples/build_context/pubspec.yaml +++ b/website/src/examples/build_context/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/build_context_select/pubspec.yaml b/website/src/examples/build_context_select/pubspec.yaml index c6ae390c..7235c85b 100644 --- a/website/src/examples/build_context_select/pubspec.yaml +++ b/website/src/examples/build_context_select/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/counter/pubspec.yaml b/website/src/examples/counter/pubspec.yaml index b303886b..f1fb8fe3 100644 --- a/website/src/examples/counter/pubspec.yaml +++ b/website/src/examples/counter/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/custom_hook/lib/my_custom_form.dart b/website/src/examples/custom_hook/lib/my_custom_form.dart index 6456d34c..a9e79a33 100644 --- a/website/src/examples/custom_hook/lib/my_custom_form.dart +++ b/website/src/examples/custom_hook/lib/my_custom_form.dart @@ -12,7 +12,7 @@ class MyCustomForm extends StatelessWidget { builder: (context, myController, child) { return Scaffold( appBar: AppBar( - title: const Text("Retrieve Text Input"), + title: const Text("Custom hook example"), ), body: Padding( padding: const EdgeInsets.all(16), diff --git a/website/src/examples/custom_hook/lib/use_text_input.dart b/website/src/examples/custom_hook/lib/use_text_input.dart index 120f090f..e35955c4 100644 --- a/website/src/examples/custom_hook/lib/use_text_input.dart +++ b/website/src/examples/custom_hook/lib/use_text_input.dart @@ -10,7 +10,8 @@ class UseTextInput extends RtHook { String _value = ''; String? get value => _value; - UseTextInput() { + @override + initHook() { UseEffect(() { controller.addListener(() { update(() => _value = controller.text); diff --git a/website/src/examples/custom_hook/pubspec.yaml b/website/src/examples/custom_hook/pubspec.yaml index 0fc26139..dd318b93 100644 --- a/website/src/examples/custom_hook/pubspec.yaml +++ b/website/src/examples/custom_hook/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_builder/pubspec.yaml b/website/src/examples/dependency_injection_builder/pubspec.yaml index 008ebc63..824c7905 100644 --- a/website/src/examples/dependency_injection_builder/pubspec.yaml +++ b/website/src/examples/dependency_injection_builder/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_create/pubspec.yaml b/website/src/examples/dependency_injection_create/pubspec.yaml index 74818dda..1133d717 100644 --- a/website/src/examples/dependency_injection_create/pubspec.yaml +++ b/website/src/examples/dependency_injection_create/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_delete/pubspec.yaml b/website/src/examples/dependency_injection_delete/pubspec.yaml index 4357618b..abb3c046 100644 --- a/website/src/examples/dependency_injection_delete/pubspec.yaml +++ b/website/src/examples/dependency_injection_delete/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_destroy/pubspec.yaml b/website/src/examples/dependency_injection_destroy/pubspec.yaml index 858e2b30..cd28c3e9 100644 --- a/website/src/examples/dependency_injection_destroy/pubspec.yaml +++ b/website/src/examples/dependency_injection_destroy/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_exists/pubspec.yaml b/website/src/examples/dependency_injection_exists/pubspec.yaml index 831d6860..5503268c 100644 --- a/website/src/examples/dependency_injection_exists/pubspec.yaml +++ b/website/src/examples/dependency_injection_exists/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_factory/pubspec.yaml b/website/src/examples/dependency_injection_factory/pubspec.yaml index 2bca9da4..ad4a09c1 100644 --- a/website/src/examples/dependency_injection_factory/pubspec.yaml +++ b/website/src/examples/dependency_injection_factory/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_find/pubspec.yaml b/website/src/examples/dependency_injection_find/pubspec.yaml index 3de884a7..d822a9f2 100644 --- a/website/src/examples/dependency_injection_find/pubspec.yaml +++ b/website/src/examples/dependency_injection_find/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_get/pubspec.yaml b/website/src/examples/dependency_injection_get/pubspec.yaml index 0034bba6..e0257999 100644 --- a/website/src/examples/dependency_injection_get/pubspec.yaml +++ b/website/src/examples/dependency_injection_get/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_get_dependency_mode/pubspec.yaml b/website/src/examples/dependency_injection_get_dependency_mode/pubspec.yaml index 8a044440..dd8c13a6 100644 --- a/website/src/examples/dependency_injection_get_dependency_mode/pubspec.yaml +++ b/website/src/examples/dependency_injection_get_dependency_mode/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_is_active/pubspec.yaml b/website/src/examples/dependency_injection_is_active/pubspec.yaml index bec3fb48..b48e4bc2 100644 --- a/website/src/examples/dependency_injection_is_active/pubspec.yaml +++ b/website/src/examples/dependency_injection_is_active/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_lazy_builder/pubspec.yaml b/website/src/examples/dependency_injection_lazy_builder/pubspec.yaml index cc94bf46..24288010 100644 --- a/website/src/examples/dependency_injection_lazy_builder/pubspec.yaml +++ b/website/src/examples/dependency_injection_lazy_builder/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_lazy_factory/pubspec.yaml b/website/src/examples/dependency_injection_lazy_factory/pubspec.yaml index 9fe67fa1..86ac9089 100644 --- a/website/src/examples/dependency_injection_lazy_factory/pubspec.yaml +++ b/website/src/examples/dependency_injection_lazy_factory/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_lazy_singleton/pubspec.yaml b/website/src/examples/dependency_injection_lazy_singleton/pubspec.yaml index 5fc6a8a2..52917f1c 100644 --- a/website/src/examples/dependency_injection_lazy_singleton/pubspec.yaml +++ b/website/src/examples/dependency_injection_lazy_singleton/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_register/pubspec.yaml b/website/src/examples/dependency_injection_register/pubspec.yaml index 1d3775d2..d222f35d 100644 --- a/website/src/examples/dependency_injection_register/pubspec.yaml +++ b/website/src/examples/dependency_injection_register/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_singleton/pubspec.yaml b/website/src/examples/dependency_injection_singleton/pubspec.yaml index 0ebebac9..5914a0d0 100644 --- a/website/src/examples/dependency_injection_singleton/pubspec.yaml +++ b/website/src/examples/dependency_injection_singleton/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/dependency_injection_unregister/pubspec.yaml b/website/src/examples/dependency_injection_unregister/pubspec.yaml index 391e4649..c213a71d 100644 --- a/website/src/examples/dependency_injection_unregister/pubspec.yaml +++ b/website/src/examples/dependency_injection_unregister/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/event_handler_off/lib/main.dart b/website/src/examples/event_handler_off/lib/main.dart index eadc8c3a..2021d6b2 100644 --- a/website/src/examples/event_handler_off/lib/main.dart +++ b/website/src/examples/event_handler_off/lib/main.dart @@ -21,7 +21,7 @@ void onDidUpdateStateB(instance, Signal state) { void main() { // Listen to the `myEvent` event of the `MyDependency` before it's created. Rt.on( - RtDependency(), + RtDependencyRef(), CustomEvent.myEvent, onMyEvent, ); @@ -61,7 +61,7 @@ void main() { // Stop listening to the `myEvent` event of the `MyDependency` Rt.off( - RtDependency(), + RtDependencyRef(), CustomEvent.myEvent, onMyEvent, ); diff --git a/website/src/examples/event_handler_off/pubspec.yaml b/website/src/examples/event_handler_off/pubspec.yaml index 7bdd0915..84816d07 100644 --- a/website/src/examples/event_handler_off/pubspec.yaml +++ b/website/src/examples/event_handler_off/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/event_handler_off_all/lib/main.dart b/website/src/examples/event_handler_off_all/lib/main.dart index e158afd1..1d82cf40 100644 --- a/website/src/examples/event_handler_off_all/lib/main.dart +++ b/website/src/examples/event_handler_off_all/lib/main.dart @@ -5,7 +5,7 @@ import 'my_dependency.dart'; void main() { // Listen to the `myEvent` event of the `MyDependency` before it's created. Rt.on( - RtDependency(), + RtDependencyRef(), CustomEvent.myEvent, (instance, param) => print('CustomEvent emitted with param: $param'), ); @@ -55,7 +55,7 @@ void main() { Rt.offAll(myDependency.stateA); // Remove all event listeners of `myDependency`, - // including the generic listeners (using `RtDependency`). + // including the generic listeners (using `RtDependencyRef`). Rt.offAll(myDependency, true); // No print for neither `stateA` nor the `MyDependency` instance diff --git a/website/src/examples/event_handler_off_all/pubspec.yaml b/website/src/examples/event_handler_off_all/pubspec.yaml index 20a3d4bb..9b96a98c 100644 --- a/website/src/examples/event_handler_off_all/pubspec.yaml +++ b/website/src/examples/event_handler_off_all/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/event_handler_on_emit/lib/main.dart b/website/src/examples/event_handler_on_emit/lib/main.dart index 9e06e8d4..4a6de1d1 100644 --- a/website/src/examples/event_handler_on_emit/lib/main.dart +++ b/website/src/examples/event_handler_on_emit/lib/main.dart @@ -5,7 +5,7 @@ import 'my_dependency.dart'; void main() { // Listen to the `myEvent` event of the `MyDependency` before it's created. Rt.on( - RtDependency(), + RtDependencyRef(), CustomEvent.myEvent, (instance, param) => print('CustomEvent emitted with param: $param'), ); @@ -62,10 +62,10 @@ void main() { // because it's deleted. Rt.emit(myDependency, CustomEvent.myEvent, 'This is not printed'); - // Can still emit to the `myEvent` event using the `RtDependency` + // Can still emit to the `myEvent` event using the `RtDependencyRef` // Print: // CustomEvent.myEvent emitted with param: 42 - Rt.emit(RtDependency(), CustomEvent.myEvent, 42); + Rt.emit(RtDependencyRef(), CustomEvent.myEvent, 42); runApp(MyApp()); } diff --git a/website/src/examples/event_handler_on_emit/pubspec.yaml b/website/src/examples/event_handler_on_emit/pubspec.yaml index e82982c8..6c7f0936 100644 --- a/website/src/examples/event_handler_on_emit/pubspec.yaml +++ b/website/src/examples/event_handler_on_emit/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/event_handler_one/lib/main.dart b/website/src/examples/event_handler_one/lib/main.dart index 4457fd76..98ee04de 100644 --- a/website/src/examples/event_handler_one/lib/main.dart +++ b/website/src/examples/event_handler_one/lib/main.dart @@ -5,7 +5,7 @@ import 'my_dependency.dart'; void main() { // Listen to the `myEvent` event of the `MyDependency` before it's created. Rt.one( - RtDependency(), + RtDependencyRef(), CustomEvent.myEvent, (instance, param) => print('CustomEvent emitted with param: $param'), ); @@ -61,7 +61,7 @@ void main() { /// Cannot listen to the `myEvent` event using the `MyDependency` instance /// because the listener is one-time. - Rt.emit(RtDependency(), CustomEvent.myEvent, 42); + Rt.emit(RtDependencyRef(), CustomEvent.myEvent, 42); runApp(MyApp()); } diff --git a/website/src/examples/event_handler_one/pubspec.yaml b/website/src/examples/event_handler_one/pubspec.yaml index e9fdb74b..efc1554f 100644 --- a/website/src/examples/event_handler_one/pubspec.yaml +++ b/website/src/examples/event_handler_one/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/lifecycle_event_handler/lib/counter_view.dart b/website/src/examples/lifecycle_event_handler/lib/counter_view.dart index ce794c95..d3918632 100644 --- a/website/src/examples/lifecycle_event_handler/lib/counter_view.dart +++ b/website/src/examples/lifecycle_event_handler/lib/counter_view.dart @@ -9,25 +9,25 @@ class CounterView extends StatelessWidget { @override Widget build(BuildContext context) { Rt.on( - RtDependency(), + RtDependencyRef(), Lifecycle.unregistered, (_, __) => print('CounterController unregistered'), ); Rt.on( - RtDependency(), + RtDependencyRef(), Lifecycle.registered, (_, __) => print('CounterController registered'), ); Rt.on( - RtDependency(), + RtDependencyRef(), Lifecycle.created, (_, __) => print('CounterController created'), ); Rt.on( - RtDependency(), + RtDependencyRef(), Lifecycle.deleted, (_, __) => print('CounterController deleted'), ); diff --git a/website/src/examples/lifecycle_event_handler/pubspec.yaml b/website/src/examples/lifecycle_event_handler/pubspec.yaml index 9c77499e..a6371eef 100644 --- a/website/src/examples/lifecycle_event_handler/pubspec.yaml +++ b/website/src/examples/lifecycle_event_handler/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/lifecycle_use_effect/pubspec.yaml b/website/src/examples/lifecycle_use_effect/pubspec.yaml index b3a31e74..5b96f096 100644 --- a/website/src/examples/lifecycle_use_effect/pubspec.yaml +++ b/website/src/examples/lifecycle_use_effect/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/marks.ts b/website/src/examples/marks.ts index 7af0b91d..786ecc55 100644 --- a/website/src/examples/marks.ts +++ b/website/src/examples/marks.ts @@ -1,4 +1,5 @@ export const marks = [ + "Rt.creteState", "Rt.lazyState", "Rt.batch", "Rt.untracked", @@ -24,8 +25,12 @@ export const marks = [ "Rt.off", "Rt.offAll", "RtHook", + "RtContextMixin", "RtState", - "RtDependency", + "RtStateObserver", + "RtDependencyRef", + "RtDependencyLifecycle", + 'RtDependencyObserver', "RtDependencyMode", "RtDependencyMode.builder", "RtDependencyMode.factory", @@ -49,7 +54,6 @@ export const marks = [ "UseCompute", "UseEffect", "UseDependency", - "LifecycleObserver", "Lifecycle", "Lifecycle.registered", "Lifecycle.created", diff --git a/website/src/examples/rt_component/pubspec.yaml b/website/src/examples/rt_component/pubspec.yaml index 596f383d..fb211f8b 100644 --- a/website/src/examples/rt_component/pubspec.yaml +++ b/website/src/examples/rt_component/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_consumer/pubspec.yaml b/website/src/examples/rt_consumer/pubspec.yaml index 595aa10f..c4380608 100644 --- a/website/src/examples/rt_consumer/pubspec.yaml +++ b/website/src/examples/rt_consumer/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_consumer_child/pubspec.yaml b/website/src/examples/rt_consumer_child/pubspec.yaml index 71758022..16a02ef8 100644 --- a/website/src/examples/rt_consumer_child/pubspec.yaml +++ b/website/src/examples/rt_consumer_child/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_consumer_id/pubspec.yaml b/website/src/examples/rt_consumer_id/pubspec.yaml index 7e3e8cd0..2ae88d4a 100644 --- a/website/src/examples/rt_consumer_id/pubspec.yaml +++ b/website/src/examples/rt_consumer_id/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_consumer_listen_all/pubspec.yaml b/website/src/examples/rt_consumer_listen_all/pubspec.yaml index 12f73c06..475e258a 100644 --- a/website/src/examples/rt_consumer_listen_all/pubspec.yaml +++ b/website/src/examples/rt_consumer_listen_all/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_consumer_listen_all_2/pubspec.yaml b/website/src/examples/rt_consumer_listen_all_2/pubspec.yaml index 980a0c4a..144e04cc 100644 --- a/website/src/examples/rt_consumer_listen_all_2/pubspec.yaml +++ b/website/src/examples/rt_consumer_listen_all_2/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_consumer_listen_states/pubspec.yaml b/website/src/examples/rt_consumer_listen_states/pubspec.yaml index 6665d73c..2edc0448 100644 --- a/website/src/examples/rt_consumer_listen_states/pubspec.yaml +++ b/website/src/examples/rt_consumer_listen_states/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/lifecycle_observer/lib/counter.dart b/website/src/examples/rt_dependency_lifecycle/lib/counter.dart similarity index 100% rename from website/src/examples/lifecycle_observer/lib/counter.dart rename to website/src/examples/rt_dependency_lifecycle/lib/counter.dart diff --git a/website/src/examples/lifecycle_observer/lib/counter_controller.dart b/website/src/examples/rt_dependency_lifecycle/lib/counter_controller.dart similarity index 81% rename from website/src/examples/lifecycle_observer/lib/counter_controller.dart rename to website/src/examples/rt_dependency_lifecycle/lib/counter_controller.dart index 9f6d656b..a45d8b12 100644 --- a/website/src/examples/lifecycle_observer/lib/counter_controller.dart +++ b/website/src/examples/rt_dependency_lifecycle/lib/counter_controller.dart @@ -1,41 +1,48 @@ import 'package:flutter_reactter/flutter_reactter.dart'; -class CounterController extends LifecycleObserver { +class CounterController extends RtDependencyLifecycle { final count = Signal(0); - void onInitialized() { + void increment() { + count.value++; + } + + void decrement() { + count.value--; + } + + @override + void onCreated() { print('CounterController initialized'); } + @override void onDidMount() { print('CounterController mounted'); } + @override void onWillMount() { print('CounterController will mount'); } + @override void onWillUpdate(RtState state) { print('CounterController will update by ${state.runtimeType}'); } + @override void onDidUpdate(RtState state) { print('CounterController did update by ${state.runtimeType}'); } + @override void onWillUnmount() { print('CounterController will unmount'); } + @override void onDidUnmount() { print('CounterController did unmount'); } - - void increment() { - count.value++; - } - - void decrement() { - count.value--; - } } diff --git a/website/src/examples/lifecycle_observer/lib/counter_view.dart b/website/src/examples/rt_dependency_lifecycle/lib/counter_view.dart similarity index 92% rename from website/src/examples/lifecycle_observer/lib/counter_view.dart rename to website/src/examples/rt_dependency_lifecycle/lib/counter_view.dart index af3cdd9b..b0b00c15 100644 --- a/website/src/examples/lifecycle_observer/lib/counter_view.dart +++ b/website/src/examples/rt_dependency_lifecycle/lib/counter_view.dart @@ -11,7 +11,7 @@ class CounterView extends StatelessWidget { return Scaffold( appBar: AppBar( - title: const Text("Counter - Lifecycle using LifecycleObserver"), + title: const Text("Counter - Lifecycle using RtDependencyLifecycle"), ), body: RtSignalWatcher( builder: (context, child) { diff --git a/website/src/examples/lifecycle_observer/lib/main.dart b/website/src/examples/rt_dependency_lifecycle/lib/main.dart similarity index 100% rename from website/src/examples/lifecycle_observer/lib/main.dart rename to website/src/examples/rt_dependency_lifecycle/lib/main.dart diff --git a/website/src/examples/lifecycle_observer/pubspec.yaml b/website/src/examples/rt_dependency_lifecycle/pubspec.yaml similarity index 57% rename from website/src/examples/lifecycle_observer/pubspec.yaml rename to website/src/examples/rt_dependency_lifecycle/pubspec.yaml index 66d70fe3..bec822d0 100644 --- a/website/src/examples/lifecycle_observer/pubspec.yaml +++ b/website/src/examples/rt_dependency_lifecycle/pubspec.yaml @@ -1,5 +1,5 @@ -name: LifecycleObserver -description: LifecycleObserver example +name: RtDependencyLifecycle +description: RtDependencyLifecycle example version: 0.1.0 environment: @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_multi_provider/pubspec.yaml b/website/src/examples/rt_multi_provider/pubspec.yaml index 3aeef3c2..50f64a0e 100644 --- a/website/src/examples/rt_multi_provider/pubspec.yaml +++ b/website/src/examples/rt_multi_provider/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_provider_child/pubspec.yaml b/website/src/examples/rt_provider_child/pubspec.yaml index b4455925..d266e0c8 100644 --- a/website/src/examples/rt_provider_child/pubspec.yaml +++ b/website/src/examples/rt_provider_child/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_provider_id/pubspec.yaml b/website/src/examples/rt_provider_id/pubspec.yaml index 5746b3dd..1131fb60 100644 --- a/website/src/examples/rt_provider_id/pubspec.yaml +++ b/website/src/examples/rt_provider_id/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_provider_init/pubspec.yaml b/website/src/examples/rt_provider_init/pubspec.yaml index 182cb422..e6cbdf82 100644 --- a/website/src/examples/rt_provider_init/pubspec.yaml +++ b/website/src/examples/rt_provider_init/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_provider_lazy/pubspec.yaml b/website/src/examples/rt_provider_lazy/pubspec.yaml index c15f9230..1983f66f 100644 --- a/website/src/examples/rt_provider_lazy/pubspec.yaml +++ b/website/src/examples/rt_provider_lazy/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_provider_mode/lib/counter_view.dart b/website/src/examples/rt_provider_mode/lib/counter_view.dart index 24ef5dec..5975c449 100644 --- a/website/src/examples/rt_provider_mode/lib/counter_view.dart +++ b/website/src/examples/rt_provider_mode/lib/counter_view.dart @@ -51,7 +51,7 @@ class CounterView extends StatelessWidget { void _listenLifecycle() { for (final mode in DependencyMode.values) { Rt.on( - RtDependency( + RtDependencyRef( mode.toString(), ), Lifecycle.registered, @@ -61,7 +61,7 @@ class CounterView extends StatelessWidget { ); Rt.on( - RtDependency( + RtDependencyRef( mode.toString(), ), Lifecycle.created, @@ -71,7 +71,7 @@ class CounterView extends StatelessWidget { ); Rt.on( - RtDependency( + RtDependencyRef( mode.toString(), ), Lifecycle.deleted, @@ -81,7 +81,7 @@ class CounterView extends StatelessWidget { ); Rt.on( - RtDependency( + RtDependencyRef( mode.toString(), ), Lifecycle.unregistered, diff --git a/website/src/examples/rt_provider_mode/pubspec.yaml b/website/src/examples/rt_provider_mode/pubspec.yaml index 20093fca..112ca5fc 100644 --- a/website/src/examples/rt_provider_mode/pubspec.yaml +++ b/website/src/examples/rt_provider_mode/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_scope/pubspec.yaml b/website/src/examples/rt_scope/pubspec.yaml index 7917dc05..1b39e40e 100644 --- a/website/src/examples/rt_scope/pubspec.yaml +++ b/website/src/examples/rt_scope/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_selector/pubspec.yaml b/website/src/examples/rt_selector/pubspec.yaml index 21771a8f..0ffd2d82 100644 --- a/website/src/examples/rt_selector/pubspec.yaml +++ b/website/src/examples/rt_selector/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_selector_child/pubspec.yaml b/website/src/examples/rt_selector_child/pubspec.yaml index e71ea568..3ee5c4ea 100644 --- a/website/src/examples/rt_selector_child/pubspec.yaml +++ b/website/src/examples/rt_selector_child/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_selector_id/pubspec.yaml b/website/src/examples/rt_selector_id/pubspec.yaml index 7db20128..993cbaa5 100644 --- a/website/src/examples/rt_selector_id/pubspec.yaml +++ b/website/src/examples/rt_selector_id/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_signal_watcher/pubspec.yaml b/website/src/examples/rt_signal_watcher/pubspec.yaml index 00f86734..e45e7010 100644 --- a/website/src/examples/rt_signal_watcher/pubspec.yaml +++ b/website/src/examples/rt_signal_watcher/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/src/examples/rt_watcher/pubspec.yaml b/website/src/examples/rt_watcher/pubspec.yaml index d661ec0f..64b51095 100644 --- a/website/src/examples/rt_watcher/pubspec.yaml +++ b/website/src/examples/rt_watcher/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: flutter: sdk: flutter - flutter_reactter: ^7.3.1 + flutter_reactter: ^8.0.0-dev flutter: uses-material-design: true diff --git a/website/tailwind.config.mjs b/website/tailwind.config.mjs index 49c576c6..55f6e485 100644 --- a/website/tailwind.config.mjs +++ b/website/tailwind.config.mjs @@ -35,7 +35,6 @@ export default { }, fontFamily: { sans: ['"Inter"'], - mono: ['"IBM Plex Mono"'], }, }, }, diff --git a/website/yarn.lock b/website/yarn.lock new file mode 100644 index 00000000..56536636 --- /dev/null +++ b/website/yarn.lock @@ -0,0 +1,5605 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@alloc/quick-lru@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@antfu/install-pkg@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@antfu/install-pkg/-/install-pkg-0.4.1.tgz#d1d7f3be96ecdb41581629cafe8626d1748c0cf1" + integrity sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw== + dependencies: + package-manager-detector "^0.2.0" + tinyexec "^0.3.0" + +"@antfu/utils@^0.7.10": + version "0.7.10" + resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-0.7.10.tgz#ae829f170158e297a9b6a28f161a8e487d00814d" + integrity sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww== + +"@astrojs/alpinejs@^0.4.0": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@astrojs/alpinejs/-/alpinejs-0.4.1.tgz#38d957cec24fd46bebdf6063a249c7c409e6edf9" + integrity sha512-ifC2dh0eaEMFwqXrx5y1pXhuKjqlOo9OmcLjNmHSMV866bH5VLGcpjy+VNf0MR9Qeax82olDib/o5Vmmzg6brA== + +"@astrojs/check@^0.5.10": + version "0.5.10" + resolved "https://registry.yarnpkg.com/@astrojs/check/-/check-0.5.10.tgz#1e6aa4d2392bb34ae9938f894b6765bd858363b4" + integrity sha512-vliHXM9cu/viGeKiksUM4mXfO816ohWtawTl2ADPgTsd4nUMjFiyAl7xFZhF34yy4hq4qf7jvK1F2PlR3b5I5w== + dependencies: + "@astrojs/language-server" "^2.8.4" + chokidar "^3.5.3" + fast-glob "^3.3.1" + kleur "^4.1.5" + yargs "^17.7.2" + +"@astrojs/compiler@^1.5.5": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-1.8.2.tgz#f305d5724c45a9932a8ef4e87b2e7227d15d1c2b" + integrity sha512-o/ObKgtMzl8SlpIdzaxFnt7SATKPxu4oIP/1NL+HDJRzxfJcAkOTAb/ZKMRyULbz4q+1t2/DAebs2Z1QairkZw== + +"@astrojs/compiler@^2.10.3": + version "2.10.3" + resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-2.10.3.tgz#852386445029f7765a70b4c1d1140e175e1d8c27" + integrity sha512-bL/O7YBxsFt55YHU021oL+xz+B/9HvGNId3F9xURN16aeqDK9juHGktdkCSXz+U4nqFACq6ZFvWomOzhV+zfPw== + +"@astrojs/internal-helpers@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@astrojs/internal-helpers/-/internal-helpers-0.4.1.tgz#ceb5de49346dbdbfb6cba1b683c07fef7df56e1c" + integrity sha512-bMf9jFihO8YP940uD70SI/RDzIhUHJAolWVcO1v5PUivxGKvfLZTLTVVxEYzGYyPsA3ivdLNqMnL5VgmQySa+g== + +"@astrojs/language-server@^2.8.4": + version "2.15.4" + resolved "https://registry.yarnpkg.com/@astrojs/language-server/-/language-server-2.15.4.tgz#9c2eeb64e4b9df9a52f19c6bfdce5397b8dba094" + integrity sha512-JivzASqTPR2bao9BWsSc/woPHH7OGSGc9aMxXL4U6egVTqBycB3ZHdBJPuOCVtcGLrzdWTosAqVPz1BVoxE0+A== + dependencies: + "@astrojs/compiler" "^2.10.3" + "@astrojs/yaml2ts" "^0.2.2" + "@jridgewell/sourcemap-codec" "^1.4.15" + "@volar/kit" "~2.4.7" + "@volar/language-core" "~2.4.7" + "@volar/language-server" "~2.4.7" + "@volar/language-service" "~2.4.7" + fast-glob "^3.2.12" + muggle-string "^0.4.1" + volar-service-css "0.0.62" + volar-service-emmet "0.0.62" + volar-service-html "0.0.62" + volar-service-prettier "0.0.62" + volar-service-typescript "0.0.62" + volar-service-typescript-twoslash-queries "0.0.62" + volar-service-yaml "0.0.62" + vscode-html-languageservice "^5.2.0" + vscode-uri "^3.0.8" + +"@astrojs/markdown-remark@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@astrojs/markdown-remark/-/markdown-remark-5.1.0.tgz#1d99b451fc73c5ef775d243057a80a66638cc5cf" + integrity sha512-S6Z3K2hOB7MfjeDoHsotnP/q2UsnEDB8NlNAaCjMDsGBZfTUbWxyLW3CaphEWw08f6KLZi2ibK9yC3BaMhh2NQ== + dependencies: + "@astrojs/prism" "^3.1.0" + github-slugger "^2.0.0" + hast-util-from-html "^2.0.0" + hast-util-to-text "^4.0.0" + import-meta-resolve "^4.0.0" + mdast-util-definitions "^6.0.0" + rehype-raw "^7.0.0" + rehype-stringify "^10.0.0" + remark-gfm "^4.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + remark-smartypants "^2.0.0" + shiki "^1.1.2" + unified "^11.0.4" + unist-util-remove-position "^5.0.0" + unist-util-visit "^5.0.0" + unist-util-visit-parents "^6.0.0" + vfile "^6.0.1" + +"@astrojs/markdown-remark@5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@astrojs/markdown-remark/-/markdown-remark-5.3.0.tgz#fd1f8874f2bd1e2c33a7447d069fc75005b677f2" + integrity sha512-r0Ikqr0e6ozPb5bvhup1qdWnSPUvQu6tub4ZLYaKyG50BXZ0ej6FhGz3GpChKpH7kglRFPObJd/bDyf2VM9pkg== + dependencies: + "@astrojs/prism" "3.1.0" + github-slugger "^2.0.0" + hast-util-from-html "^2.0.3" + hast-util-to-text "^4.0.2" + import-meta-resolve "^4.1.0" + mdast-util-definitions "^6.0.0" + rehype-raw "^7.0.0" + rehype-stringify "^10.0.1" + remark-gfm "^4.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.1.1" + remark-smartypants "^3.0.2" + shiki "^1.22.0" + unified "^11.0.5" + unist-util-remove-position "^5.0.0" + unist-util-visit "^5.0.0" + unist-util-visit-parents "^6.0.1" + vfile "^6.0.3" + +"@astrojs/mdx@^2.1.1": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@astrojs/mdx/-/mdx-2.3.1.tgz#638e28c29f502de095e7fa4a46fd674be05c0a88" + integrity sha512-BOQFKD2Pi9cRntNQJlpF2fh4xV8doNpmVy9NKI95r4jsitrY4X5aTOhAowi+fkQgP/zW1A4HwCyQ6Pdam6z8zQ== + dependencies: + "@astrojs/markdown-remark" "5.1.0" + "@mdx-js/mdx" "^3.0.0" + acorn "^8.11.2" + es-module-lexer "^1.4.1" + estree-util-visit "^2.0.0" + github-slugger "^2.0.0" + gray-matter "^4.0.3" + hast-util-to-html "^9.0.0" + kleur "^4.1.4" + rehype-raw "^7.0.0" + remark-gfm "^4.0.0" + remark-smartypants "^2.0.0" + source-map "^0.7.4" + unist-util-visit "^5.0.0" + vfile "^6.0.1" + +"@astrojs/prism@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@astrojs/prism/-/prism-3.1.0.tgz#1b70432e0b16fafda191ce780c2820822a55bc46" + integrity sha512-Z9IYjuXSArkAUx3N6xj6+Bnvx8OdUSHA8YoOgyepp3+zJmtVYJIl/I18GozdJVW1p5u/CNpl3Km7/gwTJK85cw== + dependencies: + prismjs "^1.29.0" + +"@astrojs/prism@^3.1.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@astrojs/prism/-/prism-3.2.0.tgz#e8ad1698395bd05f8d1177b365a8de899655c912" + integrity sha512-GilTHKGCW6HMq7y3BUv9Ac7GMe/MO9gi9GW62GzKtth0SwukCu/qp2wLiGpEujhY+VVhaG9v7kv/5vFzvf4NYw== + dependencies: + prismjs "^1.29.0" + +"@astrojs/sitemap@^3.0.5": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@astrojs/sitemap/-/sitemap-3.2.1.tgz#ed3874861fbca83f9ca3e66ac24a0f7ae3f9cf49" + integrity sha512-uxMfO8f7pALq0ADL6Lk68UV6dNYjJ2xGUzyjjVj60JLBs5a6smtlkBYv3tQ0DzoqwS7c9n4FUx5lgv0yPo/fgA== + dependencies: + sitemap "^8.0.0" + stream-replace-string "^2.0.0" + zod "^3.23.8" + +"@astrojs/starlight-tailwind@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@astrojs/starlight-tailwind/-/starlight-tailwind-2.0.3.tgz#6ecb07e922b6d25e5c9daac866ae657a0e01194a" + integrity sha512-ZwbdXS/9rxYlo3tKZoTZoBPUnaaqek02b341dHwOkmMT0lIR2w+8k0mRUGxnRaYtPdMcaL+nYFd8RUa8sjdyRg== + +"@astrojs/starlight@^0.22.1": + version "0.22.4" + resolved "https://registry.yarnpkg.com/@astrojs/starlight/-/starlight-0.22.4.tgz#c85e306a71132ead4cd5e120b90e729c776ca6a4" + integrity sha512-AgiVEVv2ZkGHkoJcjY0azXG2K7892i+z4FpKtasnESTciomO91I/X9vAfKfHxmTxdVP5BGPxBFVi0Bp2X4Lxvg== + dependencies: + "@astrojs/mdx" "^2.1.1" + "@astrojs/sitemap" "^3.0.5" + "@pagefind/default-ui" "^1.0.3" + "@types/hast" "^3.0.3" + "@types/mdast" "^4.0.3" + astro-expressive-code "^0.35.2" + bcp-47 "^2.1.0" + hast-util-from-html "^2.0.1" + hast-util-select "^6.0.2" + hast-util-to-string "^3.0.0" + hastscript "^8.0.0" + mdast-util-directive "^3.0.0" + mdast-util-to-markdown "^2.1.0" + pagefind "^1.0.3" + rehype "^13.0.1" + rehype-format "^5.0.0" + remark-directive "^3.0.0" + unified "^11.0.4" + unist-util-visit "^5.0.0" + vfile "^6.0.1" + +"@astrojs/tailwind@^5.1.0": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@astrojs/tailwind/-/tailwind-5.1.4.tgz#7c2ce5f1c9e20a84839e39bc7488bbfa84b872fd" + integrity sha512-EJ3uoTZZr0RYwTrVS2HgYN0+VbXvg7h87AtwpD5OzqS3GyMwRmzfOwHfORTxoWGQRrY9k/Fi+Awk60kwpvRL5Q== + dependencies: + autoprefixer "^10.4.20" + postcss "^8.4.49" + postcss-load-config "^4.0.2" + +"@astrojs/telemetry@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@astrojs/telemetry/-/telemetry-3.1.0.tgz#1038bea408a0f8cf363fb939afeefed751f1f86f" + integrity sha512-/ca/+D8MIKEC8/A9cSaPUqQNZm+Es/ZinRv0ZAzvu2ios7POQSsVD+VOj7/hypWNsNM3T7RpfgNq7H2TU1KEHA== + dependencies: + ci-info "^4.0.0" + debug "^4.3.4" + dlv "^1.1.3" + dset "^3.1.3" + is-docker "^3.0.0" + is-wsl "^3.0.0" + which-pm-runs "^1.1.0" + +"@astrojs/yaml2ts@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@astrojs/yaml2ts/-/yaml2ts-0.2.2.tgz#eabcb75a57a97c5a2f0422a0a03ca14f000f4f5e" + integrity sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ== + dependencies: + yaml "^2.5.0" + +"@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/compat-data@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.5.tgz#df93ac37f4417854130e21d72c66ff3d4b897fc7" + integrity sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg== + +"@babel/core@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" + integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.0" + "@babel/generator" "^7.26.0" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.0" + "@babel/parser" "^7.26.0" + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.26.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.26.0", "@babel/generator@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.5.tgz#e44d4ab3176bbcaf78a5725da5f1dc28802a9458" + integrity sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw== + dependencies: + "@babel/parser" "^7.26.5" + "@babel/types" "^7.26.5" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + +"@babel/helper-annotate-as-pure@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" + integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== + dependencies: + "@babel/types" "^7.25.9" + +"@babel/helper-compilation-targets@^7.25.9": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" + integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== + dependencies: + "@babel/compat-data" "^7.26.5" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/helper-plugin-utils@^7.25.9": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" + integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== + +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + +"@babel/helpers@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4" + integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw== + dependencies: + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.25.4", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.5.tgz#6fec9aebddef25ca57a935c86dbb915ae2da3e1f" + integrity sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw== + dependencies: + "@babel/types" "^7.26.5" + +"@babel/plugin-syntax-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-react-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz#06367940d8325b36edff5e2b9cbe782947ca4166" + integrity sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/template@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/traverse@^7.25.9": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.5.tgz#6d0be3e772ff786456c1a37538208286f6e79021" + integrity sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.5" + "@babel/parser" "^7.26.5" + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.5" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.4", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.5.tgz#7a1e1c01d28e26d1fe7f8ec9567b3b92b9d07747" + integrity sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + +"@ctrl/tinycolor@^4.0.4": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz#91a8f8120ffc9da2feb2a38f7862b300d5e9691a" + integrity sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ== + +"@emmetio/abbreviation@^2.3.3": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz#ed2b88fe37b972292d6026c7c540aaf887cecb6e" + integrity sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA== + dependencies: + "@emmetio/scanner" "^1.0.4" + +"@emmetio/css-abbreviation@^2.1.8": + version "2.1.8" + resolved "https://registry.yarnpkg.com/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz#b785313486eba6cb7eb623ad39378c4e1063dc00" + integrity sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw== + dependencies: + "@emmetio/scanner" "^1.0.4" + +"@emmetio/css-parser@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@emmetio/css-parser/-/css-parser-0.4.0.tgz#96135093480c79703df0e4f178f7f8f2b669fbc2" + integrity sha512-z7wkxRSZgrQHXVzObGkXG+Vmj3uRlpM11oCZ9pbaz0nFejvCDmAiNDpY75+wgXOcffKpj4rzGtwGaZxfJKsJxw== + dependencies: + "@emmetio/stream-reader" "^2.2.0" + "@emmetio/stream-reader-utils" "^0.1.0" + +"@emmetio/html-matcher@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz#43b7a71b91cdc511cb699cbe9c67bb5d4cab6754" + integrity sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ== + dependencies: + "@emmetio/scanner" "^1.0.0" + +"@emmetio/scanner@^1.0.0", "@emmetio/scanner@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@emmetio/scanner/-/scanner-1.0.4.tgz#e9cdc67194fd91f8b7eb141014be4f2d086c15f1" + integrity sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA== + +"@emmetio/stream-reader-utils@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz#244cb02c77ec2e74f78a9bd318218abc9c500a61" + integrity sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A== + +"@emmetio/stream-reader@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz#46cffea119a0a003312a21c2d9b5628cb5fcd442" + integrity sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw== + +"@emnapi/runtime@^1.2.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60" + integrity sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw== + dependencies: + tslib "^2.4.0" + +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + +"@expressive-code/core@^0.35.6": + version "0.35.6" + resolved "https://registry.yarnpkg.com/@expressive-code/core/-/core-0.35.6.tgz#7fd2f6b5c130588acd956c0d8fd042a2d8d096cb" + integrity sha512-xGqCkmfkgT7lr/rvmfnYdDSeTdCSp1otAHgoFS6wNEeO7wGDPpxdosVqYiIcQ8CfWUABh/pGqWG90q+MV3824A== + dependencies: + "@ctrl/tinycolor" "^4.0.4" + hast-util-select "^6.0.2" + hast-util-to-html "^9.0.1" + hast-util-to-text "^4.0.1" + hastscript "^9.0.0" + postcss "^8.4.38" + postcss-nested "^6.0.1" + unist-util-visit "^5.0.0" + unist-util-visit-parents "^6.0.1" + +"@expressive-code/plugin-collapsible-sections@^0.35.3": + version "0.35.6" + resolved "https://registry.yarnpkg.com/@expressive-code/plugin-collapsible-sections/-/plugin-collapsible-sections-0.35.6.tgz#4a2143dc80083479579a2f469dc35a91ef985d3c" + integrity sha512-PciZoBynxp3DCrK3dvcc/rxkj2HVbFxX992yqez1pircmPj0g1STySslkOBVMHh9COy6whJ4Mbq2k9DPV1A5/Q== + dependencies: + "@expressive-code/core" "^0.35.6" + +"@expressive-code/plugin-frames@^0.35.6": + version "0.35.6" + resolved "https://registry.yarnpkg.com/@expressive-code/plugin-frames/-/plugin-frames-0.35.6.tgz#37566970a7e71cbb793cde8d9aeab8fd5e6c6abb" + integrity sha512-CqjSWjDJ3wabMJZfL9ZAzH5UAGKg7KWsf1TBzr4xvUbZvWoBtLA/TboBML0U1Ls8h/4TRCIvR4VEb8dv5+QG3w== + dependencies: + "@expressive-code/core" "^0.35.6" + +"@expressive-code/plugin-line-numbers@^0.35.3": + version "0.35.6" + resolved "https://registry.yarnpkg.com/@expressive-code/plugin-line-numbers/-/plugin-line-numbers-0.35.6.tgz#38980019e5d170861306de1e01ed40ce736dcaf4" + integrity sha512-WVPk1ghCP1i1CfW4xDVQptlY1Py1X7u5tWhFZnLAvcuPtSo6iYP3DPOGtQpmrucnmvYIwWTdruiNFUlDUUIAcQ== + dependencies: + "@expressive-code/core" "^0.35.6" + +"@expressive-code/plugin-shiki@^0.35.6": + version "0.35.6" + resolved "https://registry.yarnpkg.com/@expressive-code/plugin-shiki/-/plugin-shiki-0.35.6.tgz#9f52f5e557a12fff702b94a414d3d41a5b0aebf8" + integrity sha512-xm+hzi9BsmhkDUGuyAWIydOAWer7Cs9cj8FM0t4HXaQ+qCubprT6wJZSKUxuvFJIUsIOqk1xXFaJzGJGnWtKMg== + dependencies: + "@expressive-code/core" "^0.35.6" + shiki "^1.1.7" + +"@expressive-code/plugin-text-markers@^0.35.6": + version "0.35.6" + resolved "https://registry.yarnpkg.com/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.35.6.tgz#bcca76e8eb90dc44fa8fd937f28d5eb78a4bc0c8" + integrity sha512-/k9eWVZSCs+uEKHR++22Uu6eIbHWEciVHbIuD8frT8DlqTtHYaaiwHPncO6KFWnGDz5i/gL7oyl6XmOi/E6GVg== + dependencies: + "@expressive-code/core" "^0.35.6" + +"@iconify-json/mdi@^1.2.1": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@iconify-json/mdi/-/mdi-1.2.2.tgz#757d5780470c9e539dca9dd97b94f0bb4c8dbfb8" + integrity sha512-84aznJXzfGdbOXGe8xB7E5uNAb7Yo5IABwTgq2X3kczb819qZeS9eL31bTVn7wJdCLK5ieaoUc2GTS3QYIkJ6g== + dependencies: + "@iconify/types" "*" + +"@iconify/tools@^4.0.5": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@iconify/tools/-/tools-4.1.1.tgz#40cf7b49efd72b2ea4e332aa883ed78365231ea8" + integrity sha512-Hybu/HGhv6T8nLQhiG9rKx+ekF7NNpPOEQAy7JRSKht3s3dcFSsPccYzk24Znc9MTxrR6Gak3cg6CPP5dyvS2Q== + dependencies: + "@iconify/types" "^2.0.0" + "@iconify/utils" "^2.2.0" + "@types/tar" "^6.1.13" + axios "^1.7.9" + cheerio "1.0.0" + domhandler "^5.0.3" + extract-zip "^2.0.1" + local-pkg "^0.5.1" + pathe "^1.1.2" + svgo "^3.3.2" + tar "^6.2.1" + +"@iconify/types@*", "@iconify/types@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@iconify/types/-/types-2.0.0.tgz#ab0e9ea681d6c8a1214f30cd741fe3a20cc57f57" + integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg== + +"@iconify/utils@^2.1.30", "@iconify/utils@^2.2.0": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@iconify/utils/-/utils-2.2.1.tgz#635b9bd8fd3e5e53742471bc0b5291f1570dda41" + integrity sha512-0/7J7hk4PqXmxo5PDBDxmnecw5PxklZJfNjIVG9FM0mEfVrvfudS22rYWsqVk6gR3UJ/mSYS90X4R3znXnqfNA== + dependencies: + "@antfu/install-pkg" "^0.4.1" + "@antfu/utils" "^0.7.10" + "@iconify/types" "^2.0.0" + debug "^4.4.0" + globals "^15.13.0" + kolorist "^1.8.0" + local-pkg "^0.5.1" + mlly "^1.7.3" + +"@img/sharp-darwin-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" + integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.0.4" + +"@img/sharp-darwin-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61" + integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.0.4" + +"@img/sharp-libvips-darwin-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz#447c5026700c01a993c7804eb8af5f6e9868c07f" + integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== + +"@img/sharp-libvips-darwin-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062" + integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== + +"@img/sharp-libvips-linux-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704" + integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== + +"@img/sharp-libvips-linux-arm@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197" + integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== + +"@img/sharp-libvips-linux-s390x@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz#f8a5eb1f374a082f72b3f45e2fb25b8118a8a5ce" + integrity sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA== + +"@img/sharp-libvips-linux-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0" + integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== + +"@img/sharp-libvips-linuxmusl-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz#166778da0f48dd2bded1fa3033cee6b588f0d5d5" + integrity sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA== + +"@img/sharp-libvips-linuxmusl-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz#93794e4d7720b077fcad3e02982f2f1c246751ff" + integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== + +"@img/sharp-linux-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22" + integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.0.4" + +"@img/sharp-linux-arm@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff" + integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.0.5" + +"@img/sharp-linux-s390x@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz#f5c077926b48e97e4a04d004dfaf175972059667" + integrity sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.0.4" + +"@img/sharp-linux-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb" + integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.4" + +"@img/sharp-linuxmusl-arm64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz#252975b915894fb315af5deea174651e208d3d6b" + integrity sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + +"@img/sharp-linuxmusl-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz#3f4609ac5d8ef8ec7dadee80b560961a60fd4f48" + integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + +"@img/sharp-wasm32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz#6f44f3283069d935bb5ca5813153572f3e6f61a1" + integrity sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg== + dependencies: + "@emnapi/runtime" "^1.2.0" + +"@img/sharp-win32-ia32@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz#1a0c839a40c5351e9885628c85f2e5dfd02b52a9" + integrity sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ== + +"@img/sharp-win32-x64@0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342" + integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@jridgewell/gen-mapping@^0.3.2", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz#4f0e06362e01362f823d348f1872b08f666d8142" + integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@mdx-js/mdx@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-3.1.0.tgz#10235cab8ad7d356c262e8c21c68df5850a97dc3" + integrity sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw== + dependencies: + "@types/estree" "^1.0.0" + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdx" "^2.0.0" + collapse-white-space "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + estree-util-scope "^1.0.0" + estree-walker "^3.0.0" + hast-util-to-jsx-runtime "^2.0.0" + markdown-extensions "^2.0.0" + recma-build-jsx "^1.0.0" + recma-jsx "^1.0.0" + recma-stringify "^1.0.0" + rehype-recma "^1.0.0" + remark-mdx "^3.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + source-map "^0.7.0" + unified "^11.0.0" + unist-util-position-from-estree "^2.0.0" + unist-util-stringify-position "^4.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@oslojs/encoding@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@oslojs/encoding/-/encoding-1.1.0.tgz#55f3d9a597430a01f2a5ef63c6b42f769f9ce34e" + integrity sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ== + +"@pagefind/darwin-arm64@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@pagefind/darwin-arm64/-/darwin-arm64-1.3.0.tgz#f1e63d031ba710c98b0b83db85df9251a255f543" + integrity sha512-365BEGl6ChOsauRjyVpBjXybflXAOvoMROw3TucAROHIcdBvXk9/2AmEvGFU0r75+vdQI4LJdJdpH4Y6Yqaj4A== + +"@pagefind/darwin-x64@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@pagefind/darwin-x64/-/darwin-x64-1.3.0.tgz#10aa3c5988daa464c5c0db5c5aa4bf72e9bbfba1" + integrity sha512-zlGHA23uuXmS8z3XxEGmbHpWDxXfPZ47QS06tGUq0HDcZjXjXHeLG+cboOy828QIV5FXsm9MjfkP5e4ZNbOkow== + +"@pagefind/default-ui@^1.0.3": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@pagefind/default-ui/-/default-ui-1.3.0.tgz#e3fb585d2fb08d463a8abc3c8f430420f0310109" + integrity sha512-CGKT9ccd3+oRK6STXGgfH+m0DbOKayX6QGlq38TfE1ZfUcPc5+ulTuzDbZUnMo+bubsEOIypm4Pl2iEyzZ1cNg== + +"@pagefind/linux-arm64@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@pagefind/linux-arm64/-/linux-arm64-1.3.0.tgz#cceb0391901736427738ee1232ff326a985eda8a" + integrity sha512-8lsxNAiBRUk72JvetSBXs4WRpYrQrVJXjlRRnOL6UCdBN9Nlsz0t7hWstRk36+JqHpGWOKYiuHLzGYqYAqoOnQ== + +"@pagefind/linux-x64@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@pagefind/linux-x64/-/linux-x64-1.3.0.tgz#06ec4c2907780a75d2fb65a22203c5a48abe7a82" + integrity sha512-hAvqdPJv7A20Ucb6FQGE6jhjqy+vZ6pf+s2tFMNtMBG+fzcdc91uTw7aP/1Vo5plD0dAOHwdxfkyw0ugal4kcQ== + +"@pagefind/windows-x64@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@pagefind/windows-x64/-/windows-x64-1.3.0.tgz#ce3394e5143aaca4850a33473a07628971773655" + integrity sha512-BR1bIRWOMqkf8IoU576YDhij1Wd/Zf2kX/kCI0b2qzCKC8wcc2GQJaaRMCpzvCCrmliO4vtJ6RITp/AnoYUUmQ== + +"@parcel/watcher-android-arm64@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz#e32d3dda6647791ee930556aee206fcd5ea0fb7a" + integrity sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ== + +"@parcel/watcher-darwin-arm64@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz#0d9e680b7e9ec1c8f54944f1b945aa8755afb12f" + integrity sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw== + +"@parcel/watcher-darwin-x64@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz#f9f1d5ce9d5878d344f14ef1856b7a830c59d1bb" + integrity sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA== + +"@parcel/watcher-freebsd-x64@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz#2b77f0c82d19e84ff4c21de6da7f7d096b1a7e82" + integrity sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw== + +"@parcel/watcher-linux-arm-glibc@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz#92ed322c56dbafa3d2545dcf2803334aee131e42" + integrity sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA== + +"@parcel/watcher-linux-arm-musl@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz#cd48e9bfde0cdbbd2ecd9accfc52967e22f849a4" + integrity sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA== + +"@parcel/watcher-linux-arm64-glibc@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz#7b81f6d5a442bb89fbabaf6c13573e94a46feb03" + integrity sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA== + +"@parcel/watcher-linux-arm64-musl@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz#dcb8ff01077cdf59a18d9e0a4dff7a0cfe5fd732" + integrity sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q== + +"@parcel/watcher-linux-x64-glibc@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz#2e254600fda4e32d83942384d1106e1eed84494d" + integrity sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw== + +"@parcel/watcher-linux-x64-musl@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz#01fcea60fedbb3225af808d3f0a7b11229792eef" + integrity sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA== + +"@parcel/watcher-win32-arm64@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz#87cdb16e0783e770197e52fb1dc027bb0c847154" + integrity sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig== + +"@parcel/watcher-win32-ia32@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz#778c39b56da33e045ba21c678c31a9f9d7c6b220" + integrity sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA== + +"@parcel/watcher-win32-x64@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz#33873876d0bbc588aacce38e90d1d7480ce81cb7" + integrity sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw== + +"@parcel/watcher@^2.4.1": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.0.tgz#5c88818b12b8de4307a9d3e6dc3e28eba0dfbd10" + integrity sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ== + dependencies: + detect-libc "^1.0.3" + is-glob "^4.0.3" + micromatch "^4.0.5" + node-addon-api "^7.0.0" + optionalDependencies: + "@parcel/watcher-android-arm64" "2.5.0" + "@parcel/watcher-darwin-arm64" "2.5.0" + "@parcel/watcher-darwin-x64" "2.5.0" + "@parcel/watcher-freebsd-x64" "2.5.0" + "@parcel/watcher-linux-arm-glibc" "2.5.0" + "@parcel/watcher-linux-arm-musl" "2.5.0" + "@parcel/watcher-linux-arm64-glibc" "2.5.0" + "@parcel/watcher-linux-arm64-musl" "2.5.0" + "@parcel/watcher-linux-x64-glibc" "2.5.0" + "@parcel/watcher-linux-x64-musl" "2.5.0" + "@parcel/watcher-win32-arm64" "2.5.0" + "@parcel/watcher-win32-ia32" "2.5.0" + "@parcel/watcher-win32-x64" "2.5.0" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@rollup/pluginutils@^5.1.3": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz#bb94f1f9eaaac944da237767cdfee6c5b2262d4a" + integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^4.0.2" + +"@rollup/rollup-android-arm-eabi@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz#14c737dc19603a096568044eadaa60395eefb809" + integrity sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q== + +"@rollup/rollup-android-arm64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz#9d81ea54fc5650eb4ebbc0a7d84cee331bfa30ad" + integrity sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w== + +"@rollup/rollup-darwin-arm64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz#29448cb1370cf678b50743d2e392be18470abc23" + integrity sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q== + +"@rollup/rollup-darwin-x64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz#0ca99741c3ed096700557a43bb03359450c7857d" + integrity sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA== + +"@rollup/rollup-freebsd-arm64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz#233f8e4c2f54ad9b719cd9645887dcbd12b38003" + integrity sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ== + +"@rollup/rollup-freebsd-x64@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz#dfba762a023063dc901610722995286df4a48360" + integrity sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw== + +"@rollup/rollup-linux-arm-gnueabihf@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz#b9da54171726266c5ef4237f462a85b3c3cf6ac9" + integrity sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg== + +"@rollup/rollup-linux-arm-musleabihf@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz#b9db69b3f85f5529eb992936d8f411ee6d04297b" + integrity sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug== + +"@rollup/rollup-linux-arm64-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz#2550cf9bb4d47d917fd1ab4af756d7bbc3ee1528" + integrity sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw== + +"@rollup/rollup-linux-arm64-musl@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz#9d06b26d286c7dded6336961a2f83e48330e0c80" + integrity sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA== + +"@rollup/rollup-linux-loongarch64-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz#e957bb8fee0c8021329a34ca8dfa825826ee0e2e" + integrity sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ== + +"@rollup/rollup-linux-powerpc64le-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz#e8585075ddfb389222c5aada39ea62d6d2511ccc" + integrity sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw== + +"@rollup/rollup-linux-riscv64-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz#7d0d40cee7946ccaa5a4e19a35c6925444696a9e" + integrity sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw== + +"@rollup/rollup-linux-s390x-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz#c2dcd8a4b08b2f2778eceb7a5a5dfde6240ebdea" + integrity sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA== + +"@rollup/rollup-linux-x64-gnu@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz#183637d91456877cb83d0a0315eb4788573aa588" + integrity sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg== + +"@rollup/rollup-linux-x64-musl@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz#036a4c860662519f1f9453807547fd2a11d5bb01" + integrity sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow== + +"@rollup/rollup-win32-arm64-msvc@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz#51cad812456e616bfe4db5238fb9c7497e042a52" + integrity sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw== + +"@rollup/rollup-win32-ia32-msvc@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz#661c8b3e4cd60f51deaa39d153aac4566e748e5e" + integrity sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw== + +"@rollup/rollup-win32-x64-msvc@4.30.1": + version "4.30.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz#73bf1885ff052b82fbb0f82f8671f73c36e9137c" + integrity sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og== + +"@shikijs/core@1.26.2": + version "1.26.2" + resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.26.2.tgz#05fe0d3fa88b2a2008d883457b8d243688378835" + integrity sha512-ORyu3MrY7dCC7FDLDsFSkBM9b/AT9/Y8rH+UQ07Rtek48pp0ZhQOMPTKolqszP4bBCas6FqTZQYt18BBamVl/g== + dependencies: + "@shikijs/engine-javascript" "1.26.2" + "@shikijs/engine-oniguruma" "1.26.2" + "@shikijs/types" "1.26.2" + "@shikijs/vscode-textmate" "^10.0.1" + "@types/hast" "^3.0.4" + hast-util-to-html "^9.0.4" + +"@shikijs/engine-javascript@1.26.2": + version "1.26.2" + resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-1.26.2.tgz#28b0278130d3a76093da8be0c070df6fa2d1c7d0" + integrity sha512-ngkIu9swLVo9Zt5QBtz5Sk08vmPcwuj01r7pPK/Zjmo2U2WyKMK4WMUMmkdQiUacdcLth0zt8u1onp4zhkFXKQ== + dependencies: + "@shikijs/types" "1.26.2" + "@shikijs/vscode-textmate" "^10.0.1" + oniguruma-to-es "^1.0.0" + +"@shikijs/engine-oniguruma@1.26.2": + version "1.26.2" + resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.26.2.tgz#a8a37ef33624dee783e79f7756ef1d0a2b907639" + integrity sha512-mlN7Qrs+w60nKrd7at7XkXSwz6728Pe34taDmHrG6LRHjzCqQ+ysg+/AT6/D2LMk0s2lsr71DjpI73430QP4/w== + dependencies: + "@shikijs/types" "1.26.2" + "@shikijs/vscode-textmate" "^10.0.1" + +"@shikijs/langs@1.26.2": + version "1.26.2" + resolved "https://registry.yarnpkg.com/@shikijs/langs/-/langs-1.26.2.tgz#0880cd47919b507585f312ca805bb16aaee9bc8d" + integrity sha512-o5cdPycB2Kw3IgncHxWopWPiTkjAj7dG01fLkkUyj3glb5ftxL/Opecq9F54opMlrgXy7ZIqDERvFLlUzsCOuA== + dependencies: + "@shikijs/types" "1.26.2" + +"@shikijs/themes@1.26.2": + version "1.26.2" + resolved "https://registry.yarnpkg.com/@shikijs/themes/-/themes-1.26.2.tgz#b5385fef2f3d26a68a61144331ba301ce28a832d" + integrity sha512-y4Pn6PM5mODz/e3yF6jAUG7WLKJzqL2tJ5qMJCUkMUB1VRgtQVvoa1cHh7NScryGXyrYGJ8nPnRDhdv2rw0xpA== + dependencies: + "@shikijs/types" "1.26.2" + +"@shikijs/types@1.26.2": + version "1.26.2" + resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-1.26.2.tgz#6180cdb5023d21dfe2d2bd784afdfab4e2bce776" + integrity sha512-PO2jucx2FIdlLBPYbIUlMtWSLs5ulcRcuV93cR3T65lkK5SJP4MGBRt9kmWGXiQc0f7+FHj/0BEawditZcI/fQ== + dependencies: + "@shikijs/vscode-textmate" "^10.0.1" + "@types/hast" "^3.0.4" + +"@shikijs/vscode-textmate@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz#d06d45b67ac5e9b0088e3f67ebd3f25c6c3d711a" + integrity sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg== + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@types/acorn@^4.0.0": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/acorn/-/acorn-4.0.6.tgz#d61ca5480300ac41a7d973dd5b84d0a591154a22" + integrity sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ== + dependencies: + "@types/estree" "*" + +"@types/alpinejs@^3.13.10": + version "3.13.11" + resolved "https://registry.yarnpkg.com/@types/alpinejs/-/alpinejs-3.13.11.tgz#ecf938c0f4678d617c77933961e9385e45ece148" + integrity sha512-3KhGkDixCPiLdL3Z/ok1GxHwLxEWqQOKJccgaQL01wc0EVM2tCTaqlC3NIedmxAXkVzt/V6VTM8qPgnOHKJ1MA== + +"@types/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*": + version "7.20.6" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" + integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== + dependencies: + "@babel/types" "^7.20.7" + +"@types/cookie@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" + integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== + +"@types/debug@^4.0.0": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + +"@types/estree-jsx@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== + dependencies: + "@types/estree" "*" + +"@types/estree@*", "@types/estree@1.0.6", "@types/estree@^1.0.0": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + +"@types/hast@^3.0.0", "@types/hast@^3.0.3", "@types/hast@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== + dependencies: + "@types/unist" "*" + +"@types/mdast@^4.0.0", "@types/mdast@^4.0.3": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" + +"@types/mdx@^2.0.0": + version "2.0.13" + resolved "https://registry.yarnpkg.com/@types/mdx/-/mdx-2.0.13.tgz#68f6877043d377092890ff5b298152b0a21671bd" + integrity sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw== + +"@types/ms@*": + version "0.7.34" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" + integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== + +"@types/nlcst@^1.0.0": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@types/nlcst/-/nlcst-1.0.4.tgz#3b8a9c279a2367602512588a0ba6a0e93634ee3e" + integrity sha512-ABoYdNQ/kBSsLvZAekMhIPMQ3YUZvavStpKYs7BjLLuKVmIMA0LUgZ7b54zzuWJRbHF80v1cNf4r90Vd6eMQDg== + dependencies: + "@types/unist" "^2" + +"@types/nlcst@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/nlcst/-/nlcst-2.0.3.tgz#31cad346eaab48a9a8a58465d3d05e2530dda762" + integrity sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA== + dependencies: + "@types/unist" "*" + +"@types/node@*": + version "22.10.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.6.tgz#5c6795e71635876039f853cbccd59f523d9e4239" + integrity sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ== + dependencies: + undici-types "~6.20.0" + +"@types/node@^17.0.5": + version "17.0.45" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" + integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== + +"@types/sax@^1.2.1": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.7.tgz#ba5fe7df9aa9c89b6dff7688a19023dd2963091d" + integrity sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A== + dependencies: + "@types/node" "*" + +"@types/tar@^6.1.13": + version "6.1.13" + resolved "https://registry.yarnpkg.com/@types/tar/-/tar-6.1.13.tgz#9b5801c02175344101b4b91086ab2bbc8e93a9b6" + integrity sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw== + dependencies: + "@types/node" "*" + minipass "^4.0.0" + +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + +"@types/unist@^2", "@types/unist@^2.0.0": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== + +"@types/yauzl@^2.9.1": + version "2.10.3" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" + integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== + dependencies: + "@types/node" "*" + +"@ungap/structured-clone@^1.0.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.1.tgz#28fa185f67daaf7b7a1a8c1d445132c5d979f8bd" + integrity sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA== + +"@volar/kit@~2.4.7": + version "2.4.11" + resolved "https://registry.yarnpkg.com/@volar/kit/-/kit-2.4.11.tgz#12fa1825bdbaa54752e86d9eecb0d3b6d1c60f5e" + integrity sha512-ups5RKbMzMCr6RKafcCqDRnJhJDNWqo2vfekwOAj6psZ15v5TlcQFQAyokQJ3wZxVkzxrQM+TqTRDENfQEXpmA== + dependencies: + "@volar/language-service" "2.4.11" + "@volar/typescript" "2.4.11" + typesafe-path "^0.2.2" + vscode-languageserver-textdocument "^1.0.11" + vscode-uri "^3.0.8" + +"@volar/language-core@2.4.11", "@volar/language-core@~2.4.7": + version "2.4.11" + resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.4.11.tgz#d95a9ec4f14fbdb41a6a64f9f321d11d23a5291c" + integrity sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg== + dependencies: + "@volar/source-map" "2.4.11" + +"@volar/language-server@~2.4.7": + version "2.4.11" + resolved "https://registry.yarnpkg.com/@volar/language-server/-/language-server-2.4.11.tgz#e0d87bd8d4eee0470e806e832ed26f27caf08d81" + integrity sha512-W9P8glH1M8LGREJ7yHRCANI5vOvTrRO15EMLdmh5WNF9sZYSEbQxiHKckZhvGIkbeR1WAlTl3ORTrJXUghjk7g== + dependencies: + "@volar/language-core" "2.4.11" + "@volar/language-service" "2.4.11" + "@volar/typescript" "2.4.11" + path-browserify "^1.0.1" + request-light "^0.7.0" + vscode-languageserver "^9.0.1" + vscode-languageserver-protocol "^3.17.5" + vscode-languageserver-textdocument "^1.0.11" + vscode-uri "^3.0.8" + +"@volar/language-service@2.4.11", "@volar/language-service@~2.4.7": + version "2.4.11" + resolved "https://registry.yarnpkg.com/@volar/language-service/-/language-service-2.4.11.tgz#44008ad68ff82c618fe4f6ad338af9164853e82b" + integrity sha512-KIb6g8gjUkS2LzAJ9bJCLIjfsJjeRtmXlu7b2pDFGD3fNqdbC53cCAKzgWDs64xtQVKYBU13DLWbtSNFtGuMLQ== + dependencies: + "@volar/language-core" "2.4.11" + vscode-languageserver-protocol "^3.17.5" + vscode-languageserver-textdocument "^1.0.11" + vscode-uri "^3.0.8" + +"@volar/source-map@2.4.11": + version "2.4.11" + resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.4.11.tgz#5876d4531508129724c2755e295db1df98bd5895" + integrity sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ== + +"@volar/typescript@2.4.11": + version "2.4.11" + resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.4.11.tgz#aafbfa413337654db211bf4d8fb6670c89f6fa57" + integrity sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw== + dependencies: + "@volar/language-core" "2.4.11" + path-browserify "^1.0.1" + vscode-uri "^3.0.8" + +"@vscode/emmet-helper@^2.9.3": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz#7a53e4fdb17329cc2ed88036905c78d811d231d6" + integrity sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw== + dependencies: + emmet "^2.4.3" + jsonc-parser "^2.3.0" + vscode-languageserver-textdocument "^1.0.1" + vscode-languageserver-types "^3.15.1" + vscode-uri "^3.0.8" + +"@vscode/l10n@^0.0.18": + version "0.0.18" + resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.18.tgz#916d3a5e960dbab47c1c56f58a7cb5087b135c95" + integrity sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ== + +"@vue/reactivity@~3.1.1": + version "3.1.5" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.1.5.tgz#dbec4d9557f7c8f25c2635db1e23a78a729eb991" + integrity sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg== + dependencies: + "@vue/shared" "3.1.5" + +"@vue/shared@3.1.5": + version "3.1.5" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.1.5.tgz#74ee3aad995d0a3996a6bb9533d4d280514ede03" + integrity sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA== + +acorn-jsx@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.0.0, acorn@^8.11.2, acorn@^8.14.0: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + +ajv@^8.11.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +alpinejs@^3.13.10: + version "3.14.8" + resolved "https://registry.yarnpkg.com/alpinejs/-/alpinejs-3.14.8.tgz#062de45daa219db14375b3cb35ba28e5b0627337" + integrity sha512-wT2fuP2DXpGk/jKaglwy7S/IJpm1FD+b7U6zUrhwErjoq5h27S4dxkJEXVvhbdwyPv9U+3OkUuNLkZT4h2Kfrg== + dependencies: + "@vue/reactivity" "~3.1.1" + +ansi-align@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0, ansi-styles@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^5.0.0, arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aria-query@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" + integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== + +array-iterate@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/array-iterate/-/array-iterate-2.0.1.tgz#6efd43f8295b3fee06251d3d62ead4bd9805dd24" + integrity sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg== + +astring@^1.8.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/astring/-/astring-1.9.0.tgz#cc73e6062a7eb03e7d19c22d8b0b3451fd9bfeef" + integrity sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg== + +astro-expressive-code@^0.35.2: + version "0.35.6" + resolved "https://registry.yarnpkg.com/astro-expressive-code/-/astro-expressive-code-0.35.6.tgz#5029d44cba99c674e1225968de1382466b997f3d" + integrity sha512-1U4KrvFuodaCV3z4I1bIR16SdhQlPkolGsYTtiANxPZUVv/KitGSCTjzksrkPonn1XuwVqvnwmUUVzTLWngnBA== + dependencies: + rehype-expressive-code "^0.35.6" + +astro-icon@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/astro-icon/-/astro-icon-1.1.5.tgz#5d24a55caed5886514a6edde5ea7e623dfc22f59" + integrity sha512-CJYS5nWOw9jz4RpGWmzNQY7D0y2ZZacH7atL2K9DeJXJVaz7/5WrxeyIxO8KASk1jCM96Q4LjRx/F3R+InjJrw== + dependencies: + "@iconify/tools" "^4.0.5" + "@iconify/types" "^2.0.0" + "@iconify/utils" "^2.1.30" + +astro@^4.7.1: + version "4.16.18" + resolved "https://registry.yarnpkg.com/astro/-/astro-4.16.18.tgz#c7db47d5554d865543d6917f42b5129819c6bc88" + integrity sha512-G7zfwJt9BDHEZwlaLNvjbInIw2hPryyD654314KV/XT34pJU6SfN1S+mWa8RAkALcZNJnJXCJmT3JXLQStD3Lw== + dependencies: + "@astrojs/compiler" "^2.10.3" + "@astrojs/internal-helpers" "0.4.1" + "@astrojs/markdown-remark" "5.3.0" + "@astrojs/telemetry" "3.1.0" + "@babel/core" "^7.26.0" + "@babel/plugin-transform-react-jsx" "^7.25.9" + "@babel/types" "^7.26.0" + "@oslojs/encoding" "^1.1.0" + "@rollup/pluginutils" "^5.1.3" + "@types/babel__core" "^7.20.5" + "@types/cookie" "^0.6.0" + acorn "^8.14.0" + aria-query "^5.3.2" + axobject-query "^4.1.0" + boxen "8.0.1" + ci-info "^4.1.0" + clsx "^2.1.1" + common-ancestor-path "^1.0.1" + cookie "^0.7.2" + cssesc "^3.0.0" + debug "^4.3.7" + deterministic-object-hash "^2.0.2" + devalue "^5.1.1" + diff "^5.2.0" + dlv "^1.1.3" + dset "^3.1.4" + es-module-lexer "^1.5.4" + esbuild "^0.21.5" + estree-walker "^3.0.3" + fast-glob "^3.3.2" + flattie "^1.1.1" + github-slugger "^2.0.0" + gray-matter "^4.0.3" + html-escaper "^3.0.3" + http-cache-semantics "^4.1.1" + js-yaml "^4.1.0" + kleur "^4.1.5" + magic-string "^0.30.14" + magicast "^0.3.5" + micromatch "^4.0.8" + mrmime "^2.0.0" + neotraverse "^0.6.18" + ora "^8.1.1" + p-limit "^6.1.0" + p-queue "^8.0.1" + preferred-pm "^4.0.0" + prompts "^2.4.2" + rehype "^13.0.2" + semver "^7.6.3" + shiki "^1.23.1" + tinyexec "^0.3.1" + tsconfck "^3.1.4" + unist-util-visit "^5.0.0" + vfile "^6.0.3" + vite "^5.4.11" + vitefu "^1.0.4" + which-pm "^3.0.0" + xxhash-wasm "^1.1.0" + yargs-parser "^21.1.1" + zod "^3.23.8" + zod-to-json-schema "^3.23.5" + zod-to-ts "^1.2.0" + optionalDependencies: + sharp "^0.33.3" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +autoprefixer@^10.4.20: + version "10.4.20" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b" + integrity sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g== + dependencies: + browserslist "^4.23.3" + caniuse-lite "^1.0.30001646" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.1" + postcss-value-parser "^4.2.0" + +axios@^1.7.9: + version "1.7.9" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a" + integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +axobject-query@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" + integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== + +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base-64@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/base-64/-/base-64-1.0.0.tgz#09d0f2084e32a3fd08c2475b973788eee6ae8f4a" + integrity sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg== + +bcp-47-match@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/bcp-47-match/-/bcp-47-match-2.0.3.tgz#603226f6e5d3914a581408be33b28a53144b09d0" + integrity sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ== + +bcp-47@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/bcp-47/-/bcp-47-2.1.0.tgz#7e80734c3338fe8320894981dccf4968c3092df6" + integrity sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w== + dependencies: + is-alphabetical "^2.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +boxen@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-8.0.1.tgz#7e9fcbb45e11a2d7e6daa8fdcebfc3242fc19fe3" + integrity sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw== + dependencies: + ansi-align "^3.0.1" + camelcase "^8.0.0" + chalk "^5.3.0" + cli-boxes "^3.0.0" + string-width "^7.2.0" + type-fest "^4.21.0" + widest-line "^5.0.0" + wrap-ansi "^9.0.0" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.23.3, browserslist@^4.24.0: + version "4.24.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" + integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== + dependencies: + caniuse-lite "^1.0.30001688" + electron-to-chromium "^1.5.73" + node-releases "^2.0.19" + update-browserslist-db "^1.1.1" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +camelcase@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-8.0.0.tgz#c0d36d418753fb6ad9c5e0437579745c1c14a534" + integrity sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA== + +caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001688: + version "1.0.30001692" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz#4585729d95e6b95be5b439da6ab55250cd125bf9" + integrity sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A== + +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + +chalk@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8" + integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w== + +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== + +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== + +cheerio-select@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" + integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== + dependencies: + boolbase "^1.0.0" + css-select "^5.1.0" + css-what "^6.1.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + +cheerio@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0.tgz#1ede4895a82f26e8af71009f961a9b8cb60d6a81" + integrity sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww== + dependencies: + cheerio-select "^2.1.0" + dom-serializer "^2.0.0" + domhandler "^5.0.3" + domutils "^3.1.0" + encoding-sniffer "^0.2.0" + htmlparser2 "^9.1.0" + parse5 "^7.1.2" + parse5-htmlparser2-tree-adapter "^7.0.0" + parse5-parser-stream "^7.1.2" + undici "^6.19.5" + whatwg-mimetype "^4.0.0" + +chokidar@^3.5.3, chokidar@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chokidar@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +ci-info@^4.0.0, ci-info@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.1.0.tgz#92319d2fa29d2620180ea5afed31f589bc98cf83" + integrity sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A== + +cli-boxes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" + integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== + +cli-cursor@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38" + integrity sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== + dependencies: + restore-cursor "^5.0.0" + +cli-spinners@^2.9.2: + version "2.9.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + +collapse-white-space@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz#640257174f9f42c740b40f3b55ee752924feefca" + integrity sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== + +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +common-ancestor-path@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" + integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== + +confbox@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06" + integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + +cross-spawn@^7.0.0: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-selector-parser@^3.0.0: + version "3.0.5" + resolved "https://registry.yarnpkg.com/css-selector-parser/-/css-selector-parser-3.0.5.tgz#9b636ebccf7c4bcce5c1ac21ae27de9f01180ae9" + integrity sha512-3itoDFbKUNx1eKmVpYMFyqKX04Ww9osZ+dLgrk6GEv6KMVeXUhUnp4I5X+evw+u3ZxVU6RFXSSRxlTeMh8bA+g== + +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== + dependencies: + css-tree "~2.2.0" + +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.7, debug@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +decode-named-character-reference@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e" + integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg== + dependencies: + character-entities "^2.0.0" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +dequal@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + +detect-libc@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + +deterministic-object-hash@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz#b251ddc801443905f0e9fef08816a46bc9fe3807" + integrity sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ== + dependencies: + base-64 "^1.0.0" + +devalue@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/devalue/-/devalue-5.1.1.tgz#a71887ac0f354652851752654e4bd435a53891ae" + integrity sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw== + +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + +diff@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== + +direction@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/direction/-/direction-2.0.1.tgz#71800dd3c4fa102406502905d3866e65bdebb985" + integrity sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA== + +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1, domutils@^3.1.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78" + integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +dset@^3.1.3, dset@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.4.tgz#f8eaf5f023f068a036d08cd07dc9ffb7d0065248" + integrity sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +electron-to-chromium@^1.5.73: + version "1.5.80" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.80.tgz#ca7a8361d7305f0ec9e203ce4e633cbb8a8ef1b1" + integrity sha512-LTrKpW0AqIuHwmlVNV+cjFYTnXtM9K37OGhpe0ZI10ScPSxqVSryZHIY3WnCS5NSYbBODRTZyhRMS2h5FAEqAw== + +emmet@^2.4.3: + version "2.4.11" + resolved "https://registry.yarnpkg.com/emmet/-/emmet-2.4.11.tgz#b331f572df37a252360ebee7dc4462c8d2e32f5c" + integrity sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ== + dependencies: + "@emmetio/abbreviation" "^2.3.3" + "@emmetio/css-abbreviation" "^2.1.8" + +emoji-regex-xs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724" + integrity sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg== + +emoji-regex@^10.3.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4" + integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encoding-sniffer@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz#799569d66d443babe82af18c9f403498365ef1d5" + integrity sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg== + dependencies: + iconv-lite "^0.6.3" + whatwg-encoding "^3.1.1" + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +entities@^4.2.0, entities@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +es-module-lexer@^1.4.1, es-module-lexer@^1.5.4: + version "1.6.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz#da49f587fd9e68ee2404fe4e256c0c7d3a81be21" + integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ== + +esast-util-from-estree@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz#8d1cfb51ad534d2f159dc250e604f3478a79f1ad" + integrity sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + devlop "^1.0.0" + estree-util-visit "^2.0.0" + unist-util-position-from-estree "^2.0.0" + +esast-util-from-js@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz#5147bec34cc9da44accf52f87f239a40ac3e8225" + integrity sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw== + dependencies: + "@types/estree-jsx" "^1.0.0" + acorn "^8.0.0" + esast-util-from-estree "^2.0.0" + vfile-message "^4.0.0" + +esbuild@^0.21.3, esbuild@^0.21.5: + version "0.21.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estree-util-attach-comments@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz#344bde6a64c8a31d15231e5ee9e297566a691c2d" + integrity sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw== + dependencies: + "@types/estree" "^1.0.0" + +estree-util-build-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz#b6d0bced1dcc4f06f25cf0ceda2b2dcaf98168f1" + integrity sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + estree-walker "^3.0.0" + +estree-util-is-identifier-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== + +estree-util-scope@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/estree-util-scope/-/estree-util-scope-1.0.0.tgz#9cbdfc77f5cb51e3d9ed4ad9c4adbff22d43e585" + integrity sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + +estree-util-to-js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz#10a6fb924814e6abb62becf0d2bc4dea51d04f17" + integrity sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg== + dependencies: + "@types/estree-jsx" "^1.0.0" + astring "^1.8.0" + source-map "^0.7.0" + +estree-util-visit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/estree-util-visit/-/estree-util-visit-2.0.0.tgz#13a9a9f40ff50ed0c022f831ddf4b58d05446feb" + integrity sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/unist" "^3.0.0" + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +estree-walker@^3.0.0, estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + +expressive-code@^0.35.6: + version "0.35.6" + resolved "https://registry.yarnpkg.com/expressive-code/-/expressive-code-0.35.6.tgz#ad8ee87bd5d5376c07a5f4475bbf98366b5b307a" + integrity sha512-+mx+TPTbMqgo0mL92Xh9QgjW0kSQIsEivMgEcOnaqKqL7qCw8Vkqc5Rg/di7ZYw4aMUSr74VTc+w8GQWu05j1g== + dependencies: + "@expressive-code/core" "^0.35.6" + "@expressive-code/plugin-frames" "^0.35.6" + "@expressive-code/plugin-shiki" "^0.35.6" + "@expressive-code/plugin-text-markers" "^0.35.6" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== + dependencies: + is-extendable "^0.1.0" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.12, fast-glob@^3.3.1, fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-uri@^3.0.1: + version "3.0.5" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.5.tgz#19f5f9691d0dab9b85861a7bb5d98fca961da9cd" + integrity sha512-5JnBCWpFlMo0a3ciDy/JckMzzv1U9coZrIhedq+HXxxUfDTAiS0LA8OKVao4G9BxmCVck/jtA5r3KAtRWEyD8Q== + +fastq@^1.6.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.18.0.tgz#d631d7e25faffea81887fe5ea8c9010e1b36fee0" + integrity sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw== + dependencies: + reusify "^1.0.4" + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up-simple@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/find-up-simple/-/find-up-simple-1.0.0.tgz#21d035fde9fdbd56c8f4d2f63f32fd93a1cfc368" + integrity sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw== + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-yarn-workspace-root2@1.2.16: + version "1.2.16" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz#60287009dd2f324f59646bdb4b7610a6b301c2a9" + integrity sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA== + dependencies: + micromatch "^4.0.2" + pkg-dir "^4.2.0" + +flattie@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flattie/-/flattie-1.1.1.tgz#88182235723113667d36217fec55359275d6fe3d" + integrity sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ== + +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +foreground-child@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-east-asian-width@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz#21b4071ee58ed04ee0db653371b55b4299875389" + integrity sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ== + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +github-slugger@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-2.0.0.tgz#52cf2f9279a21eb6c59dd385b410f0c0adda8f1a" + integrity sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^10.3.10: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^15.13.0: + version "15.14.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-15.14.0.tgz#b8fd3a8941ff3b4d38f3319d433b61bbb482e73f" + integrity sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig== + +graceful-fs@^4.1.5: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +gray-matter@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" + integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== + dependencies: + js-yaml "^3.13.1" + kind-of "^6.0.2" + section-matter "^1.0.0" + strip-bom-string "^1.0.0" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hast-util-embedded@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz#be4477780fbbe079cdba22982e357a0de4ba853e" + integrity sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA== + dependencies: + "@types/hast" "^3.0.0" + hast-util-is-element "^3.0.0" + +hast-util-format@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hast-util-format/-/hast-util-format-1.1.0.tgz#373e77382e07deb04f6676f1b4437e7d8549d985" + integrity sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA== + dependencies: + "@types/hast" "^3.0.0" + hast-util-embedded "^3.0.0" + hast-util-minify-whitespace "^1.0.0" + hast-util-phrasing "^3.0.0" + hast-util-whitespace "^3.0.0" + html-whitespace-sensitive-tag-names "^3.0.0" + unist-util-visit-parents "^6.0.0" + +hast-util-from-html@^2.0.0, hast-util-from-html@^2.0.1, hast-util-from-html@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz#485c74785358beb80c4ba6346299311ac4c49c82" + integrity sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw== + dependencies: + "@types/hast" "^3.0.0" + devlop "^1.1.0" + hast-util-from-parse5 "^8.0.0" + parse5 "^7.0.0" + vfile "^6.0.0" + vfile-message "^4.0.0" + +hast-util-from-parse5@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.2.tgz#29b42758ba96535fd6021f0f533c000886c0f00f" + integrity sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + hastscript "^9.0.0" + property-information "^6.0.0" + vfile "^6.0.0" + vfile-location "^5.0.0" + web-namespaces "^2.0.0" + +hast-util-has-property@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz#4e595e3cddb8ce530ea92f6fc4111a818d8e7f93" + integrity sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-is-body-ok-link@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz#ef63cb2f14f04ecf775139cd92bda5026380d8b4" + integrity sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-is-element@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz#6e31a6532c217e5b533848c7e52c9d9369ca0932" + integrity sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-minify-whitespace@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz#7588fd1a53f48f1d30406b81959dffc3650daf55" + integrity sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw== + dependencies: + "@types/hast" "^3.0.0" + hast-util-embedded "^3.0.0" + hast-util-is-element "^3.0.0" + hast-util-whitespace "^3.0.0" + unist-util-is "^6.0.0" + +hast-util-parse-selector@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27" + integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-phrasing@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz#fa284c0cd4a82a0dd6020de8300a7b1ebffa1690" + integrity sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ== + dependencies: + "@types/hast" "^3.0.0" + hast-util-embedded "^3.0.0" + hast-util-has-property "^3.0.0" + hast-util-is-body-ok-link "^3.0.0" + hast-util-is-element "^3.0.0" + +hast-util-raw@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-9.1.0.tgz#79b66b26f6f68fb50dfb4716b2cdca90d92adf2e" + integrity sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + "@ungap/structured-clone" "^1.0.0" + hast-util-from-parse5 "^8.0.0" + hast-util-to-parse5 "^8.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + parse5 "^7.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-select@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/hast-util-select/-/hast-util-select-6.0.3.tgz#d30471b26efc88ae8a126ec36cd8ee6420fe3839" + integrity sha512-OVRQlQ1XuuLP8aFVLYmC2atrfWHS5UD3shonxpnyrjcCkwtvmt/+N6kYJdcY4mkMJhxp4kj2EFIxQ9kvkkt/eQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + bcp-47-match "^2.0.0" + comma-separated-tokens "^2.0.0" + css-selector-parser "^3.0.0" + devlop "^1.0.0" + direction "^2.0.0" + hast-util-has-property "^3.0.0" + hast-util-to-string "^3.0.0" + hast-util-whitespace "^3.0.0" + nth-check "^2.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + +hast-util-to-estree@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/hast-util-to-estree/-/hast-util-to-estree-3.1.1.tgz#b7f0b247d9f62127bb5db34e3a86c93d17279071" + integrity sha512-IWtwwmPskfSmma9RpzCappDUitC8t5jhAynHhc1m2+5trOgsrp7txscUSavc5Ic8PATyAjfrCK1wgtxh2cICVQ== + dependencies: + "@types/estree" "^1.0.0" + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-attach-comments "^3.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + style-to-object "^1.0.0" + unist-util-position "^5.0.0" + zwitch "^2.0.0" + +hast-util-to-html@^9.0.0, hast-util-to-html@^9.0.1, hast-util-to-html@^9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz#d689c118c875aab1def692c58603e34335a0f5c5" + integrity sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + comma-separated-tokens "^2.0.0" + hast-util-whitespace "^3.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + stringify-entities "^4.0.0" + zwitch "^2.0.4" + +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz#6d11b027473e69adeaa00ca4cfb5bb68e3d282fa" + integrity sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + style-to-object "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" + +hast-util-to-parse5@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz#477cd42d278d4f036bc2ea58586130f6f39ee6ed" + integrity sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-to-string@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz#a4f15e682849326dd211c97129c94b0c3e76527c" + integrity sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-to-text@^4.0.0, hast-util-to-text@^4.0.1, hast-util-to-text@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz#57b676931e71bf9cb852453678495b3080bfae3e" + integrity sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + hast-util-is-element "^3.0.0" + unist-util-find-after "^5.0.0" + +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== + dependencies: + "@types/hast" "^3.0.0" + +hastscript@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-8.0.0.tgz#4ef795ec8dee867101b9f23cc830d4baf4fd781a" + integrity sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^4.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + +hastscript@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.0.tgz#2b76b9aa3cba8bf6d5280869f6f6f7165c230763" + integrity sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^4.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + +html-escaper@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-3.0.3.tgz#4d336674652beb1dcbc29ef6b6ba7f6be6fdfed6" + integrity sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ== + +html-void-elements@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== + +html-whitespace-sensitive-tag-names@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz#c35edd28205f3bf8c1fd03274608d60b923de5b2" + integrity sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA== + +htmlparser2@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23" + integrity sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.1.0" + entities "^4.5.0" + +http-cache-semantics@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +iconv-lite@0.6.3, iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +immutable@^5.0.2: + version "5.0.3" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1" + integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw== + +import-meta-resolve@^4.0.0, import-meta-resolve@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz#f9db8bead9fafa61adb811db77a2bf22c5399706" + integrity sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw== + +inline-style-parser@0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz#f4af5fe72e612839fcd453d989a586566d695f22" + integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q== + +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-buffer@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + +is-core-module@^2.16.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + +is-docker@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + +is-extendable@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + dependencies: + is-docker "^3.0.0" + +is-interactive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-2.0.0.tgz#40c57614593826da1100ade6059778d597f16e90" + integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + +is-unicode-supported@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714" + integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== + +is-unicode-supported@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz#09f0ab0de6d3744d48d265ebb98f65d11f2a9b3a" + integrity sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ== + +is-wsl@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" + integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== + dependencies: + is-inside-container "^1.0.0" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jiti@^1.21.6: + version "1.21.7" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.7.tgz#9dd81043424a3d28458b193d965f0d18a2300ba9" + integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.0, js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonc-parser@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342" + integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg== + +jsonc-parser@^3.0.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" + integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +kleur@^4.1.4, kleur@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" + integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== + +kolorist@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c" + integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ== + +lilconfig@^3.0.0, lilconfig@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4" + integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +linkedom@^0.16.11: + version "0.16.11" + resolved "https://registry.yarnpkg.com/linkedom/-/linkedom-0.16.11.tgz#2419649b178be5a627f6b8b2cfad521dfa726ae9" + integrity sha512-WgaTVbj7itjyXTsCvgerpneERXShcnNJF5VIV+/4SLtyRLN+HppPre/WDHRofAr2IpEuujSNgJbCBd5lMl6lRw== + dependencies: + css-select "^5.1.0" + cssom "^0.5.0" + html-escaper "^3.0.3" + htmlparser2 "^9.1.0" + uhyphen "^0.2.0" + +load-yaml-file@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/load-yaml-file/-/load-yaml-file-0.2.0.tgz#af854edaf2bea89346c07549122753c07372f64d" + integrity sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw== + dependencies: + graceful-fs "^4.1.5" + js-yaml "^3.13.0" + pify "^4.0.1" + strip-bom "^3.0.0" + +local-pkg@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.5.1.tgz#69658638d2a95287534d4c2fff757980100dbb6d" + integrity sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ== + dependencies: + mlly "^1.7.3" + pkg-types "^1.2.1" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash@4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-6.0.0.tgz#bb95e5f05322651cac30c0feb6404f9f2a8a9439" + integrity sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw== + dependencies: + chalk "^5.3.0" + is-unicode-supported "^1.3.0" + +longest-streak@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" + integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +magic-string@^0.30.14: + version "0.30.17" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" + integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + +magicast@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.5.tgz#8301c3c7d66704a0771eb1bad74274f0ec036739" + integrity sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ== + dependencies: + "@babel/parser" "^7.25.4" + "@babel/types" "^7.25.4" + source-map-js "^1.2.0" + +markdown-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-2.0.0.tgz#34bebc83e9938cae16e0e017e4a9814a8330d3c4" + integrity sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q== + +markdown-table@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.4.tgz#fe44d6d410ff9d6f2ea1797a3f60aa4d2b631c2a" + integrity sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw== + +mdast-util-definitions@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz#c1bb706e5e76bb93f9a09dd7af174002ae69ac24" + integrity sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + unist-util-visit "^5.0.0" + +mdast-util-directive@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz#3fb1764e705bbdf0afb0d3f889e4404c3e82561f" + integrity sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-visit-parents "^6.0.0" + +mdast-util-find-and-replace@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz#70a3174c894e14df722abf43bc250cbae44b11df" + integrity sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg== + dependencies: + "@types/mdast" "^4.0.0" + escape-string-regexp "^5.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +mdast-util-from-markdown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a" + integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" + +mdast-util-gfm-autolink-literal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz#abd557630337bd30a6d5a4bd8252e1c2dc0875d5" + integrity sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ== + dependencies: + "@types/mdast" "^4.0.0" + ccount "^2.0.0" + devlop "^1.0.0" + mdast-util-find-and-replace "^3.0.0" + micromark-util-character "^2.0.0" + +mdast-util-gfm-footnote@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz#25a1753c7d16db8bfd53cd84fe50562bd1e6d6a9" + integrity sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + +mdast-util-gfm-strikethrough@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz#d44ef9e8ed283ac8c1165ab0d0dfd058c2764c16" + integrity sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz#7a435fb6223a72b0862b33afbd712b6dae878d38" + integrity sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + markdown-table "^3.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm-task-list-item@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz#e68095d2f8a4303ef24094ab642e1047b991a936" + integrity sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz#3f2aecc879785c3cb6a81ff3a243dc11eca61095" + integrity sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw== + dependencies: + mdast-util-from-markdown "^2.0.0" + mdast-util-gfm-autolink-literal "^2.0.0" + mdast-util-gfm-footnote "^2.0.0" + mdast-util-gfm-strikethrough "^2.0.0" + mdast-util-gfm-table "^2.0.0" + mdast-util-gfm-task-list-item "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-expression@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096" + integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-jsx@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d" + integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +mdast-util-mdx@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz#792f9cf0361b46bee1fdf1ef36beac424a099c41" + integrity sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w== + dependencies: + mdast-util-from-markdown "^2.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" + integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-phrasing@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3" + integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + +mdast-util-to-hast@^13.0.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4" + integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +mdast-util-to-markdown@^2.0.0, mdast-util-to-markdown@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b" + integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + longest-streak "^3.0.0" + mdast-util-phrasing "^4.0.0" + mdast-util-to-string "^4.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-decode-string "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + +mdast-util-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" + +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromark-core-commonmark@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz#6a45bbb139e126b3f8b361a10711ccc7c6e15e93" + integrity sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w== + dependencies: + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-directive@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz#2eb61985d1995a7c1ff7621676a4f32af29409e8" + integrity sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + parse-entities "^4.0.0" + +micromark-extension-gfm-autolink-literal@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz#6286aee9686c4462c1e3552a9d505feddceeb935" + integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-footnote@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz#4dab56d4e398b9853f6fe4efac4fc9361f3e0750" + integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw== + dependencies: + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-strikethrough@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz#86106df8b3a692b5f6a92280d3879be6be46d923" + integrity sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-table@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz#5cadedfbb29fca7abf752447967003dc3b6583c9" + integrity sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-tagfilter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz#f26d8a7807b5985fba13cf61465b58ca5ff7dc57" + integrity sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-extension-gfm-task-list-item@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz#bcc34d805639829990ec175c3eea12bb5b781f2c" + integrity sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz#3e13376ab95dd7a5cfd0e29560dfe999657b3c5b" + integrity sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w== + dependencies: + micromark-extension-gfm-autolink-literal "^2.0.0" + micromark-extension-gfm-footnote "^2.0.0" + micromark-extension-gfm-strikethrough "^2.0.0" + micromark-extension-gfm-table "^2.0.0" + micromark-extension-gfm-tagfilter "^2.0.0" + micromark-extension-gfm-task-list-item "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-mdx-expression@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz#1407b9ce69916cf5e03a196ad9586889df25302a" + integrity sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + micromark-factory-mdx-expression "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-mdx-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz#5abb83da5ddc8e473a374453e6ea56fbd66b59ad" + integrity sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg== + dependencies: + "@types/acorn" "^4.0.0" + "@types/estree" "^1.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + micromark-factory-mdx-expression "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + vfile-message "^4.0.0" + +micromark-extension-mdx-md@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz#1d252881ea35d74698423ab44917e1f5b197b92d" + integrity sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ== + dependencies: + micromark-util-types "^2.0.0" + +micromark-extension-mdxjs-esm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz#de21b2b045fd2059bd00d36746081de38390d54a" + integrity sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-position-from-estree "^2.0.0" + vfile-message "^4.0.0" + +micromark-extension-mdxjs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz#b5a2e0ed449288f3f6f6c544358159557549de18" + integrity sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ== + dependencies: + acorn "^8.0.0" + acorn-jsx "^5.0.0" + micromark-extension-mdx-expression "^3.0.0" + micromark-extension-mdx-jsx "^3.0.0" + micromark-extension-mdx-md "^2.0.0" + micromark-extension-mdxjs-esm "^3.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-destination@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-label@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== + dependencies: + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-mdx-expression@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz#2afaa8ba6d5f63e0cead3e4dee643cad184ca260" + integrity sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-events-to-acorn "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-position-from-estree "^2.0.0" + vfile-message "^4.0.0" + +micromark-factory-space@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc" + integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-title@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-whitespace@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-character@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629" + integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-combine-extensions@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-decode-string@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2" + integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-encode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== + +micromark-util-events-to-acorn@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz#4275834f5453c088bd29cd72dfbf80e3327cec07" + integrity sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA== + dependencies: + "@types/acorn" "^4.0.0" + "@types/estree" "^1.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + estree-util-visit "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + vfile-message "^4.0.0" + +micromark-util-html-tag-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== + +micromark-util-normalize-identifier@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-resolve-all@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-subtokenize@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz#70ffb99a454bd8c913c8b709c3dc97baefb65f96" + integrity sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== + +micromark-util-types@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.1.tgz#a3edfda3022c6c6b55bfb049ef5b75d70af50709" + integrity sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ== + +micromark@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.1.tgz#294c2f12364759e5f9e925a767ae3dfde72223ff" + integrity sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromatch@^4.0.2, micromatch@^4.0.5, micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-function@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" + integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^4.0.0: + version "4.2.8" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" + integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mlly@^1.7.3: + version "1.7.4" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.7.4.tgz#3d7295ea2358ec7a271eaa5d000a0f84febe100f" + integrity sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw== + dependencies: + acorn "^8.14.0" + pathe "^2.0.1" + pkg-types "^1.3.0" + ufo "^1.5.4" + +mrmime@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4" + integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +muggle-string@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.4.1.tgz#3b366bd43b32f809dc20659534dd30e7c8a0d328" + integrity sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ== + +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nanoid@^3.3.8: + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== + +neotraverse@^0.6.18: + version "0.6.18" + resolved "https://registry.yarnpkg.com/neotraverse/-/neotraverse-0.6.18.tgz#abcb33dda2e8e713cf6321b29405e822230cdb30" + integrity sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA== + +nlcst-to-string@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-3.1.1.tgz#83b90f2e1ee2081e14701317efc26d3bbadc806e" + integrity sha512-63mVyqaqt0cmn2VcI2aH6kxe1rLAmSROqHMA0i4qqg1tidkfExgpb0FGMikMCn86mw5dFtBtEANfmSSK7TjNHw== + dependencies: + "@types/nlcst" "^1.0.0" + +nlcst-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz#05511e8461ebfb415952eb0b7e9a1a7d40471bd4" + integrity sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA== + dependencies: + "@types/nlcst" "^2.0.0" + +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +nth-check@^2.0.0, nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60" + integrity sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== + dependencies: + mimic-function "^5.0.0" + +oniguruma-to-es@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-1.0.0.tgz#6f7104cf0492e25d42b203d892b6d2d5f5f4d2e7" + integrity sha512-kihvp0O4lFwf5tZMkfanwQLIZ9ORe9OeOFgZonH0BQeThgwfJiaZFeOfvvJVnJIM9TiVmx0RDD35hUJDR0++rQ== + dependencies: + emoji-regex-xs "^1.0.0" + regex "^5.1.1" + regex-recursion "^5.1.1" + +ora@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-8.1.1.tgz#8efc8865e44c87e4b55468a47e80a03e678b0e54" + integrity sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw== + dependencies: + chalk "^5.3.0" + cli-cursor "^5.0.0" + cli-spinners "^2.9.2" + is-interactive "^2.0.0" + is-unicode-supported "^2.0.0" + log-symbols "^6.0.0" + stdin-discarder "^0.2.2" + string-width "^7.2.0" + strip-ansi "^7.1.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^6.1.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-6.2.0.tgz#c254d22ba6aeef441a3564c5e6c2f2da59268a0f" + integrity sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA== + dependencies: + yocto-queue "^1.1.1" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-queue@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-8.0.1.tgz#718b7f83836922ef213ddec263ff4223ce70bef8" + integrity sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA== + dependencies: + eventemitter3 "^5.0.1" + p-timeout "^6.1.2" + +p-timeout@^6.1.2: + version "6.1.4" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-6.1.4.tgz#418e1f4dd833fa96a2e3f532547dd2abdb08dbc2" + integrity sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg== + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + +package-manager-detector@^0.2.0: + version "0.2.8" + resolved "https://registry.yarnpkg.com/package-manager-detector/-/package-manager-detector-0.2.8.tgz#f5ace2dbd37666af54e5acec11bc37c8450f72d0" + integrity sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA== + +pagefind@^1.0.3: + version "1.3.0" + resolved "https://registry.yarnpkg.com/pagefind/-/pagefind-1.3.0.tgz#467560447dcc7bbe590f1b888cc8bc733bb377fa" + integrity sha512-8KPLGT5g9s+olKMRTU9LFekLizkVIu9tes90O1/aigJ0T5LmyPqTzGJrETnSw3meSYg58YH7JTzhTTW/3z6VAw== + optionalDependencies: + "@pagefind/darwin-arm64" "1.3.0" + "@pagefind/darwin-x64" "1.3.0" + "@pagefind/linux-arm64" "1.3.0" + "@pagefind/linux-x64" "1.3.0" + "@pagefind/windows-x64" "1.3.0" + +parse-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== + dependencies: + "@types/unist" "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" + +parse-latin@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/parse-latin/-/parse-latin-5.0.1.tgz#f3b4fac54d06f6a0501cf8b8ecfafa4cbb4f2f47" + integrity sha512-b/K8ExXaWC9t34kKeDV8kGXBkXZ1HCSAZRYE7HR14eA1GlXX5L8iWhs8USJNhQU9q5ci413jCKF0gOyovvyRBg== + dependencies: + nlcst-to-string "^3.0.0" + unist-util-modify-children "^3.0.0" + unist-util-visit-children "^2.0.0" + +parse-latin@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/parse-latin/-/parse-latin-7.0.0.tgz#8dfacac26fa603f76417f36233fc45602a323e1d" + integrity sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ== + dependencies: + "@types/nlcst" "^2.0.0" + "@types/unist" "^3.0.0" + nlcst-to-string "^4.0.0" + unist-util-modify-children "^4.0.0" + unist-util-visit-children "^3.0.0" + vfile "^6.0.0" + +parse5-htmlparser2-tree-adapter@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz#b5a806548ed893a43e24ccb42fbb78069311e81b" + integrity sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g== + dependencies: + domhandler "^5.0.3" + parse5 "^7.0.0" + +parse5-parser-stream@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz#d7c20eadc37968d272e2c02660fff92dd27e60e1" + integrity sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow== + dependencies: + parse5 "^7.0.0" + +parse5@^7.0.0, parse5@^7.1.2: + version "7.2.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a" + integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ== + dependencies: + entities "^4.5.0" + +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +pathe@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.1.tgz#ee1e6965c5ccfc98dc5a4b366a6ba6dd624a33d6" + integrity sha512-6jpjMpOth5S9ITVu5clZ7NOgHNsv5vRQdheL9ztp2vZmM6fRbLvyua1tiBIL4lk8SAe3ARzeXEly6siXCjDHDw== + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + +picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pirates@^4.0.1: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-types@^1.2.1, pkg-types@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.3.0.tgz#53d915eb99485798c554ad8eb2dc2af7c03006eb" + integrity sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg== + dependencies: + confbox "^0.1.8" + mlly "^1.7.3" + pathe "^1.1.2" + +postcss-import@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" + integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-js@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" + integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== + dependencies: + camelcase-css "^2.0.1" + +postcss-load-config@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3" + integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ== + dependencies: + lilconfig "^3.0.0" + yaml "^2.3.4" + +postcss-nested@^6.0.1, postcss-nested@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131" + integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ== + dependencies: + postcss-selector-parser "^6.1.1" + +postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.4.38, postcss@^8.4.43, postcss@^8.4.47, postcss@^8.4.49: + version "8.5.0" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.0.tgz#15244b9fd65f809b2819682456f0e7e1e30c145b" + integrity sha512-27VKOqrYfPncKA2NrFOVhP5MGAfHKLYn/Q0mz9cNQyRAKYi3VNHwYU2qKKqPCqgBmeeJ0uAFB56NumXZ5ZReXg== + dependencies: + nanoid "^3.3.8" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +preferred-pm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/preferred-pm/-/preferred-pm-4.0.0.tgz#6b256a44d39181fb3829b3abbd9ea2ead6db082b" + integrity sha512-gYBeFTZLu055D8Vv3cSPox/0iTPtkzxpLroSYYA7WXgRi31WCJ51Uyl8ZiPeUUjyvs2MBzK+S8v9JVUgHU/Sqw== + dependencies: + find-up-simple "^1.0.0" + find-yarn-workspace-root2 "1.2.16" + which-pm "^3.0.0" + +prettier-plugin-astro@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/prettier-plugin-astro/-/prettier-plugin-astro-0.13.0.tgz#0e1ff91caae17cad5b9493eb55bbd7114515bc5b" + integrity sha512-5HrJNnPmZqTUNoA97zn4gNQv9BgVhv+et03314WpQ9H9N8m2L9OSV798olwmG2YLXPl1iSstlJCR1zB3x5xG4g== + dependencies: + "@astrojs/compiler" "^1.5.5" + prettier "^3.0.0" + sass-formatter "^0.7.6" + +prettier@2.8.7: + version "2.8.7" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450" + integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw== + +prettier@^3.0.0, prettier@^3.2.5: + version "3.4.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.4.2.tgz#a5ce1fb522a588bf2b78ca44c6e6fe5aa5a2b13f" + integrity sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ== + +prismjs@^1.29.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" + integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== + +prompts@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +property-information@^6.0.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" + integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pump@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8" + integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + +readdirp@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.1.tgz#bd115327129672dc47f87408f05df9bd9ca3ef55" + integrity sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +recma-build-jsx@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz#c02f29e047e103d2fab2054954e1761b8ea253c4" + integrity sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew== + dependencies: + "@types/estree" "^1.0.0" + estree-util-build-jsx "^3.0.0" + vfile "^6.0.0" + +recma-jsx@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-jsx/-/recma-jsx-1.0.0.tgz#f7bef02e571a49d6ba3efdfda8e2efab48dbe3aa" + integrity sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q== + dependencies: + acorn-jsx "^5.0.0" + estree-util-to-js "^2.0.0" + recma-parse "^1.0.0" + recma-stringify "^1.0.0" + unified "^11.0.0" + +recma-parse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-parse/-/recma-parse-1.0.0.tgz#c351e161bb0ab47d86b92a98a9d891f9b6814b52" + integrity sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ== + dependencies: + "@types/estree" "^1.0.0" + esast-util-from-js "^2.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +recma-stringify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-stringify/-/recma-stringify-1.0.0.tgz#54632030631e0c7546136ff9ef8fde8e7b44f130" + integrity sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g== + dependencies: + "@types/estree" "^1.0.0" + estree-util-to-js "^2.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +regex-recursion@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/regex-recursion/-/regex-recursion-5.1.1.tgz#5a73772d18adbf00f57ad097bf54171b39d78f8b" + integrity sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w== + dependencies: + regex "^5.1.1" + regex-utilities "^2.3.0" + +regex-utilities@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/regex-utilities/-/regex-utilities-2.3.0.tgz#87163512a15dce2908cf079c8960d5158ff43280" + integrity sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng== + +regex@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/regex/-/regex-5.1.1.tgz#cf798903f24d6fe6e531050a36686e082b29bd03" + integrity sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw== + dependencies: + regex-utilities "^2.3.0" + +rehype-expressive-code@^0.35.6: + version "0.35.6" + resolved "https://registry.yarnpkg.com/rehype-expressive-code/-/rehype-expressive-code-0.35.6.tgz#aacae31a68664b4055b4c969a69992787ace5307" + integrity sha512-pPdE+pRcRw01kxMOwHQjuRxgwlblZt5+wAc3w2aPGgmcnn57wYjn07iKO7zaznDxYVxMYVvYlnL+R3vWFQS4Gw== + dependencies: + expressive-code "^0.35.6" + +rehype-format@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/rehype-format/-/rehype-format-5.0.1.tgz#e255e59bed0c062156aaf51c16fad5a521a1f5c8" + integrity sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ== + dependencies: + "@types/hast" "^3.0.0" + hast-util-format "^1.0.0" + +rehype-parse@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-9.0.1.tgz#9993bda129acc64c417a9d3654a7be38b2a94c20" + integrity sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag== + dependencies: + "@types/hast" "^3.0.0" + hast-util-from-html "^2.0.0" + unified "^11.0.0" + +rehype-raw@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-7.0.0.tgz#59d7348fd5dbef3807bbaa1d443efd2dd85ecee4" + integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww== + dependencies: + "@types/hast" "^3.0.0" + hast-util-raw "^9.0.0" + vfile "^6.0.0" + +rehype-recma@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rehype-recma/-/rehype-recma-1.0.0.tgz#d68ef6344d05916bd96e25400c6261775411aa76" + integrity sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + hast-util-to-estree "^3.0.0" + +rehype-stringify@^10.0.0, rehype-stringify@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-10.0.1.tgz#2ec1ebc56c6aba07905d3b4470bdf0f684f30b75" + integrity sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA== + dependencies: + "@types/hast" "^3.0.0" + hast-util-to-html "^9.0.0" + unified "^11.0.0" + +rehype@^13.0.1, rehype@^13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/rehype/-/rehype-13.0.2.tgz#ab0b3ac26573d7b265a0099feffad450e4cf1952" + integrity sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A== + dependencies: + "@types/hast" "^3.0.0" + rehype-parse "^9.0.0" + rehype-stringify "^10.0.0" + unified "^11.0.0" + +remark-directive@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remark-directive/-/remark-directive-3.0.0.tgz#34452d951b37e6207d2e2a4f830dc33442923268" + integrity sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-directive "^3.0.0" + micromark-extension-directive "^3.0.0" + unified "^11.0.0" + +remark-gfm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-4.0.0.tgz#aea777f0744701aa288b67d28c43565c7e8c35de" + integrity sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-gfm "^3.0.0" + micromark-extension-gfm "^3.0.0" + remark-parse "^11.0.0" + remark-stringify "^11.0.0" + unified "^11.0.0" + +remark-mdx@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-3.1.0.tgz#f979be729ecb35318fa48e2135c1169607a78343" + integrity sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA== + dependencies: + mdast-util-mdx "^3.0.0" + micromark-extension-mdxjs "^3.0.0" + +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" + +remark-rehype@^11.0.0, remark-rehype@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.1.tgz#f864dd2947889a11997c0a2667cd6b38f685bca7" + integrity sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + mdast-util-to-hast "^13.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +remark-smartypants@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/remark-smartypants/-/remark-smartypants-2.1.0.tgz#afd26d8ff40def346c6516e38b46994449fb2efe" + integrity sha512-qoF6Vz3BjU2tP6OfZqHOvCU0ACmu/6jhGaINSQRI9mM7wCxNQTKB3JUAN4SVoN2ybElEDTxBIABRep7e569iJw== + dependencies: + retext "^8.1.0" + retext-smartypants "^5.2.0" + unist-util-visit "^5.0.0" + +remark-smartypants@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/remark-smartypants/-/remark-smartypants-3.0.2.tgz#cbaf2b39624c78fcbd6efa224678c1d2e9bc1dfb" + integrity sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA== + dependencies: + retext "^9.0.0" + retext-smartypants "^6.0.0" + unified "^11.0.4" + unist-util-visit "^5.0.0" + +remark-stringify@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-11.0.0.tgz#4c5b01dd711c269df1aaae11743eb7e2e7636fd3" + integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-to-markdown "^2.0.0" + unified "^11.0.0" + +request-light@^0.5.7: + version "0.5.8" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.8.tgz#8bf73a07242b9e7b601fac2fa5dc22a094abcc27" + integrity sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg== + +request-light@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.7.0.tgz#885628bb2f8040c26401ebf258ec51c4ae98ac2a" + integrity sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve@^1.1.7, resolve@^1.22.8: + version "1.22.10" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" + integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + dependencies: + is-core-module "^2.16.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-5.1.0.tgz#0766d95699efacb14150993f55baf0953ea1ebe7" + integrity sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== + dependencies: + onetime "^7.0.0" + signal-exit "^4.1.0" + +retext-latin@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/retext-latin/-/retext-latin-3.1.0.tgz#72b0176af2c69a373fd0d37eadd3924418bb3a89" + integrity sha512-5MrD1tuebzO8ppsja5eEu+ZbBeUNCjoEarn70tkXOS7Bdsdf6tNahsv2bY0Z8VooFF6cw7/6S+d3yI/TMlMVVQ== + dependencies: + "@types/nlcst" "^1.0.0" + parse-latin "^5.0.0" + unherit "^3.0.0" + unified "^10.0.0" + +retext-latin@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/retext-latin/-/retext-latin-4.0.0.tgz#d02498aa1fd39f1bf00e2ff59b1384c05d0c7ce3" + integrity sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA== + dependencies: + "@types/nlcst" "^2.0.0" + parse-latin "^7.0.0" + unified "^11.0.0" + +retext-smartypants@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/retext-smartypants/-/retext-smartypants-5.2.0.tgz#da9cb79cc60f36aa33a20a462dfc663bec0068b4" + integrity sha512-Do8oM+SsjrbzT2UNIKgheP0hgUQTDDQYyZaIY3kfq0pdFzoPk+ZClYJ+OERNXveog4xf1pZL4PfRxNoVL7a/jw== + dependencies: + "@types/nlcst" "^1.0.0" + nlcst-to-string "^3.0.0" + unified "^10.0.0" + unist-util-visit "^4.0.0" + +retext-smartypants@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/retext-smartypants/-/retext-smartypants-6.2.0.tgz#4e852c2974cf2cfa253eeec427c97efc43b5d158" + integrity sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ== + dependencies: + "@types/nlcst" "^2.0.0" + nlcst-to-string "^4.0.0" + unist-util-visit "^5.0.0" + +retext-stringify@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/retext-stringify/-/retext-stringify-3.1.0.tgz#46ed45e077bfc4a8334977f6c2d6611e1d36263a" + integrity sha512-767TLOaoXFXyOnjx/EggXlb37ZD2u4P1n0GJqVdpipqACsQP+20W+BNpMYrlJkq7hxffnFk+jc6mAK9qrbuB8w== + dependencies: + "@types/nlcst" "^1.0.0" + nlcst-to-string "^3.0.0" + unified "^10.0.0" + +retext-stringify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/retext-stringify/-/retext-stringify-4.0.0.tgz#501d5440bd4d121e351c7c509f8507de9611e159" + integrity sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA== + dependencies: + "@types/nlcst" "^2.0.0" + nlcst-to-string "^4.0.0" + unified "^11.0.0" + +retext@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/retext/-/retext-8.1.0.tgz#c43437fb84cd46285ad240a9279142e239bada8d" + integrity sha512-N9/Kq7YTn6ZpzfiGW45WfEGJqFf1IM1q8OsRa1CGzIebCJBNCANDRmOrholiDRGKo/We7ofKR4SEvcGAWEMD3Q== + dependencies: + "@types/nlcst" "^1.0.0" + retext-latin "^3.0.0" + retext-stringify "^3.0.0" + unified "^10.0.0" + +retext@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/retext/-/retext-9.0.0.tgz#ab5cd72836894167b0ca6ae70fdcfaa166267f7a" + integrity sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA== + dependencies: + "@types/nlcst" "^2.0.0" + retext-latin "^4.0.0" + retext-stringify "^4.0.0" + unified "^11.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rollup@^4.20.0: + version "4.30.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.30.1.tgz#d5c3d066055259366cdc3eb6f1d051c5d6afaf74" + integrity sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w== + dependencies: + "@types/estree" "1.0.6" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.30.1" + "@rollup/rollup-android-arm64" "4.30.1" + "@rollup/rollup-darwin-arm64" "4.30.1" + "@rollup/rollup-darwin-x64" "4.30.1" + "@rollup/rollup-freebsd-arm64" "4.30.1" + "@rollup/rollup-freebsd-x64" "4.30.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.30.1" + "@rollup/rollup-linux-arm-musleabihf" "4.30.1" + "@rollup/rollup-linux-arm64-gnu" "4.30.1" + "@rollup/rollup-linux-arm64-musl" "4.30.1" + "@rollup/rollup-linux-loongarch64-gnu" "4.30.1" + "@rollup/rollup-linux-powerpc64le-gnu" "4.30.1" + "@rollup/rollup-linux-riscv64-gnu" "4.30.1" + "@rollup/rollup-linux-s390x-gnu" "4.30.1" + "@rollup/rollup-linux-x64-gnu" "4.30.1" + "@rollup/rollup-linux-x64-musl" "4.30.1" + "@rollup/rollup-win32-arm64-msvc" "4.30.1" + "@rollup/rollup-win32-ia32-msvc" "4.30.1" + "@rollup/rollup-win32-x64-msvc" "4.30.1" + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +s.color@0.0.15: + version "0.0.15" + resolved "https://registry.yarnpkg.com/s.color/-/s.color-0.0.15.tgz#6b32cd22d8dba95703a5122ddede2020a1560186" + integrity sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sass-formatter@^0.7.6: + version "0.7.9" + resolved "https://registry.yarnpkg.com/sass-formatter/-/sass-formatter-0.7.9.tgz#cf77e02e98f81daabd91b185192144d29fc04ca5" + integrity sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw== + dependencies: + suf-log "^2.5.3" + +sass@^1.76.0: + version "1.83.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.83.1.tgz#dee1ab94b47a6f9993d3195d36f556bcbda64846" + integrity sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA== + dependencies: + chokidar "^4.0.0" + immutable "^5.0.2" + source-map-js ">=0.6.2 <2.0.0" + optionalDependencies: + "@parcel/watcher" "^2.4.1" + +sax@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + +section-matter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" + integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA== + dependencies: + extend-shallow "^2.0.1" + kind-of "^6.0.0" + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.8, semver@^7.6.2, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +sharp@^0.33.3: + version "0.33.5" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.5.tgz#13e0e4130cc309d6a9497596715240b2ec0c594e" + integrity sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw== + dependencies: + color "^4.2.3" + detect-libc "^2.0.3" + semver "^7.6.3" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.33.5" + "@img/sharp-darwin-x64" "0.33.5" + "@img/sharp-libvips-darwin-arm64" "1.0.4" + "@img/sharp-libvips-darwin-x64" "1.0.4" + "@img/sharp-libvips-linux-arm" "1.0.5" + "@img/sharp-libvips-linux-arm64" "1.0.4" + "@img/sharp-libvips-linux-s390x" "1.0.4" + "@img/sharp-libvips-linux-x64" "1.0.4" + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + "@img/sharp-linux-arm" "0.33.5" + "@img/sharp-linux-arm64" "0.33.5" + "@img/sharp-linux-s390x" "0.33.5" + "@img/sharp-linux-x64" "0.33.5" + "@img/sharp-linuxmusl-arm64" "0.33.5" + "@img/sharp-linuxmusl-x64" "0.33.5" + "@img/sharp-wasm32" "0.33.5" + "@img/sharp-win32-ia32" "0.33.5" + "@img/sharp-win32-x64" "0.33.5" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shiki@^1.1.2, shiki@^1.1.7, shiki@^1.22.0, shiki@^1.23.1: + version "1.26.2" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.26.2.tgz#b4b0434dca20e22150fbcf89b7cd59297df3d74f" + integrity sha512-iP7u2NA9A6JwRRCkIUREEX2cMhlYV5EBmbbSlfSRvPThwca8HBRbVkWuNWW+kw9+i6BSUZqqG6YeUs5dC2SjZw== + dependencies: + "@shikijs/core" "1.26.2" + "@shikijs/engine-javascript" "1.26.2" + "@shikijs/engine-oniguruma" "1.26.2" + "@shikijs/langs" "1.26.2" + "@shikijs/themes" "1.26.2" + "@shikijs/types" "1.26.2" + "@shikijs/vscode-textmate" "^10.0.1" + "@types/hast" "^3.0.4" + +signal-exit@^4.0.1, signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +sitemap@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-8.0.0.tgz#eb6ea48f95787cd680b83683c555d6f6b5a903fd" + integrity sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A== + dependencies: + "@types/node" "^17.0.5" + "@types/sax" "^1.2.1" + arg "^5.0.0" + sax "^1.2.4" + +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.2.0, source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +source-map@^0.7.0, source-map@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +space-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stdin-discarder@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be" + integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== + +stream-replace-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/stream-replace-string/-/stream-replace-string-2.0.0.tgz#e49fd584bd1c633613e010bc73b9db49cb5024ad" + integrity sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w== + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string-width@^7.0.0, string-width@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + dependencies: + emoji-regex "^10.3.0" + get-east-asian-width "^1.0.0" + strip-ansi "^7.1.0" + +stringify-entities@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3" + integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== + dependencies: + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1, strip-ansi@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" + integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g== + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +style-to-object@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.8.tgz#67a29bca47eaa587db18118d68f9d95955e81292" + integrity sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g== + dependencies: + inline-style-parser "0.2.4" + +sucrase@^3.35.0: + version "3.35.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" + integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + glob "^10.3.10" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + +suf-log@^2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/suf-log/-/suf-log-2.5.3.tgz#0919a7fceea532a99b578c97814c4e335b2d64d1" + integrity sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow== + dependencies: + s.color "0.0.15" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +svgo@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.3.2.tgz#ad58002652dffbb5986fc9716afe52d869ecbda8" + integrity sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^5.1.0" + css-tree "^2.3.1" + css-what "^6.1.0" + csso "^5.0.5" + picocolors "^1.0.0" + +tailwindcss@^3.4.3: + version "3.4.17" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.17.tgz#ae8406c0f96696a631c790768ff319d46d5e5a63" + integrity sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.6.0" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.3.2" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.21.6" + lilconfig "^3.1.3" + micromatch "^4.0.8" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.1.1" + postcss "^8.4.47" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.2" + postcss-nested "^6.2.0" + postcss-selector-parser "^6.1.2" + resolve "^1.22.8" + sucrase "^3.35.0" + +tar@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +tinyexec@^0.3.0, tinyexec@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== + +trough@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" + integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== + +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + +tsconfck@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.4.tgz#de01a15334962e2feb526824339b51be26712229" + integrity sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ== + +tslib@^2.4.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-fest@^4.21.0: + version "4.32.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.32.0.tgz#55bacdd6f2cf1392b7e9cde894e9b1d726807e97" + integrity sha512-rfgpoi08xagF3JSdtJlCwMq9DGNDE0IMh3Mkpc1wUypg9vPi786AiqeBBKcqvIkq42azsBM85N490fyZjeUftw== + +typesafe-path@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/typesafe-path/-/typesafe-path-0.2.2.tgz#91a436681b2f514badb114061b6a5e5c2b8943b1" + integrity sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA== + +typescript-auto-import-cache@^0.3.3: + version "0.3.5" + resolved "https://registry.yarnpkg.com/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.5.tgz#402f98995037734ef3fc208180331adfd5e495fc" + integrity sha512-fAIveQKsoYj55CozUiBoj4b/7WpN0i4o74wiGY5JVUEoD0XiqDk1tJqTEjgzL2/AizKQrXxyRosSebyDzBZKjw== + dependencies: + semver "^7.3.8" + +typescript@^5.4.5: + version "5.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e" + integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== + +ufo@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754" + integrity sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ== + +uhyphen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/uhyphen/-/uhyphen-0.2.0.tgz#8fdf0623314486e020a3c00ee5cc7a12fe722b81" + integrity sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA== + +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + +undici@^6.19.5: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.21.0.tgz#4b3d3afaef984e07b48e7620c34ed8a285ed4cd4" + integrity sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw== + +unherit@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/unherit/-/unherit-3.0.1.tgz#65b98bb7cb58cee755d7ec699a49e9e8ff172e23" + integrity sha512-akOOQ/Yln8a2sgcLj4U0Jmx0R5jpIg2IUyRrWOzmEbjBtGzBdHtSeFKgoEcoH4KYIG/Pb8GQ/BwtYm0GCq1Sqg== + +unified@^10.0.0: + version "10.1.2" + resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.2.tgz#b1d64e55dafe1f0b98bb6c719881103ecf6c86df" + integrity sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q== + dependencies: + "@types/unist" "^2.0.0" + bail "^2.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^5.0.0" + +unified@^11.0.0, unified@^11.0.4, unified@^11.0.5: + version "11.0.5" + resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== + dependencies: + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" + +unist-util-find-after@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz#3fccc1b086b56f34c8b798e1ff90b5c54468e896" + integrity sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + +unist-util-is@^5.0.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.2.1.tgz#b74960e145c18dcb6226bc57933597f5486deae9" + integrity sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-is@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424" + integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-modify-children@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-3.1.1.tgz#c4018b86441aa3b54b3edff1151d0aa062384c82" + integrity sha512-yXi4Lm+TG5VG+qvokP6tpnk+r1EPwyYL04JWDxLvgvPV40jANh7nm3udk65OOWquvbMDe+PL9+LmkxDpTv/7BA== + dependencies: + "@types/unist" "^2.0.0" + array-iterate "^2.0.0" + +unist-util-modify-children@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz#981d6308e887b005d1f491811d3cbcc254b315e9" + integrity sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw== + dependencies: + "@types/unist" "^3.0.0" + array-iterate "^2.0.0" + +unist-util-position-from-estree@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz#d94da4df596529d1faa3de506202f0c9a23f2200" + integrity sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-remove-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz#fea68a25658409c9460408bc6b4991b965b52163" + integrity sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q== + dependencies: + "@types/unist" "^3.0.0" + unist-util-visit "^5.0.0" + +unist-util-stringify-position@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz#03ad3348210c2d930772d64b489580c13a7db39d" + integrity sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-visit-children@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unist-util-visit-children/-/unist-util-visit-children-2.0.2.tgz#0f00a5caff567074568da2d89c54b5ee4a8c5440" + integrity sha512-+LWpMFqyUwLGpsQxpumsQ9o9DG2VGLFrpz+rpVXYIEdPy57GSy5HioC0g3bg/8WP9oCLlapQtklOzQ8uLS496Q== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-visit-children@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz#4bced199b71d7f3c397543ea6cc39e7a7f37dc7e" + integrity sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-visit-parents@^5.1.1: + version "5.1.3" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb" + integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + +unist-util-visit-parents@^6.0.0, unist-util-visit-parents@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815" + integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + +unist-util-visit@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz#125a42d1eb876283715a3cb5cceaa531828c72e2" + integrity sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + unist-util-visit-parents "^5.1.1" + +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +update-browserslist-db@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580" + integrity sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +vfile-location@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3" + integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg== + dependencies: + "@types/unist" "^3.0.0" + vfile "^6.0.0" + +vfile-message@^3.0.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.4.tgz#15a50816ae7d7c2d1fa87090a7f9f96612b59dea" + integrity sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^3.0.0" + +vfile-message@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181" + integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + +vfile@^5.0.0: + version "5.3.7" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.7.tgz#de0677e6683e3380fafc46544cfe603118826ab7" + integrity sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^3.0.0" + vfile-message "^3.0.0" + +vfile@^6.0.0, vfile@^6.0.1, vfile@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.0.0" + vfile-message "^4.0.0" + +vite@^5.4.11: + version "5.4.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5" + integrity sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + +vitefu@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-1.0.5.tgz#eab501e07da167bbb68e957685823e6b425e7ce2" + integrity sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA== + +volar-service-css@0.0.62: + version "0.0.62" + resolved "https://registry.yarnpkg.com/volar-service-css/-/volar-service-css-0.0.62.tgz#4866091bd217b548470f24706f53feba7a57345b" + integrity sha512-JwNyKsH3F8PuzZYuqPf+2e+4CTU8YoyUHEHVnoXNlrLe7wy9U3biomZ56llN69Ris7TTy/+DEX41yVxQpM4qvg== + dependencies: + vscode-css-languageservice "^6.3.0" + vscode-languageserver-textdocument "^1.0.11" + vscode-uri "^3.0.8" + +volar-service-emmet@0.0.62: + version "0.0.62" + resolved "https://registry.yarnpkg.com/volar-service-emmet/-/volar-service-emmet-0.0.62.tgz#451c60f73cb2c84c5ce2e4b70901de09c38920af" + integrity sha512-U4dxWDBWz7Pi4plpbXf4J4Z/ss6kBO3TYrACxWNsE29abu75QzVS0paxDDhI6bhqpbDFXlpsDhZ9aXVFpnfGRQ== + dependencies: + "@emmetio/css-parser" "^0.4.0" + "@emmetio/html-matcher" "^1.3.0" + "@vscode/emmet-helper" "^2.9.3" + vscode-uri "^3.0.8" + +volar-service-html@0.0.62: + version "0.0.62" + resolved "https://registry.yarnpkg.com/volar-service-html/-/volar-service-html-0.0.62.tgz#791c2b05f5e97bc4c35fac4dbae1cb57cc66570a" + integrity sha512-Zw01aJsZRh4GTGUjveyfEzEqpULQUdQH79KNEiKVYHZyuGtdBRYCHlrus1sueSNMxwwkuF5WnOHfvBzafs8yyQ== + dependencies: + vscode-html-languageservice "^5.3.0" + vscode-languageserver-textdocument "^1.0.11" + vscode-uri "^3.0.8" + +volar-service-prettier@0.0.62: + version "0.0.62" + resolved "https://registry.yarnpkg.com/volar-service-prettier/-/volar-service-prettier-0.0.62.tgz#aae89a26b27ad048f4452482888533ed7123f5c4" + integrity sha512-h2yk1RqRTE+vkYZaI9KYuwpDfOQRrTEMvoHol0yW4GFKc75wWQRrb5n/5abDrzMPrkQbSip8JH2AXbvrRtYh4w== + dependencies: + vscode-uri "^3.0.8" + +volar-service-typescript-twoslash-queries@0.0.62: + version "0.0.62" + resolved "https://registry.yarnpkg.com/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.62.tgz#9bf63fcf89688fae12f492168d3b447be3bdf385" + integrity sha512-KxFt4zydyJYYI0kFAcWPTh4u0Ha36TASPZkAnNY784GtgajerUqM80nX/W1d0wVhmcOFfAxkVsf/Ed+tiYU7ng== + dependencies: + vscode-uri "^3.0.8" + +volar-service-typescript@0.0.62: + version "0.0.62" + resolved "https://registry.yarnpkg.com/volar-service-typescript/-/volar-service-typescript-0.0.62.tgz#d99c42e2e08742f27b9bb186180dac93ce730ee6" + integrity sha512-p7MPi71q7KOsH0eAbZwPBiKPp9B2+qrdHAd6VY5oTo9BUXatsOAdakTm9Yf0DUj6uWBAaOT01BSeVOPwucMV1g== + dependencies: + path-browserify "^1.0.1" + semver "^7.6.2" + typescript-auto-import-cache "^0.3.3" + vscode-languageserver-textdocument "^1.0.11" + vscode-nls "^5.2.0" + vscode-uri "^3.0.8" + +volar-service-yaml@0.0.62: + version "0.0.62" + resolved "https://registry.yarnpkg.com/volar-service-yaml/-/volar-service-yaml-0.0.62.tgz#143aaab83cae8c7c82f68502100d300ec687b59e" + integrity sha512-k7gvv7sk3wa+nGll3MaSKyjwQsJjIGCHFjVkl3wjaSP2nouKyn9aokGmqjrl39mi88Oy49giog2GkZH526wjig== + dependencies: + vscode-uri "^3.0.8" + yaml-language-server "~1.15.0" + +vscode-css-languageservice@^6.3.0: + version "6.3.2" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.3.2.tgz#dd54161776f1663fa514a1b5df0d3990bda604bb" + integrity sha512-GEpPxrUTAeXWdZWHev1OJU9lz2Q2/PPBxQ2TIRmLGvQiH3WZbqaNoute0n0ewxlgtjzTW3AKZT+NHySk5Rf4Eg== + dependencies: + "@vscode/l10n" "^0.0.18" + vscode-languageserver-textdocument "^1.0.12" + vscode-languageserver-types "3.17.5" + vscode-uri "^3.0.8" + +vscode-html-languageservice@^5.2.0, vscode-html-languageservice@^5.3.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.3.1.tgz#93cac1cebb42165b52a15220f02c47d1320fc43a" + integrity sha512-ysUh4hFeW/WOWz/TO9gm08xigiSsV/FOAZ+DolgJfeLftna54YdmZ4A+lIn46RbdO3/Qv5QHTn1ZGqmrXQhZyA== + dependencies: + "@vscode/l10n" "^0.0.18" + vscode-languageserver-textdocument "^1.0.12" + vscode-languageserver-types "^3.17.5" + vscode-uri "^3.0.8" + +vscode-json-languageservice@4.1.8: + version "4.1.8" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz#397a39238d496e3e08a544a8b93df2cd13347d0c" + integrity sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg== + dependencies: + jsonc-parser "^3.0.0" + vscode-languageserver-textdocument "^1.0.1" + vscode-languageserver-types "^3.16.0" + vscode-nls "^5.0.0" + vscode-uri "^3.0.2" + +vscode-jsonrpc@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" + integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== + +vscode-jsonrpc@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9" + integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== + +vscode-languageserver-protocol@3.16.0: + version "3.16.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" + integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== + dependencies: + vscode-jsonrpc "6.0.0" + vscode-languageserver-types "3.16.0" + +vscode-languageserver-protocol@3.17.5, vscode-languageserver-protocol@^3.17.5: + version "3.17.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea" + integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg== + dependencies: + vscode-jsonrpc "8.2.0" + vscode-languageserver-types "3.17.5" + +vscode-languageserver-textdocument@^1.0.1, vscode-languageserver-textdocument@^1.0.11, vscode-languageserver-textdocument@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz#457ee04271ab38998a093c68c2342f53f6e4a631" + integrity sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA== + +vscode-languageserver-types@3.16.0: + version "3.16.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" + integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== + +vscode-languageserver-types@3.17.5, vscode-languageserver-types@^3.15.1, vscode-languageserver-types@^3.16.0, vscode-languageserver-types@^3.17.5: + version "3.17.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a" + integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg== + +vscode-languageserver@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0" + integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw== + dependencies: + vscode-languageserver-protocol "3.16.0" + +vscode-languageserver@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz#500aef82097eb94df90d008678b0b6b5f474015b" + integrity sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g== + dependencies: + vscode-languageserver-protocol "3.17.5" + +vscode-nls@^5.0.0, vscode-nls@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.2.0.tgz#3cb6893dd9bd695244d8a024bdf746eea665cc3f" + integrity sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng== + +vscode-uri@^3.0.2, vscode-uri@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f" + integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw== + +web-namespaces@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" + integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== + +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + +which-pm-runs@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.1.0.tgz#35ccf7b1a0fce87bd8b92a478c9d045785d3bf35" + integrity sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA== + +which-pm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/which-pm/-/which-pm-3.0.0.tgz#78f2088b345a63cec9f838b390332fb1e680221f" + integrity sha512-ysVYmw6+ZBhx3+ZkcPwRuJi38ZOTLJJ33PSHaitLxSKUMsh0LkKd0nC69zZCwt5D+AYUcMK2hhw4yWny20vSGg== + dependencies: + load-yaml-file "^0.2.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +widest-line@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-5.0.0.tgz#b74826a1e480783345f0cd9061b49753c9da70d0" + integrity sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA== + dependencies: + string-width "^7.0.0" + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrap-ansi@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e" + integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q== + dependencies: + ansi-styles "^6.2.1" + string-width "^7.0.0" + strip-ansi "^7.1.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +xxhash-wasm@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz#ffe7f0b98220a4afac171e3fb9b6d1f8771f015e" + integrity sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml-language-server@~1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/yaml-language-server/-/yaml-language-server-1.15.0.tgz#3bd36f1f7fd74e63b591e5148df992c7327be05a" + integrity sha512-N47AqBDCMQmh6mBLmI6oqxryHRzi33aPFPsJhYy3VTUGCdLHYjGh4FZzpUjRlphaADBBkDmnkM/++KNIOHi5Rw== + dependencies: + ajv "^8.11.0" + lodash "4.17.21" + request-light "^0.5.7" + vscode-json-languageservice "4.1.8" + vscode-languageserver "^7.0.0" + vscode-languageserver-textdocument "^1.0.1" + vscode-languageserver-types "^3.16.0" + vscode-nls "^5.0.0" + vscode-uri "^3.0.2" + yaml "2.2.2" + optionalDependencies: + prettier "2.8.7" + +yaml@2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073" + integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA== + +yaml@^2.3.4, yaml@^2.5.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98" + integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + +yocto-queue@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110" + integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g== + +zod-to-json-schema@^3.23.5: + version "3.24.1" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz#f08c6725091aadabffa820ba8d50c7ab527f227a" + integrity sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w== + +zod-to-ts@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/zod-to-ts/-/zod-to-ts-1.2.0.tgz#873a2fd8242d7b649237be97e0c64d7954ae0c51" + integrity sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA== + +zod@^3.23.8: + version "3.24.1" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee" + integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A== + +zwitch@^2.0.0, zwitch@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==