diff --git a/V2-COMPONENTS-REVIEW.md b/V2-COMPONENTS-REVIEW.md new file mode 100644 index 000000000..f89c6eb1d --- /dev/null +++ b/V2-COMPONENTS-REVIEW.md @@ -0,0 +1,303 @@ +# v2 component review + +Full inventory of the public surface of the `ui` packages, for the js-toolkit v4 / ui v2 major version. + +Legend: + +- `[deprecated]` — already marked `@deprecated` in v1 +- `[twig]` — Twig template only, no TypeScript class +- `[ts+twig]` — TypeScript class with a Twig template +- `[abstract]` — not a declarative component, only a base class to extend +- `[shopify]` / `[twicpics]` — third party integration variant + +## Review decisions (round 2) + +Answers to the round-1 comments, verified against source: + +- **Accordion family** (Accordion, AccordionItem) — checked against `Disclosure`/`DisclosureGroup`: clean supersession. `Disclosure` covers everything `AccordionItem` does (open/close, transitions, ARIA sync) plus independent self-registration and `Transition`/`ViewTransition` integration `AccordionItem` never got, and `DisclosureGroup`'s `multiple`/`collapsible` options already express "accordion" as one configuration (`multiple: false, collapsible: true`) rather than a separate component. **Drop**, superseded by `Disclosure` + `DisclosureGroup`. +- **AnchorNav family** — still relevant: it's a scrollspy/table-of-contents pattern (highlight nav link for the section in view), nothing else in the catalog covers it. **Keep.** +- **AnchorScrollTo** — **Rename to `ScrollTo`.** +- **Cursor** — **Keep, redesign for a more generic/customizable API.** +- **Frame family** (Frame, FrameAnchor, FrameForm, FrameLoader, FrameTarget, FrameTriggerLoader) — **Drop**, superseded by `Fetch`. +- **LargeText** — **Keep, redesign for more generic usage.** +- **LazyInclude** — checked against `Fetch` + `Action` + `InViewOnce`: composable in theory, but `Fetch`'s DOM update is `[id]`-selector matching (built for partial-page nav), not raw-HTML injection, so the fetched fragment would need an extra `id` wrapper to satisfy it. `LazyInclude` is ~80 lines and does `innerHTML = content` directly. **Keep** as the lightweight primitive, **renamed to `Defer`**. Naming survey against comparable prior art (Unpoly's `up-defer`, Remix's `defer()`/``, htmx's "Lazy Loading" trigger pattern, Turbo's `loading="lazy"`, the community `` element) — "defer" is the established web-platform term for "fetch and inject after initial load" (` ``` -Load the Mapbox stylesheet yourself, and the geocoder stylesheet only when you use `MapboxGeocoder`. Give each map a valid access token through its `data-option-map-options`. Mapbox components default to the `visible` strategy, so the map code loads when a map nears the viewport. +Load the Mapbox stylesheet yourself, and the geocoder stylesheet only when you use `MapboxGeocoder`. Give each map a valid access token through its `data-option-map-options`. Mapbox components default to the `visible` mount strategy, so the map code loads when a map crosses into the viewport. You own the `mapbox-gl` module, so its Web Worker is same-origin and a strict Content Security Policy works. When you load `mapbox-gl` from a CDN that builds its worker from a `blob:` URL, allow it: `Content-Security-Policy: worker-src blob:;`. @@ -188,17 +190,21 @@ Most Shopify components autoload as they do in a bundled build. `FetchShopifyPar ## Diagnostics -The runtime logs warnings under the `[@studiometa/js-toolkit/autoload]` prefix for recoverable conditions: a conflicting runtime version, an unknown token, an unknown eager component, an invalid `data-load` value, an invalid manifest strategy, or an unavailable browser API. - -When a component fails to import or register, the runtime logs an error and dispatches a bubbling `js-toolkit:error` `CustomEvent` on the document element. Its `detail` carries the `token`, the `stage` (`import` or `registration`), and the `error`: +Recoverable conditions — a duplicate manifest token, an invalid `data-mount` value, a registry conflict, a component that fails to load or mount — are reported on js-toolkit's diagnostic channel rather than written straight to the console. Each one is a bubbling `js-toolkit:diagnostic` `CustomEvent` whose `detail` carries a stable `code`, a `severity` of `warning` or `error`, a `message`, the `component` name, and, for an error, the original `error`: ```js -document.addEventListener('js-toolkit:error', (event) => { - const { token, stage, error } = event.detail; - console.error(`Component ${token} failed at ${stage}:`, error); +import { DIAGNOSTICS, EVENTS } from '@studiometa/js-toolkit'; + +document.addEventListener(EVENTS.diagnostic, (event) => { + const { code, severity, message, component, error } = event.detail; + if (code === DIAGNOSTICS.component.loadFailed) { + console.error(`Component ${component} failed to load: ${message}`, error); + } }); ``` +`DIAGNOSTICS` enumerates every code core reports. A component outside core mints its own in the same `namespace.detail` shape, which is what makes a listener able to filter. + esm.sh serves a source map with every asset, so developer tools show the original sources. The autoloader targets ES2020 module browsers (Chrome 63+, Firefox 67+, Safari 11.1+, Edge 79+). ## Limitations @@ -209,7 +215,7 @@ A no-build install trades flexibility for a zero-build setup. Its constraints ar - **One js-toolkit runtime.** Every component on the page must resolve to one `@studiometa/js-toolkit` runtime. Do not mix CDN components with a separate npm build that bundles its own copy. - **No `data-component` mutation.** The runtime observes inserted and removed nodes only. - **No Shadow DOM.** Components own standard light-DOM elements. -- **No templates or stylesheets.** No Twig, no `data-mount`, and no CSS — including no Mapbox CSS. +- **No templates or stylesheets.** No Twig and no CSS — including no Mapbox CSS. - **ES2020 module browsers only.** - **Mapbox is not provided.** You supply `mapbox-gl` and its CSS through an import map. - **Motion is not provided.** You supply `motion` through an import map. @@ -217,6 +223,5 @@ A no-build install trades flexibility for a zero-build setup. Its constraints ar ## Next steps -- [js-toolkit autoload guide](https://js-toolkit.studiometa.dev) — autoload your own components. -- [autoload API reference](https://js-toolkit.studiometa.dev/api/autoload) — every export. +- [js-toolkit documentation](https://js-toolkit-v4.studiometa.dev) — the registry, manifests and mount strategies. - [Declarative runtime](/guide/concepts/declarative-runtime) — the `data-component` / `data-option-*` contract. diff --git a/packages/docs/guide/concepts/composition.md b/packages/docs/guide/concepts/composition.md index ca859f881..e97097730 100644 --- a/packages/docs/guide/concepts/composition.md +++ b/packages/docs/guide/concepts/composition.md @@ -4,14 +4,13 @@ The library separates ready-to-use solutions from reusable building blocks. Clas ## Choose the right abstraction -| Need | Prefer | Example | -| --- | --- | --- | -| A complete interaction or interface solution | Component | [`Dialog`](/reference/items/Dialog/), [`Slider`](/reference/items/Slider/) | -| Lower-level behavior to compose or extend | Primitive | [`Transition`](/reference/items/Transition/), [`Indexable`](/reference/items/Indexable/) | -| Reusable behavior applied to a class | Decorator | [`withTransition`](/reference/items/withTransition/), [`withIndex`](/reference/items/withIndex/) | -| A standalone operation | Helper | [`viewTransition`](/reference/items/view-transition-helper/) | -| Local behavior expressed in markup | Declarative component | [`Action`](/reference/items/Action/), the [Data family](./index.md#the-data-family) | -| Coordination across unrelated page features | Application component | [`createApp`](./declarative-runtime.md#when-to-use-createapp) | +| Need | Prefer | Example | +| -------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------ | +| A complete interaction or interface solution | Component | [`Dialog`](/reference/items/Dialog/), [`Slider`](/reference/items/Slider/) | +| Lower-level behavior to compose or extend | Primitive | [`Transition`](/reference/items/Transition/), [`Indexable`](/reference/items/Indexable/) | +| Reusable behavior applied to a class | Decorator | [`withTransition`](/reference/items/withTransition/), [`withDeprecation`](/reference/items/withDeprecation/) | +| Local behavior expressed in markup | Declarative component | [`Action`](/reference/items/Action/), the [Data family](./index.md#the-data-family) | +| Coordination across unrelated page features | Application component | [an ordinary component on the page root](./declarative-runtime.md#when-to-use-an-application-component) | Start at the highest-level abstraction that satisfies the requirement. Moving to a lower-level primitive or custom class gives more control but also makes the application responsible for more state, structure and accessibility behavior. @@ -20,17 +19,15 @@ Start at the highest-level abstraction that satisfies the requirement. Moving to A compound component divides one feature into a root orchestrator and focused children. The root registers child classes through its js-toolkit `components` configuration, and markup declares the hierarchy: ```html -
-
- -
-
-
+
+
+

