@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,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Introduction'
|
|
3
|
+
emoji: '📖'
|
|
4
|
+
description: 'Overview and key benefits of Le Truc'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
{% hero %}
|
|
8
|
+
|
|
9
|
+
# 📖 Introduction
|
|
10
|
+
|
|
11
|
+
**Web development doesn't need to be complicated**. Le Truc lets you create reactive Web Components that enhance your existing HTML.
|
|
12
|
+
{% /hero %}
|
|
13
|
+
|
|
14
|
+
{% section class="breakout" %}
|
|
15
|
+
|
|
16
|
+
## What is Le Truc?
|
|
17
|
+
|
|
18
|
+
{% carousel %}
|
|
19
|
+
|
|
20
|
+
{% slide title="We Can Have Nice Things!" style="background: var(--color-purple-20);" %}
|
|
21
|
+
- Embrace the Web Platform
|
|
22
|
+
- Use any server-side technology to render HTML
|
|
23
|
+
- Have components
|
|
24
|
+
- Have reactivity
|
|
25
|
+
- Have type safety
|
|
26
|
+
- Have optimal performance
|
|
27
|
+
- Have fun!
|
|
28
|
+
{% /slide %}
|
|
29
|
+
|
|
30
|
+
{% slide title="HTML First." style="background: var(--color-pink-20);" %}
|
|
31
|
+
Le Truc assumes you start with semantic HTML and want to enhance it with behavior:
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<hello-world>
|
|
35
|
+
<p>Hello, <span>Alice</span>!</p>
|
|
36
|
+
</hello-world>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This means better SEO, faster initial page loads, and progressive enhancement that works even when JavaScript fails.
|
|
40
|
+
{% /slide %}
|
|
41
|
+
|
|
42
|
+
{% slide title="Add JavaScript." style="background: var(--color-orange-20);" %}
|
|
43
|
+
Progressively enhance the user experience by adding interactivity:
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
import { asString, defineComponent, setText } from '@zeix/le-truc'
|
|
47
|
+
|
|
48
|
+
defineComponent(
|
|
49
|
+
'hello-world',
|
|
50
|
+
{ name: asString() },
|
|
51
|
+
q => ({ span: q.first('span') }),
|
|
52
|
+
() => ({ span: setText('name') }),
|
|
53
|
+
])
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Le Truc augments what the platform already provides. It leverages the Web Components standard while adding just enough convenience functions to make reactive UI behaviors easy to implement.
|
|
57
|
+
{% /slide %}
|
|
58
|
+
|
|
59
|
+
{% slide title="Faster. Because We Do Less." style="background: var(--color-green-20);" %}
|
|
60
|
+
- Unlike SPA frameworks (React, Vue, Angular, Svelte, Lit, etc.) we **never render** on the client. Instead, the server and browser do this work. Like it's 1995.
|
|
61
|
+
- Because we never render on the client, we need no JSON data and no JS templates either. This means less data over the wire and no plumbing DB → JSON → JS → HTML.
|
|
62
|
+
- Unlike Hypermedia frameworks (HTMX, Datastar) we don't compensate for the lack of client-side rendering by a network request if not needed. If possible, we calculate the new state on the client.
|
|
63
|
+
- We just add event listeners and set up a signal graph. Invisible work that doesn't cause layout shifts.
|
|
64
|
+
- When the user interacts with the UI, we know exactly what to do. We just do fine-grained updates to the DOM. No VDOM, no diffing. Wait for signal 🚦 and go! 🏁
|
|
65
|
+
{% /slide %}
|
|
66
|
+
|
|
67
|
+
{% slide title="Minimal Size." style="background: var(--color-blue-20);" %}
|
|
68
|
+
Because we add less abstractions, we can keep the library small (approximately 8kB gzipped).
|
|
69
|
+
|
|
70
|
+
Le Truc is a lightweight library that provides a simple and efficient way to build reactive user interfaces. It is designed to be easy to use and understand, while still providing powerful features for building complex applications.
|
|
71
|
+
|
|
72
|
+
HTML ain't broken. CSS ain't broken. JavaScript ain't broken. We just want to split it in chunks (components), detect bugs early (type safety), and have predictable updates without tight coupling (reactivity). That's what we stand for.
|
|
73
|
+
{% /slide %}
|
|
74
|
+
|
|
75
|
+
{% /carousel %}
|
|
76
|
+
|
|
77
|
+
{% /section %}
|
|
78
|
+
|
|
79
|
+
{% section %}
|
|
80
|
+
|
|
81
|
+
## Why Choose Le Truc?
|
|
82
|
+
|
|
83
|
+
Le Truc shines when you want:
|
|
84
|
+
|
|
85
|
+
- **Server-rendered content** with client-side enhancements
|
|
86
|
+
- **High performance** on all devices (no virtual DOM overhead)
|
|
87
|
+
- **Component reusability** without framework lock-in
|
|
88
|
+
- **Future-proof** code built on web standards
|
|
89
|
+
- **Easy integration** with existing codebases
|
|
90
|
+
|
|
91
|
+
**Key Benefits:**
|
|
92
|
+
|
|
93
|
+
- ~8kB gzipped with no dependencies
|
|
94
|
+
- TypeScript support with full type safety
|
|
95
|
+
- Works with any backend or build setup
|
|
96
|
+
- Progressive enhancement friendly
|
|
97
|
+
|
|
98
|
+
{% /section %}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Styling'
|
|
3
|
+
emoji: '🎨'
|
|
4
|
+
description: 'Scoped styles, CSS custom properties'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
{% hero %}
|
|
8
|
+
# 🎨 Styling
|
|
9
|
+
|
|
10
|
+
**Keep your components' styles self-contained while supporting shared design tokens.** Le Truc offers a refreshingly simple approach to create reactive Web Components that enhance your existing HTML.
|
|
11
|
+
{% /hero %}
|
|
12
|
+
|
|
13
|
+
{% section %}
|
|
14
|
+
## Design Principles
|
|
15
|
+
|
|
16
|
+
Le Truc is focused on **state management and reactivity**, not styling. However, to **ensure consistent, maintainable, and reusable styles**, we recommend techniques that **scope component styles properly while allowing shared design tokens** (e.g., spacing, font sizes, colors, layout grids).
|
|
17
|
+
|
|
18
|
+
- **Each component brings along its own specific styles.**
|
|
19
|
+
- Component styles should be **scoped or encapsulated** so they don't leak out.
|
|
20
|
+
- **Allow customizations** via CSS custom properties or pre-defined classes.
|
|
21
|
+
|
|
22
|
+
Parent components may apply styles to the wrapper element of known sub-components for layout purposes. But avoid styling inner elements of sub-components directly. This would tightly couple the styles of the outer and inner components.
|
|
23
|
+
|
|
24
|
+
{% /section %}
|
|
25
|
+
|
|
26
|
+
{% section %}
|
|
27
|
+
## Scope Styles to Custom Element
|
|
28
|
+
|
|
29
|
+
Use the **custom element name** to scope component styles if **you control the page and the components within**. This protects against component styles leaking out, while still allowing to use the CSS cascade. No need for Shadow DOM, no duplicate style rules.
|
|
30
|
+
|
|
31
|
+
```css
|
|
32
|
+
my-component {
|
|
33
|
+
& button {
|
|
34
|
+
/* Button style rules */
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* More selectors for inner elements */
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Advantages of Custom Element Names
|
|
42
|
+
|
|
43
|
+
- By definition **unique within the document** with a descriptive name.
|
|
44
|
+
- **Low specificity**, making it easy to override when you need to with a single class.
|
|
45
|
+
|
|
46
|
+
{% callout class="tip" %}
|
|
47
|
+
**When to use**
|
|
48
|
+
|
|
49
|
+
**Best when** you control the page and need styles to cascade naturally.
|
|
50
|
+
**Avoid if** you expect style clashes from third-party styles.
|
|
51
|
+
{% /callout %}
|
|
52
|
+
|
|
53
|
+
{% /section %}
|
|
54
|
+
|
|
55
|
+
{% section %}
|
|
56
|
+
## Encapsulate Styles with Shadow DOM
|
|
57
|
+
|
|
58
|
+
Use **Shadow DOM** to encapsulate styles if your component is going to be used in a pages **where you don't control the styles**. This way you make sure page styles don't leak in and component styles don't leak out.
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
<my-component>
|
|
62
|
+
<template shadowrootmode="open">
|
|
63
|
+
<style>
|
|
64
|
+
button {
|
|
65
|
+
/* Button style rules */
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* More selectors for inner elements */
|
|
69
|
+
</style>
|
|
70
|
+
<!-- Inner elements -->
|
|
71
|
+
</template>
|
|
72
|
+
</my-component>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
{% callout class="tip" %}
|
|
76
|
+
**When to use**
|
|
77
|
+
|
|
78
|
+
**Best when** your component is used in environments where you don’t control styles.
|
|
79
|
+
**Avoid if** you need global styles to apply inside the component.
|
|
80
|
+
{% /callout %}
|
|
81
|
+
|
|
82
|
+
{% /section %}
|
|
83
|
+
|
|
84
|
+
{% section %}
|
|
85
|
+
## Shared Design Tokens with CSS Custom Properties
|
|
86
|
+
|
|
87
|
+
Web Components can't inherit global styles inside **Shadow DOM**, but CSS custom properties allow components to remain **flexible and themeable**.
|
|
88
|
+
|
|
89
|
+
### Defining Design Tokens
|
|
90
|
+
|
|
91
|
+
Set global tokens in a stylesheet:
|
|
92
|
+
|
|
93
|
+
```css
|
|
94
|
+
:root {
|
|
95
|
+
--button-bg: #007bff;
|
|
96
|
+
--button-text: #fff;
|
|
97
|
+
--spacing: 1rem;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Using Tokens in a Component
|
|
102
|
+
|
|
103
|
+
```css
|
|
104
|
+
my-component {
|
|
105
|
+
padding: var(--spacing);
|
|
106
|
+
|
|
107
|
+
& button {
|
|
108
|
+
background: var(--button-bg);
|
|
109
|
+
color: var(--button-text);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Advantages of CSS Custom Properties
|
|
115
|
+
|
|
116
|
+
- **Supports theming** – users can override styles globally.
|
|
117
|
+
- **Works inside Shadow DOM** – unlike normal CSS, custom properties are inherited inside the shadow tree.
|
|
118
|
+
{% /section %}
|
|
119
|
+
|
|
120
|
+
{% section %}
|
|
121
|
+
## Defined Variants with Classes
|
|
122
|
+
|
|
123
|
+
Use **classes** if your components can appear in a **limited set of specific manifestations**. For example, buttons could come in certain sizes and have primary, secondary and tertiary variants.
|
|
124
|
+
|
|
125
|
+
```css
|
|
126
|
+
my-button {
|
|
127
|
+
/* Style rules for default (medium-sized, secondary) buttons */
|
|
128
|
+
|
|
129
|
+
&.small {
|
|
130
|
+
/* Style rules for small buttons */
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
&.large {
|
|
134
|
+
/* Style rules for large buttons */
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
&.primary {
|
|
138
|
+
/* Style rules for primary buttons */
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
&.tertiary {
|
|
142
|
+
/* Style rules for tertiary buttons */
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
{% /section %}
|
|
147
|
+
|
|
148
|
+
{% section %}
|
|
149
|
+
## CSS-only Components
|
|
150
|
+
|
|
151
|
+
Just because Le Truc is a JavaScript library doesn't mean you have to use JavaScript in every component. It's perfectly fine to use custom elements just for styling purposes.
|
|
152
|
+
|
|
153
|
+
Here's the example of the `<card-callout>` we're using in this documentation:
|
|
154
|
+
|
|
155
|
+
{% demo %}
|
|
156
|
+
<card-callout>This is an informational message.</card-callout>
|
|
157
|
+
<card-callout class="tip">Remember to hydrate while coding!</card-callout>
|
|
158
|
+
<card-callout class="caution">Be careful with this operation.</card-callout>
|
|
159
|
+
<card-callout class="danger">This action is irreversible!</card-callout>
|
|
160
|
+
<card-callout class="note">This is just a side note.</card-callout>
|
|
161
|
+
---
|
|
162
|
+
{% source title="Source code" src="./examples/card-callout.html" /%}
|
|
163
|
+
{% /demo %}
|
|
164
|
+
|
|
165
|
+
{% /section %}
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import pluginJs from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import tseslint from 'typescript-eslint'
|
|
4
|
+
|
|
5
|
+
/** @type {import('eslint').Linter.Config[]} */
|
|
6
|
+
export default [
|
|
7
|
+
// Global ignores to prevent warnings about these files
|
|
8
|
+
{
|
|
9
|
+
ignores: [
|
|
10
|
+
'index.js',
|
|
11
|
+
'index.dev.js',
|
|
12
|
+
'types/**/*.d.ts',
|
|
13
|
+
'docs/assets/**/*.js',
|
|
14
|
+
'**/*.min.js',
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
// Base configuration for all files
|
|
18
|
+
{
|
|
19
|
+
files: [
|
|
20
|
+
'index.ts',
|
|
21
|
+
'index.dev.ts',
|
|
22
|
+
'src/**/*.{js,mjs,cjs,ts}',
|
|
23
|
+
'docs-src/**/*.{js,mjs,cjs,ts}',
|
|
24
|
+
],
|
|
25
|
+
languageOptions: { globals: globals.browser },
|
|
26
|
+
...pluginJs.configs.recommended,
|
|
27
|
+
},
|
|
28
|
+
// TypeScript configuration
|
|
29
|
+
...tseslint.configs.recommended.map(config => ({
|
|
30
|
+
...config,
|
|
31
|
+
files: [
|
|
32
|
+
'index.ts',
|
|
33
|
+
'index.dev.ts',
|
|
34
|
+
'src/**/*.{js,mjs,cjs,ts}',
|
|
35
|
+
'docs-src/**/*.{js,mjs,cjs,ts}',
|
|
36
|
+
],
|
|
37
|
+
})),
|
|
38
|
+
// Custom rule overrides for all files
|
|
39
|
+
{
|
|
40
|
+
files: [
|
|
41
|
+
'index.ts',
|
|
42
|
+
'index.dev.ts',
|
|
43
|
+
'src/**/*.{js,mjs,cjs,ts}',
|
|
44
|
+
'docs-src/**/*.{js,mjs,cjs,ts}',
|
|
45
|
+
],
|
|
46
|
+
rules: {
|
|
47
|
+
// we know what we're doing ;-)
|
|
48
|
+
'@typescript-eslint/no-empty-object-type': 'off',
|
|
49
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
50
|
+
'@typescript-eslint/no-unused-vars': [
|
|
51
|
+
'error',
|
|
52
|
+
{
|
|
53
|
+
args: 'all',
|
|
54
|
+
argsIgnorePattern: '^_',
|
|
55
|
+
caughtErrors: 'all',
|
|
56
|
+
caughtErrorsIgnorePattern: '^_',
|
|
57
|
+
destructuredArrayIgnorePattern: '^_',
|
|
58
|
+
varsIgnorePattern: '^_',
|
|
59
|
+
ignoreRestSiblings: true,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type Component, ComponentUI, type Effect, on, show, UI } from '../..'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a clear method for input components
|
|
5
|
+
*
|
|
6
|
+
* @param {HTMLInputElement | HTMLTextAreaElement} selector - The native input or textarea element
|
|
7
|
+
*/
|
|
8
|
+
export const clearMethod = <
|
|
9
|
+
P extends {
|
|
10
|
+
clear: () => void
|
|
11
|
+
value: string | number
|
|
12
|
+
readonly length: number
|
|
13
|
+
},
|
|
14
|
+
U extends {
|
|
15
|
+
host: Component<P>
|
|
16
|
+
textbox: HTMLInputElement | HTMLTextAreaElement
|
|
17
|
+
},
|
|
18
|
+
>({
|
|
19
|
+
host,
|
|
20
|
+
textbox,
|
|
21
|
+
}: ComponentUI<P, U>) => {
|
|
22
|
+
host.clear = () => {
|
|
23
|
+
host.value = ''
|
|
24
|
+
textbox.value = ''
|
|
25
|
+
textbox.setCustomValidity('')
|
|
26
|
+
textbox.checkValidity()
|
|
27
|
+
textbox.dispatchEvent(new Event('input', { bubbles: true }))
|
|
28
|
+
textbox.dispatchEvent(new Event('change', { bubbles: true }))
|
|
29
|
+
textbox.focus()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Standard effects for clearing input components on button elements
|
|
35
|
+
*
|
|
36
|
+
* @param {ComponentUI<P, U>} ui - The component UI with a host that has clear, length properties
|
|
37
|
+
* @returns {Effect<P, HTMLElement>[]} - Effects for clearing the input component
|
|
38
|
+
*/
|
|
39
|
+
export const clearEffects = <
|
|
40
|
+
P extends { clear: () => void; readonly length: number },
|
|
41
|
+
U extends UI,
|
|
42
|
+
>(
|
|
43
|
+
ui: ComponentUI<P, U>,
|
|
44
|
+
): Effect<P, HTMLElement>[] => [
|
|
45
|
+
show(() => !!ui.host.length),
|
|
46
|
+
on('click', () => {
|
|
47
|
+
ui.host.clear()
|
|
48
|
+
}),
|
|
49
|
+
]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/* === Types === */
|
|
2
|
+
|
|
3
|
+
type CacheEntry<T = unknown> = {
|
|
4
|
+
content: T
|
|
5
|
+
timestamp: number
|
|
6
|
+
etag?: string
|
|
7
|
+
lastModified?: string
|
|
8
|
+
maxAge?: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* === Internal Stuff === */
|
|
12
|
+
|
|
13
|
+
const cache = new Map<string, CacheEntry>()
|
|
14
|
+
|
|
15
|
+
const parseCacheControl = (
|
|
16
|
+
header: string,
|
|
17
|
+
): { maxAge?: number; noCache: boolean; noStore: boolean } => {
|
|
18
|
+
const directives = header
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.split(',')
|
|
21
|
+
.map(d => d.trim())
|
|
22
|
+
const result = {
|
|
23
|
+
noCache: false,
|
|
24
|
+
noStore: false,
|
|
25
|
+
maxAge: undefined as number | undefined,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (const directive of directives) {
|
|
29
|
+
if (directive === 'no-cache') result.noCache = true
|
|
30
|
+
else if (directive === 'no-store') result.noStore = true
|
|
31
|
+
else if (directive.startsWith('max-age=')) {
|
|
32
|
+
const value = parseInt(directive.substring(8), 10)
|
|
33
|
+
if (!isNaN(value)) result.maxAge = value
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return result
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const isCacheEntryValid = (entry: CacheEntry): boolean => {
|
|
41
|
+
if (entry.maxAge !== undefined) {
|
|
42
|
+
const age = (Date.now() - entry.timestamp) / 1000
|
|
43
|
+
return age < entry.maxAge
|
|
44
|
+
}
|
|
45
|
+
return true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* === Exported Functions === */
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if an URL is recursive
|
|
52
|
+
*
|
|
53
|
+
* @param {string} value - URL to check
|
|
54
|
+
* @param {HTMLElement} host - Host element
|
|
55
|
+
* @param {string} attr - Attribute name
|
|
56
|
+
* @returns {boolean} - True if the URL is recursive, false otherwise
|
|
57
|
+
*/
|
|
58
|
+
export const isRecursiveURL = (
|
|
59
|
+
value: string,
|
|
60
|
+
host: HTMLElement,
|
|
61
|
+
attr: string = 'src',
|
|
62
|
+
): boolean =>
|
|
63
|
+
!!value &&
|
|
64
|
+
!!(host.parentElement || (host.getRootNode() as ShadowRoot).host)?.closest(
|
|
65
|
+
`${host.localName}[${attr}="${value}"]`,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if an URL is valid
|
|
70
|
+
*
|
|
71
|
+
* @param {string} value - URL to check
|
|
72
|
+
* @returns {boolean} - True if the URL is valid, false otherwise
|
|
73
|
+
*/
|
|
74
|
+
export const isValidURL = (value: string): boolean => {
|
|
75
|
+
if (!value) return false
|
|
76
|
+
try {
|
|
77
|
+
const url = new URL(value, location.href)
|
|
78
|
+
if (url.origin === location.origin) return true
|
|
79
|
+
} catch {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Fetch with HTTP caching support
|
|
87
|
+
*
|
|
88
|
+
* @param url - URL to fetch
|
|
89
|
+
* @param signal - AbortSignal for cancellation
|
|
90
|
+
* @param parseResponse - Function to parse the response body (defaults to text)
|
|
91
|
+
* @returns Promise with parsed content and cache status
|
|
92
|
+
*/
|
|
93
|
+
export const fetchWithCache = async <T = string>(
|
|
94
|
+
url: string,
|
|
95
|
+
signal?: AbortSignal,
|
|
96
|
+
parseResponse: (response: Response) => Promise<T> = (response: Response) =>
|
|
97
|
+
response.text() as Promise<T>,
|
|
98
|
+
): Promise<{ content: T; fromCache: boolean }> => {
|
|
99
|
+
const cached = cache.get(url) as CacheEntry<T> | undefined
|
|
100
|
+
const headers: HeadersInit = {}
|
|
101
|
+
|
|
102
|
+
// Add conditional headers if we have cached data
|
|
103
|
+
if (cached?.etag) headers['If-None-Match'] = cached.etag
|
|
104
|
+
if (cached?.lastModified) headers['If-Modified-Since'] = cached.lastModified
|
|
105
|
+
|
|
106
|
+
const response = await fetch(url, { signal, headers })
|
|
107
|
+
|
|
108
|
+
// Handle 304 Not Modified
|
|
109
|
+
if (response.status === 304 && cached)
|
|
110
|
+
return { content: cached.content, fromCache: true }
|
|
111
|
+
|
|
112
|
+
if (!response.ok) throw new Error(`HTTP error: ${response.statusText}`)
|
|
113
|
+
|
|
114
|
+
const content = await parseResponse(response)
|
|
115
|
+
const cacheControl = response.headers.get('cache-control')
|
|
116
|
+
const etag = response.headers.get('etag')
|
|
117
|
+
const lastModified = response.headers.get('last-modified')
|
|
118
|
+
|
|
119
|
+
// Parse cache directives
|
|
120
|
+
const cacheDirectives = cacheControl
|
|
121
|
+
? parseCacheControl(cacheControl)
|
|
122
|
+
: { noCache: false, noStore: false }
|
|
123
|
+
|
|
124
|
+
// Store in cache if allowed
|
|
125
|
+
if (!cacheDirectives.noStore) {
|
|
126
|
+
const entry: CacheEntry<T> = {
|
|
127
|
+
content,
|
|
128
|
+
timestamp: Date.now(),
|
|
129
|
+
etag: etag || undefined,
|
|
130
|
+
lastModified: lastModified || undefined,
|
|
131
|
+
maxAge: cacheDirectives.maxAge,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!cacheDirectives.noCache || isCacheEntryValid(entry))
|
|
135
|
+
cache.set(url, entry)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { content, fromCache: false }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Clear the entire cache
|
|
143
|
+
*/
|
|
144
|
+
export const clearCache = (): void => {
|
|
145
|
+
cache.clear()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Remove a specific URL from cache
|
|
150
|
+
*/
|
|
151
|
+
export const removeCacheEntry = (url: string): boolean => {
|
|
152
|
+
return cache.delete(url)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get cache size
|
|
157
|
+
*/
|
|
158
|
+
export const getCacheSize = (): number => {
|
|
159
|
+
return cache.size
|
|
160
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Collection, on } from '../..'
|
|
2
|
+
|
|
3
|
+
/* === Constants === */
|
|
4
|
+
|
|
5
|
+
const ENTER_KEY = 'Enter'
|
|
6
|
+
const DECREMENT_KEYS = ['ArrowLeft', 'ArrowUp']
|
|
7
|
+
const INCREMENT_KEYS = ['ArrowRight', 'ArrowDown']
|
|
8
|
+
const FIRST_KEY = 'Home'
|
|
9
|
+
const LAST_KEY = 'End'
|
|
10
|
+
const HANDLED_KEYS = [...DECREMENT_KEYS, ...INCREMENT_KEYS, FIRST_KEY, LAST_KEY]
|
|
11
|
+
|
|
12
|
+
/* === Exported Functions === */
|
|
13
|
+
|
|
14
|
+
export const manageFocus = <E extends HTMLInputElement | HTMLButtonElement>(
|
|
15
|
+
collection: Collection<E>,
|
|
16
|
+
getSelectedIndex: (radios: Collection<E>) => number,
|
|
17
|
+
) => {
|
|
18
|
+
let index = getSelectedIndex(collection)
|
|
19
|
+
|
|
20
|
+
return [
|
|
21
|
+
on('click', ({ target }) => {
|
|
22
|
+
if (!(target instanceof HTMLElement)) return
|
|
23
|
+
if (target && target.hasAttribute('value'))
|
|
24
|
+
index = collection.get().findIndex(item => item === target)
|
|
25
|
+
}),
|
|
26
|
+
on('keydown', e => {
|
|
27
|
+
const { key } = e
|
|
28
|
+
if (!HANDLED_KEYS.includes(key)) return
|
|
29
|
+
e.preventDefault()
|
|
30
|
+
e.stopPropagation()
|
|
31
|
+
if (key === FIRST_KEY) index = 0
|
|
32
|
+
else if (key === LAST_KEY) index = collection.length - 1
|
|
33
|
+
else
|
|
34
|
+
index =
|
|
35
|
+
(index +
|
|
36
|
+
(INCREMENT_KEYS.includes(key) ? 1 : -1) +
|
|
37
|
+
collection.length) %
|
|
38
|
+
collection.length
|
|
39
|
+
if (collection[index]) collection[index].focus()
|
|
40
|
+
}),
|
|
41
|
+
on('keyup', ({ key }) => {
|
|
42
|
+
if (key === ENTER_KEY && collection[index]) collection[index].click()
|
|
43
|
+
}),
|
|
44
|
+
]
|
|
45
|
+
}
|