@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,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asInteger,
|
|
3
|
+
type Component,
|
|
4
|
+
defineComponent,
|
|
5
|
+
setText,
|
|
6
|
+
show,
|
|
7
|
+
UI,
|
|
8
|
+
} from '../..'
|
|
9
|
+
|
|
10
|
+
export type BasicPluralizeProps = {
|
|
11
|
+
count: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type BasicPluralizeUI = Partial<
|
|
15
|
+
Record<
|
|
16
|
+
| 'count'
|
|
17
|
+
| 'none'
|
|
18
|
+
| 'some'
|
|
19
|
+
| 'zero'
|
|
20
|
+
| 'one'
|
|
21
|
+
| 'two'
|
|
22
|
+
| 'few'
|
|
23
|
+
| 'many'
|
|
24
|
+
| 'other',
|
|
25
|
+
HTMLElement
|
|
26
|
+
>
|
|
27
|
+
>
|
|
28
|
+
|
|
29
|
+
declare global {
|
|
30
|
+
interface HTMLElementTagNameMap {
|
|
31
|
+
'basic-pluralize': Component<BasicPluralizeProps>
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const FALLBACK_LOCALE = 'en'
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Parse a string as a positive integer (>= 0), falling back to 0 for negative values
|
|
39
|
+
*/
|
|
40
|
+
const asPositiveInteger =
|
|
41
|
+
() =>
|
|
42
|
+
<U extends UI>(ui: U, value: string | null | undefined) => {
|
|
43
|
+
const parsed = asInteger()(ui, value)
|
|
44
|
+
return parsed < 0 ? 0 : parsed
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default defineComponent<BasicPluralizeProps, BasicPluralizeUI>(
|
|
48
|
+
'basic-pluralize',
|
|
49
|
+
{
|
|
50
|
+
count: asPositiveInteger(),
|
|
51
|
+
},
|
|
52
|
+
({ first }) => ({
|
|
53
|
+
count: first('.count'),
|
|
54
|
+
none: first('.none'),
|
|
55
|
+
some: first('.some'),
|
|
56
|
+
zero: first('.zero'),
|
|
57
|
+
one: first('.one'),
|
|
58
|
+
two: first('.two'),
|
|
59
|
+
few: first('.few'),
|
|
60
|
+
many: first('.many'),
|
|
61
|
+
other: first('.other'),
|
|
62
|
+
}),
|
|
63
|
+
({ host }) => {
|
|
64
|
+
const pluralizer = new Intl.PluralRules(
|
|
65
|
+
host.closest('[lang]')?.getAttribute('lang') || FALLBACK_LOCALE,
|
|
66
|
+
host.hasAttribute('ordinal') ? { type: 'ordinal' } : undefined,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
// Basic effects
|
|
70
|
+
const effects = {
|
|
71
|
+
count: setText(() => String(host.count)),
|
|
72
|
+
none: show(() => host.count === 0),
|
|
73
|
+
some: show(() => host.count > 0),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Subset of plural categories for applicable pluralizer: ['zero', 'one', 'two', 'few', 'many', 'other']
|
|
77
|
+
const categories = pluralizer.resolvedOptions().pluralCategories
|
|
78
|
+
for (const category of categories)
|
|
79
|
+
effects[category] = show(() => pluralizer.select(host.count) === category)
|
|
80
|
+
return effects
|
|
81
|
+
},
|
|
82
|
+
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
card-callout {
|
|
2
|
+
display: block;
|
|
3
|
+
position: relative;
|
|
4
|
+
padding: var(--space-s) var(--space-s) var(--space-s) var(--space-xl);
|
|
5
|
+
margin: 0 0 var(--space-l);
|
|
6
|
+
font-size: var(--font-size-m);
|
|
7
|
+
line-height: var(--line-height-l);
|
|
8
|
+
background: var(--color-blue-20);
|
|
9
|
+
border-left: var(--space-xxs) solid var(--color-blue-50);
|
|
10
|
+
border-radius: var(--space-s);
|
|
11
|
+
|
|
12
|
+
&::before {
|
|
13
|
+
position: absolute;
|
|
14
|
+
content: "ℹ️";
|
|
15
|
+
left: var(--space-s);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
> *:last-child {
|
|
19
|
+
margin-bottom: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&.tip {
|
|
23
|
+
background: var(--color-green-20);
|
|
24
|
+
border-color: var(--color-green-50);
|
|
25
|
+
|
|
26
|
+
&::before {
|
|
27
|
+
content: "💡";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&.note {
|
|
32
|
+
background: var(--color-neutral-20);
|
|
33
|
+
border-color: var(--color-neutral-50);
|
|
34
|
+
|
|
35
|
+
&::before {
|
|
36
|
+
content: "💬";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&.caution {
|
|
41
|
+
background: var(--color-orange-20);
|
|
42
|
+
border-color: var(--color-orange-50);
|
|
43
|
+
|
|
44
|
+
&::before {
|
|
45
|
+
content: "⚠️";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&.danger {
|
|
50
|
+
background: var(--color-pink-20);
|
|
51
|
+
border-color: var(--color-pink-50);
|
|
52
|
+
|
|
53
|
+
&::before {
|
|
54
|
+
content: "🚨";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@media (prefers-color-scheme: dark) {
|
|
60
|
+
card-callout {
|
|
61
|
+
background: var(--color-blue-80);
|
|
62
|
+
|
|
63
|
+
&.tip {
|
|
64
|
+
background: var(--color-green-80);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&.note {
|
|
68
|
+
background: var(--color-neutral-80);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
&.caution {
|
|
72
|
+
background: var(--color-orange-80);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&.danger {
|
|
76
|
+
background: var(--color-pink-80);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<card-callout>This is an informational message.</card-callout>
|
|
2
|
+
<card-callout class="tip">Remember to hydrate while coding!</card-callout>
|
|
3
|
+
<card-callout class="caution">Be careful with this operation.</card-callout>
|
|
4
|
+
<card-callout class="danger">This action is irreversible!</card-callout>
|
|
5
|
+
<card-callout class="note">This is just a side note.</card-callout>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<card-mediaqueries>
|
|
2
|
+
<h2>Without Context</h2>
|
|
3
|
+
<dl>
|
|
4
|
+
<dt>Motion Preference:</dt>
|
|
5
|
+
<dd class="motion"></dd>
|
|
6
|
+
<dt>Theme Preference</dt>
|
|
7
|
+
<dd class="theme"></dd>
|
|
8
|
+
<dt>Device Viewport:</dt>
|
|
9
|
+
<dd class="viewport"></dd>
|
|
10
|
+
<dt>Device Orientation:</dt>
|
|
11
|
+
<dd class="orientation"></dd>
|
|
12
|
+
</dl>
|
|
13
|
+
</card-mediaqueries>
|
|
14
|
+
|
|
15
|
+
<context-media>
|
|
16
|
+
<card-mediaqueries>
|
|
17
|
+
<h2>With Context</h2>
|
|
18
|
+
<dl>
|
|
19
|
+
<dt>Motion Preference:</dt>
|
|
20
|
+
<dd class="motion"></dd>
|
|
21
|
+
<dt>Theme Preference:</dt>
|
|
22
|
+
<dd class="theme"></dd>
|
|
23
|
+
<dt>Device Viewport:</dt>
|
|
24
|
+
<dd class="viewport"></dd>
|
|
25
|
+
<dt>Device Orientation:</dt>
|
|
26
|
+
<dd class="orientation"></dd>
|
|
27
|
+
</dl>
|
|
28
|
+
</card-mediaqueries>
|
|
29
|
+
</context-media>
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { expect, test } from '@playwright/test'
|
|
2
|
+
|
|
3
|
+
test.describe('card-mediaqueries component', () => {
|
|
4
|
+
test.beforeEach(async ({ page }) => {
|
|
5
|
+
await page.goto('http://localhost:3000/test/card-mediaqueries.html')
|
|
6
|
+
await page.waitForSelector('card-mediaqueries')
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
test('renders component elements correctly', async ({ page }) => {
|
|
10
|
+
// Check both instances exist
|
|
11
|
+
const componentWithoutContext = page.locator('card-mediaqueries').first()
|
|
12
|
+
const componentWithContext = page.locator('context-media card-mediaqueries')
|
|
13
|
+
|
|
14
|
+
await expect(componentWithoutContext).toBeVisible()
|
|
15
|
+
await expect(componentWithContext).toBeVisible()
|
|
16
|
+
|
|
17
|
+
// Check that each has the expected structure
|
|
18
|
+
for (const component of [componentWithoutContext, componentWithContext]) {
|
|
19
|
+
await expect(component.locator('h2')).toBeVisible()
|
|
20
|
+
await expect(component.locator('.motion')).toBeVisible()
|
|
21
|
+
await expect(component.locator('.theme')).toBeVisible()
|
|
22
|
+
await expect(component.locator('.viewport')).toBeVisible()
|
|
23
|
+
await expect(component.locator('.orientation')).toBeVisible()
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('shows fallback values without context provider', async ({ page }) => {
|
|
28
|
+
const componentWithoutContext = page.locator('card-mediaqueries').first()
|
|
29
|
+
|
|
30
|
+
// All values should show 'unknown' fallback
|
|
31
|
+
await expect(componentWithoutContext.locator('.motion')).toHaveText(
|
|
32
|
+
'unknown',
|
|
33
|
+
)
|
|
34
|
+
await expect(componentWithoutContext.locator('.theme')).toHaveText(
|
|
35
|
+
'unknown',
|
|
36
|
+
)
|
|
37
|
+
await expect(componentWithoutContext.locator('.viewport')).toHaveText(
|
|
38
|
+
'unknown',
|
|
39
|
+
)
|
|
40
|
+
await expect(componentWithoutContext.locator('.orientation')).toHaveText(
|
|
41
|
+
'unknown',
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('receives context values from provider', async ({ page }) => {
|
|
46
|
+
const componentWithContext = page.locator('context-media card-mediaqueries')
|
|
47
|
+
|
|
48
|
+
// Motion preference should be detected
|
|
49
|
+
const motionText = await componentWithContext
|
|
50
|
+
.locator('.motion')
|
|
51
|
+
.textContent()
|
|
52
|
+
expect(['no-preference', 'reduce']).toContain(motionText)
|
|
53
|
+
|
|
54
|
+
// Theme preference should be detected
|
|
55
|
+
const themeText = await componentWithContext.locator('.theme').textContent()
|
|
56
|
+
expect(['light', 'dark']).toContain(themeText)
|
|
57
|
+
|
|
58
|
+
// Viewport should be detected based on current window size
|
|
59
|
+
const viewportText = await componentWithContext
|
|
60
|
+
.locator('.viewport')
|
|
61
|
+
.textContent()
|
|
62
|
+
expect(['xs', 'sm', 'md', 'lg', 'xl']).toContain(viewportText)
|
|
63
|
+
|
|
64
|
+
// Orientation should be detected
|
|
65
|
+
const orientationText = await componentWithContext
|
|
66
|
+
.locator('.orientation')
|
|
67
|
+
.textContent()
|
|
68
|
+
expect(['portrait', 'landscape']).toContain(orientationText)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('context values differ from fallback values', async ({ page }) => {
|
|
72
|
+
const componentWithoutContext = page.locator('card-mediaqueries').first()
|
|
73
|
+
const componentWithContext = page.locator('context-media card-mediaqueries')
|
|
74
|
+
|
|
75
|
+
// Get values from both components
|
|
76
|
+
const [
|
|
77
|
+
motionWithoutContext,
|
|
78
|
+
motionWithContext,
|
|
79
|
+
themeWithoutContext,
|
|
80
|
+
themeWithContext,
|
|
81
|
+
viewportWithoutContext,
|
|
82
|
+
viewportWithContext,
|
|
83
|
+
orientationWithoutContext,
|
|
84
|
+
orientationWithContext,
|
|
85
|
+
] = await Promise.all([
|
|
86
|
+
componentWithoutContext.locator('.motion').textContent(),
|
|
87
|
+
componentWithContext.locator('.motion').textContent(),
|
|
88
|
+
componentWithoutContext.locator('.theme').textContent(),
|
|
89
|
+
componentWithContext.locator('.theme').textContent(),
|
|
90
|
+
componentWithoutContext.locator('.viewport').textContent(),
|
|
91
|
+
componentWithContext.locator('.viewport').textContent(),
|
|
92
|
+
componentWithoutContext.locator('.orientation').textContent(),
|
|
93
|
+
componentWithContext.locator('.orientation').textContent(),
|
|
94
|
+
])
|
|
95
|
+
|
|
96
|
+
// Without context should always be 'unknown'
|
|
97
|
+
expect(motionWithoutContext).toBe('unknown')
|
|
98
|
+
expect(themeWithoutContext).toBe('unknown')
|
|
99
|
+
expect(viewportWithoutContext).toBe('unknown')
|
|
100
|
+
expect(orientationWithoutContext).toBe('unknown')
|
|
101
|
+
|
|
102
|
+
// With context should be actual detected values (not 'unknown')
|
|
103
|
+
expect(motionWithContext).not.toBe('unknown')
|
|
104
|
+
expect(themeWithContext).not.toBe('unknown')
|
|
105
|
+
expect(viewportWithContext).not.toBe('unknown')
|
|
106
|
+
expect(orientationWithContext).not.toBe('unknown')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('properties reflect context values', async ({ page }) => {
|
|
110
|
+
const componentWithContext = page.locator('context-media card-mediaqueries')
|
|
111
|
+
|
|
112
|
+
// Check that component properties are accessible and match displayed values
|
|
113
|
+
const [
|
|
114
|
+
displayedMotion,
|
|
115
|
+
displayedTheme,
|
|
116
|
+
displayedViewport,
|
|
117
|
+
displayedOrientation,
|
|
118
|
+
] = await Promise.all([
|
|
119
|
+
componentWithContext.locator('.motion').textContent(),
|
|
120
|
+
componentWithContext.locator('.theme').textContent(),
|
|
121
|
+
componentWithContext.locator('.viewport').textContent(),
|
|
122
|
+
componentWithContext.locator('.orientation').textContent(),
|
|
123
|
+
])
|
|
124
|
+
|
|
125
|
+
const [
|
|
126
|
+
propertyMotion,
|
|
127
|
+
propertyTheme,
|
|
128
|
+
propertyViewport,
|
|
129
|
+
propertyOrientation,
|
|
130
|
+
] = await page.evaluate(() => {
|
|
131
|
+
const element = document.querySelector(
|
|
132
|
+
'context-media card-mediaqueries',
|
|
133
|
+
) as any
|
|
134
|
+
return [
|
|
135
|
+
element.motion,
|
|
136
|
+
element.theme,
|
|
137
|
+
element.viewport,
|
|
138
|
+
element.orientation,
|
|
139
|
+
]
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
expect(propertyMotion).toBe(displayedMotion)
|
|
143
|
+
expect(propertyTheme).toBe(displayedTheme)
|
|
144
|
+
expect(propertyViewport).toBe(displayedViewport)
|
|
145
|
+
expect(propertyOrientation).toBe(displayedOrientation)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('responds to media query changes', async ({ page, isMobile }) => {
|
|
149
|
+
const componentWithContext = page.locator('context-media card-mediaqueries')
|
|
150
|
+
|
|
151
|
+
// Change viewport size to trigger media query changes
|
|
152
|
+
if (!isMobile) {
|
|
153
|
+
// Test desktop -> mobile transition
|
|
154
|
+
await page.setViewportSize({ width: 400, height: 600 })
|
|
155
|
+
await page.waitForTimeout(100) // Allow time for media query listeners
|
|
156
|
+
|
|
157
|
+
const mobileViewport = await componentWithContext
|
|
158
|
+
.locator('.viewport')
|
|
159
|
+
.textContent()
|
|
160
|
+
|
|
161
|
+
// Should show mobile viewport size
|
|
162
|
+
expect(['xs', 'sm']).toContain(mobileViewport)
|
|
163
|
+
|
|
164
|
+
// Change back to desktop
|
|
165
|
+
await page.setViewportSize({ width: 1200, height: 800 })
|
|
166
|
+
await page.waitForTimeout(100)
|
|
167
|
+
|
|
168
|
+
const desktopViewport = await componentWithContext
|
|
169
|
+
.locator('.viewport')
|
|
170
|
+
.textContent()
|
|
171
|
+
|
|
172
|
+
// Should show larger viewport size
|
|
173
|
+
expect(['md', 'lg', 'xl']).toContain(desktopViewport)
|
|
174
|
+
|
|
175
|
+
// Values should have changed
|
|
176
|
+
expect(mobileViewport).not.toBe(desktopViewport)
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test('context provider supports custom breakpoints via attributes', async ({
|
|
181
|
+
page,
|
|
182
|
+
}) => {
|
|
183
|
+
// Add custom breakpoint attributes to the context-media element
|
|
184
|
+
await page.evaluate(() => {
|
|
185
|
+
const contextMedia = document.querySelector('context-media')
|
|
186
|
+
if (contextMedia) {
|
|
187
|
+
contextMedia.setAttribute('sm', '40em')
|
|
188
|
+
contextMedia.setAttribute('md', '60em')
|
|
189
|
+
contextMedia.setAttribute('lg', '80em')
|
|
190
|
+
contextMedia.setAttribute('xl', '120em')
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// Wait a moment for the changes to take effect
|
|
195
|
+
await page.waitForTimeout(100)
|
|
196
|
+
|
|
197
|
+
const componentWithContext = page.locator('context-media card-mediaqueries')
|
|
198
|
+
|
|
199
|
+
// Viewport should still be a valid value
|
|
200
|
+
const viewportText = await componentWithContext
|
|
201
|
+
.locator('.viewport')
|
|
202
|
+
.textContent()
|
|
203
|
+
expect(['xs', 'sm', 'md', 'lg', 'xl']).toContain(viewportText)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('multiple components receive same context values', async ({ page }) => {
|
|
207
|
+
// Add another card-mediaqueries component inside the context provider
|
|
208
|
+
await page.evaluate(() => {
|
|
209
|
+
const contextMedia = document.querySelector('context-media')
|
|
210
|
+
if (contextMedia) {
|
|
211
|
+
const newCard = document.createElement('card-mediaqueries')
|
|
212
|
+
newCard.innerHTML = `
|
|
213
|
+
<h2>Additional Card</h2>
|
|
214
|
+
<dl>
|
|
215
|
+
<dt>Motion:</dt><dd class="motion"></dd>
|
|
216
|
+
<dt>Theme:</dt><dd class="theme"></dd>
|
|
217
|
+
<dt>Viewport:</dt><dd class="viewport"></dd>
|
|
218
|
+
<dt>Orientation:</dt><dd class="orientation"></dd>
|
|
219
|
+
</dl>
|
|
220
|
+
`
|
|
221
|
+
contextMedia.appendChild(newCard)
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
const firstCard = page.locator('context-media card-mediaqueries').first()
|
|
226
|
+
const secondCard = page.locator('context-media card-mediaqueries').nth(1)
|
|
227
|
+
|
|
228
|
+
await expect(secondCard).toBeVisible()
|
|
229
|
+
|
|
230
|
+
// Both cards should receive the same context values
|
|
231
|
+
const [
|
|
232
|
+
firstMotion,
|
|
233
|
+
firstTheme,
|
|
234
|
+
firstViewport,
|
|
235
|
+
firstOrientation,
|
|
236
|
+
secondMotion,
|
|
237
|
+
secondTheme,
|
|
238
|
+
secondViewport,
|
|
239
|
+
secondOrientation,
|
|
240
|
+
] = await Promise.all([
|
|
241
|
+
firstCard.locator('.motion').textContent(),
|
|
242
|
+
firstCard.locator('.theme').textContent(),
|
|
243
|
+
firstCard.locator('.viewport').textContent(),
|
|
244
|
+
firstCard.locator('.orientation').textContent(),
|
|
245
|
+
secondCard.locator('.motion').textContent(),
|
|
246
|
+
secondCard.locator('.theme').textContent(),
|
|
247
|
+
secondCard.locator('.viewport').textContent(),
|
|
248
|
+
secondCard.locator('.orientation').textContent(),
|
|
249
|
+
])
|
|
250
|
+
|
|
251
|
+
expect(firstMotion).toBe(secondMotion)
|
|
252
|
+
expect(firstTheme).toBe(secondTheme)
|
|
253
|
+
expect(firstViewport).toBe(secondViewport)
|
|
254
|
+
expect(firstOrientation).toBe(secondOrientation)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
// TODO: Re-enable this test when context-theme component is implemented
|
|
258
|
+
// This test doesn't make sense with context-media since it always returns
|
|
259
|
+
// the same browser/system values regardless of nesting
|
|
260
|
+
test.skip('context isolation - nested providers override parent values', async ({
|
|
261
|
+
page,
|
|
262
|
+
}) => {
|
|
263
|
+
// Create a nested context structure where inner context might override values
|
|
264
|
+
await page.evaluate(() => {
|
|
265
|
+
const outerContext = document.querySelector('context-media')
|
|
266
|
+
if (outerContext) {
|
|
267
|
+
const innerContext = document.createElement('context-media')
|
|
268
|
+
const nestedCard = document.createElement('card-mediaqueries')
|
|
269
|
+
nestedCard.innerHTML = `
|
|
270
|
+
<h2>Nested Card</h2>
|
|
271
|
+
<dl>
|
|
272
|
+
<dt>Motion:</dt><dd class="motion"></dd>
|
|
273
|
+
<dt>Theme:</dt><dd class="theme"></dd>
|
|
274
|
+
<dt>Viewport:</dt><dd class="viewport"></dd>
|
|
275
|
+
<dt>Orientation:</dt><dd class="orientation"></dd>
|
|
276
|
+
</dl>
|
|
277
|
+
`
|
|
278
|
+
innerContext.appendChild(nestedCard)
|
|
279
|
+
outerContext.appendChild(innerContext)
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
const outerCard = page.locator('context-media > card-mediaqueries')
|
|
284
|
+
const nestedCard = page.locator(
|
|
285
|
+
'context-media context-media card-mediaqueries',
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
await expect(nestedCard).toBeVisible()
|
|
289
|
+
|
|
290
|
+
// Both should receive context values (same source since it's media queries)
|
|
291
|
+
const [outerMotion, nestedMotion] = await Promise.all([
|
|
292
|
+
outerCard.locator('.motion').textContent(),
|
|
293
|
+
nestedCard.locator('.motion').textContent(),
|
|
294
|
+
])
|
|
295
|
+
|
|
296
|
+
// Should both be valid motion preferences (not 'unknown')
|
|
297
|
+
expect(['no-preference', 'reduce']).toContain(outerMotion)
|
|
298
|
+
expect(['no-preference', 'reduce']).toContain(nestedMotion)
|
|
299
|
+
})
|
|
300
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Component, defineComponent, requestContext, setText } from '../..'
|
|
2
|
+
import {
|
|
3
|
+
MEDIA_MOTION,
|
|
4
|
+
MEDIA_ORIENTATION,
|
|
5
|
+
MEDIA_THEME,
|
|
6
|
+
MEDIA_VIEWPORT,
|
|
7
|
+
} from '../context-media/context-media'
|
|
8
|
+
|
|
9
|
+
type CardMediaqueriesPropKeys = 'motion' | 'theme' | 'viewport' | 'orientation'
|
|
10
|
+
|
|
11
|
+
export type CardMediaqueriesProps = Record<CardMediaqueriesPropKeys, string>
|
|
12
|
+
|
|
13
|
+
type CardMediaqueriesUI = Partial<Record<CardMediaqueriesPropKeys, HTMLElement>>
|
|
14
|
+
|
|
15
|
+
declare global {
|
|
16
|
+
interface HTMLElementTagNameMap {
|
|
17
|
+
'card-mediaqueries': Component<CardMediaqueriesProps>
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default defineComponent<CardMediaqueriesProps, CardMediaqueriesUI>(
|
|
22
|
+
'card-mediaqueries',
|
|
23
|
+
{
|
|
24
|
+
motion: requestContext(MEDIA_MOTION, 'unknown'),
|
|
25
|
+
theme: requestContext(MEDIA_THEME, 'unknown'),
|
|
26
|
+
viewport: requestContext(MEDIA_VIEWPORT, 'unknown'),
|
|
27
|
+
orientation: requestContext(MEDIA_ORIENTATION, 'unknown'),
|
|
28
|
+
},
|
|
29
|
+
({ first }) => ({
|
|
30
|
+
motion: first('.motion'),
|
|
31
|
+
theme: first('.theme'),
|
|
32
|
+
viewport: first('.viewport'),
|
|
33
|
+
orientation: first('.orientation'),
|
|
34
|
+
}),
|
|
35
|
+
() => ({
|
|
36
|
+
motion: setText('motion'),
|
|
37
|
+
theme: setText('theme'),
|
|
38
|
+
viewport: setText('viewport'),
|
|
39
|
+
orientation: setText('orientation'),
|
|
40
|
+
}),
|
|
41
|
+
)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Component,
|
|
3
|
+
type Context,
|
|
4
|
+
createState,
|
|
5
|
+
defineComponent,
|
|
6
|
+
provideContexts,
|
|
7
|
+
} from '../..'
|
|
8
|
+
|
|
9
|
+
export type ContextMediaMotion = 'no-preference' | 'reduce'
|
|
10
|
+
export type ContextMediaTheme = 'light' | 'dark'
|
|
11
|
+
export type ContextMediaViewport = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
12
|
+
export type ContextMediaOrientation = 'portrait' | 'landscape'
|
|
13
|
+
|
|
14
|
+
export type ContextMediaProps = {
|
|
15
|
+
readonly 'media-motion': ContextMediaMotion
|
|
16
|
+
readonly 'media-theme': ContextMediaTheme
|
|
17
|
+
readonly 'media-viewport': ContextMediaViewport
|
|
18
|
+
readonly 'media-orientation': ContextMediaOrientation
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare global {
|
|
22
|
+
interface HTMLElementTagNameMap {
|
|
23
|
+
'context-media': Component<ContextMediaProps>
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* === Exported Contexts === */
|
|
28
|
+
|
|
29
|
+
export const MEDIA_MOTION = 'media-motion' as Context<
|
|
30
|
+
'media-motion',
|
|
31
|
+
() => ContextMediaMotion
|
|
32
|
+
>
|
|
33
|
+
export const MEDIA_THEME = 'media-theme' as Context<
|
|
34
|
+
'media-theme',
|
|
35
|
+
() => ContextMediaTheme
|
|
36
|
+
>
|
|
37
|
+
export const MEDIA_VIEWPORT = 'media-viewport' as Context<
|
|
38
|
+
'media-viewport',
|
|
39
|
+
() => ContextMediaViewport
|
|
40
|
+
>
|
|
41
|
+
export const MEDIA_ORIENTATION = 'media-orientation' as Context<
|
|
42
|
+
'media-orientation',
|
|
43
|
+
() => ContextMediaOrientation
|
|
44
|
+
>
|
|
45
|
+
|
|
46
|
+
/* === Component === */
|
|
47
|
+
|
|
48
|
+
export default defineComponent<ContextMediaProps>(
|
|
49
|
+
'context-media',
|
|
50
|
+
{
|
|
51
|
+
// Context for motion preference; true for no-preference, false for reduce
|
|
52
|
+
[MEDIA_MOTION]: () => {
|
|
53
|
+
const mql = matchMedia('(prefers-reduced-motion: reduce)')
|
|
54
|
+
const motion = createState(mql.matches ? 'reduce' : 'no-preference')
|
|
55
|
+
mql.addEventListener('change', e => {
|
|
56
|
+
motion.set(e.matches ? 'reduce' : 'no-preference')
|
|
57
|
+
})
|
|
58
|
+
return motion
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// Context for preferred color scheme
|
|
62
|
+
[MEDIA_THEME]: () => {
|
|
63
|
+
const mql = matchMedia('(prefers-color-scheme: dark)')
|
|
64
|
+
const theme = createState(mql.matches ? 'dark' : 'light')
|
|
65
|
+
mql.addEventListener('change', e => {
|
|
66
|
+
theme.set(e.matches ? 'dark' : 'light')
|
|
67
|
+
})
|
|
68
|
+
return theme
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// Context for screen viewport size
|
|
72
|
+
[MEDIA_VIEWPORT]: (ui: { host: HTMLElement }) => {
|
|
73
|
+
const getBreakpoint = (attr: string, fallback: string) => {
|
|
74
|
+
const value = ui.host.getAttribute(attr)
|
|
75
|
+
const trimmed = value?.trim()
|
|
76
|
+
if (!trimmed) return fallback
|
|
77
|
+
const unit = trimmed.match(/em$/) ? 'em' : 'px'
|
|
78
|
+
const v = parseFloat(trimmed)
|
|
79
|
+
return Number.isFinite(v) ? v + unit : fallback
|
|
80
|
+
}
|
|
81
|
+
const mqlSM = matchMedia(`(min-width: ${getBreakpoint('sm', '32em')})`)
|
|
82
|
+
const mqlMD = matchMedia(`(min-width: ${getBreakpoint('md', '48em')})`)
|
|
83
|
+
const mqlLG = matchMedia(`(min-width: ${getBreakpoint('lg', '72em')})`)
|
|
84
|
+
const mqlXL = matchMedia(`(min-width: ${getBreakpoint('xl', '104em')})`)
|
|
85
|
+
const getViewport = () => {
|
|
86
|
+
if (mqlXL.matches) return 'xl'
|
|
87
|
+
if (mqlLG.matches) return 'lg'
|
|
88
|
+
if (mqlMD.matches) return 'md'
|
|
89
|
+
if (mqlSM.matches) return 'sm'
|
|
90
|
+
return 'xs'
|
|
91
|
+
}
|
|
92
|
+
const viewport = createState(getViewport())
|
|
93
|
+
mqlSM.addEventListener('change', () => {
|
|
94
|
+
viewport.set(getViewport())
|
|
95
|
+
})
|
|
96
|
+
mqlMD.addEventListener('change', () => {
|
|
97
|
+
viewport.set(getViewport())
|
|
98
|
+
})
|
|
99
|
+
mqlLG.addEventListener('change', () => {
|
|
100
|
+
viewport.set(getViewport())
|
|
101
|
+
})
|
|
102
|
+
mqlXL.addEventListener('change', () => {
|
|
103
|
+
viewport.set(getViewport())
|
|
104
|
+
})
|
|
105
|
+
return viewport
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// Context for screen orientation
|
|
109
|
+
[MEDIA_ORIENTATION]: () => {
|
|
110
|
+
const mql = matchMedia('(orientation: landscape)')
|
|
111
|
+
const orientation = createState(mql.matches ? 'landscape' : 'portrait')
|
|
112
|
+
mql.addEventListener('change', e => {
|
|
113
|
+
orientation.set(e.matches ? 'landscape' : 'portrait')
|
|
114
|
+
})
|
|
115
|
+
return orientation
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
undefined,
|
|
119
|
+
() => ({
|
|
120
|
+
host: provideContexts([
|
|
121
|
+
MEDIA_MOTION,
|
|
122
|
+
MEDIA_THEME,
|
|
123
|
+
MEDIA_VIEWPORT,
|
|
124
|
+
MEDIA_ORIENTATION,
|
|
125
|
+
]),
|
|
126
|
+
}),
|
|
127
|
+
)
|