@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
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
**le-truc**
|
|
2
|
+
|
|
3
|
+
***
|
|
4
|
+
|
|
5
|
+
# Le Truc
|
|
6
|
+
|
|
7
|
+
Version 0.15.0
|
|
8
|
+
|
|
9
|
+
**Le Truc - the thing for type-safe reactive web components**
|
|
10
|
+
|
|
11
|
+
Le Truc helps you create reusable, interactive web components that work with any backend or static site generator. Build once, use everywhere.
|
|
12
|
+
|
|
13
|
+
Le Truc is a set of functions to build reusable, loosely coupled Web Components with reactive properties. It provides structure through components and simplifies state management and DOM synchronization using signals and effects, leading to more organized and maintainable code without a steep learning curve.
|
|
14
|
+
|
|
15
|
+
Unlike SPA frameworks (React, Vue, Svelte, etc.) Le Truc takes a HTML-first approach, progressively enhancing server-rendered HTML rather than recreating (rendering) it using JavaScript. Le Truc achieves the same result as SPA frameworks with SSR, with a simpler, more efficient approach.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
Add interactivity to your HTML in three steps:
|
|
20
|
+
|
|
21
|
+
1. Start with HTML:
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<basic-hello>
|
|
25
|
+
<label for="name">Your name</label>
|
|
26
|
+
<input id="name" name="name" type="text" autocomplete="given-name" />
|
|
27
|
+
<p>Hello, <output for="name">World</output>!</p>
|
|
28
|
+
</basic-hello>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. Define the component:
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
import { asString, defineComponent, on, setText } from '@zeix/le-truc'
|
|
35
|
+
|
|
36
|
+
defineComponent(
|
|
37
|
+
'basic-hello', // 1. Component name
|
|
38
|
+
{ name: asString('World') }, // 2. Reactive property
|
|
39
|
+
q => ({ // 3. Find DOM elements
|
|
40
|
+
input: q.first('input'),
|
|
41
|
+
output: q.first('output'),
|
|
42
|
+
}),
|
|
43
|
+
({ host, input }) => ({ // 4. Define behavior
|
|
44
|
+
input: on('input', () => { host.name = input.value }),
|
|
45
|
+
output: setText('name'),
|
|
46
|
+
}),
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
3. Import and watch it work!
|
|
51
|
+
|
|
52
|
+
## Key Features
|
|
53
|
+
|
|
54
|
+
- 🧱 **HTML Web Components**: Build on standard HTML and enhance it with reusable Web Components. No Virtual DOM – Le Truc works directly with the real DOM.
|
|
55
|
+
- 🚦 **Reactive Properties**: Get and set values like with normal element properties, but they automatically track reads and notify on changes (signals).
|
|
56
|
+
- ⚡️ **Fine-grained Effects**: Pinpoint updates to the parts of the DOM that need updating, avoiding unnecessary re-renders.
|
|
57
|
+
- 🧩 **Function Composition**: Declare component behavior by composing small, reusable functions (parsers and effects).
|
|
58
|
+
- 🛠️ **Customizable**: Le Truc is designed to be easily customizable and extensible. Create your own custom parsers and effects to suit your specific needs.
|
|
59
|
+
- 🌐 **Context Support**: Share global states across components without prop drilling or tightly coupling logic.
|
|
60
|
+
- 🪶 **Tiny footprint**: Minimal core (~8kB gzipped) with tree-shaking support, minimizing JavaScript bundle size.
|
|
61
|
+
- 🛡️ **Type Safety**: Early warnings when types don't match improve code quality and reduce bugs.
|
|
62
|
+
|
|
63
|
+
Le Truc uses [Cause & Effect](https://github.com/zeixcom/cause-effect) internally for state management with signals and glitch-free DOM updates. If wanted, you could fork Le Truc and replace Cause & Effect with a different state management library without changes to the user-facing `createComponent()` API.
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# with npm
|
|
69
|
+
npm install @zeix/le-truc
|
|
70
|
+
|
|
71
|
+
# or with bun
|
|
72
|
+
bun add @zeix/le-truc
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Documentation
|
|
76
|
+
|
|
77
|
+
The full documentation is still work in progress. The following chapters are already reasonably complete:
|
|
78
|
+
|
|
79
|
+
- [Introduction](https://zeixcom.github.io/le-truc/index.html)
|
|
80
|
+
- [Getting Started](https://zeixcom.github.io/le-truc/getting-started.html)
|
|
81
|
+
- [Components](https://zeixcom.github.io/le-truc/components.html)
|
|
82
|
+
- [Styling](https://zeixcom.github.io/le-truc/styling.html)
|
|
83
|
+
- [Data Flow](https://zeixcom.github.io/le-truc/data-flow.html)
|
|
84
|
+
- [About](https://zeixcom.github.io/le-truc/about.html)
|
|
85
|
+
|
|
86
|
+
## Basic Usage
|
|
87
|
+
|
|
88
|
+
1. Start with HTML:
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<basic-counter>
|
|
92
|
+
<button type="button">💐 <span>5</span></button>
|
|
93
|
+
</basic-counter>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
2. Define the component:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
import { asInteger, defineComponent, on, read, setText } from '@zeix/le-truc'
|
|
100
|
+
|
|
101
|
+
export default defineComponent(
|
|
102
|
+
// 1. Component name
|
|
103
|
+
'basic-counter',
|
|
104
|
+
|
|
105
|
+
// 2. Reactive properties (signals)
|
|
106
|
+
{
|
|
107
|
+
// Count property is read from the DOM (ui.count) and converted to an integer
|
|
108
|
+
count: read(ui => ui.count.textContent, asInteger()),
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
// 3. Find DOM elements
|
|
112
|
+
({ first }) => ({
|
|
113
|
+
// first() returns the first element matching the selector
|
|
114
|
+
increment: first(
|
|
115
|
+
'button',
|
|
116
|
+
'Add a native button element to increment the count.',
|
|
117
|
+
),
|
|
118
|
+
count: first('span', 'Add a span to display the count.'),
|
|
119
|
+
}),
|
|
120
|
+
|
|
121
|
+
// 4. Define behavior (effects)
|
|
122
|
+
({ host }) => ({ // host is the component element with reactive properties
|
|
123
|
+
// Add a click event listener to the increment button
|
|
124
|
+
increment: on('click', () => {
|
|
125
|
+
host.count++
|
|
126
|
+
}),
|
|
127
|
+
// Set the text of the count element to the count property whenever it changes
|
|
128
|
+
count: setText('count'),
|
|
129
|
+
}),
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Example styles:
|
|
134
|
+
|
|
135
|
+
```css
|
|
136
|
+
basic-counter {
|
|
137
|
+
& button {
|
|
138
|
+
border: 1px solid var(--color-border);
|
|
139
|
+
border-radius: var(--space-xs);
|
|
140
|
+
background-color: var(--color-secondary);
|
|
141
|
+
padding: var(--space-xs) var(--space-s);
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
color: var(--color-text);
|
|
144
|
+
font-size: var(--font-size-m);
|
|
145
|
+
line-height: var(--line-height-xs);
|
|
146
|
+
transition: background-color var(--transition-short) var(--easing-inout);
|
|
147
|
+
|
|
148
|
+
&:hover {
|
|
149
|
+
background-color: var(--color-secondary-hover);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
&:active {
|
|
153
|
+
background-color: var(--color-secondary-active);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
3. Import and watch it work!
|
|
160
|
+
|
|
161
|
+
## Advanced Examples
|
|
162
|
+
|
|
163
|
+
### Tab Group
|
|
164
|
+
|
|
165
|
+
An example demonstrating how to create a fully accessible tab navigation.
|
|
166
|
+
|
|
167
|
+
Server-rendered markup:
|
|
168
|
+
|
|
169
|
+
```html
|
|
170
|
+
<module-tabgroup>
|
|
171
|
+
<div role="tablist">
|
|
172
|
+
<button
|
|
173
|
+
type="button"
|
|
174
|
+
role="tab"
|
|
175
|
+
id="trigger1"
|
|
176
|
+
aria-controls="panel1"
|
|
177
|
+
aria-selected="true"
|
|
178
|
+
tabindex="0"
|
|
179
|
+
>
|
|
180
|
+
Tab 1
|
|
181
|
+
</button>
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
role="tab"
|
|
185
|
+
id="trigger2"
|
|
186
|
+
aria-controls="panel2"
|
|
187
|
+
aria-selected="false"
|
|
188
|
+
tabindex="-1"
|
|
189
|
+
>
|
|
190
|
+
Tab 2
|
|
191
|
+
</button>
|
|
192
|
+
<button
|
|
193
|
+
type="button"
|
|
194
|
+
role="tab"
|
|
195
|
+
id="trigger3"
|
|
196
|
+
aria-controls="panel3"
|
|
197
|
+
aria-selected="false"
|
|
198
|
+
tabindex="-1"
|
|
199
|
+
>
|
|
200
|
+
Tab 3
|
|
201
|
+
</button>
|
|
202
|
+
</div>
|
|
203
|
+
<div role="tabpanel" id="panel1" aria-labelledby="trigger1">
|
|
204
|
+
Tab 1 content
|
|
205
|
+
</div>
|
|
206
|
+
<div role="tabpanel" id="panel2" aria-labelledby="trigger2" hidden>
|
|
207
|
+
Tab 2 content
|
|
208
|
+
</div>
|
|
209
|
+
<div role="tabpanel" id="panel3" aria-labelledby="trigger3" hidden>
|
|
210
|
+
Tab 3 content
|
|
211
|
+
</div>
|
|
212
|
+
</module-tabgroup>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Le Truc component:
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
import { createSensor, defineComponent, read, setProperty, show } from '@zeix/le-truc'
|
|
219
|
+
|
|
220
|
+
const getAriaControls = element => element.getAttribute('aria-controls') ?? ''
|
|
221
|
+
|
|
222
|
+
const getSelected = (elements, isCurrent, offset = 0) => {
|
|
223
|
+
const tabs = elements.get()
|
|
224
|
+
const currentIndex = tabs.findIndex(isCurrent)
|
|
225
|
+
const newIndex = (currentIndex + offset + tabs.length) % tabs.length
|
|
226
|
+
return getAriaControls(tabs[newIndex])
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export default defineComponent(
|
|
230
|
+
// 1. Component name
|
|
231
|
+
'module-tabgroup',
|
|
232
|
+
|
|
233
|
+
// 2. Reactive properties (signals)
|
|
234
|
+
{
|
|
235
|
+
// Sensors are read-only signals that update on user interaction only (events)
|
|
236
|
+
selected: createSensor(
|
|
237
|
+
// Initial value from aria-selected attribute
|
|
238
|
+
read(ui => getSelected(ui.tabs, tab => tab.ariaSelected === 'true'), ''),
|
|
239
|
+
// Target element(s) key
|
|
240
|
+
'tabs',
|
|
241
|
+
// Event handlers return a value to update the signal
|
|
242
|
+
{
|
|
243
|
+
click: ({ target }) => getAriaControls(target),
|
|
244
|
+
keyup: ({ event, ui, target }) => {
|
|
245
|
+
const key = event.key
|
|
246
|
+
if (
|
|
247
|
+
[
|
|
248
|
+
'ArrowLeft',
|
|
249
|
+
'ArrowRight',
|
|
250
|
+
'ArrowUp',
|
|
251
|
+
'ArrowDown',
|
|
252
|
+
'Home',
|
|
253
|
+
'End',
|
|
254
|
+
].includes(key)
|
|
255
|
+
) {
|
|
256
|
+
event.preventDefault()
|
|
257
|
+
event.stopPropagation()
|
|
258
|
+
const tabs = ui.tabs.get()
|
|
259
|
+
const next =
|
|
260
|
+
key === 'Home'
|
|
261
|
+
? getAriaControls(tabs[0])
|
|
262
|
+
: key === 'End'
|
|
263
|
+
? getAriaControls(tabs[tabs.length - 1])
|
|
264
|
+
: getSelected(
|
|
265
|
+
ui.tabs,
|
|
266
|
+
tab => tab === target,
|
|
267
|
+
key === 'ArrowLeft' || key === 'ArrowUp' ? -1 : 1,
|
|
268
|
+
)
|
|
269
|
+
tabs.filter(tab => getAriaControls(tab) === next)[0].focus()
|
|
270
|
+
return next
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
),
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
// 3. Find DOM elements
|
|
278
|
+
({ all }) => ({
|
|
279
|
+
// all() returns a Collection signal that holds all elements matching the selector,
|
|
280
|
+
// dynamically updating when the DOM changes
|
|
281
|
+
tabs: all(
|
|
282
|
+
'button[role="tab"]',
|
|
283
|
+
'At least 2 tabs as children of a <[role="tablist"]> element are needed. Each tab must reference a unique id of a <[role="tabpanel"]> element.',
|
|
284
|
+
),
|
|
285
|
+
panels: all(
|
|
286
|
+
'[role="tabpanel"]',
|
|
287
|
+
'At least 2 tabpanels are needed. Each tabpanel must have a unique id.',
|
|
288
|
+
),
|
|
289
|
+
}),
|
|
290
|
+
|
|
291
|
+
// 4. Define behavior (effects)
|
|
292
|
+
({ host }) => {
|
|
293
|
+
// Extracted function to check if a tab is the current selected tab
|
|
294
|
+
const isCurrentTab = tab => host.selected === getAriaControls(tab)
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
// Set properties on tabs based on their selection status
|
|
298
|
+
tabs: [
|
|
299
|
+
setProperty('ariaSelected', target => String(isCurrentTab(target))),
|
|
300
|
+
setProperty('tabIndex', target => (isCurrentTab(target) ? 0 : -1)),
|
|
301
|
+
],
|
|
302
|
+
// Toggle visibility of panels based on the selected tab
|
|
303
|
+
panels: show(target => host.selected === target.id),
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Example styles:
|
|
310
|
+
|
|
311
|
+
```css
|
|
312
|
+
module-tabgroup {
|
|
313
|
+
display: block;
|
|
314
|
+
margin-bottom: var(--space-l);
|
|
315
|
+
|
|
316
|
+
> [role="tablist"] {
|
|
317
|
+
display: flex;
|
|
318
|
+
border-bottom: 1px solid var(--color-border);
|
|
319
|
+
padding: 0;
|
|
320
|
+
margin-bottom: 0;
|
|
321
|
+
|
|
322
|
+
> [role="tab"] {
|
|
323
|
+
border: 0;
|
|
324
|
+
border-top: 2px solid transparent;
|
|
325
|
+
border-bottom-width: 0;
|
|
326
|
+
border-radius: var(--space-xs) var(--space-xs) 0 0;
|
|
327
|
+
font-family: var(--font-family-sans);
|
|
328
|
+
font-size: var(--font-size-s);
|
|
329
|
+
font-weight: var(--font-weight-bold);
|
|
330
|
+
padding: var(--space-s) var(--space-m);
|
|
331
|
+
color: var(--color-text-soft);
|
|
332
|
+
background-color: var(--color-secondary);
|
|
333
|
+
cursor: pointer;
|
|
334
|
+
transition: all var(--transition-short) var(--easing-inout);
|
|
335
|
+
|
|
336
|
+
&:hover,
|
|
337
|
+
&:focus {
|
|
338
|
+
color: var(--color-text);
|
|
339
|
+
background-color: var(--color-secondary-hover);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
&:focus {
|
|
343
|
+
z-index: 1;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
&:active {
|
|
347
|
+
color: var(--color-text);
|
|
348
|
+
background-color: var(--color-secondary-active);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
&[aria-selected="true"] {
|
|
352
|
+
color: var(--color-primary-active);
|
|
353
|
+
border-top: 3px solid var(--color-primary);
|
|
354
|
+
background-color: var(--color-background);
|
|
355
|
+
margin-bottom: -1px;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
> [role="tabpanel"] {
|
|
361
|
+
font-family: sans-serif;
|
|
362
|
+
font-size: var(--font-size-m);
|
|
363
|
+
background: var(--color-background);
|
|
364
|
+
margin-block: var(--space-l);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Lazy Load
|
|
370
|
+
|
|
371
|
+
An example demonstrating how to use a custom attribute parser (sanitize an URL) and a signal producer (async fetch) to implement lazy loading.
|
|
372
|
+
|
|
373
|
+
```html
|
|
374
|
+
<module-lazyload src="/module-lazyload/snippet.html">
|
|
375
|
+
<card-callout>
|
|
376
|
+
<p class="loading" role="status">Loading...</p>
|
|
377
|
+
<p class="error" role="alert" aria-live="assertive" hidden></p>
|
|
378
|
+
</card-callout>
|
|
379
|
+
<div class="content" hidden></div>
|
|
380
|
+
</module-lazyload>
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Le Truc component:
|
|
384
|
+
|
|
385
|
+
```js
|
|
386
|
+
import {
|
|
387
|
+
asString,
|
|
388
|
+
type Component,
|
|
389
|
+
createComputed,
|
|
390
|
+
dangerouslySetInnerHTML,
|
|
391
|
+
defineComponent,
|
|
392
|
+
setText,
|
|
393
|
+
show,
|
|
394
|
+
toggleClass,
|
|
395
|
+
} from '@zeix/le-truc'
|
|
396
|
+
import { isRecursiveURL, isValidURL } from '../_common/fetch'
|
|
397
|
+
|
|
398
|
+
export default defineComponent(
|
|
399
|
+
// 1. Component name
|
|
400
|
+
'module-lazyload',
|
|
401
|
+
|
|
402
|
+
// 2. Reactive properties (signals)
|
|
403
|
+
{
|
|
404
|
+
src: asString(),
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
// 3. Find DOM elements
|
|
408
|
+
({ first }) => ({
|
|
409
|
+
callout: first(
|
|
410
|
+
'card-callout',
|
|
411
|
+
'Needed to display loading state and error messages.',
|
|
412
|
+
),
|
|
413
|
+
loading: first('.loading', 'Needed to display loading state.'),
|
|
414
|
+
error: first('.error', 'Needed to display error messages.'),
|
|
415
|
+
content: first('.content', 'Needed to display content.'),
|
|
416
|
+
}),
|
|
417
|
+
|
|
418
|
+
// 4. Define behavior (effects)
|
|
419
|
+
ui => {
|
|
420
|
+
const { host } = ui
|
|
421
|
+
|
|
422
|
+
// Private async computed signal to fetch content from the provided URL
|
|
423
|
+
const result = createComputed(
|
|
424
|
+
async (_prev, abort) => {
|
|
425
|
+
const url = host.src
|
|
426
|
+
const error = !url
|
|
427
|
+
? 'No URL provided'
|
|
428
|
+
: !isValidURL(url)
|
|
429
|
+
? 'Invalid URL'
|
|
430
|
+
: isRecursiveURL(url, host)
|
|
431
|
+
? 'Recursive URL detected'
|
|
432
|
+
: ''
|
|
433
|
+
if (error) return { ok: false, value: '', error, pending: false }
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
const response = await fetch(url, abort)
|
|
437
|
+
if (!response.ok) throw new Error(`HTTP error: ${response.statusText}`)
|
|
438
|
+
const content = await response.text()
|
|
439
|
+
return { ok: true, value: content, error: '', pending: false }
|
|
440
|
+
} catch (error) {
|
|
441
|
+
return {
|
|
442
|
+
ok: false,
|
|
443
|
+
value: '',
|
|
444
|
+
error: `Failed to fetch content for "${url}": ${String(error)}`,
|
|
445
|
+
pending: false,
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
// Initial value of the signal before the Promise is resolved
|
|
450
|
+
{ ok: false, value: '', error: '', pending: true },
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
// Extracted function to check if an error occurred
|
|
454
|
+
const hasError = () => !!result.get().error
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
callout: [show(() => !result.get().ok), toggleClass('danger', hasError)],
|
|
458
|
+
loading: show(() => !!result.get().pending),
|
|
459
|
+
error: [show(hasError), setText(() => result.get().error ?? '')],
|
|
460
|
+
content: [
|
|
461
|
+
show(() => result.get().ok),
|
|
462
|
+
// Set inner HTML to the fetched content (use only for trusted sources)
|
|
463
|
+
dangerouslySetInnerHTML(() => result.get().value ?? '', {
|
|
464
|
+
allowScripts: host.hasAttribute('allow-scripts'),
|
|
465
|
+
}),
|
|
466
|
+
],
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
)
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## Contributing & License
|
|
473
|
+
|
|
474
|
+
Feel free to contribute, report issues, or suggest improvements.
|
|
475
|
+
|
|
476
|
+
License: [MIT](_media/LICENSE)
|
|
477
|
+
|
|
478
|
+
(c) 2025 [Zeix AG](https://zeix.com)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 - 2025 Zeix AG
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|