@studiometa/js-toolkit 4.0.0-alpha.1 → 4.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/Base.d.ts +0 -2
- package/dist/Base.js +1 -2
- package/dist/Base.js.map +1 -1
- package/dist/attribute-namespaces.d.ts +0 -2
- package/dist/attribute-namespaces.js +1 -2
- package/dist/attributes.d.ts +0 -2
- package/dist/attributes.js +1 -2
- package/dist/children-watchers.d.ts +0 -2
- package/dist/children-watchers.js +1 -2
- package/dist/component-brand.d.ts +0 -2
- package/dist/component-brand.js +1 -2
- package/dist/component-declarations.d.ts +0 -2
- package/dist/component-declarations.js +1 -2
- package/dist/context-subscription.d.ts +0 -2
- package/dist/context-subscription.js +1 -2
- package/dist/context.d.ts +0 -2
- package/dist/context.js +1 -2
- package/dist/decorators.d.ts +0 -2
- package/dist/decorators.js +1 -2
- package/dist/diagnostic-contract.d.ts +0 -2
- package/dist/diagnostic-contract.js +1 -2
- package/dist/diagnostics.d.ts +0 -2
- package/dist/diagnostics.js +1 -2
- package/dist/document-order.d.ts +0 -2
- package/dist/document-order.js +1 -2
- package/dist/dom-mutations.d.ts +0 -2
- package/dist/dom-mutations.js +1 -2
- package/dist/events.d.ts +0 -2
- package/dist/events.js +1 -2
- package/dist/group.d.ts +0 -2
- package/dist/group.js +1 -2
- package/dist/instances.d.ts +0 -2
- package/dist/instances.js +1 -2
- package/dist/manifest.d.ts +0 -2
- package/dist/manifest.js +1 -2
- package/dist/mount-strategies.d.ts +0 -2
- package/dist/mount-strategies.js +1 -2
- package/dist/negotiated-events.d.ts +0 -2
- package/dist/negotiated-events.js +1 -2
- package/dist/protocol-symbols.d.ts +0 -2
- package/dist/protocol-symbols.js +1 -2
- package/dist/registry.d.ts +0 -2
- package/dist/registry.js +1 -2
- package/dist/responsive-options.d.ts +0 -2
- package/dist/responsive-options.js +1 -2
- package/dist/scheduler.d.ts +0 -2
- package/dist/scheduler.js +1 -2
- package/dist/scheduler.js.map +1 -1
- package/dist/services/breakpoint.d.ts +0 -2
- package/dist/services/breakpoint.js +1 -2
- package/dist/services/drag.d.ts +0 -2
- package/dist/services/drag.js +1 -2
- package/dist/services/in-view.d.ts +0 -2
- package/dist/services/in-view.js +1 -2
- package/dist/services/key.d.ts +0 -2
- package/dist/services/key.js +1 -2
- package/dist/services/media.d.ts +0 -2
- package/dist/services/media.js +1 -2
- package/dist/services/mixin.d.ts +0 -2
- package/dist/services/mixin.js +1 -2
- package/dist/services/mutation.d.ts +0 -2
- package/dist/services/mutation.js +1 -2
- package/dist/services/pointer.d.ts +0 -2
- package/dist/services/pointer.js +1 -2
- package/dist/services/raf.d.ts +0 -2
- package/dist/services/raf.js +1 -2
- package/dist/services/resize.d.ts +0 -2
- package/dist/services/resize.js +1 -2
- package/dist/services/scroll-progress-offset.d.ts +0 -2
- package/dist/services/scroll-progress-offset.js +1 -2
- package/dist/services/scroll-progress.d.ts +0 -2
- package/dist/services/scroll-progress.js +1 -2
- package/dist/services/scroll.d.ts +0 -2
- package/dist/services/scroll.js +1 -2
- package/dist/services/service.d.ts +0 -2
- package/dist/services/service.js +1 -2
- package/dist/services/toggle.d.ts +0 -2
- package/dist/services/toggle.js +1 -2
- package/dist/services/until.d.ts +0 -2
- package/dist/services/until.js +1 -2
- package/dist/shared-runtime.d.ts +0 -2
- package/dist/shared-runtime.js +1 -2
- package/dist/storage/createStorage.d.ts +0 -2
- package/dist/storage/createStorage.js +1 -2
- package/dist/storage/index.d.ts +0 -2
- package/dist/storage/index.js +1 -2
- package/dist/storage/providers.d.ts +0 -2
- package/dist/storage/providers.js +1 -2
- package/dist/storage/serializers.d.ts +0 -2
- package/dist/storage/serializers.js +1 -2
- package/dist/storage/sync.d.ts +0 -2
- package/dist/storage/sync.js +1 -2
- package/dist/storage/types.d.ts +0 -2
- package/dist/subpaths/BREAKPOINTS.js +1 -2
- package/dist/subpaths/Base.js +1 -2
- package/dist/subpaths/DIAGNOSTICS.js +1 -2
- package/dist/subpaths/DRAG_MODES.js +1 -2
- package/dist/subpaths/EVENTS.js +1 -2
- package/dist/subpaths/MOUNT_ATTRIBUTE.js +1 -2
- package/dist/subpaths/SWAP_MODES.js +1 -2
- package/dist/subpaths/children.js +1 -2
- package/dist/subpaths/component.js +1 -2
- package/dist/subpaths/createContext.js +1 -2
- package/dist/subpaths/createFallbackProvider.js +1 -2
- package/dist/subpaths/createGroup.js +1 -2
- package/dist/subpaths/createLocalStorage.js +1 -2
- package/dist/subpaths/createMemoryStorageProvider.js +1 -2
- package/dist/subpaths/createService.js +1 -2
- package/dist/subpaths/createServiceMixin.js +1 -2
- package/dist/subpaths/createSessionStorage.js +1 -2
- package/dist/subpaths/createStorage.js +1 -2
- package/dist/subpaths/createUrlSearchParamsInHashProvider.js +1 -2
- package/dist/subpaths/createUrlSearchParamsInHashStorage.js +1 -2
- package/dist/subpaths/createUrlSearchParamsProvider.js +1 -2
- package/dist/subpaths/createUrlSearchParamsStorage.js +1 -2
- package/dist/subpaths/defaultScheduler.js +1 -2
- package/dist/subpaths/defineManifest.js +1 -2
- package/dist/subpaths/domUpdate.js +1 -2
- package/dist/subpaths/emitExtendable.js +1 -2
- package/dist/subpaths/fromMetaGlob.js +1 -2
- package/dist/subpaths/fromWebpackContext.js +1 -2
- package/dist/subpaths/getBreakpoints.js +1 -2
- package/dist/subpaths/getInstance.js +1 -2
- package/dist/subpaths/getInstances.js +1 -2
- package/dist/subpaths/getMountedInstances.js +1 -2
- package/dist/subpaths/getUnmountedInstances.js +1 -2
- package/dist/subpaths/inject.js +1 -2
- package/dist/subpaths/injectContext.js +1 -2
- package/dist/subpaths/injectContextSync.js +1 -2
- package/dist/subpaths/jsonSerializer.js +1 -2
- package/dist/subpaths/localStorageProvider.js +1 -2
- package/dist/subpaths/memoryStorageProvider.js +1 -2
- package/dist/subpaths/namespaceQualifier.js +1 -2
- package/dist/subpaths/nextFrame.js +1 -2
- package/dist/subpaths/on.js +1 -2
- package/dist/subpaths/perTarget.js +1 -2
- package/dist/subpaths/provide.js +1 -2
- package/dist/subpaths/provideContext.js +1 -2
- package/dist/subpaths/provideRootContext.js +1 -2
- package/dist/subpaths/read.js +1 -2
- package/dist/subpaths/registerComponent.js +1 -2
- package/dist/subpaths/registerComponents.js +1 -2
- package/dist/subpaths/registerManifest.js +1 -2
- package/dist/subpaths/reportDiagnostic.js +1 -2
- package/dist/subpaths/sessionStorageProvider.js +1 -2
- package/dist/subpaths/setBreakpoints.js +1 -2
- package/dist/subpaths/signal.js +1 -2
- package/dist/subpaths/subscribeContext.js +1 -2
- package/dist/subpaths/swap.js +1 -2
- package/dist/subpaths/toggle.js +1 -2
- package/dist/subpaths/until.js +1 -2
- package/dist/subpaths/urlSearchParamsInHashProvider.js +1 -2
- package/dist/subpaths/urlSearchParamsProvider.js +1 -2
- package/dist/subpaths/useBreakpoint.js +1 -2
- package/dist/subpaths/useDrag.js +1 -2
- package/dist/subpaths/useInView.js +1 -2
- package/dist/subpaths/useKey.js +1 -2
- package/dist/subpaths/useMediaQuery.js +1 -2
- package/dist/subpaths/useMutation.js +1 -2
- package/dist/subpaths/usePointer.js +1 -2
- package/dist/subpaths/usePrefersReducedMotion.js +1 -2
- package/dist/subpaths/useRaf.js +1 -2
- package/dist/subpaths/useResize.js +1 -2
- package/dist/subpaths/useScroll.js +1 -2
- package/dist/subpaths/useScrollProgress.js +1 -2
- package/dist/subpaths/useWindowScroll.js +1 -2
- package/dist/subpaths/useWindowSize.js +1 -2
- package/dist/subpaths/utils/DEFAULT_DAMP_FACTOR.js +1 -2
- package/dist/subpaths/utils/INERTIA_FRAME.js +1 -2
- package/dist/subpaths/utils/MAX_SPRING_RATIO.js +1 -2
- package/dist/subpaths/utils/SCROLL_ALIGNMENTS.js +1 -2
- package/dist/subpaths/utils/SCROLL_AXES.js +1 -2
- package/dist/subpaths/utils/TRANSFORM_PROPS.js +1 -2
- package/dist/subpaths/utils/TRANSITION_OPTIONS.js +1 -2
- package/dist/subpaths/utils/camelCase.js +1 -2
- package/dist/subpaths/utils/capitalize.js +1 -2
- package/dist/subpaths/utils/clamp.js +1 -2
- package/dist/subpaths/utils/clamp01.js +1 -2
- package/dist/subpaths/utils/clampDampFactor.js +1 -2
- package/dist/subpaths/utils/createEaseInOut.js +1 -2
- package/dist/subpaths/utils/createEaseOut.js +1 -2
- package/dist/subpaths/utils/createElement.js +1 -2
- package/dist/subpaths/utils/createRange.js +1 -2
- package/dist/subpaths/utils/damp.js +1 -2
- package/dist/subpaths/utils/debounce.js +1 -2
- package/dist/subpaths/utils/decayOver.js +1 -2
- package/dist/subpaths/utils/deepmerge.js +1 -2
- package/dist/subpaths/utils/easeInCirc.js +1 -2
- package/dist/subpaths/utils/easeInCubic.js +1 -2
- package/dist/subpaths/utils/easeInExpo.js +1 -2
- package/dist/subpaths/utils/easeInOutCirc.js +1 -2
- package/dist/subpaths/utils/easeInOutCubic.js +1 -2
- package/dist/subpaths/utils/easeInOutExpo.js +1 -2
- package/dist/subpaths/utils/easeInOutQuad.js +1 -2
- package/dist/subpaths/utils/easeInOutQuart.js +1 -2
- package/dist/subpaths/utils/easeInOutQuint.js +1 -2
- package/dist/subpaths/utils/easeInOutSine.js +1 -2
- package/dist/subpaths/utils/easeInQuad.js +1 -2
- package/dist/subpaths/utils/easeInQuart.js +1 -2
- package/dist/subpaths/utils/easeInQuint.js +1 -2
- package/dist/subpaths/utils/easeInSine.js +1 -2
- package/dist/subpaths/utils/easeLinear.js +1 -2
- package/dist/subpaths/utils/easeOutCirc.js +1 -2
- package/dist/subpaths/utils/easeOutCubic.js +1 -2
- package/dist/subpaths/utils/easeOutExpo.js +1 -2
- package/dist/subpaths/utils/easeOutQuad.js +1 -2
- package/dist/subpaths/utils/easeOutQuart.js +1 -2
- package/dist/subpaths/utils/easeOutQuint.js +1 -2
- package/dist/subpaths/utils/easeOutSine.js +1 -2
- package/dist/subpaths/utils/enterTransition.js +1 -2
- package/dist/subpaths/utils/fold.js +1 -2
- package/dist/subpaths/utils/getOffsetSizes.js +1 -2
- package/dist/subpaths/utils/historyPush.js +1 -2
- package/dist/subpaths/utils/historyReplace.js +1 -2
- package/dist/subpaths/utils/inertiaDecay.js +1 -2
- package/dist/subpaths/utils/inertiaFinalValue.js +1 -2
- package/dist/subpaths/utils/inertiaStep.js +1 -2
- package/dist/subpaths/utils/inertiaTimeConstant.js +1 -2
- package/dist/subpaths/utils/isBoolean.js +1 -2
- package/dist/subpaths/utils/isDefined.js +1 -2
- package/dist/subpaths/utils/isFunction.js +1 -2
- package/dist/subpaths/utils/isNull.js +1 -2
- package/dist/subpaths/utils/isNumber.js +1 -2
- package/dist/subpaths/utils/isObject.js +1 -2
- package/dist/subpaths/utils/isString.js +1 -2
- package/dist/subpaths/utils/kebabCase.js +1 -2
- package/dist/subpaths/utils/leaveTransition.js +1 -2
- package/dist/subpaths/utils/lerp.js +1 -2
- package/dist/subpaths/utils/loadImage.js +1 -2
- package/dist/subpaths/utils/loadLink.js +1 -2
- package/dist/subpaths/utils/loadScript.js +1 -2
- package/dist/subpaths/utils/lockScroll.js +1 -2
- package/dist/subpaths/utils/lowerCase.js +1 -2
- package/dist/subpaths/utils/map.js +1 -2
- package/dist/subpaths/utils/matrix.js +1 -2
- package/dist/subpaths/utils/mean.js +1 -2
- package/dist/subpaths/utils/memo.js +1 -2
- package/dist/subpaths/utils/noop.js +1 -2
- package/dist/subpaths/utils/noopValue.js +1 -2
- package/dist/subpaths/utils/objectToURLSearchParams.js +1 -2
- package/dist/subpaths/utils/pascalCase.js +1 -2
- package/dist/subpaths/utils/random.js +1 -2
- package/dist/subpaths/utils/randomInt.js +1 -2
- package/dist/subpaths/utils/randomItem.js +1 -2
- package/dist/subpaths/utils/round.js +1 -2
- package/dist/subpaths/utils/saveActiveElement.js +1 -2
- package/dist/subpaths/utils/scrollPosition.js +1 -2
- package/dist/subpaths/utils/scrollTo.js +1 -2
- package/dist/subpaths/utils/selectorFor.js +1 -2
- package/dist/subpaths/utils/setClassesOrStyles.js +1 -2
- package/dist/subpaths/utils/smoothTo.js +1 -2
- package/dist/subpaths/utils/snakeCase.js +1 -2
- package/dist/subpaths/utils/spring.js +1 -2
- package/dist/subpaths/utils/throttle.js +1 -2
- package/dist/subpaths/utils/transform.js +1 -2
- package/dist/subpaths/utils/transition.js +1 -2
- package/dist/subpaths/utils/trapFocus.js +1 -2
- package/dist/subpaths/utils/untrapFocus.js +1 -2
- package/dist/subpaths/utils/upperCase.js +1 -2
- package/dist/subpaths/utils/wait.js +1 -2
- package/dist/subpaths/utils/withLeadingCharacters.js +1 -2
- package/dist/subpaths/utils/withLeadingSlash.js +1 -2
- package/dist/subpaths/utils/withTrailingCharacters.js +1 -2
- package/dist/subpaths/utils/withTrailingSlash.js +1 -2
- package/dist/subpaths/utils/withoutLeadingCharacters.js +1 -2
- package/dist/subpaths/utils/withoutLeadingCharactersRecursive.js +1 -2
- package/dist/subpaths/utils/withoutLeadingSlash.js +1 -2
- package/dist/subpaths/utils/withoutTrailingCharacters.js +1 -2
- package/dist/subpaths/utils/withoutTrailingCharactersRecursive.js +1 -2
- package/dist/subpaths/utils/withoutTrailingSlash.js +1 -2
- package/dist/subpaths/utils/wrap.js +1 -2
- package/dist/subpaths/viewTransition.js +1 -2
- package/dist/subpaths/warn.js +1 -2
- package/dist/subpaths/watchAttributeNamespace.js +1 -2
- package/dist/subpaths/watchAttributes.js +1 -2
- package/dist/subpaths/whenDOMSettled.js +1 -2
- package/dist/subpaths/withDrag.js +1 -2
- package/dist/subpaths/withInView.js +1 -2
- package/dist/subpaths/withKey.js +1 -2
- package/dist/subpaths/withMutation.js +1 -2
- package/dist/subpaths/withPointer.js +1 -2
- package/dist/subpaths/withRaf.js +1 -2
- package/dist/subpaths/withResize.js +1 -2
- package/dist/subpaths/withScroll.js +1 -2
- package/dist/subpaths/withScrollProgress.js +1 -2
- package/dist/subpaths/write.js +1 -2
- package/dist/swap.d.ts +0 -2
- package/dist/swap.js +1 -2
- package/dist/test/index.d.ts +0 -2
- package/dist/test/index.js +1 -2
- package/dist/utils/deepmerge.d.ts +0 -2
- package/dist/utils/deepmerge.js +1 -2
- package/dist/utils/dom.d.ts +0 -2
- package/dist/utils/dom.js +1 -2
- package/dist/utils/easings.d.ts +0 -2
- package/dist/utils/easings.js +1 -2
- package/dist/utils/focus.d.ts +0 -2
- package/dist/utils/focus.js +1 -2
- package/dist/utils/history.d.ts +0 -2
- package/dist/utils/history.js +1 -2
- package/dist/utils/is.d.ts +0 -2
- package/dist/utils/is.js +1 -2
- package/dist/utils/load.d.ts +0 -2
- package/dist/utils/load.js +1 -2
- package/dist/utils/maths.d.ts +0 -2
- package/dist/utils/maths.js +1 -2
- package/dist/utils/memo.d.ts +0 -2
- package/dist/utils/memo.js +1 -2
- package/dist/utils/noop.d.ts +0 -2
- package/dist/utils/noop.js +1 -2
- package/dist/utils/random.d.ts +0 -2
- package/dist/utils/random.js +1 -2
- package/dist/utils/scroll-lock.d.ts +0 -2
- package/dist/utils/scroll-lock.js +1 -2
- package/dist/utils/scrollTo.d.ts +0 -2
- package/dist/utils/scrollTo.js +1 -2
- package/dist/utils/selectors.d.ts +0 -2
- package/dist/utils/selectors.js +1 -2
- package/dist/utils/smoothTo.d.ts +0 -2
- package/dist/utils/smoothTo.js +1 -2
- package/dist/utils/strings.d.ts +0 -2
- package/dist/utils/strings.js +1 -2
- package/dist/utils/timing.d.ts +0 -2
- package/dist/utils/timing.js +1 -2
- package/dist/utils/transform.d.ts +0 -2
- package/dist/utils/transform.js +1 -2
- package/dist/utils/transition.d.ts +0 -2
- package/dist/utils/transition.js +1 -2
- package/dist/viewTransition.d.ts +0 -2
- package/dist/viewTransition.js +1 -2
- package/package.json +6 -3
- package/skills/studiometa-js-toolkit/SKILL.md +87 -0
- package/skills/studiometa-js-toolkit/references/api/configuration.md +169 -0
- package/skills/studiometa-js-toolkit/references/api/context/createContext.md +46 -0
- package/skills/studiometa-js-toolkit/references/api/context/createGroup.md +67 -0
- package/skills/studiometa-js-toolkit/references/api/context/index.md +69 -0
- package/skills/studiometa-js-toolkit/references/api/context/injectContext.md +58 -0
- package/skills/studiometa-js-toolkit/references/api/context/injectContextSync.md +44 -0
- package/skills/studiometa-js-toolkit/references/api/context/provideContext.md +61 -0
- package/skills/studiometa-js-toolkit/references/api/context/provideRootContext.md +42 -0
- package/skills/studiometa-js-toolkit/references/api/context/signal.md +85 -0
- package/skills/studiometa-js-toolkit/references/api/context/subscribeContext.md +71 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/children.md +92 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/component.md +75 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/index.md +41 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/inject.md +73 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/on.md +149 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/provide.md +77 -0
- package/skills/studiometa-js-toolkit/references/api/decorators/read-write.md +93 -0
- package/skills/studiometa-js-toolkit/references/api/diagnostics/DIAGNOSTICS.md +137 -0
- package/skills/studiometa-js-toolkit/references/api/diagnostics/EVENTS.md +45 -0
- package/skills/studiometa-js-toolkit/references/api/diagnostics/index.md +58 -0
- package/skills/studiometa-js-toolkit/references/api/diagnostics/reportDiagnostic.md +67 -0
- package/skills/studiometa-js-toolkit/references/api/diagnostics/warn.md +54 -0
- package/skills/studiometa-js-toolkit/references/api/dom/breakpoints.md +81 -0
- package/skills/studiometa-js-toolkit/references/api/dom/domUpdate.md +70 -0
- package/skills/studiometa-js-toolkit/references/api/dom/emitExtendable.md +68 -0
- package/skills/studiometa-js-toolkit/references/api/dom/index.md +26 -0
- package/skills/studiometa-js-toolkit/references/api/dom/swap.md +105 -0
- package/skills/studiometa-js-toolkit/references/api/dom/watchAttributeNamespace.md +96 -0
- package/skills/studiometa-js-toolkit/references/api/dom/watchAttributes.md +56 -0
- package/skills/studiometa-js-toolkit/references/api/dom/whenDOMSettled.md +41 -0
- package/skills/studiometa-js-toolkit/references/api/html/data-component.md +105 -0
- package/skills/studiometa-js-toolkit/references/api/html/data-mount.md +69 -0
- package/skills/studiometa-js-toolkit/references/api/html/data-option.md +106 -0
- package/skills/studiometa-js-toolkit/references/api/html/data-ref.md +63 -0
- package/skills/studiometa-js-toolkit/references/api/html/index.md +32 -0
- package/skills/studiometa-js-toolkit/references/api/index.md +73 -0
- package/skills/studiometa-js-toolkit/references/api/instance-events.md +75 -0
- package/skills/studiometa-js-toolkit/references/api/instance-methods.md +267 -0
- package/skills/studiometa-js-toolkit/references/api/instance-properties.md +90 -0
- package/skills/studiometa-js-toolkit/references/api/methods-hooks-events.md +149 -0
- package/skills/studiometa-js-toolkit/references/api/methods-hooks-lifecycle.md +90 -0
- package/skills/studiometa-js-toolkit/references/api/methods-hooks-options.md +96 -0
- package/skills/studiometa-js-toolkit/references/api/methods-hooks-services.md +164 -0
- package/skills/studiometa-js-toolkit/references/api/registry/defineManifest.md +57 -0
- package/skills/studiometa-js-toolkit/references/api/registry/fromMetaGlob.md +33 -0
- package/skills/studiometa-js-toolkit/references/api/registry/fromWebpackContext.md +37 -0
- package/skills/studiometa-js-toolkit/references/api/registry/getInstance.md +52 -0
- package/skills/studiometa-js-toolkit/references/api/registry/getInstances.md +48 -0
- package/skills/studiometa-js-toolkit/references/api/registry/getMountedInstances.md +39 -0
- package/skills/studiometa-js-toolkit/references/api/registry/getUnmountedInstances.md +46 -0
- package/skills/studiometa-js-toolkit/references/api/registry/index.md +49 -0
- package/skills/studiometa-js-toolkit/references/api/registry/registerComponent.md +93 -0
- package/skills/studiometa-js-toolkit/references/api/registry/registerComponents.md +37 -0
- package/skills/studiometa-js-toolkit/references/api/registry/registerManifest.md +72 -0
- package/skills/studiometa-js-toolkit/references/api/scheduler/defaultScheduler.md +104 -0
- package/skills/studiometa-js-toolkit/references/api/scheduler/index.md +27 -0
- package/skills/studiometa-js-toolkit/references/api/scheduler/nextFrame.md +31 -0
- package/skills/studiometa-js-toolkit/references/api/scheduler/viewTransition.md +53 -0
- package/skills/studiometa-js-toolkit/references/api/services/createService.md +95 -0
- package/skills/studiometa-js-toolkit/references/api/services/createServiceMixin.md +81 -0
- package/skills/studiometa-js-toolkit/references/api/services/index.md +94 -0
- package/skills/studiometa-js-toolkit/references/api/services/mixins.md +148 -0
- package/skills/studiometa-js-toolkit/references/api/services/perTarget.md +65 -0
- package/skills/studiometa-js-toolkit/references/api/services/toggle.md +80 -0
- package/skills/studiometa-js-toolkit/references/api/services/until.md +44 -0
- package/skills/studiometa-js-toolkit/references/api/services/useBreakpoint.md +42 -0
- package/skills/studiometa-js-toolkit/references/api/services/useDrag.md +119 -0
- package/skills/studiometa-js-toolkit/references/api/services/useInView.md +64 -0
- package/skills/studiometa-js-toolkit/references/api/services/useKey.md +105 -0
- package/skills/studiometa-js-toolkit/references/api/services/useMediaQuery.md +45 -0
- package/skills/studiometa-js-toolkit/references/api/services/useMutation.md +69 -0
- package/skills/studiometa-js-toolkit/references/api/services/usePointer.md +91 -0
- package/skills/studiometa-js-toolkit/references/api/services/usePrefersReducedMotion.md +42 -0
- package/skills/studiometa-js-toolkit/references/api/services/useRaf.md +91 -0
- package/skills/studiometa-js-toolkit/references/api/services/useResize.md +60 -0
- package/skills/studiometa-js-toolkit/references/api/services/useScroll.md +88 -0
- package/skills/studiometa-js-toolkit/references/api/services/useScrollProgress.md +88 -0
- package/skills/studiometa-js-toolkit/references/api/services/useWindowScroll.md +25 -0
- package/skills/studiometa-js-toolkit/references/api/services/useWindowSize.md +25 -0
- package/skills/studiometa-js-toolkit/references/api/storage/createStorage.md +126 -0
- package/skills/studiometa-js-toolkit/references/api/storage/index.md +33 -0
- package/skills/studiometa-js-toolkit/references/api/storage/presets.md +58 -0
- package/skills/studiometa-js-toolkit/references/api/storage/providers.md +109 -0
- package/skills/studiometa-js-toolkit/references/api/test/index.md +214 -0
- package/skills/studiometa-js-toolkit/references/guide/concepts/attribute-grammar.md +93 -0
- package/skills/studiometa-js-toolkit/references/guide/concepts/mutation-engine.md +71 -0
- package/skills/studiometa-js-toolkit/references/guide/concepts/philosophy.md +78 -0
- package/skills/studiometa-js-toolkit/references/guide/concepts/the-registry.md +110 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/autoloading.md +104 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/handling-diagnostics.md +104 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/mount-strategies.md +95 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/scheduling-work.md +124 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/sharing-state.md +218 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/swapping-content.md +92 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/testing-components.md +137 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/typing-components.md +190 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/using-decorators.md +168 -0
- package/skills/studiometa-js-toolkit/references/guide/going-further/using-storage.md +125 -0
- package/skills/studiometa-js-toolkit/references/guide/index.md +163 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/installation.md +144 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/lifecycle-hooks.md +174 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/managing-components.md +229 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/managing-options.md +235 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/managing-refs.md +174 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/using-services.md +218 -0
- package/skills/studiometa-js-toolkit/references/guide/introduction/working-with-events.md +251 -0
- package/skills/studiometa-js-toolkit/references/guide/migration/v3-to-v4.md +373 -0
- package/skills/studiometa-js-toolkit/references/utils/css.md +98 -0
- package/skills/studiometa-js-toolkit/references/utils/dom.md +54 -0
- package/skills/studiometa-js-toolkit/references/utils/easings.md +217 -0
- package/skills/studiometa-js-toolkit/references/utils/focus.md +84 -0
- package/skills/studiometa-js-toolkit/references/utils/history.md +69 -0
- package/skills/studiometa-js-toolkit/references/utils/index.md +53 -0
- package/skills/studiometa-js-toolkit/references/utils/is.md +84 -0
- package/skills/studiometa-js-toolkit/references/utils/load.md +59 -0
- package/skills/studiometa-js-toolkit/references/utils/math.md +129 -0
- package/skills/studiometa-js-toolkit/references/utils/motion.md +218 -0
- package/skills/studiometa-js-toolkit/references/utils/objects.md +68 -0
- package/skills/studiometa-js-toolkit/references/utils/scroll.md +128 -0
- package/skills/studiometa-js-toolkit/references/utils/strings.md +176 -0
- package/skills/studiometa-js-toolkit/references/utils/timing.md +111 -0
- package/skills/studiometa-js-toolkit/references/utils/transitions.md +122 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Mount strategies
|
|
2
|
+
|
|
3
|
+
A mount strategy decides **when** the registry mounts an instance. It constructs nothing and it wraps nothing.
|
|
4
|
+
|
|
5
|
+
## The seven values
|
|
6
|
+
|
|
7
|
+
| Strategy | Mounts when | Reversible |
|
|
8
|
+
| ------------------------ | -------------------------------------- | ---------- |
|
|
9
|
+
| `eager` (default) | the element enters the document | no |
|
|
10
|
+
| `visible[:<rootMargin>]` | it first intersects the viewport | no |
|
|
11
|
+
| `in-view[:<rootMargin>]` | it intersects the viewport | **yes** |
|
|
12
|
+
| `idle` | the main thread becomes idle | no |
|
|
13
|
+
| `interaction` | the user first aims at **it** | no |
|
|
14
|
+
| `interaction:page` | the user first interacts with the page | no |
|
|
15
|
+
| `media:<query>` | the query is not empty and it matches | **yes** |
|
|
16
|
+
|
|
17
|
+
**One-shot and reversible are separate values.** `visible` mounts once and stays. `in-view` mounts and unmounts on each crossing.
|
|
18
|
+
|
|
19
|
+
## Declaring one
|
|
20
|
+
|
|
21
|
+
A component declares its default in its config; any element overrides it with `data-mount`:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
import { Base } from '@studiometa/js-toolkit';
|
|
25
|
+
|
|
26
|
+
class Map extends Base {
|
|
27
|
+
static config = {
|
|
28
|
+
name: 'Map',
|
|
29
|
+
mountStrategy: 'visible',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<!-- uses the component default -->
|
|
36
|
+
<div data-component="Map"></div>
|
|
37
|
+
|
|
38
|
+
<!-- overridden per element -->
|
|
39
|
+
<div data-component="Map" data-mount="eager"></div>
|
|
40
|
+
<div data-component="Map" data-mount="visible:200px"></div>
|
|
41
|
+
<div data-component="Chat" data-mount="interaction:page"></div>
|
|
42
|
+
<div data-component="Parallax" data-mount="media:(prefers-reduced-motion: no-preference)"></div>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The resolution chain is:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
data-mount > manifest entry mountStrategy > config.mountStrategy > 'eager'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## The grammar
|
|
52
|
+
|
|
53
|
+
The accepted values are exactly `eager`, `visible`, `visible:`, `visible:<rootMargin>`, `in-view`, `in-view:`, `in-view:<rootMargin>`, `idle`, `interaction`, `interaction:`, `interaction:page` and `media:<non-empty query>`.
|
|
54
|
+
|
|
55
|
+
- A viewport suffix is passed straight through as `IntersectionObserverInit.rootMargin`.
|
|
56
|
+
- The empty suffix behaves as the bare strategy.
|
|
57
|
+
- `page` is the only scope the interaction strategy takes.
|
|
58
|
+
- There is **no** threshold, no root element, no JSON options and no second attribute.
|
|
59
|
+
|
|
60
|
+
An unknown value, an empty media query, or a `rootMargin` the browser refuses leaves the component **unmounted** and reports `component.invalid-mount-strategy` once. The inert controller stays current while the declaration does not change, so it is not reported again; a corrected attribute replaces it. A failed strategy cannot stop the rest of a reconciliation.
|
|
61
|
+
|
|
62
|
+
## `interaction` — intent on the element
|
|
63
|
+
|
|
64
|
+
`pointerenter`, `pointerdown` and `focusin`, bound to the element itself, so the component mounts **before** the click arrives. Two of the three bubble, so an interaction anywhere inside the subtree counts; `pointerenter` fires for the root element only.
|
|
65
|
+
|
|
66
|
+
## `interaction:page` — the visit, not the element
|
|
67
|
+
|
|
68
|
+
This is the deferred-widget case: a chat panel, an embed, a third-party script, where nothing about the element predicts the moment and any sign of a live user does.
|
|
69
|
+
|
|
70
|
+
- The events are the deliberate ones: `pointerdown`, `keydown` and `focusin`. Hovering is deliberately **not** one of them — aiming is intent for one element and noise for a document.
|
|
71
|
+
- **One listener set for the page**, in the shared runtime, not one per waiting element. They attach with the first waiting element and release on the first interaction.
|
|
72
|
+
- **They are captured**, so a handler that stops propagation cannot hide the visit.
|
|
73
|
+
- **The signal is a fact about the visit.** An element that arrives after the interaction mounts at once — but only when the strategy was already listening, since nothing observes a page that has asked for nothing.
|
|
74
|
+
|
|
75
|
+
## What a waiting component is
|
|
76
|
+
|
|
77
|
+
**A component that waits has no instance.** It is invisible to `$query()`, `$closest()`, `$watchChildren()` and `getInstances()`, and it announces nothing. There is nothing to guard against and nothing to check.
|
|
78
|
+
|
|
79
|
+
Once it has mounted and been stood down again — which only `in-view` and `media:` do — the instance exists and is unmounted, so it is what [`getUnmountedInstances()`](../../api/registry/getUnmountedInstances.md) answers.
|
|
80
|
+
|
|
81
|
+
## Lifetime
|
|
82
|
+
|
|
83
|
+
- **Several components on one element share that element's `data-mount`.** A component that needs its own policy declares it in its config.
|
|
84
|
+
- **Teardown follows the element.** A strategy is disposed when its element leaves the document. A move ends as an unmount and a mount of the same instance.
|
|
85
|
+
- **The attribute is live.** A change to `data-mount` disposes the old strategy and applies the final one. Controller identity guards a queued callback of a disposed strategy.
|
|
86
|
+
|
|
87
|
+
## What this replaces
|
|
88
|
+
|
|
89
|
+
The `withMountWhenInView`, `withMountOnMediaQuery` and `withMountWhenPrefersMotion` decorators of v3 are **deleted**. A decorator that wraps a constructor to decide whether to mount is the registry's job, and the registry does it without touching the class.
|
|
90
|
+
|
|
91
|
+
`data-load` and `loadStrategy` are gone too: deferring the import and deferring the mount are one decision. See [Autoloading](autoloading.md).
|
|
92
|
+
|
|
93
|
+
**`withInView` is a different thing**
|
|
94
|
+
|
|
95
|
+
[`withInView`](../../api/services/mixins.md) observes a component that is **already mounted** and calls `intersected()`. It does not replace the `visible` or `in-view` mount strategy.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# The scheduler
|
|
2
|
+
|
|
3
|
+
One frame-aligned scheduler is the clock of the framework. It replaces `domScheduler`, the `RafService` loop, `SmartQueue` and the view-transition scheduler of `@studiometa/ui`.
|
|
4
|
+
|
|
5
|
+
## The lanes
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
frame start (rAF)
|
|
9
|
+
1. tick — fan out to the subscribers of the clock
|
|
10
|
+
2. read — measure: layout reads only
|
|
11
|
+
3. write — mutate: DOM writes only
|
|
12
|
+
style / layout / paint
|
|
13
|
+
|
|
14
|
+
between frames, on its own turns
|
|
15
|
+
background — time-sliced lane: mount and update lifecycle work,
|
|
16
|
+
mutation-record processing, manifest loading
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- **Frame alignment.** One flush per frame, at rAF. Every read runs before every write, once, before paint.
|
|
20
|
+
- **No thrashing.** A `read` scheduled from a `write` runs in the **next** frame. A `write` scheduled from a `read` runs in the **same** frame.
|
|
21
|
+
- **Bounded phases.** Each queue array is swapped for an empty one when its phase starts, so a task scheduled into the running phase lands in the next frame's batch. The `write` batch is taken after the reads run.
|
|
22
|
+
- **Error isolation.** One try/catch per task. A task that throws is reported and dropped; the flush continues and the scheduler never deadlocks.
|
|
23
|
+
- **Queued execution only.** There is no synchronous escape.
|
|
24
|
+
|
|
25
|
+
## Reading and writing from a component
|
|
26
|
+
|
|
27
|
+
`this.$read(fn)` and `this.$write(fn)` tie tasks to the instance, and unmount cancels the pending ones:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
import { Base } from '@studiometa/js-toolkit';
|
|
31
|
+
|
|
32
|
+
class Reveal extends Base {
|
|
33
|
+
static config = { name: 'Reveal' };
|
|
34
|
+
|
|
35
|
+
async mounted() {
|
|
36
|
+
const box = await this.$read(() => this.$el.getBoundingClientRect()).promise;
|
|
37
|
+
await this.$write(() => {
|
|
38
|
+
this.$el.style.setProperty('--height', `${box?.height}px`);
|
|
39
|
+
}).promise;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Scheduling returns a cancelable **handle** whose promise resolves with the return value of the task:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { defaultScheduler } from '@studiometa/js-toolkit';
|
|
48
|
+
|
|
49
|
+
const el = document.body;
|
|
50
|
+
async function measure() {
|
|
51
|
+
const task = defaultScheduler.read(() => el.getBoundingClientRect());
|
|
52
|
+
const box = await task.promise;
|
|
53
|
+
task.cancel(); // idempotent
|
|
54
|
+
return box;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## The tick
|
|
59
|
+
|
|
60
|
+
`scheduler.tick(callback)` subscribes to the clock:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
import { defaultScheduler } from '@studiometa/js-toolkit';
|
|
64
|
+
|
|
65
|
+
const unsubscribe = defaultScheduler.tick(({ time, delta }) => {
|
|
66
|
+
console.log(time, delta);
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- Tick callbacks run at the **start** of the flush, before `read`, so what they schedule belongs to the same frame. A callback measures in `read` and the render function it returns mutates in `write`.
|
|
71
|
+
- **The subscription is the only handle, and it keeps the loop alive.** The scheduler requests the next frame when a queue inside the frame is not empty, or when a tick subscriber stays. There is no permanent rAF loop.
|
|
72
|
+
- Tick subscribers are not queued work, so `whenIdle()` ignores them.
|
|
73
|
+
- A tick callback that throws is reported and skipped, never unsubscribed.
|
|
74
|
+
- **`delta` is clamped** to `[1, 40]` ms, and the first tick after the loop wakes reports `1000/60`. `time` stays the raw rAF timestamp.
|
|
75
|
+
|
|
76
|
+
A component that needs the loop for part of a cycle uses [`toggle()`](../../api/services/toggle.md) rather than subscribing and unsubscribing by hand.
|
|
77
|
+
|
|
78
|
+
## The background lane
|
|
79
|
+
|
|
80
|
+
It posts its own turns through `scheduler.postTask({ priority: 'background' })`, and falls back to a `MessageChannel` message. Each turn runs a **5 ms slice** measured from the start of the drain, then gives the thread back and posts the next turn.
|
|
81
|
+
|
|
82
|
+
Background work alone never requests an animation frame. `whenIdle()` counts background tasks and resolves at the end of a background drain as well as at the end of a flush.
|
|
83
|
+
|
|
84
|
+
## `nextFrame()`
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
import { nextFrame } from '@studiometa/js-toolkit';
|
|
88
|
+
|
|
89
|
+
async function afterPaint() {
|
|
90
|
+
await nextFrame();
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## `afterWrite` is gone
|
|
95
|
+
|
|
96
|
+
rAF callbacks run before style, layout and paint, so **no phase inside the frame can read post-layout geometry**. Measure in the `read` phase of the next frame, or use a `ResizeObserver`.
|
|
97
|
+
|
|
98
|
+
## View transitions
|
|
99
|
+
|
|
100
|
+
`viewTransition(update)` is a standalone export with a progressive-enhancement contract: where the platform has no `startViewTransition`, the update simply runs.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { viewTransition } from '@studiometa/js-toolkit';
|
|
104
|
+
|
|
105
|
+
async function swapPanel(el: Element, html: string) {
|
|
106
|
+
await viewTransition(() => {
|
|
107
|
+
el.innerHTML = html;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- Updates queued in the **same flush** batch into one `startViewTransition()` call, so a backdrop and a panel animate as one transition. Each later batch is appended to one promise tail, so several flushes during one transition stay serialized.
|
|
113
|
+
- The scheduler flushes the pending `write` tasks before the snapshot. Writes scheduled inside the update callback run within the transition.
|
|
114
|
+
- The helper is standalone. `Base` has no view-transition method and no import of one.
|
|
115
|
+
|
|
116
|
+
It composes with a negotiated `domUpdate()`, where the ancestor chooses the lane because it knows whether the region animates:
|
|
117
|
+
|
|
118
|
+
| claim | effect |
|
|
119
|
+
| --------------------------------------------- | -------------------------------------------------------------------- |
|
|
120
|
+
| `wrap(viewTransition)` | the change plays as one batched native view transition |
|
|
121
|
+
| `wrap((apply) => this.$write(apply).promise)` | the change lands in the `write` phase, batched, cancelled on unmount |
|
|
122
|
+
| `wrap(motionView)` | any object with `update(mutate)` |
|
|
123
|
+
|
|
124
|
+
Neither runner is the default.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Shared state
|
|
2
|
+
|
|
3
|
+
Two components that must agree on something share it through **provide/inject** — the shape of Vue's `provide`/`inject`, with the mechanics of the WICG context protocol.
|
|
4
|
+
|
|
5
|
+
## A typed key
|
|
6
|
+
|
|
7
|
+
A key is a value, not a string, so keys cannot collide:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { createContext, type Signal } from '@studiometa/js-toolkit';
|
|
11
|
+
|
|
12
|
+
interface SliderApi {
|
|
13
|
+
state: Signal<{ index: number; total: number }>;
|
|
14
|
+
goNext(): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const SliderContext = createContext<SliderApi>('slider');
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The description is for debugging only. The identity of the key is what resolves.
|
|
21
|
+
|
|
22
|
+
## Providing
|
|
23
|
+
|
|
24
|
+
The coordinator provides what a control can ask for. **The value is provided as it is** — nothing is wrapped, so the type of the key is the contract from end to end:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { Base, createContext, signal, type Signal } from '@studiometa/js-toolkit';
|
|
28
|
+
|
|
29
|
+
interface SliderApi {
|
|
30
|
+
state: Signal<{ index: number; total: number }>;
|
|
31
|
+
goNext(): void;
|
|
32
|
+
}
|
|
33
|
+
const SliderContext = createContext<SliderApi>('slider');
|
|
34
|
+
class Slider extends Base {
|
|
35
|
+
static config = { name: 'Slider' };
|
|
36
|
+
|
|
37
|
+
api = this.$provide(SliderContext, {
|
|
38
|
+
state: signal({ index: 0, total: 0 }), // what changes
|
|
39
|
+
goNext: () => this.goNext(), // what a control can command
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
goNext() {
|
|
43
|
+
this.api.state.value = {
|
|
44
|
+
...this.api.state.value,
|
|
45
|
+
index: this.api.state.value.index + 1,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- The **scope is the subtree**, and the nearest provider wins.
|
|
52
|
+
- A reactive value is a provided `Signal`. A command surface is a provided object.
|
|
53
|
+
- `$provide()` in a field initializer is **instance-scoped**: it is never released and dies with the element. A component whose declaration is withdrawn keeps providing until its element goes.
|
|
54
|
+
|
|
55
|
+
## Injecting
|
|
56
|
+
|
|
57
|
+
| Form | Resolves | When nothing provides |
|
|
58
|
+
| ------------------ | --------------------------------- | ----------------------------------------------------- |
|
|
59
|
+
| `$inject(key)` | a promise, awaited in `mounted()` | it never settles: a missing provider means "not yet". |
|
|
60
|
+
| `$injectSync(key)` | the value, synchronously | `undefined`: the caller falls back or does nothing. |
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { Base, createContext, type Signal } from '@studiometa/js-toolkit';
|
|
64
|
+
|
|
65
|
+
interface SliderApi {
|
|
66
|
+
state: Signal<{ index: number; total: number }>;
|
|
67
|
+
goNext(): void;
|
|
68
|
+
}
|
|
69
|
+
const SliderContext = createContext<SliderApi>('slider');
|
|
70
|
+
class SliderNext extends Base {
|
|
71
|
+
static config = { name: 'SliderNext' };
|
|
72
|
+
|
|
73
|
+
async mounted() {
|
|
74
|
+
const api = await this.$inject(SliderContext);
|
|
75
|
+
return api.state.subscribe(({ index, total }) => {
|
|
76
|
+
this.$el.toggleAttribute('disabled', index >= total - 1);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The pending request of the async form is **unmount-scoped**. A new mount runs `mounted()` again and asks again.
|
|
83
|
+
|
|
84
|
+
**Which form to reach for**
|
|
85
|
+
|
|
86
|
+
`$inject()` from `mounted()` is the default: it waits, so mount order does not matter. `$injectSync()` is for the case where the answer is optional and the caller has a fallback. The [`@inject`](../../api/decorators/inject.md) field decorator asks once, at construction.
|
|
87
|
+
|
|
88
|
+
## Page-wide state
|
|
89
|
+
|
|
90
|
+
`provideRootContext(key, create)` makes the page-wide case the outermost scope of the same mechanism. The value is provided on `document.documentElement`, so a request from anywhere reaches it by bubbling — and a nearer provider still wins:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { createContext, injectContextSync, provideRootContext } from '@studiometa/js-toolkit';
|
|
94
|
+
|
|
95
|
+
const DataChannels = createContext<Map<string, unknown>>('channels');
|
|
96
|
+
const el = document.body;
|
|
97
|
+
// Scoped or page-wide, resolved the same way, nearest first.
|
|
98
|
+
const channels =
|
|
99
|
+
injectContextSync(el, DataChannels) ?? provideRootContext(DataChannels, () => new Map());
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`create` runs at most once per key, and nothing is created at import time. A root provider cannot be disposed and it outlives the instance that asked first, because it is page state.
|
|
103
|
+
|
|
104
|
+
`withGroup` is not ported.
|
|
105
|
+
|
|
106
|
+
## `Signal`
|
|
107
|
+
|
|
108
|
+
`signal(initialValue)` is a factory over a closure. The accessor is `.value`:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { signal } from '@studiometa/js-toolkit';
|
|
112
|
+
|
|
113
|
+
const count = signal(0);
|
|
114
|
+
|
|
115
|
+
const unsubscribe = count.subscribe((value) => console.log(value), { immediate: true });
|
|
116
|
+
|
|
117
|
+
count.value = 1;
|
|
118
|
+
count.value += 1;
|
|
119
|
+
|
|
120
|
+
unsubscribe();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**A write settles synchronously and the newest value wins.** The delivery loop re-reads the value after each callback; if the value moved, the loop abandons the round and starts again on the new value, so a subscriber not reached yet skips the old value. Delivery stays in the same task.
|
|
124
|
+
|
|
125
|
+
**A subscriber that writes on every delivery live-locks the loop**
|
|
126
|
+
|
|
127
|
+
Guard the write, or move it out of the subscriber.
|
|
128
|
+
|
|
129
|
+
## Reacting to a provider that appears later
|
|
130
|
+
|
|
131
|
+
`subscribeContext(el, key, onProvide)` is the subscription behaviour of the WICG protocol. The callback runs synchronously for each answer and receives the value and the same unsubscribe function the helper returns:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { Base, createContext, subscribeContext } from '@studiometa/js-toolkit';
|
|
135
|
+
|
|
136
|
+
interface GroupApi {
|
|
137
|
+
join(peer: Base): () => void;
|
|
138
|
+
}
|
|
139
|
+
const DisclosureGroupContext = createContext<GroupApi>('disclosure-group');
|
|
140
|
+
class Disclosure extends Base {
|
|
141
|
+
static config = { name: 'Disclosure' };
|
|
142
|
+
|
|
143
|
+
group?: GroupApi;
|
|
144
|
+
|
|
145
|
+
mounted() {
|
|
146
|
+
return subscribeContext(this.$el, DisclosureGroupContext, (group) => {
|
|
147
|
+
this.group = group;
|
|
148
|
+
const leave = group.join(this);
|
|
149
|
+
// The teardown for *this* value.
|
|
150
|
+
return () => {
|
|
151
|
+
leave();
|
|
152
|
+
this.group = undefined;
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- **The trigger is the mount announcement**, not a broadcast from the provider, and it runs after `mounted()`.
|
|
160
|
+
- **A new answer replaces; it never accumulates.** The teardown runs before the next _different_ value and on unsubscribe. An identical value is not an answer.
|
|
161
|
+
- **The registry holds nothing.** A subscription is anchored on its consumer element through a `WeakMap`, and the iterable index holds `WeakRef`s that the sweep prunes.
|
|
162
|
+
- Callback and teardown failures are isolated, so one consumer cannot stop the shared sweep.
|
|
163
|
+
- Two `contains()` calls bound the cost per mount. A mount that changes nothing checks nothing.
|
|
164
|
+
|
|
165
|
+
## Groups of peers
|
|
166
|
+
|
|
167
|
+
`createGroup()` holds a `Set` and a `Signal`. It names no group and resolves no scope — the coordinator owns the membership and gives out the ways in:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { Base, createContext, createGroup, type Signal } from '@studiometa/js-toolkit';
|
|
171
|
+
|
|
172
|
+
interface GroupApi {
|
|
173
|
+
members: Signal<readonly Base[]>;
|
|
174
|
+
join(peer: Base): () => void;
|
|
175
|
+
open(peer: Base): void;
|
|
176
|
+
}
|
|
177
|
+
const DisclosureGroupContext = createContext<GroupApi>('disclosure-group');
|
|
178
|
+
class DisclosureGroup extends Base {
|
|
179
|
+
static config = { name: 'DisclosureGroup' };
|
|
180
|
+
|
|
181
|
+
#peers = createGroup<Base>();
|
|
182
|
+
|
|
183
|
+
api = this.$provide(DisclosureGroupContext, {
|
|
184
|
+
members: this.#peers.members, // the members to read, in document order
|
|
185
|
+
join: (peer: Base) => this.#peers.join(peer), // returns the leave function
|
|
186
|
+
open: (peer: Base) => this.open(peer), // the invariant stays here
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
open(peer: Base) {}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
- `join()` returns its own `leave`, so a member that moves to a nearer group leaves the old one first.
|
|
194
|
+
- Scope comes from nearest-provider-wins, so a nested group takes its own members only.
|
|
195
|
+
- **The membership is a value.** A coordinator subscribes to it and re-checks its invariant on each change.
|
|
196
|
+
- **Document order is the tie-breaker**, so the markup decides which peer keeps its state.
|
|
197
|
+
- Nothing sweeps disconnected members. The teardown of the member removes it.
|
|
198
|
+
|
|
199
|
+
## How the mechanics work
|
|
200
|
+
|
|
201
|
+
The consumer dispatches a bubbling, **module-private** `js-toolkit:context:request` event carrying a key, a callback and a subscription marker. It is deliberately not part of public `EVENTS`. The nearest mounted provider answers and stops propagation. `provideContext()` replays the requests that have no first answer yet, which is what makes mount order irrelevant. `injectContext()` and `$inject()` are one-shot.
|
|
202
|
+
|
|
203
|
+
Outside a component the same three functions work on a bare element:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import {
|
|
207
|
+
createContext,
|
|
208
|
+
injectContext,
|
|
209
|
+
injectContextSync,
|
|
210
|
+
provideContext,
|
|
211
|
+
} from '@studiometa/js-toolkit';
|
|
212
|
+
|
|
213
|
+
const Key = createContext<number>('key');
|
|
214
|
+
const el = document.body;
|
|
215
|
+
const { value, dispose } = provideContext(el, Key, 42);
|
|
216
|
+
const { promise, cancel } = injectContext(el, Key);
|
|
217
|
+
const maybe = injectContextSync(el, Key);
|
|
218
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Swapping content
|
|
2
|
+
|
|
3
|
+
`swap()` replaces the **content** of an element with new markup and resolves once the framework has caught up: the mutation is applied, the new components are mounted and the old ones are unmounted.
|
|
4
|
+
|
|
5
|
+
## The call
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { swap } from '@studiometa/js-toolkit';
|
|
9
|
+
|
|
10
|
+
async function load(target: Element) {
|
|
11
|
+
const response = await fetch('/fragment');
|
|
12
|
+
await swap(target, await response.text());
|
|
13
|
+
// Every eager component in the new markup has mounted.
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`swap(target, content, options?)` returns a promise that resolves after [`whenDOMSettled()`](../../api/dom/whenDOMSettled.md).
|
|
18
|
+
|
|
19
|
+
## What survives
|
|
20
|
+
|
|
21
|
+
By default the element **itself** is never replaced, so the caller's reference, its `id` and any instance on it survive. Only its children change.
|
|
22
|
+
|
|
23
|
+
`content` is a markup string parsed in the parsing context of the target, so `<tr>`, `<li>` and `<option>` survive. It can also be an `Element` or a `DocumentFragment`, read as the incoming counterpart of the target, whose children become the new content.
|
|
24
|
+
|
|
25
|
+
## The four modes
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { swap, SWAP_MODES } from '@studiometa/js-toolkit';
|
|
29
|
+
|
|
30
|
+
await swap(el, html); // 'replace' — the default
|
|
31
|
+
await swap(el, html, { mode: SWAP_MODES.PREPEND });
|
|
32
|
+
await swap(el, html, { mode: SWAP_MODES.APPEND });
|
|
33
|
+
await swap(el, html, { mode: SWAP_MODES.MORPH });
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Mode | Effect |
|
|
37
|
+
| --------- | --------------------------------------------------- |
|
|
38
|
+
| `replace` | the children are replaced |
|
|
39
|
+
| `prepend` | the new content goes before the existing children |
|
|
40
|
+
| `append` | the new content goes after them |
|
|
41
|
+
| `morph` | the children are morphed in place, keeping identity |
|
|
42
|
+
|
|
43
|
+
`prepend` and `append` stay in core because they need the same before-and-after script diff as the other two.
|
|
44
|
+
|
|
45
|
+
### `morph` keeps identity
|
|
46
|
+
|
|
47
|
+
Nodes, focus, expandos and instances survive a morph — **but markup that is not sent does not**. Two consequences are morphdom's policy, not `swap()`'s:
|
|
48
|
+
|
|
49
|
+
- an element the incoming markup does not contain is discarded even from a preserved parent;
|
|
50
|
+
- morphdom syncs the `value` of an input from the incoming markup.
|
|
51
|
+
|
|
52
|
+
Core passes `childrenOnly: true` without `self`, so **attributes are not synced** on the target: `data-component` and `data-mount` are lifecycle declarations of the target, not content.
|
|
53
|
+
|
|
54
|
+
## `self` — replacing the element itself
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
await swap(el, html, { mode: 'replace', self: true });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This is the axis a caller that matches an element of a response to an element of the page needs: an id-matched section otherwise keeps the classes of the element it replaces.
|
|
61
|
+
|
|
62
|
+
- With `self`, an `Element` content **is** the replacement rather than a container — the only way its own attributes reach the page. A string or a `DocumentFragment` contributes its first top-level element.
|
|
63
|
+
- `replace` puts the replacement in the place of the target, so the target **leaves** the document.
|
|
64
|
+
- `morph` morphs the target from the replacement without `childrenOnly`, so the element and its identity survive while its attributes are updated.
|
|
65
|
+
- `append` and `prepend` add to the children by definition, so `self` with either is a `swap.self-ignored` warning — as is content that holds no element.
|
|
66
|
+
|
|
67
|
+
## Scripts
|
|
68
|
+
|
|
69
|
+
A `<script>` produced by the fragment parser is flagged as already started by the HTML specification and stays inert wherever it is moved. It runs only if it is **recreated**, and a script that was already in the page runs **twice** if it is recreated.
|
|
70
|
+
|
|
71
|
+
`swap()` owns that rule, once. Script adoption follows whatever ends up in the document, so a replacement carrying a script still runs it exactly once.
|
|
72
|
+
|
|
73
|
+
## Wrapping the mutation
|
|
74
|
+
|
|
75
|
+
`wrap` is the whole seam. It receives the single mutation of the swap and decides when it runs:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { swap, viewTransition } from '@studiometa/js-toolkit';
|
|
79
|
+
|
|
80
|
+
async function load(el: Element, html: string) {
|
|
81
|
+
await swap(el, html, { wrap: (mutate) => viewTransition(mutate) });
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
[`domUpdate()`](../../api/dom/domUpdate.md) produces that wrapper and nothing else, so an ancestor can claim the lane. Resilience policy stays with `domUpdate()`.
|
|
86
|
+
|
|
87
|
+
## What is deliberately not in core
|
|
88
|
+
|
|
89
|
+
- **Attribute syncing in a default `morph`** — see above.
|
|
90
|
+
- **Transitions, view transitions, history, `id` matching and response parsing.** All of these are caller policy.
|
|
91
|
+
|
|
92
|
+
`swap()` is a free function, not a `Base` method. `morphdom` is a real, statically imported dependency, and the subpath layout contains the cost: it is reachable through `swap()` only, so a page that never swaps never downloads it.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
`@studiometa/js-toolkit/test` ships the nine helpers a component test cannot write for itself.
|
|
4
|
+
|
|
5
|
+
## Why a subpath exists
|
|
6
|
+
|
|
7
|
+
**The timing recipe is not derivable.** "Has this component mounted and finished its writes?" is answered by five rounds of a 10 ms timer followed by `defaultScheduler.whenIdle()`, and those two numbers encode the mount observer's delivery latency and the scheduler's lane order.
|
|
8
|
+
|
|
9
|
+
Neither half works alone: `whenIdle()` can resolve before the observer has reported the element, and a timer can return between two lanes. `defaultScheduler` and `nextFrame` were already public, so the pieces shipped and the recipe did not.
|
|
10
|
+
|
|
11
|
+
**It depends on no test framework.** Nothing in the module imports a runner, an assertion library or a spy; it reads the DOM and the scheduler only, so it runs under Vitest, under Playwright and on a plain browser page. That is also why it is not on the root barrel: a page has no use for it.
|
|
12
|
+
|
|
13
|
+
## A first test
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { afterAll, expect, test } from 'vitest';
|
|
17
|
+
import { Base, registerComponent } from '@studiometa/js-toolkit';
|
|
18
|
+
import { mount, resetDom, resetRegistry, waitFor } from '@studiometa/js-toolkit/test';
|
|
19
|
+
|
|
20
|
+
class Counter extends Base {
|
|
21
|
+
static config = { name: 'Counter', refs: ['output'] };
|
|
22
|
+
|
|
23
|
+
count = 0;
|
|
24
|
+
|
|
25
|
+
onClick() {
|
|
26
|
+
this.count += 1;
|
|
27
|
+
this.$refs.output.textContent = String(this.count);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
registerComponent(Counter);
|
|
32
|
+
|
|
33
|
+
test('it counts clicks', async () => {
|
|
34
|
+
const root = await mount(`
|
|
35
|
+
<button data-component="Counter">
|
|
36
|
+
<span data-ref="output">0</span>
|
|
37
|
+
</button>
|
|
38
|
+
`);
|
|
39
|
+
|
|
40
|
+
root.querySelector('button').click();
|
|
41
|
+
|
|
42
|
+
await waitFor(() => root.querySelector('[data-ref="output"]').textContent === '1');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterAll(async () => {
|
|
46
|
+
await resetDom();
|
|
47
|
+
resetRegistry();
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## The helpers
|
|
52
|
+
|
|
53
|
+
| Helper | What it does |
|
|
54
|
+
| -------------------------------- | ---------------------------------------------------------------------- |
|
|
55
|
+
| `mount(html)` | inserts the markup and resolves once the eager components have mounted |
|
|
56
|
+
| `settle()` | resolves once the framework has caught up |
|
|
57
|
+
| `frames(count?)` | awaits animation frames |
|
|
58
|
+
| `waitFor(predicate, options?)` | polls until the predicate is truthy, and returns that value |
|
|
59
|
+
| `countRequestedFrames(during)` | counts the `requestAnimationFrame()` calls made during `during` |
|
|
60
|
+
| `captureDiagnostics(target?)` | collects diagnostics and silences their console output |
|
|
61
|
+
| `recordEvents(target, ...types)` | records events in order, with their payloads |
|
|
62
|
+
| `resetDom()` | empties the document body and lets the teardown run |
|
|
63
|
+
| `resetRegistry()` | drops every registration |
|
|
64
|
+
|
|
65
|
+
See the [full reference](../../api/test/index.md).
|
|
66
|
+
|
|
67
|
+
## `mount()` returns the wrapper
|
|
68
|
+
|
|
69
|
+
It returns the `div` it created, **never** the markup's own root, so a fragment of several siblings works and `querySelector()` has a stable handle. `.firstElementChild` is one property away.
|
|
70
|
+
|
|
71
|
+
## `waitFor()` is a guard and a query
|
|
72
|
+
|
|
73
|
+
It returns the predicate's truthy value, which makes the same call either:
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
// a guard
|
|
77
|
+
await waitFor(() => el.classList.contains('is-open'));
|
|
78
|
+
|
|
79
|
+
// or a query
|
|
80
|
+
const panel = await waitFor(() => root.querySelector('.panel'));
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
It polls on a 10 ms cadence and drains the scheduler between attempts. On timeout it throws — with the caller's `message`, or one naming the timeout.
|
|
84
|
+
|
|
85
|
+
## Transitions: poll, never `settle()`
|
|
86
|
+
|
|
87
|
+
**A transition's end state is asserted by polling.** A method that starts a transition does not hand it back, and a kept end state lands only after `nextFrame()`, the `from` and `active` states, and either a `transitionend` or one more frame. `settle()` is generous rather than deterministic, which is a flake that passes alone and fails under load.
|
|
88
|
+
|
|
89
|
+
**Polling for an _absence_ is wrong** for the mirror-image reason: `leaveTransition()` clears the other direction's `to` state synchronously, so the poll passes before anything has happened.
|
|
90
|
+
|
|
91
|
+
## `recordEvents()` keeps the order
|
|
92
|
+
|
|
93
|
+
A component's contract is "`open` came before `opened`, and `opened` carried the height it measured", and a call-counting spy throws both away:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
const recording = recordEvents(el, 'open', 'opened');
|
|
97
|
+
|
|
98
|
+
el.querySelector('button').click();
|
|
99
|
+
|
|
100
|
+
await waitFor(() => recording.events.length === 2);
|
|
101
|
+
expect(recording.events.map(({ type }) => type)).toEqual(['open', 'opened']);
|
|
102
|
+
expect(recording.events[1].detail).toEqual({ height: 120 });
|
|
103
|
+
|
|
104
|
+
recording.stop();
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Recording several types into one array is the only place their relative order is visible. `$emit` dispatches synchronously but is almost never _called_ synchronously, so the count is awaited with `waitFor`, not read.
|
|
108
|
+
|
|
109
|
+
## `resetRegistry()` — where the call belongs
|
|
110
|
+
|
|
111
|
+
It is the inverse `registerComponent()` deliberately lacks, and it is **coarse on purpose**: the element→controller map is a `WeakMap`, so nothing can walk it to dispose live triggers, and a `querySelectorAll()` sweep would still miss detached elements.
|
|
112
|
+
|
|
113
|
+
Clearing everything works because the mutation-observer path already disposes a controller as its element leaves the DOM — hence the call belongs **after** `resetDom()`.
|
|
114
|
+
|
|
115
|
+
**Never in an `afterEach`**
|
|
116
|
+
|
|
117
|
+
Spec files register at module top level, so `resetRegistry()` in an `afterEach` silently unregisters everything for every later test in the file. Put it in an `afterAll`, or call it and register again straight away:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
afterEach(async () => {
|
|
121
|
+
await resetDom();
|
|
122
|
+
resetRegistry();
|
|
123
|
+
registerComponents(Subject, Emitter);
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## `countRequestedFrames()` patches a global
|
|
128
|
+
|
|
129
|
+
It swaps `globalThis.requestAnimationFrame` for a counting wrapper that forwards to the original, and restores it in a `finally` — so it comes back whether `during` returns or throws.
|
|
130
|
+
|
|
131
|
+
It is here despite the patch because the assertion it serves, "this did not schedule a frame per event", has no other seam: the framework's own scheduler makes the calls, so there is nothing of the component's for a spy to sit on.
|
|
132
|
+
|
|
133
|
+
**Two concurrent calls would nest their wrappers. Do not.**
|
|
134
|
+
|
|
135
|
+
## What is deliberately not in it
|
|
136
|
+
|
|
137
|
+
An instance lookup — `getInstance()` and `getInstances()` on the root barrel answer it — fetch stubs, and pointer sequences. Each is either specific to one spec or already served by `vi.fn()` and `@vitest/browser`'s `userEvent`.
|