@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,81 @@
|
|
|
1
|
+
section-menu {
|
|
2
|
+
grid-column: breakout;
|
|
3
|
+
|
|
4
|
+
& ol {
|
|
5
|
+
display: grid;
|
|
6
|
+
list-style: none;
|
|
7
|
+
gap: var(--space-s);
|
|
8
|
+
margin-block: var(--space-l);
|
|
9
|
+
grid-template-columns: 1fr;
|
|
10
|
+
padding: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
& li {
|
|
14
|
+
padding: 0;
|
|
15
|
+
margin: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
& a {
|
|
19
|
+
display: grid;
|
|
20
|
+
grid-template-areas: "icon title" "icon description";
|
|
21
|
+
grid-template-columns: auto 1fr;
|
|
22
|
+
grid-template-rows: auto 1fr;
|
|
23
|
+
gap: var(--space-xs) var(--space-s);
|
|
24
|
+
padding: var(--space-m);
|
|
25
|
+
border-radius: var(--space-xs);
|
|
26
|
+
height: calc(100% - 2 * var(--space-m));
|
|
27
|
+
color: var(--color-text);
|
|
28
|
+
text-decoration: none;
|
|
29
|
+
|
|
30
|
+
&:hover,
|
|
31
|
+
&:focus {
|
|
32
|
+
background-color: var(--color-secondary-hover);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&.active {
|
|
36
|
+
background-color: var(--color-background-alt);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.icon {
|
|
40
|
+
grid-area: icon;
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
font-size: var(--font-size-l);
|
|
45
|
+
width: var(--space-xl);
|
|
46
|
+
height: var(--space-xl);
|
|
47
|
+
border-radius: 50%;
|
|
48
|
+
background-color: var(--color-background);
|
|
49
|
+
border: 1px solid var(--color-border);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
& strong {
|
|
53
|
+
grid-area: title;
|
|
54
|
+
font-size: var(--font-size-m);
|
|
55
|
+
line-height: var(--line-height-s);
|
|
56
|
+
margin: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
& small {
|
|
60
|
+
grid-area: description;
|
|
61
|
+
font-size: var(--font-size-s);
|
|
62
|
+
line-height: var(--line-height-m);
|
|
63
|
+
margin: 0;
|
|
64
|
+
color: var(--color-text-soft);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@media screen and (min-width: 32em) and (max-width: 48em) {
|
|
70
|
+
section-menu ol {
|
|
71
|
+
grid-template-columns: 1fr 1fr;
|
|
72
|
+
gap: var(--space-m);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@media screen and (min-width: 48em) {
|
|
77
|
+
section-menu ol {
|
|
78
|
+
grid-template-columns: 1fr 1fr 1fr;
|
|
79
|
+
gap: var(--space-l);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { serve } from 'bun'
|
|
2
|
+
import { readdir, readFile } from 'fs/promises'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
|
|
5
|
+
// Directory containing example fragments
|
|
6
|
+
const EXAMPLES_DIR = './examples'
|
|
7
|
+
const LAYOUT_PATH = join(EXAMPLES_DIR, 'layout.html')
|
|
8
|
+
|
|
9
|
+
// Helper to get all component fragments (*.html in examples/*/)
|
|
10
|
+
async function getComponentFragments() {
|
|
11
|
+
const dirs = await readdir(EXAMPLES_DIR, { withFileTypes: true })
|
|
12
|
+
const fragments: Record<string, string> = {}
|
|
13
|
+
for (const dir of dirs) {
|
|
14
|
+
if (!dir.isDirectory() || dir.name === 'assets' || dir.name === '_common')
|
|
15
|
+
continue
|
|
16
|
+
const files = await readdir(join(EXAMPLES_DIR, dir.name))
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
if (file.endsWith('.html')) {
|
|
19
|
+
const component = file.replace(/\.html$/, '')
|
|
20
|
+
fragments[component] = join(EXAMPLES_DIR, dir.name, file)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return fragments
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Serve /test/{component}.html with injected fragment
|
|
28
|
+
async function serveTestPage(component: string): Promise<Response> {
|
|
29
|
+
try {
|
|
30
|
+
const fragments = await getComponentFragments()
|
|
31
|
+
const fragmentPath = fragments[component]
|
|
32
|
+
if (!fragmentPath) {
|
|
33
|
+
return new Response('Component not found', { status: 404 })
|
|
34
|
+
}
|
|
35
|
+
const [layout, fragment] = await Promise.all([
|
|
36
|
+
readFile(LAYOUT_PATH, 'utf8'),
|
|
37
|
+
readFile(fragmentPath, 'utf8'),
|
|
38
|
+
])
|
|
39
|
+
// Replace marker in layout with fragment
|
|
40
|
+
const html = layout.replace(/<!-- \{\{content}} -->/, fragment)
|
|
41
|
+
return new Response(html, {
|
|
42
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
43
|
+
})
|
|
44
|
+
} catch (_err) {
|
|
45
|
+
return new Response('Error rendering test page', { status: 500 })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Serve static assets (main.js, main.css, etc.)
|
|
50
|
+
async function serveStaticAsset(url: string): Promise<Response> {
|
|
51
|
+
try {
|
|
52
|
+
const assetPath = join('.', url)
|
|
53
|
+
const asset = await readFile(assetPath)
|
|
54
|
+
let contentType = 'application/octet-stream'
|
|
55
|
+
if (url.endsWith('.js')) contentType = 'application/javascript'
|
|
56
|
+
if (url.endsWith('.css')) contentType = 'text/css'
|
|
57
|
+
if (url.endsWith('.html')) contentType = 'text/html'
|
|
58
|
+
if (url.endsWith('.json')) contentType = 'application/json'
|
|
59
|
+
return new Response(asset, { headers: { 'Content-Type': contentType } })
|
|
60
|
+
} catch (_err) {
|
|
61
|
+
return new Response('Asset not found', { status: 404 })
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Main server
|
|
66
|
+
serve({
|
|
67
|
+
port: 3000,
|
|
68
|
+
async fetch(req) {
|
|
69
|
+
const url = new URL(req.url)
|
|
70
|
+
// Serve /test/{component}.html
|
|
71
|
+
const testMatch = url.pathname.match(/^\/test\/([a-zA-Z0-9_-]+)\.html$/)
|
|
72
|
+
if (testMatch) {
|
|
73
|
+
const component = testMatch[1]
|
|
74
|
+
return await serveTestPage(component)
|
|
75
|
+
}
|
|
76
|
+
// Serve static assets from /examples/assets/
|
|
77
|
+
if (url.pathname.startsWith('/assets/')) {
|
|
78
|
+
return await serveStaticAsset('examples' + url.pathname)
|
|
79
|
+
}
|
|
80
|
+
// Serve static fragments and other files for manual inspection
|
|
81
|
+
const fragmentMatch = url.pathname.match(
|
|
82
|
+
/^\/([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_-]+)\.(html|json)$/,
|
|
83
|
+
)
|
|
84
|
+
if (fragmentMatch) {
|
|
85
|
+
return await serveStaticAsset('examples' + url.pathname)
|
|
86
|
+
}
|
|
87
|
+
// Serve layout.html for root
|
|
88
|
+
if (url.pathname === '/' || url.pathname === '/layout.html') {
|
|
89
|
+
return await serveStaticAsset('examples/layout.html')
|
|
90
|
+
}
|
|
91
|
+
return new Response('Not found', { status: 404 })
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
console.log('Bun example server running at http://localhost:3000/')
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Le Truc Testing Guide
|
|
2
|
+
|
|
3
|
+
## Test Infrastructure Overview
|
|
4
|
+
|
|
5
|
+
Le Truc uses a sophisticated test setup that combines Bun's build system with Playwright for end-to-end testing. Here's how the pieces work together:
|
|
6
|
+
|
|
7
|
+
### **Test Server Architecture**
|
|
8
|
+
|
|
9
|
+
1. **Bun Development Server** (`examples/server.ts`)
|
|
10
|
+
- Serves on `http://localhost:3000`
|
|
11
|
+
- Dynamically injects component HTML fragments into a layout template
|
|
12
|
+
- Routes `/test/{component}.html` to render component examples for testing
|
|
13
|
+
- Serves compiled assets from `/assets/` directory
|
|
14
|
+
|
|
15
|
+
2. **Layout Template** (`examples/layout.html`)
|
|
16
|
+
- Base HTML structure with compiled JS/CSS imports
|
|
17
|
+
- Component HTML fragments are injected via comment placeholder
|
|
18
|
+
- Provides consistent testing environment across all components
|
|
19
|
+
|
|
20
|
+
3. **Asset Compilation**
|
|
21
|
+
- `bun run build:examples` compiles all components and examples
|
|
22
|
+
- Creates `examples/assets/main.js` and `examples/assets/main.css`
|
|
23
|
+
- Includes all component definitions and shared styles
|
|
24
|
+
|
|
25
|
+
### **Test Structure**
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// Standard test pattern
|
|
29
|
+
test.describe('component-name component', () => {
|
|
30
|
+
test.beforeEach(async ({ page }) => {
|
|
31
|
+
await page.goto('http://localhost:3000/test/component-name.html')
|
|
32
|
+
await page.waitForSelector('component-name')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('test description', async ({ page }) => {
|
|
36
|
+
// Test implementation
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### **Fixture System**
|
|
42
|
+
|
|
43
|
+
- **HTML Fixtures**: Each component's `.html` file in `examples/` serves as test fixture
|
|
44
|
+
- **Dynamic Injection**: Server reads HTML files and injects them into layout
|
|
45
|
+
- **Multiple Instances**: HTML fixtures can contain multiple component instances with different configurations
|
|
46
|
+
- **Real DOM**: Tests run against actual component implementations, not mocks
|
|
47
|
+
|
|
48
|
+
## Le Truc Component Architecture
|
|
49
|
+
|
|
50
|
+
### **1. Reactive Properties**
|
|
51
|
+
|
|
52
|
+
All custom component properties in Le Truc are reactive - they track access and notify on changes. The component's `Props` type interface defines which properties can be controlled externally:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
type FormCheckboxProps = {
|
|
56
|
+
readonly checked: boolean // Read-only sensor property
|
|
57
|
+
label: string // Writable reactive property
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Key Points**:
|
|
62
|
+
- Properties marked `readonly` cannot be set externally
|
|
63
|
+
- Writable properties can be controlled by outside components or arbitrary JavaScript
|
|
64
|
+
- All properties are reactive and will trigger updates when changed
|
|
65
|
+
|
|
66
|
+
### **2. Uncontrolled Form Components**
|
|
67
|
+
|
|
68
|
+
**Key Insight**: Le Truc form components are fundamentally different from React-style controlled components.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// 🚫 Wrong assumption - trying to control read-only sensor property
|
|
72
|
+
await page.evaluate(() => {
|
|
73
|
+
const element = document.querySelector('form-checkbox')
|
|
74
|
+
element.checked = true // This won't work - readonly property
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// ✅ Correct approach - interact with actual DOM elements
|
|
78
|
+
const checkbox = page.locator('form-checkbox input[type="checkbox"]')
|
|
79
|
+
await checkbox.click() // User interaction drives state
|
|
80
|
+
|
|
81
|
+
// ✅ Also correct - setting writable properties
|
|
82
|
+
await page.evaluate(() => {
|
|
83
|
+
const element = document.querySelector('form-checkbox')
|
|
84
|
+
element.label = 'New Label' // This works - writable property
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Why**: Le Truc protects user-entered data from external JavaScript modification while allowing controlled updates to non-sensitive properties.
|
|
89
|
+
|
|
90
|
+
### **3. Sensor-Based State Management**
|
|
91
|
+
|
|
92
|
+
Many Le Truc form components use sensors that read from DOM elements:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// Component definition
|
|
96
|
+
checked: createSensor(
|
|
97
|
+
read(ui => ui.checkbox.checked, false),
|
|
98
|
+
'checkbox',
|
|
99
|
+
{ change: ({ target }) => target.checked },
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Testing Implications**:
|
|
104
|
+
- Sensor properties are read-only and reflect DOM state
|
|
105
|
+
- State updates only through user interactions or DOM events
|
|
106
|
+
- Test by simulating real user behavior for sensor properties
|
|
107
|
+
- Other properties can be set programmatically if not marked readonly
|
|
108
|
+
|
|
109
|
+
### **4. Security-First Design**
|
|
110
|
+
|
|
111
|
+
Le Truc assumes potential malicious JavaScript on the page:
|
|
112
|
+
|
|
113
|
+
- **User interactions** (clicks, typing, form submissions) are trusted
|
|
114
|
+
- **Sensor properties** are protected from external manipulation
|
|
115
|
+
- **Writable properties** can be controlled but are still reactive
|
|
116
|
+
- **Server-rendered HTML** provides initial trusted state
|
|
117
|
+
|
|
118
|
+
## Testing Best Practices
|
|
119
|
+
|
|
120
|
+
### **1. Understanding Property Types**
|
|
121
|
+
|
|
122
|
+
Before testing, check the component's Props interface:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// Check component definition
|
|
126
|
+
type MyComponentProps = {
|
|
127
|
+
readonly sensorValue: number // Test via user interaction
|
|
128
|
+
writableValue: string // Can test both ways
|
|
129
|
+
label: string // Writable reactive property
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### **2. Test Real User Interactions for Sensors**
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// For readonly/sensor properties
|
|
137
|
+
await button.click()
|
|
138
|
+
await input.fill('text')
|
|
139
|
+
await select.selectOption('value')
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### **3. Test Programmatic Updates for Writable Properties**
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// For writable reactive properties
|
|
146
|
+
await page.evaluate(() => {
|
|
147
|
+
const element = document.querySelector('my-component')
|
|
148
|
+
element.label = 'New Label' // This works
|
|
149
|
+
element.writableValue = 'test' // This works
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### **4. Verify Property Reactivity**
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// Test that property changes trigger updates
|
|
157
|
+
await page.evaluate(() => {
|
|
158
|
+
const element = document.querySelector('my-component')
|
|
159
|
+
element.label = 'Updated Label'
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const labelElement = page.locator('my-component .label')
|
|
163
|
+
await expect(labelElement).toHaveText('Updated Label')
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### **5. Test Read-only Property Reflection**
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Check that readonly properties reflect internal state
|
|
170
|
+
const sensorValue = await page.evaluate(() => {
|
|
171
|
+
const element = document.querySelector('form-checkbox')
|
|
172
|
+
return element.checked // Read-only reflection of DOM state
|
|
173
|
+
})
|
|
174
|
+
expect(sensorValue).toBe(true)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### **6. Test Multiple Component Instances**
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const firstComponent = page.locator('form-checkbox').first()
|
|
181
|
+
const secondComponent = page.locator('form-checkbox.todo')
|
|
182
|
+
// Test independence between instances
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### **7. Validate Attribute Synchronization**
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// Components sync internal state to host attributes
|
|
189
|
+
await expect(component).toHaveAttribute('checked')
|
|
190
|
+
await expect(component).not.toHaveAttribute('checked')
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### **8. Test Form Integration**
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// Verify components work with standard form APIs
|
|
197
|
+
const formData = await page.evaluate(() => {
|
|
198
|
+
const form = document.querySelector('form')
|
|
199
|
+
return Object.fromEntries(new FormData(form).entries())
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Event-Driven Updates
|
|
204
|
+
|
|
205
|
+
Components respond to real DOM events for sensor properties:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// ✅ Correct - triggers sensor update
|
|
209
|
+
await checkbox.click()
|
|
210
|
+
|
|
211
|
+
// ✅ Also correct - dispatches real event
|
|
212
|
+
await page.evaluate(() => {
|
|
213
|
+
const input = document.querySelector('input[type="checkbox"]')
|
|
214
|
+
input.checked = true
|
|
215
|
+
input.dispatchEvent(new Event('change', { bubbles: true }))
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
// 🚫 Wrong - doesn't trigger sensor updates
|
|
219
|
+
await page.evaluate(() => {
|
|
220
|
+
const input = document.querySelector('input[type="checkbox"]')
|
|
221
|
+
input.checked = true // Missing event dispatch
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Common Testing Pitfalls
|
|
226
|
+
|
|
227
|
+
### **🚫 Don't**: Try to control readonly sensor properties
|
|
228
|
+
```typescript
|
|
229
|
+
element.checked = true // Ignored if readonly
|
|
230
|
+
element.value = 'new value' // Won't work for sensors
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### **🚫 Don't**: Assume all properties are readonly
|
|
234
|
+
```typescript
|
|
235
|
+
// Check the Props interface first
|
|
236
|
+
element.label = 'new label' // This might work!
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### **🚫 Don't**: Assume React-like behavior
|
|
240
|
+
```typescript
|
|
241
|
+
// This pattern doesn't work in Le Truc
|
|
242
|
+
component.props.onChange({ target: { checked: true }})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### **🚫 Don't**: Mock component behavior
|
|
246
|
+
```typescript
|
|
247
|
+
// Test real components, not mocked implementations
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### **✅ Do**: Check component Props interface first
|
|
251
|
+
```typescript
|
|
252
|
+
// Understand what's readonly vs writable before testing
|
|
253
|
+
type ComponentProps = {
|
|
254
|
+
readonly sensor: boolean
|
|
255
|
+
writable: string
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### **✅ Do**: Simulate real user interactions for sensors
|
|
260
|
+
```typescript
|
|
261
|
+
await checkbox.click()
|
|
262
|
+
await input.type('text')
|
|
263
|
+
await page.keyboard.press('Space')
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### **✅ Do**: Test programmatic updates for writable properties
|
|
267
|
+
```typescript
|
|
268
|
+
await page.evaluate(() => {
|
|
269
|
+
element.writableProperty = 'new value'
|
|
270
|
+
})
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### **✅ Do**: Verify property reactivity
|
|
274
|
+
```typescript
|
|
275
|
+
// All properties are reactive and trigger updates
|
|
276
|
+
const propertyValue = await page.evaluate(() => element.property)
|
|
277
|
+
expect(propertyValue).toBe('expected')
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### **✅ Do**: Verify DOM synchronization
|
|
281
|
+
```typescript
|
|
282
|
+
await expect(checkbox).toBeChecked()
|
|
283
|
+
await expect(component).toHaveAttribute('checked')
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Key Commands
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
# Start test server
|
|
290
|
+
bun run serve:examples
|
|
291
|
+
|
|
292
|
+
# Run all tests
|
|
293
|
+
bunx playwright test
|
|
294
|
+
|
|
295
|
+
# Run specific component tests
|
|
296
|
+
bunx playwright test tests/components/form-checkbox.spec.ts
|
|
297
|
+
|
|
298
|
+
# Run tests on specific browser
|
|
299
|
+
bunx playwright test --project=Chromium
|
|
300
|
+
|
|
301
|
+
# Build examples for testing
|
|
302
|
+
bun run build:examples
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Summary
|
|
306
|
+
|
|
307
|
+
Le Truc's testing approach ensures components work correctly in real-world scenarios where:
|
|
308
|
+
- User interactions drive sensor property changes
|
|
309
|
+
- Writable properties can be controlled by external JavaScript
|
|
310
|
+
- All properties are reactive and trigger appropriate updates
|
|
311
|
+
- Components maintain their security boundaries for sensitive data
|
|
312
|
+
- The distinction between readonly sensors and writable properties is clearly defined
|
|
313
|
+
|
|
314
|
+
Always check the component's Props interface to understand which properties are readonly sensors (controlled by user interaction) versus writable reactive properties (controllable by external JavaScript).
|