+
``` -The `Accordion` family includes its item behavior and public contracts. Other examples include Slider, Carousel, Menu, Frame, ScrollAnimation and MapboxMap. A family's canonical Reference page documents related symbols together even when consumers can import those symbols independently. +The `Disclosure` family includes its group behavior and public contracts. Other examples include Slider, Carousel, Menu, Track and MapboxMap. A family's canonical Reference page documents related symbols together even when consumers can import those symbols independently. Use an item's **Anatomy** page when its child hierarchy, matching refs or required structure forms part of the contract. @@ -39,9 +36,7 @@ Use an item's **Anatomy** page when its child hierarchy, matching refs or requir Several independent behaviors can share one element through a space-separated `data-component` value: ```html - - Next page - +Next page ``` Co-location is useful when each behavior can operate independently. Prefer a compound family when children exchange events, inherit configuration or depend on a specific hierarchy. diff --git a/packages/docs/guide/concepts/declarative-runtime.md b/packages/docs/guide/concepts/declarative-runtime.md index 36f3de478..aa28cd4c7 100644 --- a/packages/docs/guide/concepts/declarative-runtime.md +++ b/packages/docs/guide/concepts/declarative-runtime.md @@ -4,13 +4,13 @@ JavaScript components are registered in an application entry point and mounted f ## Register components -Use [`registerComponent`](https://js-toolkit.studiometa.dev/api/helpers/registerComponent.html) for one component or `registerComponents` for several: +Use [`registerComponent`](https://js-toolkit-v4.studiometa.dev/api/registry/registerComponent.html) for one component or `registerComponents` for several: ```js import { registerComponents } from '@studiometa/js-toolkit'; -import { Accordion, Dialog, Slider } from '@studiometa/ui'; +import { Disclosure, Dialog, Slider } from '@studiometa/ui'; -registerComponents(Accordion, Dialog, Slider); +registerComponents(Disclosure, Dialog, Slider); ``` By default, each class mounts on elements whose `data-component` value contains its configured name: @@ -19,31 +19,29 @@ By default, each class mounts on elements whose `data-component` value contains
``` -Pass a different component name when markup needs an alias: +`registerComponent()` takes the class and nothing else: a component mounts on its configured name, and there is no selector form. Subclass it and declare a name when the markup uses a different one: ```js import { registerComponent } from '@studiometa/js-toolkit'; import { Dialog } from '@studiometa/ui'; -registerComponent(Dialog, 'SiteDialog'); +class SiteDialog extends Dialog { + static config = { + name: 'SiteDialog', + }; +} + +registerComponent(SiteDialog); ``` ```html
``` -A selector-like lowercase value targets matching DOM elements instead: - -```js -import { AnchorScrollTo } from '@studiometa/ui'; - -registerComponent(AnchorScrollTo, 'a[href^="#"]'); -``` - -A name is first matched as a `data-component` token. When no matching token exists and the value starts with a lowercase character, js-toolkit treats it as a CSS selector. Prefer an explicit component name when you control the markup. - Registration is the default application boundary. It keeps feature imports explicit and avoids an application class when components do not need to coordinate with one another. +No `@studiometa/ui` class registers itself when its module is imported: importing defines the class, registering is what makes it mount. The rule holds for every component in the package, so one explicit list of what a page uses is the whole picture — or hand the job to the [autoloader](/guide/autoloading/), which registers the catalog and imports each component on demand. + ## Compose behavior on an element An element can mount more than one registered component. Separate names with spaces: @@ -64,11 +62,7 @@ Each class owns its documented options and events. Use this pattern when indepen `data-option-` attributes configure a component instance. Attribute names use kebab case while JavaScript APIs usually show camel case: ```html -
-
+
``` Values are parsed from the option type declared by the component: @@ -80,9 +74,7 @@ Values are parsed from the option type declared by the component: - arrays and objects use JSON. ```html -
- … -
+
``` Follow each item's JavaScript API for its accepted type and default. Quote JSON with valid double-quoted keys and values. @@ -92,22 +84,23 @@ Follow each item's JavaScript API for its accepted type and default. Quote JSON `data-ref` marks elements owned by a component. A singular ref names one element; a name ending in `[]` contributes to a collection: ```html -
- -
- +
+ +
+ One + Two
``` -A component reads declared refs below its root until another component boundary owns them. Co-located components can both read the same unprefixed ref when both contracts declare that name. Prefix a ref with the component name, such as `data-ref="Modal.close[]"`, when the ownership must be explicit. Item-specific anatomy pages show required structure when order or nesting matters. +A component reads declared refs below its root until another component boundary owns them. Co-located components can both read the same unprefixed ref when both contracts declare that name. Prefix a ref with the component name, such as `data-ref="Menu.btn"`, when the ownership must be explicit. Item-specific anatomy pages show required structure when order or nesting matters. ## Events and component hooks -Components emit named events such as `open`, `close`, `index` or `in-view`. Parent components and application classes can respond through js-toolkit's `on` hook convention: +Components emit named events such as `disclosure-open`, `defer-content`, `index` or `in-view`. Parent components and application classes can respond through js-toolkit's `on` hook convention. The hook receives the emitting child instance as `target` and the event's detail as `payload`: ```js -import { Base, createApp } from '@studiometa/js-toolkit'; +import { Base, registerComponent } from '@studiometa/js-toolkit'; import { Dialog } from '@studiometa/ui'; class App extends Base { @@ -116,15 +109,21 @@ class App extends Base { components: { Dialog }, }; - onDialogOpen({ target }) { - console.log('Opened dialog', target); + onDialogOpen({ target, payload }) { + console.log('Opened dialog', target, payload); } } -export default createApp(App, document.body); +registerComponent(App); +``` + +```html +
+ +
``` -The Reference documents events exposed by each item. Use native DOM events for browser interactions and component events for communication between registered behaviors. +Component events bubble, so a listener on an ancestor hears its descendants too. That is why each family prefixes its events with its own name — `disclosure-open` rather than `open`. The Reference documents events exposed by each item. Use native DOM events for browser interactions and component events for communication between registered behaviors. ## Lifecycle and DOM ownership @@ -136,14 +135,16 @@ Registered components mount when matching elements enter the observed document a - clean up application-owned listeners and resources in lifecycle hooks; - treat server-rendered markup as the source of the initial state. -Components such as [`Fetch`](/reference/items/Fetch/) and [`Frame`](/reference/items/Frame/) update parts of the DOM while cooperating with this lifecycle. +Components such as [`Fetch`](/reference/items/Fetch/) and [`Defer`](/reference/items/Defer/) update parts of the DOM while cooperating with this lifecycle. + +## When to use an application component -## When to use `createApp` +Use a normal custom component for reusable or page-local behavior owned by one root element. Write an application component only when the page itself needs root-level refs, event handlers or methods that coordinate several children. Do not introduce one only to register unrelated components. -Use a normal custom component for reusable or page-local behavior owned by one root element. Use [`createApp`](https://js-toolkit.studiometa.dev/api/helpers/createApp.html) only when the page itself needs root-level refs, event handlers or methods that coordinate multiple children. Do not introduce an app class only to register unrelated components. +There is no `createApp()` in js-toolkit v4: an application component is an ordinary component. Register it and give its root element the matching `data-component`, which is also what scopes its refs. ```js -import { Base, createApp } from '@studiometa/js-toolkit'; +import { Base, registerComponent } from '@studiometa/js-toolkit'; import { Transition } from '@studiometa/ui'; class App extends Base { @@ -158,7 +159,14 @@ class App extends Base { } } -export default createApp(App, document.body); +registerComponent(App); +``` + +```html + + +
+ ``` ## Progressive enhancement diff --git a/packages/docs/guide/concepts/index.md b/packages/docs/guide/concepts/index.md index da93f58eb..61403cf08 100644 --- a/packages/docs/guide/concepts/index.md +++ b/packages/docs/guide/concepts/index.md @@ -27,8 +27,8 @@ The documentation uses a small, consistent vocabulary: - **Component** — a ready-to-use interface or behavior solution, such as [`Dialog`](/reference/items/Dialog/) or [`Slider`](/reference/items/Slider/). It can be visual or headless. - **Primitive** — a low-level, usually headless building block intended primarily for composition, such as [`Transition`](/reference/items/Transition/) or [`Sentinel`](/reference/items/Sentinel/). - **Decorator** — a higher-order function that adds reusable behavior to a js-toolkit component class, such as [`withTransition`](/reference/items/withTransition/). -- **Helper** — a supported plain function that operates independently of a component class, such as [`viewTransition`](/reference/items/view-transition-helper/). -- **Family** — related symbols that cooperate as one feature, such as `Accordion` and `AccordionItem`. +- **Helper** — a supported plain function that operates independently of a component class. The v2 surface exposes none of its own: standalone helpers such as `viewTransition` and `scrollTo` now come from [`@studiometa/js-toolkit`](https://js-toolkit-v4.studiometa.dev). +- **Family** — related symbols that cooperate as one feature, such as `Disclosure` and `DisclosureGroup`. - **Surface** — the runtime or authoring format through which an item is used: JavaScript, Twig or Liquid. - **Parameter** — a value passed to a Twig template. Twig API pages document parameters and blocks. - **Option** — a value passed to a JavaScript component, commonly through a `data-option-*` attribute. JavaScript API pages document options, refs, methods and events. diff --git a/packages/docs/guide/concepts/packages-and-surfaces.md b/packages/docs/guide/concepts/packages-and-surfaces.md index 2b99d223c..90a11a724 100644 --- a/packages/docs/guide/concepts/packages-and-surfaces.md +++ b/packages/docs/guide/concepts/packages-and-surfaces.md @@ -11,7 +11,7 @@ The project publishes behavior, templates and integrations through separate pack | [`@studiometa/ui-mapbox`](https://www.npmjs.com/package/@studiometa/ui-mapbox) | Declarative js-toolkit components for Mapbox GL | Your interface contains an interactive Mapbox map | | [`@studiometa/ui-motion`](https://www.npmjs.com/package/@studiometa/ui-motion) | Declarative js-toolkit components for the Motion animation library | Your interface animates elements with Motion | -The JavaScript packages are ESM-only and use [`@studiometa/js-toolkit`](https://js-toolkit.studiometa.dev) as their component runtime. `@studiometa/ui-mapbox` also expects `mapbox-gl`; its geocoder integration has an optional peer dependency. `@studiometa/ui-motion` expects `motion`. +The JavaScript packages are ESM-only and use [`@studiometa/js-toolkit`](https://js-toolkit-v4.studiometa.dev) v4 as their component runtime. `@studiometa/ui-mapbox` also expects `mapbox-gl`; its geocoder integration has an optional peer dependency. `@studiometa/ui-motion` expects `motion`. The NPM and Composer distributions use the same template sources from `packages/ui`. NPM exposes those source files to JavaScript build tools, while the Composer extension registers them with Twig through namespace-based lookup. Install the distribution used by your rendering pipeline; projects combining JavaScript behavior with Twig normally install both. diff --git a/packages/docs/guide/concepts/templates-and-customization.md b/packages/docs/guide/concepts/templates-and-customization.md index 03e6ff6ee..b2387c878 100644 --- a/packages/docs/guide/concepts/templates-and-customization.md +++ b/packages/docs/guide/concepts/templates-and-customization.md @@ -26,9 +26,10 @@ Templates expose an `attr` parameter for the root element. Package templates mer Pass application classes, IDs and data attributes through `attr` instead of copying a template only to change its root element: ```twig -{% include '@ui/Modal/Modal.twig' with { +{% include '@ui/Figure/Figure.twig' with { + src: '/images/cover.jpg', attr: { - id: 'newsletter-modal', + id: 'newsletter-cover', class: 'relative z-50', }, } %} diff --git a/packages/docs/guide/contributing/index.md b/packages/docs/guide/contributing/index.md index 856e2ed03..8c44a773f 100644 --- a/packages/docs/guide/contributing/index.md +++ b/packages/docs/guide/contributing/index.md @@ -78,9 +78,9 @@ export default class Name extends Base { Once your component is ready, export its public content from the family `index.ts` file and from `packages/ui/index.ts`. Add or update its entry in the documentation reference catalog so its symbols, package, surfaces and lifecycle status remain discoverable: ```diff - export * from './Modal/index.js'; + export * from './Menu/index.js'; +export * from './MyComponent/index.js'; - export * from './Panel/index.js'; + export * from './Prefetch/index.js'; ``` ### Twig diff --git a/packages/docs/guide/eslint-plugin/index.md b/packages/docs/guide/eslint-plugin/index.md index 056e438bc..41dd235f8 100644 --- a/packages/docs/guide/eslint-plugin/index.md +++ b/packages/docs/guide/eslint-plugin/index.md @@ -64,7 +64,7 @@ Add the plugin to your `.oxlintrc.json` using the `"ui"` name to get the `ui/` r ### `ui/prefer-ui-component` -Detects classes whose name matches a component exported by `@studiometa/ui` (such as `Menu`, `Accordion`, or `Modal`) that extend `Base` directly. Suggests importing and extending the existing component instead. +Detects classes whose name matches a component exported by `@studiometa/ui` (such as `Menu`, `Disclosure`, or `Dialog`) that extend `Base` directly. Suggests importing and extending the existing component instead. ```js // ❌ Incorrect diff --git a/packages/docs/guide/usage/index.md b/packages/docs/guide/usage/index.md index 73547bd46..0db0a6700 100644 --- a/packages/docs/guide/usage/index.md +++ b/packages/docs/guide/usage/index.md @@ -4,31 +4,42 @@ This page is a quickstart for rendering markup and registering behavior. Read th ## Registering components -Import the components your page uses and register them with [`registerComponent`](https://js-toolkit.studiometa.dev/api/helpers/registerComponent.html) or `registerComponents`: +Import the components your page uses and register them with [`registerComponent`](https://js-toolkit-v4.studiometa.dev/api/registry/registerComponent.html) or `registerComponents`: ```js import { registerComponents } from '@studiometa/js-toolkit'; -import { Accordion, Cursor } from '@studiometa/ui'; +import { Disclosure, Cursor } from '@studiometa/ui'; -registerComponents(Accordion, Cursor); +registerComponents(Disclosure, Cursor); ``` Registered classes mount on matching `data-component` elements: ```html -
+
``` -Pass an alias or selector when the DOM uses a different component name: +A component mounts on its configured name and on nothing else. When the DOM +uses a different name, subclass the component and declare that name: ```js import { registerComponent } from '@studiometa/js-toolkit'; -import { AnchorScrollTo } from '@studiometa/ui'; +import { ScrollTo } from '@studiometa/ui'; -registerComponent(AnchorScrollTo, 'a[href^="#"]'); +class SmoothAnchor extends ScrollTo { + static config = { + name: 'SmoothAnchor', + }; +} + +registerComponent(SmoothAnchor); ``` -See [Declarative runtime](/guide/concepts/declarative-runtime) for options, refs, events, lifecycle, multiple components on one element and when to use `createApp`. +::: tip Importing a component does not register it +No `@studiometa/ui` component registers itself: importing a module only defines the class, and the class mounts on nothing until you register it. Every component a page uses needs its `registerComponent()` call, which keeps one readable list of what the page runs. The [autoloader](/guide/autoloading/) is the other way to get there: it registers the whole catalog for you and imports each component the first time the DOM asks for it. +::: + +See [Declarative runtime](/guide/concepts/declarative-runtime) for options, refs, events, lifecycle and multiple components on one element. ## Rendering Twig templates diff --git a/packages/docs/migration-guides/0.1.0-0.2.0/index.md b/packages/docs/migration-guides/0.1.0-0.2.0/index.md index b90e1922f..956cac2a1 100644 --- a/packages/docs/migration-guides/0.1.0-0.2.0/index.md +++ b/packages/docs/migration-guides/0.1.0-0.2.0/index.md @@ -9,8 +9,8 @@ The following components have been updated: | [Button](/reference/items/Button/) | | | • Twig Template API standardization | | [Cursor](/reference/items/Cursor/) | | | • Twig Template API standardization | | [Figure](/reference/items/Figure/) | | | • Twig Template API standardization | -| [Accordion](/reference/items/Accordion/) | | | • Twig Template API standardization | -| [Modal](/reference/items/Modal/) | | | • Twig Template API standardization | +| Accordion | | | • Twig Template API standardization | +| Modal | | | • Twig Template API standardization | | [Sticky](/reference/items/Sticky/) | | | • Twig Template API standardization | ## Install the Twig extension @@ -29,8 +29,8 @@ Before this release, the Twig templates were not ready for inclusion in your pro - [Button](/reference/items/Button/) - [Cursor](/reference/items/Cursor/) - [Figure](/reference/items/Figure/) -- [Accordion](/reference/items/Accordion/) -- [Modal](/reference/items/Modal/) +- Accordion +- Modal - [Sticky](/reference/items/Sticky/) These components have seen their version number increased from `0.0.0` to `0.1.0` to indicate this change. diff --git a/packages/docs/migration-guides/1.0-2.0/index.md b/packages/docs/migration-guides/1.0-2.0/index.md index 3549d5bbb..eb2848a45 100644 --- a/packages/docs/migration-guides/1.0-2.0/index.md +++ b/packages/docs/migration-guides/1.0-2.0/index.md @@ -1,5 +1,14 @@ # v1.x → v2.x +::: danger This page is incomplete and partly out of date +It was written before the v2 component port. Two things it says are no longer true: + +- The `ScrollAnimation` family is **removed** from v2, not refactored. `ScrollAnimationTimeline` and `ScrollAnimationTarget` do not exist. Use [`@studiometa/ui-motion`](/reference/items/Motion/) instead. +- Everything below still assumes `@studiometa/js-toolkit` v3. v2 runs on v4, whose `Base` API differs. + +The complete v1 → v2 guide is **not written yet**. Until it is, read the removals and renames on each Reference item page. +::: + You will find on this page documentation on all the breaking changes included in the v2.x of the package. [[toc]] diff --git a/packages/docs/migration-guides/vue-mapbox-gl/index.md b/packages/docs/migration-guides/vue-mapbox-gl/index.md index 94dbc9a9f..a46d82d2b 100644 --- a/packages/docs/migration-guides/vue-mapbox-gl/index.md +++ b/packages/docs/migration-guides/vue-mapbox-gl/index.md @@ -32,19 +32,26 @@ The Mapbox GL stylesheet is still required. Keep importing it as before: @import 'mapbox-gl/dist/mapbox-gl.css'; ``` -Instead of registering the components on a Vue app, register them with js-toolkit. Every component is self-registering — `MapboxMap` no longer declares its children, so each one must be registered with [`registerComponent`](https://js-toolkit.studiometa.dev/api/helpers/registerComponent.html). Register only the ones you use — a bare map needs only `MapboxMap`, but every marker, control, source or cluster you declare needs its own registration. Registration order does not matter, because a child registered before its `MapboxMap` still wires up once the map connects. Because `mapbox-gl` is heavy (~230 kB gzipped), the recommended default is to lazy-register each component with js-toolkit's [`importWhen*` helpers](https://js-toolkit.studiometa.dev/api/helpers/importWhenVisible.html) and the per-component subpaths (each subpath's default export is the component class): +Instead of registering the components on a Vue app, register them with js-toolkit. Every component is self-registering — `MapboxMap` no longer declares its children, so each one must be registered with [`registerComponent`](https://js-toolkit-v4.studiometa.dev/api/registry/registerComponent.html). Register only the ones you use — a bare map needs only `MapboxMap`, but every marker, control, source or cluster you declare needs its own registration. Registration order does not matter, because a child registered before its `MapboxMap` still wires up once the map connects. Because `mapbox-gl` is heavy (~230 kB gzipped), the recommended default is to register them through a lazy [manifest](/guide/autoloading/), whose entries import the per-component subpaths on demand (each subpath's default export is the component class): ```js -import { registerComponent, importWhenVisible } from '@studiometa/js-toolkit'; +import { registerManifest } from '@studiometa/js-toolkit'; // Register only the components your page uses; order doesn't matter. -registerComponent(importWhenVisible(() => import('@studiometa/ui-mapbox/MapboxMap'), 'MapboxMap')); -registerComponent( - importWhenVisible(() => import('@studiometa/ui-mapbox/MapboxMarker'), 'MapboxMarker'), -); -registerComponent( - importWhenVisible(() => import('@studiometa/ui-mapbox/MapboxPopup'), 'MapboxPopup'), -); +registerManifest({ + MapboxMap: { + mountStrategy: 'visible', + load: () => import('@studiometa/ui-mapbox/MapboxMap'), + }, + MapboxMarker: { + mountStrategy: 'visible', + load: () => import('@studiometa/ui-mapbox/MapboxMarker'), + }, + MapboxPopup: { + mountStrategy: 'visible', + load: () => import('@studiometa/ui-mapbox/MapboxPopup'), + }, +}); ``` ## Component mapping @@ -122,7 +129,7 @@ The Vue library re-emits Mapbox map events prefixed with `mb-` (e.g. `@mb-load`, Every public event uses the `map-` prefix. The `MapboxMap` component re-emits the full list of Mapbox map events; `MapboxCluster` emits `map-cluster-click`, `map-item-click` (an unclustered point, resolved back to the registered `MapboxClusterItem` behind it) and `map-update` (the item set changed); `MapboxGeocoder` emits `map-result`; `MapboxImage` and `MapboxImages` emit `map-ready`; map children emit `map-error` when guarded lifecycle work fails; and `StoreLocator` emits `map-select`, `map-deselect` and `map-filter`. See each component's events in the [JS API](/reference/items/MapboxMap/js-api). ```js -import { Base, createApp } from '@studiometa/js-toolkit'; +import { Base, registerComponent } from '@studiometa/js-toolkit'; import { MapboxMap } from '@studiometa/ui-mapbox'; class App extends Base { @@ -131,16 +138,16 @@ class App extends Base { components: { MapboxMap }, }; - onMapboxMapMapLoad({ args: [map] }) { + onMapboxMapMapLoad({ payload: { map } }) { console.log('Map is ready', map); } - onMapboxMapMapClick({ args: [event] }) { + onMapboxMapMapClick({ payload: { event } }) { console.log('Clicked at', event.lngLat); } } -createApp(App); +registerComponent(App); ``` ### Slots → DOM children and refs @@ -262,7 +269,7 @@ A complete map with a marker and an attached popup. ``` ```js -import { Base, createApp } from '@studiometa/js-toolkit'; +import { Base, registerComponent } from '@studiometa/js-toolkit'; import { MapboxMap } from '@studiometa/ui-mapbox'; class App extends Base { @@ -271,10 +278,10 @@ class App extends Base { components: { MapboxMap }, }; - onMapboxMapMapLoad({ args: [map] }) { + onMapboxMapMapLoad({ payload: { map } }) { // former @mb-load handler } } -createApp(App); +registerComponent(App); ``` diff --git a/packages/docs/package.json b/packages/docs/package.json index 4cb799ed7..4377f2a03 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -13,7 +13,7 @@ "postbuild": "ln -sf ../../../api/public .vitepress/dist/api" }, "dependencies": { - "@studiometa/js-toolkit": "3.9.0" + "@studiometa/js-toolkit": "4.0.0-alpha.1" }, "devDependencies": { "@iconify-json/octicon": "1.2.22", diff --git a/packages/docs/reference/helpers/index.md b/packages/docs/reference/helpers/index.md index 36cfb1138..35a028a5e 100644 --- a/packages/docs/reference/helpers/index.md +++ b/packages/docs/reference/helpers/index.md @@ -2,4 +2,8 @@ Supported plain functions that help compose or operate the library. Internal family utilities are intentionally excluded unless they are part of the supported public API. +::: info No helpers in v2 +`@studiometa/ui` v2 exposes no standalone helper function of its own. `viewTransition`, which v1 published from `@studiometa/ui/scheduler`, now belongs to [`@studiometa/js-toolkit`](https://js-toolkit-v4.studiometa.dev) and is imported from there. Reusable behavior applied to a class is documented under [Decorators](/reference/decorators/). +::: + diff --git a/packages/docs/reference/items/Accordion/anatomy.md b/packages/docs/reference/items/Accordion/anatomy.md deleted file mode 100644 index 0a2bd1f2e..000000000 --- a/packages/docs/reference/items/Accordion/anatomy.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Accordion anatomy ---- - -# Anatomy - -`Accordion` is a compound component: a root orchestrates one or more `AccordionItem` children, each wiring its parts through `data-ref` attributes. Use this map to see which parts exist and how they nest. - -## Structure - -``` -Accordion data-component="Accordion" -└─ AccordionItem (× n) data-component="AccordionItem" - ├─ button [data-ref="btn"] the toggle, controls the panel - └─ container [data-ref="container"] the collapsible wrapper - └─ content [data-ref="content"] the panel content -``` - -## Parts - -| Part | Selector | Required | Role | -| --- | --- | --- | --- | -| Root | `data-component="Accordion"` | Yes | Groups the items, handles `autoclose`, forwards the shared `item` options. | -| Item | `data-component="AccordionItem"` | Yes (× n) | A single expandable section. Holds the `isOpen` and `styles` options. | -| Trigger | `data-ref="btn"` | Yes | The `
``` -You can then add options from the [withTransition](/reference/items/Transition/#transition) and [withMountWhenInView](https://js-toolkit.studiometa.dev/api/decorators/withMountWhenInView.html): +You can then add [transition options](/reference/items/Transition/js-api) on the links, and set the viewport margin of a target with `data-mount="in-view:"`: ```html{16}
diff --git a/packages/docs/reference/items/AnchorNav/js-api.md b/packages/docs/reference/items/AnchorNav/js-api.md index 667ef4f31..73f28a615 100644 --- a/packages/docs/reference/items/AnchorNav/js-api.md +++ b/packages/docs/reference/items/AnchorNav/js-api.md @@ -14,4 +14,4 @@ The `AnchorNavLink` class implements the features of the [`Transition` primitive ## AnchorNavTarget -The `AnchorNavTarget` class implements the features of the [`withMountWhenInView` decorator](https://js-toolkit.studiometa.dev/api/decorators/withMountWhenInView.html). +The `AnchorNavTarget` class declares the `in-view` [mount strategy](/guide/autoloading/#mount-strategies): it mounts when its section crosses into the viewport and unmounts when it leaves. Set the margin per element with `data-mount="in-view:"`. diff --git a/packages/docs/reference/items/AnchorScrollTo/index.md b/packages/docs/reference/items/AnchorScrollTo/index.md deleted file mode 100644 index b33f80b5c..000000000 --- a/packages/docs/reference/items/AnchorScrollTo/index.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -badges: [JS] ---- - -# AnchorScrollTo - -The `AnchorScrollTo` component is a small interface to the [`scrollTo` utility function](https://js-toolkit.studiometa.dev/utils/scrollTo.html) from the [@studiometa/js-toolkit package](https://js-toolkit.studiometa.dev). - -::: warning -It should be used on `` elements only. -::: - -## Usage - -This component can be directly imported and defined as a dependency of your application and set up to be instanciated on elements matching the `a[href^="#"]` selector: - -```js -import { registerComponent } from '@studiometa/js-toolkit'; -import { AnchorScrollTo } from '@studiometa/ui'; - -registerComponent(AnchorScrollTo, 'a[href^="#"]'); -``` diff --git a/packages/docs/reference/items/AnchorScrollTo/js-api.md b/packages/docs/reference/items/AnchorScrollTo/js-api.md deleted file mode 100644 index d0a25c08d..000000000 --- a/packages/docs/reference/items/AnchorScrollTo/js-api.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: AnchorScrollTo JS API ---- - -# JS API - -## Getter - -### `targetSelector` - -- Return: `string | HTMLElement | number | { left?: number, top?: number }` -- Default: `this.$el.hash` - -By default, this getter returns the hash portion of its root element as it must be an `` element. diff --git a/packages/docs/reference/items/AnchorScrollTo/stories/app.js b/packages/docs/reference/items/AnchorScrollTo/stories/app.js deleted file mode 100644 index 56fd21ecd..000000000 --- a/packages/docs/reference/items/AnchorScrollTo/stories/app.js +++ /dev/null @@ -1,4 +0,0 @@ -import { registerComponent } from '@studiometa/js-toolkit'; -import { AnchorScrollTo } from '@studiometa/ui'; - -registerComponent(AnchorScrollTo, 'a[href^="#"]'); diff --git a/packages/docs/reference/items/Carousel/js-api.md b/packages/docs/reference/items/Carousel/js-api.md index 315a4aabc..f720e74e4 100644 --- a/packages/docs/reference/items/Carousel/js-api.md +++ b/packages/docs/reference/items/Carousel/js-api.md @@ -147,4 +147,4 @@ Use `next` or `prev` to step through the items, or a numeric string (e.g. `"2"`) ## CarouselDrag -Adds pointer-drag navigation to the wrapper, built with the [`withDrag`](https://js-toolkit.studiometa.dev/api/decorators/withDrag.html) and [`withMountOnMediaQuery`](https://js-toolkit.studiometa.dev/api/decorators/withMountOnMediaQuery.html) decorators. It only mounts on fine-pointer devices (`(pointer: fine)`), leaving native CSS scroll-snap to handle touch devices. Apply it to the same element as `CarouselWrapper`. +Adds pointer-drag navigation to the wrapper, built with the [`withDrag` mixin](https://js-toolkit-v4.studiometa.dev) and the `media:(pointer: fine)` [mount strategy](/guide/autoloading/#mount-strategies). It only mounts on fine-pointer devices, leaving native CSS scroll-snap to handle touch devices. Apply it to the same element as `CarouselWrapper`. diff --git a/packages/docs/reference/items/Cursor/js-api.md b/packages/docs/reference/items/Cursor/js-api.md index d8524230c..684dab629 100644 --- a/packages/docs/reference/items/Cursor/js-api.md +++ b/packages/docs/reference/items/Cursor/js-api.md @@ -4,7 +4,7 @@ title: Cursor JS API # JS API -The `Cursor` component extends the [`Base` class](https://js-toolkit.studiometa.dev/api/configuration.html) using the [Pointer Service](https://js-toolkit.studiometa.dev/api/services/usePointer.html). It inherits their respective APIs. See both linked references. +The `Cursor` component extends the [`Base` class](https://js-toolkit-v4.studiometa.dev) using the pointer and animation-frame services. It inherits their respective APIs. ## Options diff --git a/packages/docs/reference/items/DataScope/examples.md b/packages/docs/reference/items/DataScope/examples.md index 79022e221..99f24be52 100644 --- a/packages/docs/reference/items/DataScope/examples.md +++ b/packages/docs/reference/items/DataScope/examples.md @@ -48,7 +48,7 @@ The following example implements two independent tab interfaces with `DataScope` ## Accordion -This example uses the same mirrored-model pattern for two independent accordion interfaces. Each button toggles the shared `active` key with [`toggle()`](../DataBind/js-api.md); opening one panel closes the other panels in that scope, and clicking the open item closes it. The `Accordion` component is not registered. +This example uses the same mirrored-model pattern for two independent accordion interfaces. Each button toggles the shared `active` key with [`toggle()`](../DataBind/js-api.md); opening one panel closes the other panels in that scope, and clicking the open item closes it. No [`Disclosure`](../Disclosure/) component is registered. - Third accordion panel. No Accordion component is registered. + Third accordion panel. No Disclosure component is registered.
diff --git a/packages/docs/reference/items/LazyInclude/examples.md b/packages/docs/reference/items/Defer/examples.md similarity index 94% rename from packages/docs/reference/items/LazyInclude/examples.md rename to packages/docs/reference/items/Defer/examples.md index 4d9824d4f..b77c75219 100644 --- a/packages/docs/reference/items/LazyInclude/examples.md +++ b/packages/docs/reference/items/Defer/examples.md @@ -1,5 +1,5 @@ --- -title: LazyInclude examples +title: Defer examples --- # Examples diff --git a/packages/docs/reference/items/Defer/index.md b/packages/docs/reference/items/Defer/index.md new file mode 100644 index 000000000..f93b13d02 --- /dev/null +++ b/packages/docs/reference/items/Defer/index.md @@ -0,0 +1,35 @@ +--- +badges: [JS] +--- + +# Defer + +## Usage + +Use the `Defer` component to load parts of your page lazily. + +::: code-group + +```js [app.js] twoslash +import { registerComponent } from '@studiometa/js-toolkit'; +import { Defer } from '@studiometa/ui'; + +registerComponent(Defer); +``` + +```html index.html +
+ Loading... + +
+``` + +::: + +::: warning Orchestration +The content is fetched when the component is mounted. Use the `data-mount` attribute to pick one of the [js-toolkit mount strategies](https://js-toolkit-v4.studiometa.dev/) — `visible`, `in-view`, `idle`, `interaction` or `media:` — for a fine grained loading strategy. +::: + +::: tip Renamed in v2 +`Defer` was named `LazyInclude` in v1, and its events were named `content`, `error` and `always`. Both the class and its events now carry the family prefix: `defer-content`, `defer-error` and `defer-always`. +::: diff --git a/packages/docs/reference/items/LazyInclude/js-api.md b/packages/docs/reference/items/Defer/js-api.md similarity index 59% rename from packages/docs/reference/items/LazyInclude/js-api.md rename to packages/docs/reference/items/Defer/js-api.md index de01e1dc5..26cd869f9 100644 --- a/packages/docs/reference/items/LazyInclude/js-api.md +++ b/packages/docs/reference/items/Defer/js-api.md @@ -1,5 +1,5 @@ --- -title: LazyInclude JS API +title: Defer JS API --- # JS API @@ -18,7 +18,7 @@ The source URL where the content will be fetched from. - Type: `boolean` - Default: `false` -Terminates the component once the content has been inserted in the page or the error has been displayed. +Fetches the content only once per element. When the content has been inserted, a later mount of the same element does not fetch it again. A failed request is not remembered, so mounting the element again retries it. ## Refs @@ -36,20 +36,20 @@ This ref should be used to display an error message when the fetch fails. It sho ## Events -### `content` +### `defer-content` - Parameters: - `content` (`string`): the content to be inserted in the DOM Emitted when the content has been fetched. -### `error` +### `defer-error` - Parameters: - - `error` (`Error`): the error thrown + - `error` (`unknown`): the error thrown Emitted when the fetch request has failed. -### `always` +### `defer-always` -Emitted at the end of the fetch request, successful or not. +Emitted at the end of the fetch request, successful or not. On success it is emitted after the fetched content has been inserted and its own components have settled. diff --git a/packages/docs/reference/items/Defer/stories/app.js b/packages/docs/reference/items/Defer/stories/app.js new file mode 100644 index 000000000..3cf7b4768 --- /dev/null +++ b/packages/docs/reference/items/Defer/stories/app.js @@ -0,0 +1,4 @@ +import { registerComponent } from '@studiometa/js-toolkit'; +import { Defer } from '@studiometa/ui'; + +registerComponent(Defer); diff --git a/packages/docs/reference/items/Defer/stories/app.twig b/packages/docs/reference/items/Defer/stories/app.twig new file mode 100644 index 000000000..c41ade07b --- /dev/null +++ b/packages/docs/reference/items/Defer/stories/app.twig @@ -0,0 +1,5 @@ +
+ Loading... + +
+ diff --git a/packages/docs/reference/items/Defer/stories/error.twig b/packages/docs/reference/items/Defer/stories/error.twig new file mode 100644 index 000000000..0cb7f89cc --- /dev/null +++ b/packages/docs/reference/items/Defer/stories/error.twig @@ -0,0 +1,7 @@ +{# `.invalid` is reserved by RFC 2606 and never resolves, so the request fails at the network + level. A 404 would not: `fetch()` resolves for any HTTP status, so `Defer` would inject the + error page's markup instead of showing the `error` ref. #} +
+ Loading... + +
diff --git a/packages/docs/reference/items/Disclosure/js-api.md b/packages/docs/reference/items/Disclosure/js-api.md index 170a902ac..95df31e3f 100644 --- a/packages/docs/reference/items/Disclosure/js-api.md +++ b/packages/docs/reference/items/Disclosure/js-api.md @@ -118,25 +118,25 @@ Disables interaction and synchronizes the native button's `disabled` state. It d ### Events -#### `open` +#### `disclosure-open` Emitted immediately after open state is committed, with the `Disclosure` instance as payload. -#### `close` +#### `disclosure-close` Emitted immediately after close state is committed, with the `Disclosure` instance as payload. -#### `after-open` +#### `disclosure-after-open` Emitted with the `Disclosure` instance after all owned enter transitions finish and only if the disclosure is still open. -#### `after-close` +#### `disclosure-after-close` Emitted with the `Disclosure` instance after all owned leave transitions finish, the panel is hidden, and only if the disclosure is still closed. ### Transition serialization -Opening and closing state is committed synchronously, but transition work is queued. Opposing operations never interrupt a still-pending toolkit transition promise. Stale completions do not hide a panel or emit an `after-*` event for a state that has since changed. Errors from transition children are warned and do not reject the disclosure operation. +Opening and closing state is committed synchronously, but transition work is queued. Opposing operations never interrupt a still-pending toolkit transition promise. Stale completions do not hide a panel or emit a `disclosure-after-*` event for a state that has since changed. Errors from transition children are warned and do not reject the disclosure operation. ## `DisclosureGroup` @@ -223,14 +223,14 @@ Unknown indexes, foreign disclosures, and disabled target disclosures are ignore ### Events -#### `open` +#### `disclosure-group-open` Relays an item opening with the `Disclosure` instance and its DOM-order index. -#### `close` +#### `disclosure-group-close` Relays an item closing with the `Disclosure` instance and its DOM-order index. -#### `change` +#### `disclosure-group-change` Emitted after an item state change with the current `openItems` array. diff --git a/packages/docs/reference/items/Draggable/js-api.md b/packages/docs/reference/items/Draggable/js-api.md index bfdb0d046..46881e3b1 100644 --- a/packages/docs/reference/items/Draggable/js-api.md +++ b/packages/docs/reference/items/Draggable/js-api.md @@ -4,7 +4,7 @@ title: Draggable JS API # JS API -The `Draggable` component uses the [`withDrag` decorator](https://js-toolkit.studiometa.dev/api/decorators/withDrag.html) of the [`@studiometa/js-toolkit` package](https://js-toolkit.studiometa.dev) and inherits from all of its APIs. +The `Draggable` component uses the [`withDrag` mixin](https://js-toolkit-v4.studiometa.dev) of the [`@studiometa/js-toolkit` package](https://js-toolkit-v4.studiometa.dev) and inherits from all of its APIs. ## Options diff --git a/packages/docs/reference/items/Draggable/stories/dynamic-parent/app.js b/packages/docs/reference/items/Draggable/stories/dynamic-parent/app.js index c87896b7c..31b18e6a4 100644 --- a/packages/docs/reference/items/Draggable/stories/dynamic-parent/app.js +++ b/packages/docs/reference/items/Draggable/stories/dynamic-parent/app.js @@ -1,11 +1,17 @@ -import { registerComponent } from '@studiometa/js-toolkit'; -import { Draggable as DraggableCore, Action } from '@studiometa/ui'; +import { registerComponents } from '@studiometa/js-toolkit'; +import { Draggable, Action } from '@studiometa/ui'; + +// `Draggable` bounds its drag to the `parent` getter. Overriding it needs a distinct component +// name: `Draggable` registers itself when the module is imported, so a subclass reusing that name +// would be refused by the registry. +class RingDraggable extends Draggable { + static config = { + name: 'RingDraggable', + }; -class Draggable extends DraggableCore { get parent() { return document.querySelector('.ring') ?? this.$el; } } -registerComponent(Draggable); -registerComponent(Action); +registerComponents(RingDraggable, Action); diff --git a/packages/docs/reference/items/Draggable/stories/dynamic-parent/app.twig b/packages/docs/reference/items/Draggable/stories/dynamic-parent/app.twig index 96e165f08..6eff1c5e0 100644 --- a/packages/docs/reference/items/Draggable/stories/dynamic-parent/app.twig +++ b/packages/docs/reference/items/Draggable/stories/dynamic-parent/app.twig @@ -1,5 +1,5 @@
-
- -
- {{ include('@ui/Button/StyledButton.twig', { label: 'Submit', icon: 'mdi:send', attr: { type: 'submit' } }) }} -
-
-
-
- - -
-
diff --git a/packages/docs/reference/items/Frame/stories/modes/app.js b/packages/docs/reference/items/Frame/stories/modes/app.js deleted file mode 100644 index e232ec800..000000000 --- a/packages/docs/reference/items/Frame/stories/modes/app.js +++ /dev/null @@ -1,4 +0,0 @@ -import { registerComponent } from '@studiometa/js-toolkit'; -import { Frame } from '@studiometa/ui'; - -registerComponent(Frame); diff --git a/packages/docs/reference/items/Frame/stories/modes/app.twig b/packages/docs/reference/items/Frame/stories/modes/app.twig deleted file mode 100644 index 35dee9eb1..000000000 --- a/packages/docs/reference/items/Frame/stories/modes/app.twig +++ /dev/null @@ -1,79 +0,0 @@ -{%- set content -%} - {% verbatim %} -
-
{{ 'now'|date('F j, Y H:i:s') }}
-
-
-
{{ 'now'|date('F j, Y H:i:s') }}
-
-
-
{{ 'now'|date('F j, Y H:i:s') }}
-
- {% endverbatim %} -{%- endset -%} - -
-
- {{ - include( - '@ui/Button/StyledButton.twig', - { - label: 'Click me', - icon: 'mdi:mouse-left-click', - icon_position: 'end', - href: twig_toolkit_url('/api/').withQueryParameter('content', content), - attr: { - data_component: 'FrameAnchor', - }, - } - ) - }} -
-
-
-
{{ 'now'|date('F j, Y H:i:s') }}
-
-
-
{{ 'now'|date('F j, Y H:i:s') }}
-
-
-
{{ 'now'|date('F j, Y H:i:s') }}
-
-
-
diff --git a/packages/docs/reference/items/Frame/stories/products/app.css b/packages/docs/reference/items/Frame/stories/products/app.css deleted file mode 100644 index 0dc2a9515..000000000 --- a/packages/docs/reference/items/Frame/stories/products/app.css +++ /dev/null @@ -1,16 +0,0 @@ -html.dark { - background-color: #222; - color: #eee; -} - -body { - padding: 1rem; -} - -.z-above { - z-index: 1; -} - -.z-under { - z-index: -1; -} diff --git a/packages/docs/reference/items/Frame/stories/products/app.js b/packages/docs/reference/items/Frame/stories/products/app.js deleted file mode 100644 index ea6ad68fc..000000000 --- a/packages/docs/reference/items/Frame/stories/products/app.js +++ /dev/null @@ -1,7 +0,0 @@ -import { registerComponent } from '@studiometa/js-toolkit'; -import { Action, Dialog, Frame, Figure } from '@studiometa/ui'; - -registerComponent(Action); -registerComponent(Dialog); -registerComponent(Frame); -registerComponent(Figure); diff --git a/packages/docs/reference/items/Frame/stories/products/app.twig b/packages/docs/reference/items/Frame/stories/products/app.twig deleted file mode 100644 index a27e99ccb..000000000 --- a/packages/docs/reference/items/Frame/stories/products/app.twig +++ /dev/null @@ -1,201 +0,0 @@ -{% set count = 12 %} -
-
-

My shop

-
- {{ - include( - '@ui/Icon/Icon.twig', - { - name: 'svg-spinners:ring-resize', - attr: { - class: 'block h-[0.6lh] [&>svg]:w-full [&>svg]:h-full' - } - } - ) - }} -
- {# The cart drawer is a Dialog; this Action button opens it. #} - -
- - {# Cart drawer — a right-anchored Dialog. The host carries NO display utility, - so a closed drawer stays display:none instead of covering the page. #} - - {# Fading backdrop — a Transition child, dismisses on click. #} -
- - {# Right-anchored panel sliding in from the edge — a second Transition child. #} -
-
- -
-
-

Your cart is empty.

-
-
-
-
- {% for article in 1..count %} - {% set price = random(1, 4) ~ '00 €' %} -
- {{ - include( - '@ui/Figure/Figure.twig', - { - src: 'https://picsum.photos/seed/KEY/300/200'|replace({ - KEY: loop.index + 10 - }), - width: 300, - height: 200, - fit: 'cover', - attr: { - class: 'rounded overflow-hidden' - } - } - ) - }} -
- -
- {%- set cart_content -%} - - Cart: 1 - -
-
- - {{ price }} - - - -
- {{ - include( - '@ui/Icon/Icon.twig', - { - name: 'svg-spinners:ring-resize', - attr: { - class: 'block h-[0.6lh] [&>svg]:w-full [&>svg]:h-full' - } - } - ) - }} -
- - -
-
- {%- endset -%} - - - -
- {{ - include( - '@ui/Icon/Icon.twig', - { - name: 'svg-spinners:ring-resize', - attr: { - class: 'block h-[0.6lh] [&>svg]:w-full [&>svg]:h-full' - } - } - ) - }} -
- - -
-
- {% endfor %} -
-
diff --git a/packages/docs/reference/items/Frame/stories/simple/app.css b/packages/docs/reference/items/Frame/stories/simple/app.css deleted file mode 100644 index 1bce40a0e..000000000 --- a/packages/docs/reference/items/Frame/stories/simple/app.css +++ /dev/null @@ -1,17 +0,0 @@ -html.dark { - background-color: #222; - color: #eee; -} - -body { - padding: 1rem; -} - -@keyframes loader { - 0% { transform: rotate(0) } - 100% { transform: rotate(720deg) } -} - -.animate-loader { - animation: loader 1s ease-in-out infinite; -} diff --git a/packages/docs/reference/items/Frame/stories/simple/app.js b/packages/docs/reference/items/Frame/stories/simple/app.js deleted file mode 100644 index e232ec800..000000000 --- a/packages/docs/reference/items/Frame/stories/simple/app.js +++ /dev/null @@ -1,4 +0,0 @@ -import { registerComponent } from '@studiometa/js-toolkit'; -import { Frame } from '@studiometa/ui'; - -registerComponent(Frame); diff --git a/packages/docs/reference/items/Frame/stories/simple/app.twig b/packages/docs/reference/items/Frame/stories/simple/app.twig deleted file mode 100644 index 66ac5704e..000000000 --- a/packages/docs/reference/items/Frame/stories/simple/app.twig +++ /dev/null @@ -1,82 +0,0 @@ -{%- set page_b_content -%} -
-

Page B

-

Hello world from page B!

-
-{%- endset -%} - -{%- set page_a_content -%} -
-

Page A

-

- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod - tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim - veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea - commodo consequat. Duis aute irure dolor in reprehenderit in voluptate - velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat - cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-
-{%- endset -%} - -
- - {{ page_a_content|raw }} -
diff --git a/packages/docs/reference/items/Hero/stories/app.js b/packages/docs/reference/items/Hero/stories/app.js index 538be862b..1ff0d4383 100644 --- a/packages/docs/reference/items/Hero/stories/app.js +++ b/packages/docs/reference/items/Hero/stories/app.js @@ -1,7 +1,7 @@ -import { registerComponent } from '@studiometa/js-toolkit'; +import { registerComponents } from '@studiometa/js-toolkit'; import { Figure, - Slider as SliderCore, + Slider, SliderBtn, SliderCount, SliderDots, @@ -10,18 +10,13 @@ import { SliderProgress, } from '@studiometa/ui'; -class Slider extends SliderCore { - static config = { - components: { - SliderBtn, - SliderCount, - SliderDots, - SliderDrag, - SliderItem, - SliderProgress, - }, - }; -} - -registerComponent(Figure); -registerComponent(Slider, 'Slider'); +registerComponents( + Figure, + Slider, + SliderBtn, + SliderCount, + SliderDots, + SliderDrag, + SliderItem, + SliderProgress, +); diff --git a/packages/docs/reference/items/Hoverable/js-api.md b/packages/docs/reference/items/Hoverable/js-api.md index 2ef776a50..4b4aa4b84 100644 --- a/packages/docs/reference/items/Hoverable/js-api.md +++ b/packages/docs/reference/items/Hoverable/js-api.md @@ -4,7 +4,7 @@ title: Hoverable JS API # JS API -The `Hoverable` component uses the [`withRelativePointer` decorator](https://js-toolkit.studiometa.dev/api/decorators/withRelativePointer.html) of the [`@studiometa/js-toolkit` package](https://js-toolkit.studiometa.dev) and inherits from all of its APIs. +The `Hoverable` component mixes in the pointer and animation-frame services of the [`@studiometa/js-toolkit` package](https://js-toolkit-v4.studiometa.dev) and inherits from all of their APIs. ## Options diff --git a/packages/docs/reference/items/ImageGrid/examples.md b/packages/docs/reference/items/ImageGrid/examples.md deleted file mode 100644 index ef8afa8f3..000000000 --- a/packages/docs/reference/items/ImageGrid/examples.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: ImageGrid examples ---- - -# Examples - -## With 3 images - - - - - - -:::code-group - -<<< ./stories/3-images/app.twig -<<< ./stories/app.js - -::: - - - -## With 5 images - - - - - - -:::code-group - -<<< ./stories/5-images/app.twig -<<< ./stories/app.js - -::: - - - -## With scroll reveal transitions - - - - - - -:::code-group - -<<< ./stories/block-image/app.twig -<<< ./stories/block-image/app.js - -::: - - diff --git a/packages/docs/reference/items/ImageGrid/index.md b/packages/docs/reference/items/ImageGrid/index.md deleted file mode 100644 index 6b08f1b53..000000000 --- a/packages/docs/reference/items/ImageGrid/index.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -badges: [Twig] ---- - -# ImageGrid - -A component to display images in a destructured grid. - -## Usage - -Use this component directly in your Twig templates by providing it with an array of objects following the [`Figure` component](../Figure/index.md) parameters. - -```twig -{% include '@ui/ImageGrid/ImageGrid.twig' with { - images: [ - { src: 'https://picsum.photos/500/400', width: 500, height: 400 }, - { src: 'https://picsum.photos/600/400', width: 600, height: 400 }, - { src: 'https://picsum.photos/500/400', width: 500, height: 400 } - ] -} %} -``` - - - - - - -:::code-group - -<<< ./stories/3-images/app.twig -<<< ./stories/app.js - -::: - - - -### Customizing the image output - -The `ImageGrid` template exposes an `image` block which can be used to customize the rendering of each image. - -In the following example, we wrap each image in a [`ScrollReveal` component](../ScrollReveal/index.md) to add transitions when each image becomes visible. - -<<< ./stories/block-image/app.twig{18-25} - - - - - - -:::code-group - -<<< ./stories/block-image/app.twig -<<< ./stories/block-image/app.js - -::: - - diff --git a/packages/docs/reference/items/ImageGrid/stories/3-images/app.twig b/packages/docs/reference/items/ImageGrid/stories/3-images/app.twig deleted file mode 100644 index decb8270e..000000000 --- a/packages/docs/reference/items/ImageGrid/stories/3-images/app.twig +++ /dev/null @@ -1,9 +0,0 @@ -{% set images = [ - { src: 'https://picsum.photos/500/400', width: '500', height: '400' }, - { src: 'https://picsum.photos/600/400', width: '600', height: '400' }, - { src: 'https://picsum.photos/500/400', width: '500', height: '400' } -] %} - -
- {% include '@ui/ImageGrid/ImageGrid.twig' with { images: images } %} -
diff --git a/packages/docs/reference/items/ImageGrid/stories/5-images/app.twig b/packages/docs/reference/items/ImageGrid/stories/5-images/app.twig deleted file mode 100644 index 7b3de1a22..000000000 --- a/packages/docs/reference/items/ImageGrid/stories/5-images/app.twig +++ /dev/null @@ -1,11 +0,0 @@ -{% set images = [ - { src: 'https://picsum.photos/500/400', width: '500', height: '400' }, - { src: 'https://picsum.photos/600/400', width: '600', height: '400' }, - { src: 'https://picsum.photos/400/600', width: '400', height: '600' }, - { src: 'https://picsum.photos/500/400', width: '500', height: '400' }, - { src: 'https://picsum.photos/600/400', width: '600', height: '400' } -] %} - -
- {% include '@ui/ImageGrid/ImageGrid.twig' with { images: images } %} -
diff --git a/packages/docs/reference/items/ImageGrid/stories/app.js b/packages/docs/reference/items/ImageGrid/stories/app.js deleted file mode 100644 index a086e7c03..000000000 --- a/packages/docs/reference/items/ImageGrid/stories/app.js +++ /dev/null @@ -1,4 +0,0 @@ -import { registerComponent } from '@studiometa/js-toolkit'; -import { Figure } from '@studiometa/ui'; - -registerComponent(Figure); diff --git a/packages/docs/reference/items/ImageGrid/stories/block-image/app.js b/packages/docs/reference/items/ImageGrid/stories/block-image/app.js deleted file mode 100644 index 4a23a55ff..000000000 --- a/packages/docs/reference/items/ImageGrid/stories/block-image/app.js +++ /dev/null @@ -1,5 +0,0 @@ -import { registerComponent } from '@studiometa/js-toolkit'; -import { Figure, ScrollReveal } from '@studiometa/ui'; - -registerComponent(Figure); -registerComponent(ScrollReveal); diff --git a/packages/docs/reference/items/ImageGrid/stories/block-image/app.twig b/packages/docs/reference/items/ImageGrid/stories/block-image/app.twig deleted file mode 100644 index 9d0997d95..000000000 --- a/packages/docs/reference/items/ImageGrid/stories/block-image/app.twig +++ /dev/null @@ -1,27 +0,0 @@ -
- {% embed '@ui/ImageGrid/ImageGrid.twig' - with { - images: [ - { src: 'https://picsum.photos/500/400', width: 500, height: 400 }, - { src: 'https://picsum.photos/600/400', width: 600, height: 400 }, - { src: 'https://picsum.photos/500/400', width: 500, height: 400 }, - { src: 'https://picsum.photos/500/400', width: 500, height: 400 }, - { src: 'https://picsum.photos/600/400', width: 600, height: 400 }, - { src: 'https://picsum.photos/500/400', width: 500, height: 400 }, - { src: 'https://picsum.photos/600/400', width: 600, height: 400 }, - { src: 'https://picsum.photos/500/400', width: 500, height: 400 }, - { src: 'https://picsum.photos/500/400', width: 500, height: 400 }, - { src: 'https://picsum.photos/600/400', width: 600, height: 400 } - ] - } - %} - {% block image %} -
- {{ parent() }} -
- {% endblock %} - {% endembed %} -
diff --git a/packages/docs/reference/items/ImageGrid/twig-api.md b/packages/docs/reference/items/ImageGrid/twig-api.md deleted file mode 100644 index 625917bf0..000000000 --- a/packages/docs/reference/items/ImageGrid/twig-api.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: ImageGrid Twig API ---- - -# Twig API - -## Parameters - -### `images` - -- Type: `array` - -List of images to display. Each item in the array is an object that will be passed to the [`Figure` component Twig template](../Figure/twig-api.md). - -### `attr` - -- Type: `array` - -Custom attributes for the root element. - -### `inner_attr` - -- Type: `array` - -Custom attributes for the inner element. - -### `image_attr` - -Custom attributes for each image wrapper element. - -## Blocks - -### `image` - -Customizes each image output. diff --git a/packages/docs/reference/items/InView/examples.md b/packages/docs/reference/items/InView/examples.md index 892940c26..bfeb375b4 100644 --- a/packages/docs/reference/items/InView/examples.md +++ b/packages/docs/reference/items/InView/examples.md @@ -4,7 +4,7 @@ title: InView examples # Examples -The [`Action` component](../Action/index.md) can catch the `in-view` and `out-of-view` events emitted by `InView` with its [`data-on:` attributes](../Action/js-api.md#on-event-modifier), because [`$emit`](https://js-toolkit.studiometa.dev/api/methods/emit.html) dispatches them as native `CustomEvent`s on the component's root element. Mounting both components on the same element (`data-component="Action InView"`) is all it takes to react to viewport crossings declaratively. No custom JavaScript class is required. +The [`Action` component](../Action/index.md) can catch the `in-view` and `out-of-view` events emitted by `InView` with its [`data-on:` attributes](../Action/js-api.md#on-event-modifier), because `$emit()` dispatches them as native `CustomEvent`s on the component's root element. Mounting both components on the same element (`data-component="Action InView"`) is all it takes to react to viewport crossings declaratively. No custom JavaScript class is required. ## Reveal on scroll diff --git a/packages/docs/reference/items/InView/index.md b/packages/docs/reference/items/InView/index.md index 5ddc7d7f3..c32bb491f 100644 --- a/packages/docs/reference/items/InView/index.md +++ b/packages/docs/reference/items/InView/index.md @@ -10,7 +10,7 @@ The `InView` primitive emits directional events when an element enters or leaves The `InView` primitive should be used as a child component to listen to its `in-view` and `out-of-view` events. It emits a directional `in-view` / `out-of-view` pair, unlike [`Sentinel`](/reference/items/Sentinel/), whose `intersected` event fires on both enter and leave. -It is built on the [`withMountWhenInView` decorator](https://js-toolkit.studiometa.dev/api/decorators/withMountWhenInView.html): the component mounts when it enters the viewport (emitting `in-view`) and is destroyed when it leaves (emitting `out-of-view`), re-firing on each re-entry. +It is built on the `in-view` [mount strategy](/guide/autoloading/#mount-strategies): the component mounts when it enters the viewport (emitting `in-view`) and unmounts when it leaves (emitting `out-of-view`), re-firing on each re-entry. If you only need to react to the first entry, use the [`InViewOnce` variant](/reference/items/InViewOnce/), which emits `in-view` a single time and never emits `out-of-view`. @@ -56,7 +56,7 @@ The [`Action` component](/reference/items/Action/) can react to the `in-view` / ``` ::: info -[`$emit`](https://js-toolkit.studiometa.dev/api/methods/emit.html) dispatches a native `CustomEvent` on the component's root element, which is what lets `Action` react to these events. +`$emit()` dispatches a native `CustomEvent` on the component's root element, which is what lets `Action` react to these events. ::: See the [examples](./examples.md) for live reveal-on-scroll demos, and the [JavaScript API](./js-api.md) for the full list of options and events. diff --git a/packages/docs/reference/items/InView/js-api.md b/packages/docs/reference/items/InView/js-api.md index 3fac57e8f..1796fd987 100644 --- a/packages/docs/reference/items/InView/js-api.md +++ b/packages/docs/reference/items/InView/js-api.md @@ -4,13 +4,13 @@ title: InView JS API # JS API -The `InView` class extends the [`Base` class](https://js-toolkit.studiometa.dev/api/) with the [`withMountWhenInView` decorator](https://js-toolkit.studiometa.dev/api/decorators/withMountWhenInView.html). As it inherits their APIs, make sure to have a look at their own API references too. +The `InView` class is a plain [`Base`](https://js-toolkit-v4.studiometa.dev) component declaring the `in-view` mount strategy. The registry owns the observer, so the class holds no state of its own: it emits on mount and on unmount. ## Events ### `in-view` -Emitted when the element enters the viewport (i.e. when the component is mounted by the `withMountWhenInView` decorator). +Emitted when the element enters the viewport, which is when the `in-view` mount strategy mounts the component. ```js onInViewInView() { @@ -20,7 +20,7 @@ onInViewInView() { ### `out-of-view` -Emitted when the element leaves the viewport (i.e. when the component is destroyed by the `withMountWhenInView` decorator). The primitive keeps reacting to every crossing, so `in-view` and `out-of-view` re-fire on each re-entry and leave. +Emitted when the element leaves the viewport, which is when the `in-view` mount strategy unmounts the component. The strategy is reversible, so `in-view` and `out-of-view` re-fire on each re-entry and leave. If you only care about the first entry, use the [`InViewOnce` variant](/reference/items/InViewOnce/) instead, which emits `in-view` a single time and never emits `out-of-view`. @@ -32,17 +32,10 @@ onInViewOutOfView() { ## Options -### `intersectionObserver` - -- Type: `object` -- Default: `{ threshold: [0, 1] }` - -Options forwarded to the underlying [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver#options) instance created by the [`withMountWhenInView` decorator](https://js-toolkit.studiometa.dev/api/decorators/withMountWhenInView.html). Use it to adjust the `rootMargin`, `threshold` or `root` used to detect the viewport crossing. +`InView` declares none. The viewport margin belongs to the mount strategy, not to the component, so it is written on the `data-mount` attribute as the strategy's suffix — the value becomes the [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver#options) `rootMargin`: ```html -
- ... -
+
...
``` ## See also diff --git a/packages/docs/reference/items/InViewOnce/index.md b/packages/docs/reference/items/InViewOnce/index.md index bb24d7c91..091c8bf90 100644 --- a/packages/docs/reference/items/InViewOnce/index.md +++ b/packages/docs/reference/items/InViewOnce/index.md @@ -8,7 +8,7 @@ The `InViewOnce` primitive is a one-shot variant of the [`InView` primitive](/re ## Usage -Use `InViewOnce` when you only care about the first time an element becomes visible, for example to trigger a one-off reveal, lazy-load or analytics impression. It is built on the [`withMountWhenInView` decorator](https://js-toolkit.studiometa.dev/api/decorators/withMountWhenInView.html): the component mounts on entry (emitting `in-view`) and then terminates, disconnecting its observer so the event never fires again. +Use `InViewOnce` when you only care about the first time an element becomes visible, for example to trigger a one-off reveal, lazy-load or analytics impression. It is built on the `visible` [mount strategy](/guide/autoloading/#mount-strategies): the component mounts on entry (emitting `in-view`) and never unmounts, so the event never fires again. For a directional listener that keeps reacting to every viewport crossing (both enter and leave), use the [`InView` primitive](/reference/items/InView/) instead. @@ -41,15 +41,11 @@ export default class Component extends Base { The [`Action` component](/reference/items/Action/) can react to the `in-view` event without any custom class. Mount both on the same element to trigger a one-off effect: ```html -
- ... -
+
...
``` ::: info -[`$emit`](https://js-toolkit.studiometa.dev/api/methods/emit.html) dispatches a native `CustomEvent` on the component's root element, which is what lets `Action` react to the event. +`$emit()` dispatches a native `CustomEvent` on the component's root element, which is what lets `Action` react to the event. ::: See the [examples](./examples.md) for a live one-shot reveal demo, and the [JavaScript API](./js-api.md) for the full list of options and events. diff --git a/packages/docs/reference/items/InViewOnce/js-api.md b/packages/docs/reference/items/InViewOnce/js-api.md index 3733153a0..386b589cb 100644 --- a/packages/docs/reference/items/InViewOnce/js-api.md +++ b/packages/docs/reference/items/InViewOnce/js-api.md @@ -10,7 +10,7 @@ The `InViewOnce` class extends the [`InView` primitive](/reference/items/InView/ ### `in-view` -Emitted once, when the element first enters the viewport. The component then terminates and disconnects its observer, so the event never fires again. +Emitted once, when the element first enters the viewport. `InViewOnce` declares the `visible` mount strategy, which mounts and never unmounts, and it suppresses `out-of-view`, so the event never fires again. ```js onInViewOnceInView() { @@ -20,17 +20,10 @@ onInViewOnceInView() { ## Options -### `intersectionObserver` - -- Type: `object` -- Default: `{ threshold: [0, 1] }` - -Options forwarded to the underlying [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver#options) instance created by the [`withMountWhenInView` decorator](https://js-toolkit.studiometa.dev/api/decorators/withMountWhenInView.html). Use it to adjust the `rootMargin`, `threshold` or `root` used to detect the viewport crossing. +`InViewOnce` declares none. The viewport margin belongs to the mount strategy, so it is written on the `data-mount` attribute as the strategy's suffix — the value becomes the [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver#options) `rootMargin`: ```html -
- ... -
+
...
``` ## See also diff --git a/packages/docs/reference/items/Indexable/examples.md b/packages/docs/reference/items/Indexable/examples.md index dca914fa8..6aef55116 100644 --- a/packages/docs/reference/items/Indexable/examples.md +++ b/packages/docs/reference/items/Indexable/examples.md @@ -25,7 +25,7 @@ title: Indexable examples ## Slider -This example builds a slider without a dedicated component. `Indexable` owns the index — `total` sets the number of slides and `boundary="loop"` wraps around — while [`Action`](../Action/index.md) triggers `goPrev()`, `goNext()` and `goTo()`. A co-located `Action` bridges the reactive rendering: because [`$emit`](https://js-toolkit.studiometa.dev/api/instance-methods/#emit) dispatches a native event, `data-on:index` catches the `index` event and forwards `event.detail[0]` to every [`DataBind`](../DataBind/index.md) and [`DataComputed`](../DataComputed/index.md), which move the track, highlight the dots and update the counter. No `Slider` component is registered. +This example builds a slider without a dedicated component. `Indexable` owns the index — `total` sets the number of slides and `boundary="loop"` wraps around — while [`Action`](../Action/index.md) triggers `goPrev()`, `goNext()` and `goTo()`. A co-located `Action` bridges the reactive rendering: because `$emit()` dispatches a native event, `data-on:index` catches the `index` event and forwards `event.detail[0]` to every [`DataBind`](../DataBind/index.md) and [`DataComputed`](../DataComputed/index.md), which move the track, highlight the dots and update the counter. No `Slider` component is registered. - - + +
``` @@ -46,7 +56,7 @@ export default class Counter extends Indexable { Once your component is created, you can use it in your app and trigger its `goNext` and `goPrev` methods to update its states: ```js {2,10,13-15,17-19} -import { Base, createApp } from '@studiometa/js-toolkit'; +import { Base, registerComponent } from '@studiometa/js-toolkit'; import Counter from './Counter.js'; class App extends Base { @@ -59,23 +69,25 @@ class App extends Base { }; onPrevBtnClick() { - this.$children.Counter.forEach((instance) => instance.goPrev()); + for (const instance of this.$query('Counter')) instance.goPrev(); } onNextBtnClick() { - this.$children.Counter.forEach((instance) => instance.goNext()); + for (const instance of this.$query('Counter')) instance.goNext(); } } -export default createApp(App); +registerComponent(App); ``` -You can now add a counter component in your HTML and define the boundary behavior: +You can now add a counter component in your HTML and define the boundary behavior. The application component needs its own `data-component`, which is what scopes its refs: ```html -0 - - +
+ 0 + + +
``` ::: tip Example diff --git a/packages/docs/reference/items/Indexable/stories/counter/app.js b/packages/docs/reference/items/Indexable/stories/counter/app.js index d15e118ea..9722cd3e9 100644 --- a/packages/docs/reference/items/Indexable/stories/counter/app.js +++ b/packages/docs/reference/items/Indexable/stories/counter/app.js @@ -1,4 +1,4 @@ -import { Base, createApp } from '@studiometa/js-toolkit'; +import { Base, registerComponent } from '@studiometa/js-toolkit'; import { Indexable } from '@studiometa/ui'; class Counter extends Indexable { @@ -25,12 +25,12 @@ class App extends Base { }; onPrevBtnClick() { - this.$children.Counter.forEach((instance) => instance.goPrev()); + for (const instance of this.$query('Counter')) instance.goPrev(); } onNextBtnClick() { - this.$children.Counter.forEach((instance) => instance.goNext()); + for (const instance of this.$query('Counter')) instance.goNext(); } } -export default createApp(App); +registerComponent(App); diff --git a/packages/docs/reference/items/Indexable/stories/counter/app.twig b/packages/docs/reference/items/Indexable/stories/counter/app.twig index 63597321f..557a32898 100644 --- a/packages/docs/reference/items/Indexable/stories/counter/app.twig +++ b/packages/docs/reference/items/Indexable/stories/counter/app.twig @@ -1,3 +1,4 @@ +
Clamp: 0
@@ -9,3 +10,4 @@ Bounce:
+
diff --git a/packages/docs/reference/items/LargeText/js-api.md b/packages/docs/reference/items/LargeText/js-api.md index ab9fb13ca..1cec99473 100644 --- a/packages/docs/reference/items/LargeText/js-api.md +++ b/packages/docs/reference/items/LargeText/js-api.md @@ -4,7 +4,7 @@ title: LargeText JS API # JS API -The `LargeText` component extends the [`Base` class](https://js-toolkit.studiometa.dev/api/configuration.html) and implements the [`withMountWhenInView` decorator](https://js-toolkit.studiometa.dev/api/decorators/withMountWhenInView.html). It inherits their respective APIs. See both linked references. +The `LargeText` component declares the `in-view:50%` [mount strategy](/guide/autoloading/#mount-strategies), so it mounts half a viewport before it scrolls in and unmounts once it is that far past. Override the margin per element with `data-mount="in-view:"`. ## Options diff --git a/packages/docs/reference/items/LazyInclude/index.md b/packages/docs/reference/items/LazyInclude/index.md deleted file mode 100644 index 9c6ff2252..000000000 --- a/packages/docs/reference/items/LazyInclude/index.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -badges: [JS] ---- - -# LazyInclude - -## Usage - -Use the `LazyInclude` component to load parts of your page lazily. - -::: code-group - -```js [app.js] twoslash -import { registerComponent } from '@studiometa/js-toolkit'; -import { LazyInclude } from '@studiometa/ui'; - -registerComponent(LazyInclude); -``` - -```html index.html -
- Loading... - -
-``` - -::: - -::: warning Orchestration -The content is fetched when the component is mounted. Use [`withMount...` decorators](https://js-toolkit.studiometa.dev/api/decorators/withMountWhenInView.html) to wrap the ` LazyInclude` class for a fine grained loading strategy. -::: diff --git a/packages/docs/reference/items/LazyInclude/stories/app.js b/packages/docs/reference/items/LazyInclude/stories/app.js deleted file mode 100644 index 13f38c10c..000000000 --- a/packages/docs/reference/items/LazyInclude/stories/app.js +++ /dev/null @@ -1,4 +0,0 @@ -import { registerComponent } from '@studiometa/js-toolkit'; -import { LazyInclude } from '@studiometa/ui'; - -registerComponent(LazyInclude); diff --git a/packages/docs/reference/items/LazyInclude/stories/app.twig b/packages/docs/reference/items/LazyInclude/stories/app.twig deleted file mode 100644 index 6696c6727..000000000 --- a/packages/docs/reference/items/LazyInclude/stories/app.twig +++ /dev/null @@ -1,5 +0,0 @@ -
- Loading... - -
- diff --git a/packages/docs/reference/items/LazyInclude/stories/error.twig b/packages/docs/reference/items/LazyInclude/stories/error.twig deleted file mode 100644 index 3bd00c45c..000000000 --- a/packages/docs/reference/items/LazyInclude/stories/error.twig +++ /dev/null @@ -1,4 +0,0 @@ -
- Loading... - -
diff --git a/packages/docs/reference/items/MapboxMap/examples.md b/packages/docs/reference/items/MapboxMap/examples.md index 69e147d05..3d64e5f09 100644 --- a/packages/docs/reference/items/MapboxMap/examples.md +++ b/packages/docs/reference/items/MapboxMap/examples.md @@ -140,7 +140,7 @@ The cluster reports a click on an unclustered point through its `map-item-click` The `MapboxMap` component re-emits the Mapbox map events with a `map-` prefix to avoid conflicts with native events. Listen to them from a parent component by defining `on` methods — for example `onMapboxMapMapClick` or `onMapboxMapMapLoad`. ```js -import { Base, createApp } from '@studiometa/js-toolkit'; +import { Base, registerComponent } from '@studiometa/js-toolkit'; import { MapboxMap } from '@studiometa/ui-mapbox'; class App extends Base { @@ -149,14 +149,14 @@ class App extends Base { components: { MapboxMap }, }; - onMapboxMapMapLoad({ args: [map] }) { + onMapboxMapMapLoad({ payload: { map } }) { console.log('Map loaded', map); } - onMapboxMapMapClick({ args: [event] }) { + onMapboxMapMapClick({ payload: { event } }) { console.log('Clicked at', event.lngLat); } } -createApp(App); +registerComponent(App); ``` diff --git a/packages/docs/reference/items/MapboxMap/index.md b/packages/docs/reference/items/MapboxMap/index.md index 4bf252451..8129bc37f 100644 --- a/packages/docs/reference/items/MapboxMap/index.md +++ b/packages/docs/reference/items/MapboxMap/index.md @@ -73,18 +73,36 @@ registerComponent(MapboxMap); ## Lazy loading -Keep `mapbox-gl` out of your main bundle by lazy-registering each component with js-toolkit's [`importWhen*` helpers](https://js-toolkit.studiometa.dev/api/helpers/importWhenVisible.html) and the per-component subpaths. `importWhenVisible` defers the dynamic import until a `MapboxMap` element scrolls into view, then hands the resolved component to `registerComponent`, code-splitting `mapbox-gl` into its own chunk loaded only when a map is actually needed. +Keep `mapbox-gl` out of your main bundle by registering the family as a [manifest](/guide/autoloading/) rather than as classes. A manifest entry is a lazy importer plus a mount strategy, so the dynamic import is deferred until an element that needs it is about to mount — `mapbox-gl` lands in its own chunk, loaded only when a map is actually on the page. -Every component is available at its own subpath (`@studiometa/ui-mapbox/`), whose default export is the component class — so the dynamic import needs no destructuring. Because each component is registered independently, lazy-register each one you use: deferring `MapboxMap` pulls in `mapbox-gl`, but a marker or a cluster is its own module and must get its own lazy registration. +The package ships its own manifest, which is the shortest way to get all of it: ```js -import { registerComponents, importWhenVisible } from '@studiometa/js-toolkit'; +import { registerManifest } from '@studiometa/js-toolkit'; +import { manifest } from '@studiometa/ui-mapbox/manifest'; -registerComponents( - importWhenVisible(() => import('@studiometa/ui-mapbox/MapboxMap'), 'MapboxMap'), - importWhenVisible(() => import('@studiometa/ui-mapbox/MapboxMarker'), 'MapboxMarker'), - importWhenVisible(() => import('@studiometa/ui-mapbox/MapboxPopup'), 'MapboxPopup'), -); +registerManifest(manifest); ``` -Reach for a different trigger when it fits better: `importWhenIdle` (load during browser idle time), `importOnInteraction` (wait for a first click/focus/touch on the element) or `importOnMediaQuery` (load only above a breakpoint, e.g. to skip the map on small screens). +Every component is also available at its own subpath (`@studiometa/ui-mapbox/`), whose default export is the component class, so a hand-written manifest can carry only the components you use. Each component is registered independently, so list every one you declare: deferring `MapboxMap` pulls in `mapbox-gl`, but a marker or a cluster is its own module and needs its own entry. + +```js +import { registerManifest } from '@studiometa/js-toolkit'; + +registerManifest({ + MapboxMap: { + mountStrategy: 'visible', + load: () => import('@studiometa/ui-mapbox/MapboxMap'), + }, + MapboxMarker: { + mountStrategy: 'visible', + load: () => import('@studiometa/ui-mapbox/MapboxMarker'), + }, + MapboxPopup: { + mountStrategy: 'visible', + load: () => import('@studiometa/ui-mapbox/MapboxPopup'), + }, +}); +``` + +Reach for a different strategy when it fits better — `idle`, `interaction`, `media:` — and override any of them per element with `data-mount`. The [Autoloading](/guide/autoloading/) guide lists all six. diff --git a/packages/docs/reference/items/MapboxMap/js-api.md b/packages/docs/reference/items/MapboxMap/js-api.md index 0bfd634a2..1698ee45a 100644 --- a/packages/docs/reference/items/MapboxMap/js-api.md +++ b/packages/docs/reference/items/MapboxMap/js-api.md @@ -7,7 +7,7 @@ outline: deep This page documents thirteen components — a single root component, `MapboxMap`, which owns the Mapbox `Map` instance, plus the twelve children below — built on the `AbstractMapboxMapChild` (and, for controls, `AbstractMapboxControl`) base classes. The [`StoreLocator`](/reference/items/StoreLocator/) orchestrator completes the family on its own page. Every child resolves the closest parent `MapboxMap` on its own and registers itself against its map once it is loaded. -[Register](/guide/usage/#registering-components) each component you use — ideally behind a lazy [`importWhen*` helper](https://js-toolkit.studiometa.dev/api/helpers/importWhenVisible.html) so the heavy `mapbox-gl` dependency stays out of your main bundle (see [Lazy loading](/reference/items/MapboxMap/#lazy-loading)). +[Register](/guide/usage/#registering-components) each component you use — ideally through a lazy [manifest](/guide/autoloading/) entry so the heavy `mapbox-gl` dependency stays out of your main bundle (see [Lazy loading](/reference/items/MapboxMap/#lazy-loading)). - **[Map](#map)** — `MapboxMap` - **[Markers & Popups](#markers-popups)** — `MapboxMarker`, `MapboxPopup` @@ -124,10 +124,12 @@ Whether the map has finished loading. #### Events -The component emits a custom `map-load` event, plus all the common Mapbox GL map events prefixed with `map-` to avoid conflicts with native events. Each handler receives the corresponding Mapbox event object (the `map-load` handler receives the `map` instance). +The component emits a custom `map-load` event, plus all the common Mapbox GL map events prefixed with `map-` to avoid conflicts with native events. Every payload is a named object: `map-load` carries `{ map }`, and every forwarded event carries the Mapbox event object as `{ event }`. ##### `map-load` +- Payload: `{ map }` + The map finished loading (custom event). ##### `map-idle` @@ -233,7 +235,7 @@ A drag ends. Listen to these events from a parent component with `on` methods: ```js -import { Base, createApp } from '@studiometa/js-toolkit'; +import { Base, registerComponent } from '@studiometa/js-toolkit'; import { MapboxMap } from '@studiometa/ui-mapbox'; class App extends Base { @@ -242,20 +244,20 @@ class App extends Base { components: { MapboxMap }, }; - onMapboxMapMapLoad({ args: [map] }) { + onMapboxMapMapLoad({ payload: { map } }) { console.log('Map is ready', map); } - onMapboxMapMapClick({ args: [event] }) { + onMapboxMapMapClick({ payload: { event } }) { console.log('Clicked at', event.lngLat); } - onMapboxMapMapZoomend({ args: [event] }) { + onMapboxMapMapZoomend({ payload: { event } }) { console.log('New zoom level', event.target.getZoom()); } } -createApp(App); +registerComponent(App); ``` ## Markers & Popups @@ -324,7 +326,7 @@ The underlying Popup instance. ## Controls -All controls extend [`AbstractMapboxMapChild`](#abstractmapboxmapchild) and expose the underlying Mapbox control through a `control` getter. They share a `position` option and are added to the map on mount, removed on destroy. +All controls extend [`AbstractMapboxMapChild`](#abstractmapboxmapchild) and expose the underlying Mapbox control through a `control` getter. They share a `position` option and are added to the map on mount, removed on unmount. ### MapboxNavigationControl @@ -470,15 +472,15 @@ Where the control is mounted, depending on `add-to-map`. ##### `map-result` -- Payload: `result` +- Payload: `{ result }` -Emitted when the geocoder resolves an address, carrying the geocoder's `result` (its selected feature). +Emitted when the geocoder resolves an address, carrying the geocoder's selected feature. ## Data ### MapboxSource -Add a [source](https://docs.mapbox.com/style-spec/reference/sources/) to the map. On destroy, every layer tied to the source is removed before the source itself. +Add a [source](https://docs.mapbox.com/style-spec/reference/sources/) to the map. On unmount, every layer tied to the source is removed before the source itself. #### Options @@ -584,9 +586,9 @@ A list of image definitions, each `{ name, url, options? }`. See [`MapboxImage`] ##### `map-ready` -- Payload: `MapboxImage[]` +- Payload: `{ images }` -Emitted once every image has been loaded and added. +Emitted once every image has been loaded and added, carrying the loaded images. ## Cluster @@ -697,7 +699,7 @@ Register a `MapboxClusterItem` and schedule a coalesced rebuild. Called automati ##### `unregister(item)` -Unregister a `MapboxClusterItem` and schedule a coalesced rebuild. Called automatically by items on destroy. +Unregister a `MapboxClusterItem` and schedule a coalesced rebuild. Called automatically by items on unmount. ##### `setData(data)` @@ -707,25 +709,25 @@ Replace the live source data directly, bypassing the item registry — for imper ##### `map-cluster-click` -- Payload: `(clusterId, event)` +- Payload: `{ clusterId, event }` A cluster was clicked. Call `event.preventDefault()` to skip the default zoom-to-cluster behavior. ##### `map-item-click` -- Payload: `(item, feature, event)` +- Payload: `{ item, feature, event }` An unclustered point was clicked. `item` is the registered `MapboxClusterItem` behind the feature, or `undefined` when none could be resolved. ##### `map-update` -- Payload: `(items)` +- Payload: `{ items }` The item set changed (a rebuild ran). Carries the live item set so an orchestrator can re-fit and re-filter. ### MapboxClusterItem -A single entry of a `MapboxCluster` — at once a rendered list item AND a map feature. It resolves the closest `MapboxCluster` ancestor, pushes itself into its registry on mount (`register`) and pulls itself out on destroy (`unregister`), so the cluster never has to query for its children. It is headless and passive: it never selects itself — a [`StoreLocator`](/reference/items/StoreLocator/) orchestrator (when one wraps the cluster) drives its state setters. +A single entry of a `MapboxCluster` — at once a rendered list item AND a map feature. It resolves the closest `MapboxCluster` ancestor, pushes itself into its registry on mount (`register`) and pulls itself out on unmount (`unregister`), so the cluster never has to query for its children. It is headless and passive: it never selects itself — a [`StoreLocator`](/reference/items/StoreLocator/) orchestrator (when one wraps the cluster) drives its state setters. #### Options @@ -799,7 +801,9 @@ Toggle the `data-active` attribute and the `aria-current="true"` state — the s ##### `map-error` -Emitted when unregistering the item throws. The error is contained and passed as the payload. +- Payload: `{ error }` + +Emitted when unregistering the item throws. The failure is contained and reported on the diagnostic channel as well. ## AbstractMapboxMapChild @@ -823,7 +827,9 @@ The Mapbox `Map` instance of the parent map. ##### `map-error` -Emitted when guarded map injection or teardown throws. The error is contained and passed as the payload. +- Payload: `{ error }` + +Emitted when guarded map injection or teardown throws. The failure is contained and reported on the diagnostic channel as well. ```js import { AbstractMapboxMapChild } from '@studiometa/ui-mapbox'; diff --git a/packages/docs/reference/items/Menu/stories/mega-menu-responsive/app.js b/packages/docs/reference/items/Menu/stories/mega-menu-responsive/app.js index a2ad4b004..b13f46688 100644 --- a/packages/docs/reference/items/Menu/stories/mega-menu-responsive/app.js +++ b/packages/docs/reference/items/Menu/stories/mega-menu-responsive/app.js @@ -1,19 +1,4 @@ -import { - registerComponent, - withResponsiveOptions, -} from '@studiometa/js-toolkit'; -import { Menu as MenuCore } from '@studiometa/ui'; +import { registerComponents } from '@studiometa/js-toolkit'; +import { Menu, MenuBtn, MenuList } from '@studiometa/ui'; -class Menu extends withResponsiveOptions(MenuCore, { - responsiveOptions: ['mode'], -}) { - static config = { - name: 'Menu', - components: { - ...MenuCore.config.components, - Menu, - }, - }; -} - -registerComponent(Menu); +registerComponents(Menu, MenuBtn, MenuList); diff --git a/packages/docs/reference/items/Menu/stories/mega-menu-responsive/app.twig b/packages/docs/reference/items/Menu/stories/mega-menu-responsive/app.twig index bf9cc839a..466e396e0 100644 --- a/packages/docs/reference/items/Menu/stories/mega-menu-responsive/app.twig +++ b/packages/docs/reference/items/Menu/stories/mega-menu-responsive/app.twig @@ -1,4 +1,4 @@ -