@zeix/le-truc 0.15.0
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/.ai-context.md +234 -0
- package/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc +111 -0
- package/.editorconfig +12 -0
- package/.github/copilot-instructions.md +62 -0
- package/.github/workflows/codeql.yml +108 -0
- package/.github/workflows/static.yml +43 -0
- package/.prettierrc +17 -0
- package/CLAUDE.md +215 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +160 -0
- package/LICENSE +21 -0
- package/README.md +474 -0
- package/biome.json +295 -0
- package/bun.lock +239 -0
- package/docs/about.html +105 -0
- package/docs/assets/main.css +1 -0
- package/docs/assets/main.js +10 -0
- package/docs/assets/main.js.map +66 -0
- package/docs/components.html +293 -0
- package/docs/data-flow.html +308 -0
- package/docs/examples/basic-button.html +367 -0
- package/docs/examples/basic-counter.html +188 -0
- package/docs/examples/basic-hello.html +138 -0
- package/docs/examples/basic-number.html +271 -0
- package/docs/examples/basic-pluralize.html +214 -0
- package/docs/examples/card-callout.html +152 -0
- package/docs/examples/card-mediaqueries.html +138 -0
- package/docs/examples/context-media.html +198 -0
- package/docs/examples/empty.html +37 -0
- package/docs/examples/form-checkbox.html +233 -0
- package/docs/examples/form-combobox.html +420 -0
- package/docs/examples/form-listbox.html +434 -0
- package/docs/examples/form-radiogroup.html +296 -0
- package/docs/examples/form-spinbutton.html +402 -0
- package/docs/examples/form-textbox.html +361 -0
- package/docs/examples/layout.html +67 -0
- package/docs/examples/module-carousel.html +552 -0
- package/docs/examples/module-catalog.html +241 -0
- package/docs/examples/module-codeblock.html +270 -0
- package/docs/examples/module-dialog.html +343 -0
- package/docs/examples/module-lazyload.html +289 -0
- package/docs/examples/module-list.html +197 -0
- package/docs/examples/module-pagination.html +283 -0
- package/docs/examples/module-scrollarea.html +447 -0
- package/docs/examples/module-tabgroup.html +526 -0
- package/docs/examples/module-todo.html +367 -0
- package/docs/examples/module-with-type.html +63 -0
- package/docs/examples/nested-components.html +88 -0
- package/docs/examples/recursive.html +56 -0
- package/docs/examples/simple-text.html +39 -0
- package/docs/examples/snippet.html +93 -0
- package/docs/examples/with-styles.html +75 -0
- package/docs/getting-started.html +143 -0
- package/docs/index.html +112 -0
- package/docs/sitemap.xml +28 -0
- package/docs/styling.html +160 -0
- package/docs/sw.js +112 -0
- package/docs-src/api/README.md +478 -0
- package/docs-src/api/_media/LICENSE +21 -0
- package/docs-src/api/classes/CircularDependencyError.md +299 -0
- package/docs-src/api/classes/CircularMutationError.md +301 -0
- package/docs-src/api/classes/ContextRequestEvent.md +590 -0
- package/docs-src/api/classes/DependencyTimeoutError.md +301 -0
- package/docs-src/api/classes/InvalidCallbackError.md +303 -0
- package/docs-src/api/classes/InvalidComponentNameError.md +295 -0
- package/docs-src/api/classes/InvalidCustomElementError.md +301 -0
- package/docs-src/api/classes/InvalidEffectsError.md +301 -0
- package/docs-src/api/classes/InvalidPropertyNameError.md +307 -0
- package/docs-src/api/classes/InvalidReactivesError.md +307 -0
- package/docs-src/api/classes/InvalidSignalValueError.md +303 -0
- package/docs-src/api/classes/MissingElementError.md +307 -0
- package/docs-src/api/classes/NullishSignalValueError.md +299 -0
- package/docs-src/api/classes/StoreKeyExistsError.md +303 -0
- package/docs-src/api/classes/StoreKeyRangeError.md +299 -0
- package/docs-src/api/classes/StoreKeyReadonlyError.md +303 -0
- package/docs-src/api/functions/asBoolean.md +21 -0
- package/docs-src/api/functions/asEnum.md +31 -0
- package/docs-src/api/functions/asInteger.md +39 -0
- package/docs-src/api/functions/asJSON.md +49 -0
- package/docs-src/api/functions/asNumber.md +37 -0
- package/docs-src/api/functions/asString.md +37 -0
- package/docs-src/api/functions/createCollection.md +83 -0
- package/docs-src/api/functions/createSensor.md +71 -0
- package/docs-src/api/functions/dangerouslySetInnerHTML.md +48 -0
- package/docs-src/api/functions/defineComponent.md +65 -0
- package/docs-src/api/functions/isCollection.md +37 -0
- package/docs-src/api/functions/isParser.md +41 -0
- package/docs-src/api/functions/match.md +47 -0
- package/docs-src/api/functions/on.md +58 -0
- package/docs-src/api/functions/pass.md +53 -0
- package/docs-src/api/functions/provideContexts.md +47 -0
- package/docs-src/api/functions/read.md +47 -0
- package/docs-src/api/functions/requestContext.md +51 -0
- package/docs-src/api/functions/resolve.md +40 -0
- package/docs-src/api/functions/runEffects.md +51 -0
- package/docs-src/api/functions/runElementEffects.md +57 -0
- package/docs-src/api/functions/schedule.md +33 -0
- package/docs-src/api/functions/setAttribute.md +48 -0
- package/docs-src/api/functions/setProperty.md +52 -0
- package/docs-src/api/functions/setStyle.md +48 -0
- package/docs-src/api/functions/setText.md +42 -0
- package/docs-src/api/functions/show.md +42 -0
- package/docs-src/api/functions/toSignal.md +37 -0
- package/docs-src/api/functions/toggleAttribute.md +48 -0
- package/docs-src/api/functions/toggleClass.md +48 -0
- package/docs-src/api/functions/updateElement.md +53 -0
- package/docs-src/api/globals.md +131 -0
- package/docs-src/api/type-aliases/Cleanup.md +27 -0
- package/docs-src/api/type-aliases/Collection.md +91 -0
- package/docs-src/api/type-aliases/CollectionListener.md +27 -0
- package/docs-src/api/type-aliases/Component.md +17 -0
- package/docs-src/api/type-aliases/ComponentProp.md +11 -0
- package/docs-src/api/type-aliases/ComponentProps.md +11 -0
- package/docs-src/api/type-aliases/ComponentSetup.md +31 -0
- package/docs-src/api/type-aliases/ComponentUI.md +27 -0
- package/docs-src/api/type-aliases/Computed.md +49 -0
- package/docs-src/api/type-aliases/ComputedCallback.md +29 -0
- package/docs-src/api/type-aliases/Context.md +33 -0
- package/docs-src/api/type-aliases/ContextType.md +19 -0
- package/docs-src/api/type-aliases/DangerouslySetInnerHTMLOptions.md +27 -0
- package/docs-src/api/type-aliases/DiffResult.md +61 -0
- package/docs-src/api/type-aliases/Effect.md +35 -0
- package/docs-src/api/type-aliases/EffectCallback.md +23 -0
- package/docs-src/api/type-aliases/Effects.md +21 -0
- package/docs-src/api/type-aliases/ElementEffects.md +21 -0
- package/docs-src/api/type-aliases/ElementFromKey.md +21 -0
- package/docs-src/api/type-aliases/ElementQueries.md +27 -0
- package/docs-src/api/type-aliases/ElementUpdater.md +131 -0
- package/docs-src/api/type-aliases/EventHandler.md +31 -0
- package/docs-src/api/type-aliases/EventType.md +17 -0
- package/docs-src/api/type-aliases/Fallback.md +21 -0
- package/docs-src/api/type-aliases/Initializers.md +21 -0
- package/docs-src/api/type-aliases/LooseReader.md +31 -0
- package/docs-src/api/type-aliases/MatchHandlers.md +77 -0
- package/docs-src/api/type-aliases/MaybeCleanup.md +23 -0
- package/docs-src/api/type-aliases/MaybeSignal.md +17 -0
- package/docs-src/api/type-aliases/Parser.md +39 -0
- package/docs-src/api/type-aliases/ParserOrFallback.md +21 -0
- package/docs-src/api/type-aliases/PassedProp.md +25 -0
- package/docs-src/api/type-aliases/PassedProps.md +21 -0
- package/docs-src/api/type-aliases/Reactive.md +25 -0
- package/docs-src/api/type-aliases/Reader.md +31 -0
- package/docs-src/api/type-aliases/ReservedWords.md +11 -0
- package/docs-src/api/type-aliases/ResolveResult.md +29 -0
- package/docs-src/api/type-aliases/SensorEvents.md +25 -0
- package/docs-src/api/type-aliases/Signal.md +41 -0
- package/docs-src/api/type-aliases/State.md +85 -0
- package/docs-src/api/type-aliases/Store.md +29 -0
- package/docs-src/api/type-aliases/UI.md +11 -0
- package/docs-src/api/type-aliases/UnknownContext.md +13 -0
- package/docs-src/api/variables/CONTEXT_REQUEST.md +11 -0
- package/docs-src/api/variables/UNSET.md +23 -0
- package/docs-src/api/variables/batch.md +25 -0
- package/docs-src/api/variables/createComputed.md +41 -0
- package/docs-src/api/variables/createEffect.md +35 -0
- package/docs-src/api/variables/createState.md +37 -0
- package/docs-src/api/variables/createStore.md +42 -0
- package/docs-src/api/variables/diff.md +43 -0
- package/docs-src/api/variables/isAbortError.md +33 -0
- package/docs-src/api/variables/isAsyncFunction.md +39 -0
- package/docs-src/api/variables/isComputed.md +37 -0
- package/docs-src/api/variables/isEqual.md +49 -0
- package/docs-src/api/variables/isFunction.md +39 -0
- package/docs-src/api/variables/isMutableSignal.md +37 -0
- package/docs-src/api/variables/isNumber.md +33 -0
- package/docs-src/api/variables/isRecord.md +39 -0
- package/docs-src/api/variables/isRecordOrArray.md +39 -0
- package/docs-src/api/variables/isSignal.md +37 -0
- package/docs-src/api/variables/isState.md +37 -0
- package/docs-src/api/variables/isStore.md +37 -0
- package/docs-src/api/variables/isString.md +33 -0
- package/docs-src/api/variables/isSymbol.md +33 -0
- package/docs-src/api/variables/toError.md +33 -0
- package/docs-src/api/variables/valueString.md +33 -0
- package/docs-src/includes/menu.html +44 -0
- package/docs-src/pages/about.md +89 -0
- package/docs-src/pages/components.md +437 -0
- package/docs-src/pages/data-flow.md +449 -0
- package/docs-src/pages/getting-started.md +170 -0
- package/docs-src/pages/index.md +98 -0
- package/docs-src/pages/styling.md +165 -0
- package/eslint.config.js +64 -0
- package/examples/_common/clear.ts +49 -0
- package/examples/_common/fetch.ts +160 -0
- package/examples/_common/focus.ts +45 -0
- package/examples/_common/highlight.ts +5 -0
- package/examples/_global.css +463 -0
- package/examples/basic-button/basic-button.css +176 -0
- package/examples/basic-button/basic-button.html +46 -0
- package/examples/basic-button/basic-button.spec.ts +160 -0
- package/examples/basic-button/basic-button.ts +45 -0
- package/examples/basic-button/copyToClipboard.ts +37 -0
- package/examples/basic-counter/basic-counter.css +21 -0
- package/examples/basic-counter/basic-counter.html +24 -0
- package/examples/basic-counter/basic-counter.spec.ts +85 -0
- package/examples/basic-counter/basic-counter.ts +43 -0
- package/examples/basic-hello/basic-hello.html +34 -0
- package/examples/basic-hello/basic-hello.spec.ts +110 -0
- package/examples/basic-hello/basic-hello.ts +36 -0
- package/examples/basic-number/basic-number.html +79 -0
- package/examples/basic-number/basic-number.spec.ts +175 -0
- package/examples/basic-number/basic-number.ts +124 -0
- package/examples/basic-pluralize/basic-pluralize.html +64 -0
- package/examples/basic-pluralize/basic-pluralize.spec.ts +258 -0
- package/examples/basic-pluralize/basic-pluralize.ts +82 -0
- package/examples/card-callout/card-callout.css +79 -0
- package/examples/card-callout/card-callout.html +5 -0
- package/examples/card-mediaqueries/card-mediaqueries.html +29 -0
- package/examples/card-mediaqueries/card-mediaqueries.spec.ts +300 -0
- package/examples/card-mediaqueries/card-mediaqueries.ts +41 -0
- package/examples/context-media/context-media.html +3 -0
- package/examples/context-media/context-media.ts +127 -0
- package/examples/form-checkbox/form-checkbox.css +70 -0
- package/examples/form-checkbox/form-checkbox.html +13 -0
- package/examples/form-checkbox/form-checkbox.spec.ts +357 -0
- package/examples/form-checkbox/form-checkbox.ts +50 -0
- package/examples/form-checkbox/vanilla-checkbox.ts +101 -0
- package/examples/form-combobox/form-combobox.css +118 -0
- package/examples/form-combobox/form-combobox.html +74 -0
- package/examples/form-combobox/form-combobox.spec.ts +977 -0
- package/examples/form-combobox/form-combobox.ts +128 -0
- package/examples/form-listbox/form-listbox.css +71 -0
- package/examples/form-listbox/form-listbox.html +67 -0
- package/examples/form-listbox/form-listbox.spec.ts +1050 -0
- package/examples/form-listbox/form-listbox.ts +196 -0
- package/examples/form-listbox/mocks/timezones.json +495 -0
- package/examples/form-radiogroup/form-radiogroup.css +87 -0
- package/examples/form-radiogroup/form-radiogroup.html +51 -0
- package/examples/form-radiogroup/form-radiogroup.spec.ts +515 -0
- package/examples/form-radiogroup/form-radiogroup.ts +58 -0
- package/examples/form-spinbutton/form-spinbutton.css +95 -0
- package/examples/form-spinbutton/form-spinbutton.html +96 -0
- package/examples/form-spinbutton/form-spinbutton.spec.ts +688 -0
- package/examples/form-spinbutton/form-spinbutton.ts +111 -0
- package/examples/form-textbox/form-textbox.css +104 -0
- package/examples/form-textbox/form-textbox.html +53 -0
- package/examples/form-textbox/form-textbox.spec.ts +542 -0
- package/examples/form-textbox/form-textbox.ts +104 -0
- package/examples/main.css +22 -0
- package/examples/main.ts +23 -0
- package/examples/module-carousel/module-carousel.css +113 -0
- package/examples/module-carousel/module-carousel.html +208 -0
- package/examples/module-carousel/module-carousel.spec.ts +523 -0
- package/examples/module-carousel/module-carousel.ts +131 -0
- package/examples/module-catalog/module-catalog.css +22 -0
- package/examples/module-catalog/module-catalog.html +82 -0
- package/examples/module-catalog/module-catalog.spec.ts +396 -0
- package/examples/module-catalog/module-catalog.ts +37 -0
- package/examples/module-codeblock/module-codeblock.css +95 -0
- package/examples/module-codeblock/module-codeblock.html +28 -0
- package/examples/module-codeblock/module-codeblock.ts +47 -0
- package/examples/module-demo/module-demo.css +13 -0
- package/examples/module-dialog/module-dialog.css +96 -0
- package/examples/module-dialog/module-dialog.html +66 -0
- package/examples/module-dialog/module-dialog.spec.ts +557 -0
- package/examples/module-dialog/module-dialog.ts +81 -0
- package/examples/module-lazyload/mocks/empty.html +1 -0
- package/examples/module-lazyload/mocks/module-with-type.html +27 -0
- package/examples/module-lazyload/mocks/nested-components.html +52 -0
- package/examples/module-lazyload/mocks/recursive.html +20 -0
- package/examples/module-lazyload/mocks/simple-text.html +3 -0
- package/examples/module-lazyload/mocks/snippet.html +57 -0
- package/examples/module-lazyload/mocks/with-styles.html +39 -0
- package/examples/module-lazyload/module-lazyload.html +132 -0
- package/examples/module-lazyload/module-lazyload.spec.ts +734 -0
- package/examples/module-lazyload/module-lazyload.ts +89 -0
- package/examples/module-list/module-list.html +30 -0
- package/examples/module-list/module-list.spec.ts +592 -0
- package/examples/module-list/module-list.ts +99 -0
- package/examples/module-pagination/module-pagination.css +79 -0
- package/examples/module-pagination/module-pagination.html +16 -0
- package/examples/module-pagination/module-pagination.spec.ts +701 -0
- package/examples/module-pagination/module-pagination.ts +88 -0
- package/examples/module-scrollarea/module-scrollarea.css +77 -0
- package/examples/module-scrollarea/module-scrollarea.html +189 -0
- package/examples/module-scrollarea/module-scrollarea.spec.ts +445 -0
- package/examples/module-scrollarea/module-scrollarea.ts +81 -0
- package/examples/module-tabgroup/module-tabgroup.css +55 -0
- package/examples/module-tabgroup/module-tabgroup.html +269 -0
- package/examples/module-tabgroup/module-tabgroup.spec.ts +631 -0
- package/examples/module-tabgroup/module-tabgroup.ts +102 -0
- package/examples/module-toc/module-toc.css +34 -0
- package/examples/module-todo/module-todo.css +84 -0
- package/examples/module-todo/module-todo.html +92 -0
- package/examples/module-todo/module-todo.spec.ts +528 -0
- package/examples/module-todo/module-todo.ts +91 -0
- package/examples/section-hero/section-hero.css +37 -0
- package/examples/section-menu/section-menu.css +81 -0
- package/examples/server.ts +95 -0
- package/examples/test-setup.md +314 -0
- package/index.dev.js +1688 -0
- package/index.dev.ts +127 -0
- package/index.js +3 -0
- package/index.js.map +42 -0
- package/index.ts +127 -0
- package/package.json +64 -0
- package/playwright.config.ts +31 -0
- package/server/BUILD_SYSTEM.md +428 -0
- package/server/SERVER.md +286 -0
- package/server/build.ts +91 -0
- package/server/config.ts +130 -0
- package/server/effects/api.ts +28 -0
- package/server/effects/css.ts +31 -0
- package/server/effects/examples.ts +109 -0
- package/server/effects/js.ts +32 -0
- package/server/effects/menu.ts +34 -0
- package/server/effects/pages.ts +178 -0
- package/server/effects/service-worker.ts +57 -0
- package/server/effects/sitemap.ts +27 -0
- package/server/file-signals.ts +361 -0
- package/server/file-watcher.ts +77 -0
- package/server/io.ts +174 -0
- package/server/layout-engine.ts +470 -0
- package/server/layout-utils.ts +615 -0
- package/server/layouts/api.html +76 -0
- package/server/layouts/base.html +37 -0
- package/server/layouts/blog.html +115 -0
- package/server/layouts/example.html +104 -0
- package/server/layouts/overview.html +165 -0
- package/server/layouts/page.html +36 -0
- package/server/layouts/test.html +24 -0
- package/server/markdoc-helpers.ts +217 -0
- package/server/markdoc.config.ts +29 -0
- package/server/schema/callout.markdoc.ts +17 -0
- package/server/schema/carousel.markdoc.ts +118 -0
- package/server/schema/demo.markdoc.ts +74 -0
- package/server/schema/fence.markdoc.ts +84 -0
- package/server/schema/heading.markdoc.ts +23 -0
- package/server/schema/hero.markdoc.ts +59 -0
- package/server/schema/section.markdoc.ts +10 -0
- package/server/schema/slide.markdoc.ts +17 -0
- package/server/schema/source.markdoc.ts +53 -0
- package/server/schema/tabgroup.markdoc.ts +102 -0
- package/server/serve.ts +635 -0
- package/server/templates/README.md +352 -0
- package/server/templates/constants.ts +236 -0
- package/server/templates/fragments.ts +159 -0
- package/server/templates/hmr.ts +269 -0
- package/server/templates/menu.ts +33 -0
- package/server/templates/performance-hints.ts +94 -0
- package/server/templates/service-worker.ts +403 -0
- package/server/templates/sitemap.ts +57 -0
- package/server/templates/toc.ts +41 -0
- package/server/templates/utils.ts +378 -0
- package/src/component.ts +215 -0
- package/src/context.ts +156 -0
- package/src/effects/attribute.ts +82 -0
- package/src/effects/class.ts +28 -0
- package/src/effects/event.ts +67 -0
- package/src/effects/html.ts +60 -0
- package/src/effects/method.ts +57 -0
- package/src/effects/pass.ts +103 -0
- package/src/effects/property.ts +57 -0
- package/src/effects/style.ts +34 -0
- package/src/effects/text.ts +28 -0
- package/src/effects.ts +412 -0
- package/src/errors.ts +160 -0
- package/src/parsers/boolean.ts +14 -0
- package/src/parsers/json.ts +33 -0
- package/src/parsers/number.ts +55 -0
- package/src/parsers/string.ts +32 -0
- package/src/parsers.ts +90 -0
- package/src/scheduler.ts +47 -0
- package/src/signals/collection.ts +253 -0
- package/src/signals/sensor.ts +131 -0
- package/src/ui.ts +236 -0
- package/src/util.ts +187 -0
- package/tsconfig.json +34 -0
- package/types/examples/basic-button/basic-button.d.ts +16 -0
- package/types/examples/basic-hello/basic-hello.d.ts +18 -0
- package/types/index.d.ts +27 -0
- package/types/index.dev.d.ts +27 -0
- package/types/src/collection.d.ts +27 -0
- package/types/src/component.d.ts +32 -0
- package/types/src/context.d.ts +85 -0
- package/types/src/effects/attribute.d.ts +23 -0
- package/types/src/effects/callMethod.d.ts +23 -0
- package/types/src/effects/class.d.ts +13 -0
- package/types/src/effects/dangerouslySetInnerHTML.d.ts +18 -0
- package/types/src/effects/event.d.ts +18 -0
- package/types/src/effects/html.d.ts +17 -0
- package/types/src/effects/method.d.ts +22 -0
- package/types/src/effects/pass.d.ts +18 -0
- package/types/src/effects/property.d.ts +22 -0
- package/types/src/effects/setAttribute.d.ts +24 -0
- package/types/src/effects/setProperty.d.ts +23 -0
- package/types/src/effects/setStyle.d.ts +14 -0
- package/types/src/effects/setText.d.ts +13 -0
- package/types/src/effects/style.d.ts +13 -0
- package/types/src/effects/text.d.ts +12 -0
- package/types/src/effects/toggleClass.d.ts +14 -0
- package/types/src/effects.d.ts +153 -0
- package/types/src/errors.d.ts +99 -0
- package/types/src/events.d.ts +27 -0
- package/types/src/extractors.d.ts +23 -0
- package/types/src/parsers/boolean.d.ts +10 -0
- package/types/src/parsers/json.d.ts +13 -0
- package/types/src/parsers/number.d.ts +21 -0
- package/types/src/parsers/string.d.ts +19 -0
- package/types/src/parsers.d.ts +41 -0
- package/types/src/scheduler.d.ts +11 -0
- package/types/src/sensor.d.ts +27 -0
- package/types/src/signals/collection.d.ts +32 -0
- package/types/src/signals/sensor.d.ts +27 -0
- package/types/src/ui.d.ts +37 -0
- package/types/src/util.d.ts +65 -0
package/src/context.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Cleanup,
|
|
3
|
+
type Computed,
|
|
4
|
+
createComputed,
|
|
5
|
+
isFunction,
|
|
6
|
+
isString,
|
|
7
|
+
} from '@zeix/cause-effect'
|
|
8
|
+
|
|
9
|
+
import type { Component, ComponentProps } from './component'
|
|
10
|
+
import { type Fallback, getFallback, type Reader } from './parsers'
|
|
11
|
+
import type { UI } from './ui'
|
|
12
|
+
|
|
13
|
+
/** @see https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md */
|
|
14
|
+
|
|
15
|
+
/* === Types === */
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A context key.
|
|
19
|
+
*
|
|
20
|
+
* A context key can be any type of object, including strings and symbols. The
|
|
21
|
+
* Context type brands the key type with the `__context__` property that
|
|
22
|
+
* carries the type of the value the context references.
|
|
23
|
+
*/
|
|
24
|
+
type Context<K, V> = K & { __context__: V }
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* An unknown context type
|
|
28
|
+
*/
|
|
29
|
+
type UnknownContext = Context<unknown, unknown>
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A helper type which can extract a Context value type from a Context type
|
|
33
|
+
*/
|
|
34
|
+
type ContextType<T extends UnknownContext> =
|
|
35
|
+
T extends Context<infer _, infer V> ? V : never
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A callback which is provided by a context requester and is called with the value satisfying the request.
|
|
39
|
+
* This callback can be called multiple times by context providers as the requested value is changed.
|
|
40
|
+
*/
|
|
41
|
+
type ContextCallback<V> = (value: V, unsubscribe?: () => void) => void
|
|
42
|
+
|
|
43
|
+
declare global {
|
|
44
|
+
interface HTMLElementEventMap {
|
|
45
|
+
/**
|
|
46
|
+
* A 'context-request' event can be emitted by any element which desires
|
|
47
|
+
* a context value to be injected by an external provider.
|
|
48
|
+
*/
|
|
49
|
+
'context-request': ContextRequestEvent<UnknownContext>
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* === Constants === */
|
|
54
|
+
|
|
55
|
+
const CONTEXT_REQUEST = 'context-request'
|
|
56
|
+
|
|
57
|
+
/* === Exported class === */
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Class for context-request events
|
|
61
|
+
*
|
|
62
|
+
* An event fired by a context requester to signal it desires a named context.
|
|
63
|
+
*
|
|
64
|
+
* A provider should inspect the `context` property of the event to determine if it has a value that can
|
|
65
|
+
* satisfy the request, calling the `callback` with the requested value if so.
|
|
66
|
+
*
|
|
67
|
+
* If the requested context event contains a truthy `subscribe` value, then a provider can call the callback
|
|
68
|
+
* multiple times if the value is changed, if this is the case the provider should pass an `unsubscribe`
|
|
69
|
+
* function to the callback which requesters can invoke to indicate they no longer wish to receive these updates.
|
|
70
|
+
*
|
|
71
|
+
* @class ContextRequestEvent
|
|
72
|
+
* @extends {Event}
|
|
73
|
+
*
|
|
74
|
+
* @property {T} context - context key
|
|
75
|
+
* @property {ContextCallback<ContextType<T>>} callback - callback function for value getter and unsubscribe function
|
|
76
|
+
* @property {boolean} [subscribe=false] - whether to subscribe to context changes
|
|
77
|
+
*/
|
|
78
|
+
class ContextRequestEvent<T extends UnknownContext> extends Event {
|
|
79
|
+
readonly context: T
|
|
80
|
+
readonly callback: ContextCallback<ContextType<T>>
|
|
81
|
+
readonly subscribe: boolean
|
|
82
|
+
|
|
83
|
+
constructor(
|
|
84
|
+
context: T,
|
|
85
|
+
callback: ContextCallback<ContextType<T>>,
|
|
86
|
+
subscribe: boolean = false,
|
|
87
|
+
) {
|
|
88
|
+
super(CONTEXT_REQUEST, {
|
|
89
|
+
bubbles: true,
|
|
90
|
+
composed: true,
|
|
91
|
+
})
|
|
92
|
+
this.context = context
|
|
93
|
+
this.callback = callback
|
|
94
|
+
this.subscribe = subscribe
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Provide a context for descendant component consumers
|
|
100
|
+
*
|
|
101
|
+
* @since 0.13.3
|
|
102
|
+
* @param {Context<string, P[K]>[]} contexts - Array of contexts to provide
|
|
103
|
+
* @returns {(host: Component<P>) => Cleanup} Function to add an event listener for ContextRequestEvent returning a cleanup function to remove the event listener
|
|
104
|
+
*/
|
|
105
|
+
const provideContexts =
|
|
106
|
+
<P extends ComponentProps>(
|
|
107
|
+
contexts: Array<keyof P>,
|
|
108
|
+
): ((host: Component<P>) => Cleanup) =>
|
|
109
|
+
(host: Component<P>) => {
|
|
110
|
+
const listener = (e: ContextRequestEvent<UnknownContext>) => {
|
|
111
|
+
const { context, callback } = e
|
|
112
|
+
if (
|
|
113
|
+
isString(context) &&
|
|
114
|
+
contexts.includes(context as unknown as Extract<keyof P, string>) &&
|
|
115
|
+
isFunction(callback)
|
|
116
|
+
) {
|
|
117
|
+
e.stopImmediatePropagation()
|
|
118
|
+
callback(() => host[context])
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
host.addEventListener(CONTEXT_REQUEST, listener)
|
|
122
|
+
return () => host.removeEventListener(CONTEXT_REQUEST, listener)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Consume a context value for a component
|
|
127
|
+
*
|
|
128
|
+
* @since 0.15.0
|
|
129
|
+
* @param {Context<string, () => T>} context - Context key to consume
|
|
130
|
+
* @param {Fallback<T, U>} fallback - Fallback value or reader function for fallback
|
|
131
|
+
* @returns {Reader<Computed<T>, U>} Computed signal that returns the consumed context the fallback value
|
|
132
|
+
*/
|
|
133
|
+
const requestContext =
|
|
134
|
+
<T extends {}, P extends ComponentProps, U extends UI>(
|
|
135
|
+
context: Context<string, () => T>,
|
|
136
|
+
fallback: Fallback<T, U & { host: Component<P> }>,
|
|
137
|
+
): Reader<Computed<T>, U & { host: Component<P> }> =>
|
|
138
|
+
(ui: U & { host: Component<P> }) => {
|
|
139
|
+
let consumed = () => getFallback(ui, fallback)
|
|
140
|
+
ui.host.dispatchEvent(
|
|
141
|
+
new ContextRequestEvent(context, (getter: () => T) => {
|
|
142
|
+
consumed = getter
|
|
143
|
+
}),
|
|
144
|
+
)
|
|
145
|
+
return createComputed(consumed)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export {
|
|
149
|
+
type Context,
|
|
150
|
+
type UnknownContext,
|
|
151
|
+
type ContextType,
|
|
152
|
+
CONTEXT_REQUEST,
|
|
153
|
+
ContextRequestEvent,
|
|
154
|
+
provideContexts,
|
|
155
|
+
requestContext,
|
|
156
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ComponentProps } from '../component'
|
|
2
|
+
import { type Effect, type Reactive, updateElement } from '../effects'
|
|
3
|
+
|
|
4
|
+
/* === Internal Functions === */
|
|
5
|
+
|
|
6
|
+
const isSafeURL = (value: string): boolean => {
|
|
7
|
+
if (/^(mailto|tel):/i.test(value)) return true
|
|
8
|
+
if (value.includes('://')) {
|
|
9
|
+
try {
|
|
10
|
+
const url = new URL(value, window.location.origin)
|
|
11
|
+
return ['http:', 'https:', 'ftp:'].includes(url.protocol)
|
|
12
|
+
} catch {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return true
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const safeSetAttribute = (
|
|
20
|
+
element: Element,
|
|
21
|
+
attr: string,
|
|
22
|
+
value: string,
|
|
23
|
+
): void => {
|
|
24
|
+
if (/^on/i.test(attr)) throw new Error(`Unsafe attribute: ${attr}`)
|
|
25
|
+
value = String(value).trim()
|
|
26
|
+
if (!isSafeURL(value)) throw new Error(`Unsafe URL for ${attr}: ${value}`)
|
|
27
|
+
element.setAttribute(attr, value)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* === Exported Functions === */
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Effect for setting an attribute on an element.
|
|
34
|
+
* Sets the specified attribute with security validation for unsafe values.
|
|
35
|
+
*
|
|
36
|
+
* @since 0.8.0
|
|
37
|
+
* @param {string} name - Name of the attribute to set
|
|
38
|
+
* @param {Reactive<string, P, E>} reactive - Reactive value bound to the attribute value (defaults to attribute name)
|
|
39
|
+
* @returns {Effect<P, E>} Effect function that sets the attribute on the element
|
|
40
|
+
*/
|
|
41
|
+
const setAttribute = <P extends ComponentProps, E extends Element>(
|
|
42
|
+
name: string,
|
|
43
|
+
reactive: Reactive<string, P, E> = name as Reactive<string, P, E>,
|
|
44
|
+
): Effect<P, E> =>
|
|
45
|
+
updateElement(reactive, {
|
|
46
|
+
op: 'a',
|
|
47
|
+
name,
|
|
48
|
+
read: el => el.getAttribute(name),
|
|
49
|
+
update: (el, value) => {
|
|
50
|
+
safeSetAttribute(el, name, value)
|
|
51
|
+
},
|
|
52
|
+
delete: el => {
|
|
53
|
+
el.removeAttribute(name)
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Effect for toggling a boolean attribute on an element.
|
|
59
|
+
* When the reactive value is true, the attribute is present; when false, it's absent.
|
|
60
|
+
*
|
|
61
|
+
* @since 0.8.0
|
|
62
|
+
* @param {string} name - Name of the attribute to toggle
|
|
63
|
+
* @param {Reactive<boolean, P, E>} reactive - Reactive value bound to the attribute presence (defaults to attribute name)
|
|
64
|
+
* @returns {Effect<P, E>} Effect function that toggles the attribute on the element
|
|
65
|
+
*/
|
|
66
|
+
const toggleAttribute = <
|
|
67
|
+
P extends ComponentProps,
|
|
68
|
+
E extends Element = HTMLElement,
|
|
69
|
+
>(
|
|
70
|
+
name: string,
|
|
71
|
+
reactive: Reactive<boolean, P, E> = name as Reactive<boolean, P, E>,
|
|
72
|
+
): Effect<P, E> =>
|
|
73
|
+
updateElement(reactive, {
|
|
74
|
+
op: 'a',
|
|
75
|
+
name,
|
|
76
|
+
read: el => el.hasAttribute(name),
|
|
77
|
+
update: (el, value) => {
|
|
78
|
+
el.toggleAttribute(name, value)
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
export { setAttribute, toggleAttribute }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ComponentProps } from '../component'
|
|
2
|
+
import { type Effect, type Reactive, updateElement } from '../effects'
|
|
3
|
+
|
|
4
|
+
/* === Exported Function === */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Effect for toggling a CSS class token on an element.
|
|
8
|
+
* When the reactive value is true, the class is added; when false, it's removed.
|
|
9
|
+
*
|
|
10
|
+
* @since 0.8.0
|
|
11
|
+
* @param {string} token - CSS class token to toggle
|
|
12
|
+
* @param {Reactive<boolean, P, E>} reactive - Reactive value bound to the class presence (defaults to class name)
|
|
13
|
+
* @returns {Effect<P, U, E>} Effect function that toggles the class on the element
|
|
14
|
+
*/
|
|
15
|
+
const toggleClass = <P extends ComponentProps, E extends Element>(
|
|
16
|
+
token: string,
|
|
17
|
+
reactive: Reactive<boolean, P, E> = token as Reactive<boolean, P, E>,
|
|
18
|
+
): Effect<P, E> =>
|
|
19
|
+
updateElement(reactive, {
|
|
20
|
+
op: 'c',
|
|
21
|
+
name: token,
|
|
22
|
+
read: el => el.classList.contains(token),
|
|
23
|
+
update: (el, value) => {
|
|
24
|
+
el.classList.toggle(token, value)
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export { toggleClass }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { batch, type Cleanup, isRecord } from '@zeix/cause-effect'
|
|
2
|
+
import type { ComponentProps } from '../component'
|
|
3
|
+
import { type Effect } from '../effects'
|
|
4
|
+
import { PASSIVE_EVENTS, schedule } from '../scheduler'
|
|
5
|
+
import { elementName, LOG_ERROR, log } from '../util'
|
|
6
|
+
|
|
7
|
+
/* === Types === */
|
|
8
|
+
|
|
9
|
+
type EventType<K extends string> = K extends keyof HTMLElementEventMap
|
|
10
|
+
? HTMLElementEventMap[K]
|
|
11
|
+
: Event
|
|
12
|
+
|
|
13
|
+
type EventHandler<P extends ComponentProps, Evt extends Event> = (
|
|
14
|
+
event: Evt,
|
|
15
|
+
) => { [K in keyof P]?: P[K] } | void | Promise<void>
|
|
16
|
+
|
|
17
|
+
/* === Exported Function === */
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Effect for attaching an event listener to an element.
|
|
21
|
+
* Provides proper cleanup when the effect is disposed.
|
|
22
|
+
*
|
|
23
|
+
* @since 0.14.0
|
|
24
|
+
* @param {K} type - Event type
|
|
25
|
+
* @param {EventHandler<P, E, EventType<K>>} handler - Event handler function
|
|
26
|
+
* @param {AddEventListenerOptions | boolean} options - Event listener options
|
|
27
|
+
* @returns {Effect<ComponentProps, E>} Effect function that manages the event listener
|
|
28
|
+
*/
|
|
29
|
+
const on =
|
|
30
|
+
<
|
|
31
|
+
K extends keyof HTMLElementEventMap | string,
|
|
32
|
+
P extends ComponentProps,
|
|
33
|
+
E extends Element = HTMLElement,
|
|
34
|
+
>(
|
|
35
|
+
type: K,
|
|
36
|
+
handler: EventHandler<P, EventType<K>>,
|
|
37
|
+
options: AddEventListenerOptions = {},
|
|
38
|
+
): Effect<P, E> =>
|
|
39
|
+
(host, target): Cleanup => {
|
|
40
|
+
if (!('passive' in options))
|
|
41
|
+
options = { ...options, passive: PASSIVE_EVENTS.has(type) }
|
|
42
|
+
const listener = (e: Event) => {
|
|
43
|
+
const task = () => {
|
|
44
|
+
const result = handler(e as EventType<K>)
|
|
45
|
+
if (!isRecord(result)) return
|
|
46
|
+
batch(() => {
|
|
47
|
+
for (const [key, value] of Object.entries(result)) {
|
|
48
|
+
try {
|
|
49
|
+
host[key as keyof P] = value
|
|
50
|
+
} catch (error) {
|
|
51
|
+
log(
|
|
52
|
+
error,
|
|
53
|
+
`Reactive property "${key}" on ${elementName(host)} from event ${type} on ${elementName(target)} could not be set, because it is read-only.`,
|
|
54
|
+
LOG_ERROR,
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
if (options.passive) schedule(target, task)
|
|
61
|
+
else task()
|
|
62
|
+
}
|
|
63
|
+
target.addEventListener(type, listener, options)
|
|
64
|
+
return () => target.removeEventListener(type, listener)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { type EventHandler, type EventType, on }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ComponentProps } from '../component'
|
|
2
|
+
import { type Effect, type Reactive, updateElement } from '../effects'
|
|
3
|
+
import { schedule } from '../scheduler'
|
|
4
|
+
|
|
5
|
+
/* === Types === */
|
|
6
|
+
|
|
7
|
+
type DangerouslySetInnerHTMLOptions = {
|
|
8
|
+
shadowRootMode?: ShadowRootMode
|
|
9
|
+
allowScripts?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* === Exported Function === */
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Effect for setting the inner HTML of an element with optional Shadow DOM support.
|
|
16
|
+
* Provides security options for script execution and shadow root creation.
|
|
17
|
+
*
|
|
18
|
+
* @since 0.11.0
|
|
19
|
+
* @param {Reactive<string, P, E>} reactive - Reactive value bound to the inner HTML content
|
|
20
|
+
* @param {DangerouslySetInnerHTMLOptions} options - Configuration options: shadowRootMode, allowScripts
|
|
21
|
+
* @returns {Effect<P, E>} Effect function that sets the inner HTML of the element
|
|
22
|
+
*/
|
|
23
|
+
const dangerouslySetInnerHTML = <P extends ComponentProps, E extends Element>(
|
|
24
|
+
reactive: Reactive<string, P, E>,
|
|
25
|
+
options: DangerouslySetInnerHTMLOptions = {},
|
|
26
|
+
): Effect<P, E> =>
|
|
27
|
+
updateElement(reactive, {
|
|
28
|
+
op: 'h',
|
|
29
|
+
read: el =>
|
|
30
|
+
(el.shadowRoot || !options.shadowRootMode ? el : null)?.innerHTML ?? '',
|
|
31
|
+
update: (el, html) => {
|
|
32
|
+
const { shadowRootMode, allowScripts } = options
|
|
33
|
+
if (!html) {
|
|
34
|
+
if (el.shadowRoot) el.shadowRoot.innerHTML = '<slot></slot>'
|
|
35
|
+
return ''
|
|
36
|
+
}
|
|
37
|
+
if (shadowRootMode && !el.shadowRoot)
|
|
38
|
+
el.attachShadow({ mode: shadowRootMode })
|
|
39
|
+
const target = el.shadowRoot || el
|
|
40
|
+
schedule(el, () => {
|
|
41
|
+
target.innerHTML = html
|
|
42
|
+
if (allowScripts) {
|
|
43
|
+
target.querySelectorAll('script').forEach(script => {
|
|
44
|
+
const newScript = document.createElement('script')
|
|
45
|
+
newScript.appendChild(
|
|
46
|
+
document.createTextNode(script.textContent ?? ''),
|
|
47
|
+
)
|
|
48
|
+
// Safely copy only the type attribute to preserve module/MIME type info
|
|
49
|
+
const typeAttr = script.getAttribute('type')
|
|
50
|
+
if (typeAttr) newScript.setAttribute('type', typeAttr)
|
|
51
|
+
target.appendChild(newScript)
|
|
52
|
+
script.remove()
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
return allowScripts ? ' with scripts' : ''
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
export { type DangerouslySetInnerHTMLOptions, dangerouslySetInnerHTML }
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ComponentProps } from '../component'
|
|
2
|
+
import { type Effect, type Reactive, updateElement } from '../effects'
|
|
3
|
+
import { hasMethod } from '../util'
|
|
4
|
+
|
|
5
|
+
/* === Exported Functions === */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Effect for calling a method on an element.
|
|
9
|
+
*
|
|
10
|
+
* @since 0.13.3
|
|
11
|
+
* @param {K} methodName - Name of the method to call
|
|
12
|
+
* @param {Reactive<boolean, P, E>} reactive - Reactive value bound to the method call
|
|
13
|
+
* @param {unknown[]} args - Arguments to pass to the method
|
|
14
|
+
* @returns Effect function that calls the method on the element
|
|
15
|
+
*/
|
|
16
|
+
const callMethod = <
|
|
17
|
+
P extends ComponentProps,
|
|
18
|
+
E extends HTMLElement,
|
|
19
|
+
K extends keyof E & string,
|
|
20
|
+
>(
|
|
21
|
+
methodName: K,
|
|
22
|
+
reactive: Reactive<boolean, P, E>,
|
|
23
|
+
args?: unknown[],
|
|
24
|
+
): Effect<P, E> =>
|
|
25
|
+
updateElement(reactive, {
|
|
26
|
+
op: 'm',
|
|
27
|
+
name: String(methodName),
|
|
28
|
+
read: () => null,
|
|
29
|
+
update: (el, value) => {
|
|
30
|
+
if (value && hasMethod(el, methodName)) {
|
|
31
|
+
if (args) el[methodName](...args)
|
|
32
|
+
else el[methodName]()
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Effect for controlling element focus by calling the 'focus()' method.
|
|
39
|
+
* If the reactive value is true, element will be focussed; when false, nothing happens.
|
|
40
|
+
*
|
|
41
|
+
* @since 0.13.3
|
|
42
|
+
* @param {Reactive<boolean, P, E>} reactive - Reactive value bound to the focus state
|
|
43
|
+
* @returns {Effect<P, E>} Effect function that sets element focus
|
|
44
|
+
*/
|
|
45
|
+
const focus = <P extends ComponentProps, E extends HTMLElement>(
|
|
46
|
+
reactive: Reactive<boolean, P, E>,
|
|
47
|
+
): Effect<P, E> =>
|
|
48
|
+
updateElement(reactive, {
|
|
49
|
+
op: 'm',
|
|
50
|
+
name: 'focus',
|
|
51
|
+
read: el => el === document.activeElement,
|
|
52
|
+
update: (el, value) => {
|
|
53
|
+
if (value && hasMethod(el, 'focus')) el.focus()
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export { callMethod, focus }
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createComputed,
|
|
3
|
+
isComputedCallback,
|
|
4
|
+
isFunction,
|
|
5
|
+
isRecord,
|
|
6
|
+
isSignal,
|
|
7
|
+
isString,
|
|
8
|
+
type MaybeCleanup,
|
|
9
|
+
UNSET,
|
|
10
|
+
} from '@zeix/cause-effect'
|
|
11
|
+
import type { Component, ComponentProps } from '../component'
|
|
12
|
+
import type { Effect, Reactive } from '../effects'
|
|
13
|
+
import { InvalidCustomElementError, InvalidReactivesError } from '../errors'
|
|
14
|
+
import { elementName, isCustomElement } from '../util'
|
|
15
|
+
|
|
16
|
+
/* === Types === */
|
|
17
|
+
|
|
18
|
+
type PassedProp<T, P extends ComponentProps, E extends HTMLElement> =
|
|
19
|
+
| Reactive<T, P, E>
|
|
20
|
+
| [Reactive<T, P, E>, (value: T) => void]
|
|
21
|
+
|
|
22
|
+
type PassedProps<P extends ComponentProps, Q extends ComponentProps> = {
|
|
23
|
+
[K in keyof Q & string]?: PassedProp<Q[K], P, Component<Q>>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* === Exported Function === */
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Effect for passing reactive values to a descendant Le Truc component.
|
|
30
|
+
*
|
|
31
|
+
* @since 0.15.0
|
|
32
|
+
* @param {MutableReactives<Component<Q>, P>} props - Reactive values to pass
|
|
33
|
+
* @returns {Effect<P, Component<Q>>} Effect function that passes reactive values to the descendant component
|
|
34
|
+
* @throws {InvalidCustomElementError} When the target element is not a valid custom element
|
|
35
|
+
* @throws {InvalidReactivesError} When the provided reactives is not a record of signals, reactive property names or functions
|
|
36
|
+
* @throws {Error} When passing signals failed for some other reason
|
|
37
|
+
*/
|
|
38
|
+
const pass =
|
|
39
|
+
<P extends ComponentProps, Q extends ComponentProps>(
|
|
40
|
+
props: PassedProps<P, Q> | ((target: Component<Q>) => PassedProps<P, Q>),
|
|
41
|
+
): Effect<P, Component<Q>> =>
|
|
42
|
+
(host, target): MaybeCleanup => {
|
|
43
|
+
if (!isCustomElement(target))
|
|
44
|
+
throw new InvalidCustomElementError(
|
|
45
|
+
target,
|
|
46
|
+
`pass from ${elementName(host)}`,
|
|
47
|
+
)
|
|
48
|
+
const reactives = isFunction(props) ? props(target) : props
|
|
49
|
+
if (!isRecord(reactives))
|
|
50
|
+
throw new InvalidReactivesError(host, target, reactives)
|
|
51
|
+
|
|
52
|
+
const resetProperties: PropertyDescriptorMap = {}
|
|
53
|
+
|
|
54
|
+
// Return getter from signal, reactive property name or function
|
|
55
|
+
const getGetter = (value: unknown) => {
|
|
56
|
+
if (isSignal(value)) return value.get
|
|
57
|
+
const fn =
|
|
58
|
+
isString(value) && value in host
|
|
59
|
+
? () => host[value as keyof typeof host]
|
|
60
|
+
: isComputedCallback(value)
|
|
61
|
+
? value
|
|
62
|
+
: undefined
|
|
63
|
+
return fn ? createComputed(fn).get : undefined
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Iterate through reactives
|
|
67
|
+
for (const [prop, reactive] of Object.entries(reactives)) {
|
|
68
|
+
if (reactive == null) continue
|
|
69
|
+
|
|
70
|
+
// Ensure target has configurable property
|
|
71
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, prop)
|
|
72
|
+
if (!(prop in target) || !descriptor?.configurable) continue
|
|
73
|
+
|
|
74
|
+
// Determine getter and setter
|
|
75
|
+
const applied =
|
|
76
|
+
isFunction(reactive) && reactive.length === 1
|
|
77
|
+
? reactive(target)
|
|
78
|
+
: reactive
|
|
79
|
+
const isArray = Array.isArray(applied) && applied.length === 2
|
|
80
|
+
const getter = getGetter(isArray ? applied[0] : applied)
|
|
81
|
+
const setter = isArray && isFunction(applied[1]) ? applied[1] : undefined
|
|
82
|
+
if (!getter) continue
|
|
83
|
+
|
|
84
|
+
// Store original descriptor for reset and assign new descriptor
|
|
85
|
+
resetProperties[prop] = descriptor
|
|
86
|
+
Object.defineProperty(target, prop, {
|
|
87
|
+
configurable: true,
|
|
88
|
+
enumerable: true,
|
|
89
|
+
get: getter,
|
|
90
|
+
set: setter,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Unset previous value so subscribers are notified
|
|
94
|
+
descriptor.set?.call(target, UNSET)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Reset to original descriptors on cleanup
|
|
98
|
+
return () => {
|
|
99
|
+
Object.defineProperties(target, resetProperties)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { type PassedProp, type PassedProps, pass }
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { UNSET } from '@zeix/cause-effect'
|
|
2
|
+
import type { ComponentProps } from '../component'
|
|
3
|
+
import { type Effect, type Reactive, updateElement } from '../effects'
|
|
4
|
+
|
|
5
|
+
/* === Exported Functions === */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Effect for setting a property on an element.
|
|
9
|
+
* Sets the specified property directly on the element object.
|
|
10
|
+
*
|
|
11
|
+
* @since 0.8.0
|
|
12
|
+
* @param {K} key - Name of the property to set
|
|
13
|
+
* @param {Reactive<E[K], P, E>} reactive - Reactive value bound to the property value (defaults to property name)
|
|
14
|
+
* @returns {Effect<P, E>} Effect function that sets the property on the element
|
|
15
|
+
*/
|
|
16
|
+
const setProperty = <
|
|
17
|
+
P extends ComponentProps,
|
|
18
|
+
E extends Element,
|
|
19
|
+
K extends keyof E & string,
|
|
20
|
+
>(
|
|
21
|
+
key: K,
|
|
22
|
+
reactive: Reactive<E[K] & {}, P, E> = key as unknown as Reactive<
|
|
23
|
+
E[K] & {},
|
|
24
|
+
P,
|
|
25
|
+
E
|
|
26
|
+
>,
|
|
27
|
+
): Effect<P, E> =>
|
|
28
|
+
updateElement(reactive, {
|
|
29
|
+
op: 'p',
|
|
30
|
+
name: key,
|
|
31
|
+
read: el => (key in el ? el[key] : UNSET),
|
|
32
|
+
update: (el, value) => {
|
|
33
|
+
el[key] = value
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Effect for controlling element visibility by setting the 'hidden' property.
|
|
39
|
+
* When the reactive value is true, the element is shown; when false, it's hidden.
|
|
40
|
+
*
|
|
41
|
+
* @since 0.13.1
|
|
42
|
+
* @param {Reactive<boolean, P, E>} reactive - Reactive value bound to the visibility state
|
|
43
|
+
* @returns {Effect<P, E>} Effect function that controls element visibility
|
|
44
|
+
*/
|
|
45
|
+
const show = <P extends ComponentProps, E extends HTMLElement = HTMLElement>(
|
|
46
|
+
reactive: Reactive<boolean, P, E>,
|
|
47
|
+
): Effect<P, E> =>
|
|
48
|
+
updateElement(reactive, {
|
|
49
|
+
op: 'p',
|
|
50
|
+
name: 'hidden',
|
|
51
|
+
read: el => !el.hidden,
|
|
52
|
+
update: (el, value) => {
|
|
53
|
+
el.hidden = !value
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export { setProperty, show }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ComponentProps } from '../component'
|
|
2
|
+
import { type Effect, type Reactive, updateElement } from '../effects'
|
|
3
|
+
|
|
4
|
+
/* === Exported Function === */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Effect for setting a CSS style property on an element.
|
|
8
|
+
* Sets the specified style property with support for deletion via UNSET.
|
|
9
|
+
*
|
|
10
|
+
* @since 0.8.0
|
|
11
|
+
* @param {string} prop - Name of the CSS style property to set
|
|
12
|
+
* @param {Reactive<string, P, E>} reactive - Reactive value bound to the style property value (defaults to property name)
|
|
13
|
+
* @returns {Effect<P, E>} Effect function that sets the style property on the element
|
|
14
|
+
*/
|
|
15
|
+
const setStyle = <
|
|
16
|
+
P extends ComponentProps,
|
|
17
|
+
E extends HTMLElement | SVGElement | MathMLElement,
|
|
18
|
+
>(
|
|
19
|
+
prop: string,
|
|
20
|
+
reactive: Reactive<string, P, E> = prop as Reactive<string, P, E>,
|
|
21
|
+
): Effect<P, E> =>
|
|
22
|
+
updateElement(reactive, {
|
|
23
|
+
op: 's',
|
|
24
|
+
name: prop,
|
|
25
|
+
read: el => el.style.getPropertyValue(prop),
|
|
26
|
+
update: (el, value) => {
|
|
27
|
+
el.style.setProperty(prop, value)
|
|
28
|
+
},
|
|
29
|
+
delete: el => {
|
|
30
|
+
el.style.removeProperty(prop)
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export { setStyle }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ComponentProps } from '../component'
|
|
2
|
+
import { type Effect, type Reactive, updateElement } from '../effects'
|
|
3
|
+
|
|
4
|
+
/* === Exported Function === */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Effect for setting the text content of an element.
|
|
8
|
+
* Replaces all child nodes (except comments) with a single text node.
|
|
9
|
+
*
|
|
10
|
+
* @since 0.8.0
|
|
11
|
+
* @param {Reactive<string, P, E>} reactive - Reactive value bound to the text content
|
|
12
|
+
* @returns {Effect<P, E>} Effect function that sets the text content of the element
|
|
13
|
+
*/
|
|
14
|
+
const setText = <P extends ComponentProps, E extends Element>(
|
|
15
|
+
reactive: Reactive<string, P, E>,
|
|
16
|
+
): Effect<P, E> =>
|
|
17
|
+
updateElement(reactive, {
|
|
18
|
+
op: 't',
|
|
19
|
+
read: el => el.textContent,
|
|
20
|
+
update: (el, value) => {
|
|
21
|
+
Array.from(el.childNodes)
|
|
22
|
+
.filter(node => node.nodeType !== Node.COMMENT_NODE)
|
|
23
|
+
.forEach(node => node.remove())
|
|
24
|
+
el.append(document.createTextNode(value))
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export { setText }
|