diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 0c16f79a..f8c18b13 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -10,14 +10,15 @@ tone_instructions: >- a public contract. reviews: - # Assertive = wide net. Humans and Claude filter the output; CodeRabbit - # is the layer that must not stay quiet. + # Keep detailed reviews while suppressing routine PR commentary. profile: assertive # Formally request changes on findings; auto-approve once all CodeRabbit # comments are resolved and checks pass. Merging still requires a human. request_changes_workflow: true - high_level_summary: true - review_status: true + high_level_summary: false + review_status: false + in_progress_fortune: false + poem: false # Review only owned sources. Build output, vendored trees, and # byte-identical conformance copies are noise; their origins (scss/, diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c4b81725..d763594e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,8 +7,8 @@ updates: - package-ecosystem: "npm" directory: "/" schedule: - interval: "weekly" - open-pull-requests-limit: 5 + interval: "monthly" + open-pull-requests-limit: 1 groups: npm-dependencies: patterns: @@ -17,8 +17,8 @@ updates: - package-ecosystem: "pip" directory: "/" schedule: - interval: "weekly" - open-pull-requests-limit: 5 + interval: "monthly" + open-pull-requests-limit: 1 groups: pip-dependencies: patterns: @@ -27,8 +27,8 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" - open-pull-requests-limit: 5 + interval: "monthly" + open-pull-requests-limit: 1 groups: github-actions: patterns: diff --git a/ASSET_LICENSE.md b/ASSET_LICENSE.md index cf8bdb7f..eafa57ed 100644 --- a/ASSET_LICENSE.md +++ b/ASSET_LICENSE.md @@ -14,6 +14,6 @@ product, site, template, library, training dataset, or marketing asset without prior written permission from WPMoo. Third-party assets remain under their own licenses. See the -[version-pinned third-party notices](https://github.com/wpmoo-org/ui/blob/v1.0.0/THIRD_PARTY_NOTICES.md). +[version-pinned third-party notices](https://github.com/wpmoo-org/ui/blob/v1.1.0/THIRD_PARTY_NOTICES.md). Those notices are repository-only and are intentionally not shipped in the npm package. diff --git a/README.md b/README.md index 4aaaa0e6..58eb211f 100644 --- a/README.md +++ b/README.md @@ -194,11 +194,9 @@ calm. ## Status And Support -The current development snapshot is `1.1.0-dev.1`. It adds a shared Table of -Contents component and is not published to npm; use the locally packed tarball -when evaluating this snapshot. - -The stable public-contract release is `1.0.0`. Component certification remains +The stable public-contract release is `1.1.0`. It adds a shared Table of +Contents component, description-only Alerts and compatible navigation fixes +while preserving the 1.0.0 package contract. Component certification remains `preview`, with its documented manual and browser/device limits. Public exports, package boundaries, browser support, and release evidence live in [Support & Evidence](https://ui.wpmoo.org/support/). Complete release notes are on [GitHub Releases](https://github.com/wpmoo-org/ui/releases). @@ -224,4 +222,4 @@ The published `LICENSE` also retains the full MIT notices for bundled Chart.js and `@kurkle/color`. Asset terms live in [ASSET_LICENSE.md](ASSET_LICENSE.md); dependency notices live in the -[version-pinned third-party notices](https://github.com/wpmoo-org/ui/blob/v1.0.0/THIRD_PARTY_NOTICES.md). +[version-pinned third-party notices](https://github.com/wpmoo-org/ui/blob/v1.1.0/THIRD_PARTY_NOTICES.md). diff --git a/SECURITY.md b/SECURITY.md index 15b657fa..87c8455a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ DataTable, Date Picker, Slider, and Sidebar. ## Supported Versions -Security review covers the current `1.0.0` stable line. Older +Security review covers the current `1.1.0` stable line. Older tags remain available for source inspection, but fixes are prepared against the current development branch. diff --git a/SUPPORT.md b/SUPPORT.md index abfd6944..c0ce5e25 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -4,7 +4,8 @@ Moo UI Core is an open-source Bootstrap component system. This document defines the public support boundary for the Core package. It does not cover separate platform integrations or commercial products. -Moo UI `1.0.0` freezes the documented public package contract. Package stability +Moo UI `1.1.0` extends the frozen `1.0.0` public package contract with the +shared Table of Contents component and compatible additions. Package stability and component certification are independent: the certification manifest remains `preview`. Production certification is introduced incrementally; a component's presence in the catalog does not by itself mean that it has completed @@ -193,10 +194,12 @@ conformance pass for that host. The runner needs Python 3 and Playwright only; no Moo UI build tooling is involved. The archive is reproducible byte-for-byte from the repository source at any -time. The current Moo UI 1.1.0-dev.1 source carries kit contract version 1.0; -its development archive hashes to SHA-256 +time. The current Moo UI 1.1.0 source carries kit contract version 1.0; +its archive hashes to SHA-256 +`e4ab6c256ce4e2e1a3a82a42fdfbbf90f5c5871c14d4853fce928546a8cafbac`. +The 1.1.0-dev.1 source archive hashes to SHA-256 `ec92be81e205dd9602213a4a4fbabb91075b87ef227f4ac0ca5e189b92a8a3ba`. -This development archive is not a published release artifact. The Moo UI 1.0.0 +The Moo UI 1.0.0 archive also carries contract version 1.0 and hashes to SHA-256 `3bda0e10fe23e6f157f5e7bd3c09af0efd05c5827fe65615ef2017b7da6929f1`. Published artifacts are listed on the GitHub Releases page: diff --git a/build.py b/build.py index 00b70274..1b58fd45 100644 --- a/build.py +++ b/build.py @@ -67,6 +67,7 @@ "combobox.js", "context-menu.js", "slider.js", + "toc.js", "table-of-contents.js", ) BUNDLED_JS_MODULES = ( @@ -860,15 +861,22 @@ def catalog_toc_items(value: object) -> list[dict[str, str]]: return items +PUBLIC_PAGE_ROUTES = {"components/table-of-contents.html": "components/toc.html"} +PUBLIC_PRETTY_ROUTES = { + source.removesuffix(".html") + "/": target.removesuffix(".html") + "/" + for source, target in PUBLIC_PAGE_ROUTES.items() +} + + def pretty_url(path: object) -> str: value = str(path).strip() if value in {"", "index.html", "./"}: return "./" if value.endswith("/index.html"): - return value[: -len("index.html")] - if value.endswith(".html"): - return value[: -len(".html")] + "/" - return value + value = value[: -len("index.html")] + elif value.endswith(".html"): + value = value[: -len(".html")] + "/" + return PUBLIC_PRETTY_ROUTES.get(value, value) def site_href(path: object, root_path: str = "") -> str: @@ -886,6 +894,7 @@ def canonical_url(path: object) -> str: def pretty_output_path(path: Path) -> Path: + path = Path(PUBLIC_PAGE_ROUTES.get(path.as_posix(), path.as_posix())) if path.name == "index.html" or path.suffix != ".html": return path return path.with_suffix("") / "index.html" @@ -2661,6 +2670,12 @@ def render_pages( asset_version=version, ) output_file.write_text(rendered, encoding="utf-8") + if logical_relative.as_posix() in PUBLIC_PAGE_ROUTES: + # Preserve native fragments on old bookmarks; its canonical link + # and every catalog navigation link use the preferred route. + legacy_file = SITE_DIST / logical_relative.with_suffix("") / "index.html" + legacy_file.parent.mkdir(parents=True, exist_ok=True) + legacy_file.write_text(rendered, encoding="utf-8") @contextlib.contextmanager diff --git a/certification.json b/certification.json index 0f98fb5d..6bbe6ee3 100644 --- a/certification.json +++ b/certification.json @@ -1,7 +1,7 @@ { "schemaVersion": "0.1", "status": "preview", - "coreVersion": "1.1.0-dev.1", + "coreVersion": "1.1.0", "bootstrap": { "canonicalVersion": "5.3.3", "targetRange": ">=5.3.0 <5.4", @@ -28,6 +28,7 @@ "./context-menu.js", "./datatable.js", "./slider.js", + "./toc.js", "./table-of-contents.js", "./sheet.js", "./chart.js", diff --git a/conformance/fixtures/assets/moo.css b/conformance/fixtures/assets/moo.css index 25867e85..776bc350 100644 --- a/conformance/fixtures/assets/moo.css +++ b/conformance/fixtures/assets/moo.css @@ -1,5 +1,5 @@ /*! - * Moo UI v1.1.0-dev.1 (https://wpmoo.org/) + * Moo UI v1.1.0 (https://wpmoo.org/) * Copyright 2026 WPMoo Authors * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE) * diff --git a/conformance/fixtures/assets/sidebar.js b/conformance/fixtures/assets/sidebar.js index 99e2315c..c3d03f12 100644 --- a/conformance/fixtures/assets/sidebar.js +++ b/conformance/fixtures/assets/sidebar.js @@ -1,5 +1,5 @@ /*! - * Moo UI sidebar.js v1.1.0-dev.1 (https://wpmoo.org/) + * Moo UI sidebar.js v1.1.0 (https://wpmoo.org/) * Copyright 2026 WPMoo Authors * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE) */ diff --git a/conformance/fixtures/assets/state.js b/conformance/fixtures/assets/state.js index c4fde76d..5d711f99 100644 --- a/conformance/fixtures/assets/state.js +++ b/conformance/fixtures/assets/state.js @@ -1,5 +1,5 @@ /*! - * Moo UI state.js v1.1.0-dev.1 (https://wpmoo.org/) + * Moo UI state.js v1.1.0 (https://wpmoo.org/) * Copyright 2026 WPMoo Authors * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE) */ diff --git a/package-lock.json b/package-lock.json index 676b7c7c..54bf598d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@wpmoo/ui", - "version": "1.1.0-dev.1", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@wpmoo/ui", - "version": "1.1.0-dev.1", + "version": "1.1.0", "license": "MIT", "devDependencies": { "axe-core": "4.13.0", @@ -1548,9 +1548,9 @@ } }, "node_modules/wrangler": { - "version": "4.146.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.146.0.tgz", - "integrity": "sha512-c27eHUH0Isr8HTmgZ6cLtLr/0cxtiBoLren6ywBeTW8ZIzj/dINVNBcqsbZc+iETnI5jrzO7E7Z8mLgLZ/l9iw==", + "version": "4.147.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.147.0.tgz", + "integrity": "sha512-pQYRoiq8PTAxphaG69z8+GC1DkSGd19EDZehQ8zxjo/Ko3mRB6Qs1mTrd8ZuKAarLklIjTqr1lUdCK9r4q2hUg==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 2d25dd46..95952e7b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@wpmoo/ui", - "version": "1.1.0-dev.1", + "version": "1.1.0", "description": "Bootstrap markup. shadcn feel. A frontend-framework-free component system for server-rendered apps.", "license": "MIT", "author": "WPMoo (https://wpmoo.org)", @@ -38,6 +38,7 @@ "./context-menu.js": "./dist/js/context-menu.js", "./datatable.js": "./dist/js/datatable.js", "./slider.js": "./dist/js/slider.js", + "./toc.js": "./dist/js/toc.js", "./table-of-contents.js": "./dist/js/table-of-contents.js", "./sheet.js": "./dist/js/sheet.js", "./chart.js": "./dist/js/chart.js", @@ -66,6 +67,7 @@ "dist/js/context-menu.js", "dist/js/datatable.js", "dist/js/slider.js", + "dist/js/toc.js", "dist/js/table-of-contents.js", "dist/js/sheet.js", "dist/js/chart.js", diff --git a/requirements-dev.txt b/requirements-dev.txt index 001c6c53..271f7933 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ Jinja2==3.1.6 libsass==0.23.0 tinycss2==1.5.1 -coverage==7.16.1 +coverage==7.16.2 playwright==1.63.0 jsonschema==4.26.0 diff --git a/scripts/record-boundary-baseline.py b/scripts/record-boundary-baseline.py index 4bfa9a6b..8f0b4eba 100644 --- a/scripts/record-boundary-baseline.py +++ b/scripts/record-boundary-baseline.py @@ -25,6 +25,7 @@ "dist/js/sidebar.js", "dist/js/slider.js", "dist/js/sheet.js", + "dist/js/toc.js", "dist/js/table-of-contents.js", "dist/js/moo-ui.js", "dist/js/moo-ui.min.js", diff --git a/scripts/verify_package_contents.py b/scripts/verify_package_contents.py index 10933d14..b6c22db7 100644 --- a/scripts/verify_package_contents.py +++ b/scripts/verify_package_contents.py @@ -33,6 +33,7 @@ "dist/js/context-menu.js", "dist/js/datatable.js", "dist/js/slider.js", + "dist/js/toc.js", "dist/js/table-of-contents.js", "dist/js/sheet.js", "dist/js/moo-ui.js", diff --git a/scss/mixins/_banner.scss b/scss/mixins/_banner.scss index c65feae1..e9c44749 100644 --- a/scss/mixins/_banner.scss +++ b/scss/mixins/_banner.scss @@ -1,5 +1,5 @@ /*! - * Moo UI v1.1.0-dev.1 (https://wpmoo.org/) + * Moo UI v1.1.0 (https://wpmoo.org/) * Copyright 2026 WPMoo Authors * Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE) * diff --git a/site/public/llms.txt b/site/public/llms.txt index c8271784..6ba4d2a7 100644 --- a/site/public/llms.txt +++ b/site/public/llms.txt @@ -2,7 +2,7 @@ > Bootstrap markup. shadcn feel. -Moo UI 1.0.0 is the stable public-contract release of a Bootstrap 5.3-native component system with a restrained, +Moo UI 1.1.0 is the stable public-contract release of a Bootstrap 5.3-native component system with a restrained, shadcn-inspired visual language for server-rendered and Bootstrap-native apps. It is CSS-first, not JavaScript-free: Bootstrap owns native plugin behavior, while Moo UI adds optional, explicit ESM only for documented gaps. @@ -14,8 +14,7 @@ implementation. ## Version And Support -- stable package: `@wpmoo/ui@1.0.0` -- Unpublished development snapshot: `@wpmoo/ui@1.1.0-dev.1` (local tarball only) +- stable package: `@wpmoo/ui@1.1.0` - Bootstrap peer range: `>=5.3.0 <5.4` - Tested Bootstrap versions in the manifest: `5.3.0`, `5.3.3`, `5.3.8` - License: MIT for source code @@ -38,6 +37,7 @@ not independent or accredited certification. - `@wpmoo/ui/context-menu.js`: optional Context Menu ESM lifecycle - `@wpmoo/ui/datatable.js`: optional DataTable ESM lifecycle - `@wpmoo/ui/sidebar.js`: optional Sidebar ESM lifecycle +- `@wpmoo/ui/toc.js`: optional TableOfContents lifecycle and current-section tracking (`table-of-contents.js` remains a compatibility entrypoint) - `@wpmoo/ui/chart.js`: optional Chart ESM lifecycle (Chart.js 4.5.1 bundled) - `@wpmoo/ui/chart.min.js`: minified Chart bundle (artifact variant of chart.js) - `@wpmoo/ui/datepicker.js`: optional Datepicker ESM lifecycle (self-contained, no third-party runtime) @@ -54,17 +54,13 @@ not independent or accredited certification. - `@wpmoo/ui/certification.json`: versioned support and evidence manifest - `@wpmoo/ui/package.json`: package metadata -The development snapshot additionally exports `@wpmoo/ui/table-of-contents.js` -for explicit TableOfContents lifecycle and includes it in the aggregate ESM. -This additive module is not available from the stable 1.0.0 CDN links below. - The npm package does not publish catalog templates, preview artwork, catalog JavaScript, or site assets. It ships Sass source files for the documented Sass entrypoints; undocumented internal partial paths are not separate public APIs. ## Installation Facts -- Stable version-pinned CDN full stylesheet: `https://unpkg.com/@wpmoo/ui@1.0.0/dist/assets/css/moo-ui.css` +- Stable version-pinned CDN full stylesheet: `https://unpkg.com/@wpmoo/ui@1.1.0/dist/assets/css/moo-ui.css` Full replacement: @@ -115,7 +111,7 @@ explicitly. Importing `combobox.js`, `context-menu.js`, `datatable.js`, runtime - Sheet server-open marker: explicit `initSheets(root)` after Bootstrap Offcanvas is available on the document's window -- Table of Contents (development snapshot): native fragment links and a +- Table of Contents: native fragment links and a Bootstrap dropdown, with optional Moo UI ESM for current-section tracking and explicit `refresh()` / `dispose()` lifecycle @@ -173,7 +169,7 @@ explicitly. Importing `combobox.js`, `context-menu.js`, `datatable.js`, - [Spinner](https://ui.wpmoo.org/components/spinner/) - [Switch](https://ui.wpmoo.org/components/switch/) - [Table](https://ui.wpmoo.org/components/table/) -- [Table of Contents](https://ui.wpmoo.org/components/table-of-contents/) +- [Table of Contents](https://ui.wpmoo.org/components/toc/) - [Tabs](https://ui.wpmoo.org/components/tabs/) - [Textarea](https://ui.wpmoo.org/components/textarea/) - [Toast](https://ui.wpmoo.org/components/toast/) @@ -192,8 +188,8 @@ explicitly. Importing `combobox.js`, `context-menu.js`, `datatable.js`, - Preserve Bootstrap behavior before adding custom behavior. - Use Moo UI ESM only for Combobox, Context Menu, DataTable, Sidebar, Chart, - Datepicker, Slider, Sheet initialization, Table of Contents in the development - snapshot, or the documented aggregate `moo-ui.js` entrypoint. + Datepicker, Slider, Sheet initialization, Table of Contents, or the documented + aggregate `moo-ui.js` entrypoint. - Do not invent public Sass APIs, undocumented JavaScript entrypoints, or certification claims. - Treat shadcn/ui as visual direction only; examples and implementation should diff --git a/site/src/js/catalog/index.js b/site/src/js/catalog/index.js index d7b670c0..5d487027 100644 --- a/site/src/js/catalog/index.js +++ b/site/src/js/catalog/index.js @@ -161,7 +161,7 @@ export function initCatalog(root = document) { disposers, root, FEATURE_SELECTORS.tableOfContents, - () => import("../../../../src/js/components/table-of-contents.js"), + () => import("../../../../src/js/components/toc.js"), ({ default: TableOfContents }) => { const instances = Array.from(root.querySelectorAll("[data-toc]")) .map((element) => TableOfContents.getOrCreateInstance(element)); diff --git a/site/src/js/catalog/toc.js b/site/src/js/catalog/toc.js index 7987e728..debaa2e0 100644 --- a/site/src/js/catalog/toc.js +++ b/site/src/js/catalog/toc.js @@ -32,6 +32,7 @@ export function initToc(root = document) { const targets = [ ...root.querySelectorAll(".moo-doc-toc [data-toc] .nav-link"), ...root.querySelectorAll('#catalog-toc-compact a[href^="#"]'), + ...root.querySelectorAll('#toc-demo-page [data-toc] a[href^="#"]'), ] .map((link) => { const hash = link.getAttribute("href"); diff --git a/site/src/pages/blocks/previews/toc.html.jinja b/site/src/pages/blocks/previews/toc.html.jinja new file mode 100644 index 00000000..5521654d --- /dev/null +++ b/site/src/pages/blocks/previews/toc.html.jinja @@ -0,0 +1,116 @@ +{% extends "layouts/base.html.jinja" %} +{% from "layouts/app.html.jinja" import app %} +{% from "layouts/page.html.jinja" import page %} +{% from "components/toc.html.jinja" import table_of_contents %} + +{% block title %}Table of Contents Preview — Moo UI{% endblock %} +{% block font_preload %}{% endblock %} + +{% set sections = [ + {"target_id": "introduction", "label": "Introduction"}, + {"target_id": "content-and-configuration", "label": "Content and configuration"}, + {"target_id": "languages", "label": "Languages"}, + {"target_id": "navigation", "label": "Navigation"} +] %} + +{% macro toc_demo() -%} + {% call(slot) app(navigation="none") %} + {% if slot == "page" %} + {% call(region) page(width="xl", header_width="fluid", id="toc-demo-page") %} + {% if region == "header" %} +
+ Moo Starter + Placement guide +
+
+ {{ table_of_contents("demo-toc-compact", sections, presentation="compact", content_id="demo-article", scroll_root_id="main-content") }} +
+ {% elif region == "main" %} +
+
+

Placement guide

+

Content placements and an independent aside.

+ +
+

Introduction

+

A document can have an automatic table of contents beside its article. This guide brings together four parts of a small multilingual website: the article itself, the project configuration, the translated content and the navigation around it.

+

The article remains the main reading area. Its heading, description, paragraphs and lists come from the project's content file. Supporting information gives additional context without interrupting the reading order. Readers can begin with the article and find the supporting information afterwards on a narrow screen.

+

The table of contents serves a different purpose from the site menu. Its links point to headings on this page, while the site menu leads to other pages and archives. Following a heading leaves a URL fragment that can be copied and shared.

+

Read the guide from beginning to end or use the section links to move between topics. As each heading reaches the top of the reading area, the active indicator moves to the corresponding item.

+
+ +
+

Content and configuration

+

The project owns this content. The package supplies the layout and reusable blocks. Authors can update the text without editing a template, and the site can change its layout without moving the article into a theme package.

+

Content and configuration answer different questions. The article describes the subject of the page. Configuration determines which navigation is available and where supporting blocks appear. Reusable information can be maintained separately and placed beside several articles.

+

A simple editing routine keeps the responsibilities clear:

+
    +
  • Write the article and its description in the content file.
  • +
  • Choose headings that describe each section clearly.
  • +
  • Assign relevant categories and tags.
  • +
  • Keep shared notices or contact information in reusable blocks.
  • +
  • Use project preferences for layout choices that apply across the site.
  • +
+

The aside contains the table of contents and a separate page-information block. Each has its own section, and the configured gap provides consistent space between them. Adding another supporting block preserves that rhythm without empty paragraphs or manual spacing.

+
+ +
+

Languages

+

Each published translation uses its own headings and reusable content. Versions of the guide discuss the same subjects, while the article and section labels are written for their readers. A language switcher connects the corresponding published routes.

+

Translated headings may have different fragment identifiers. Generate section links from the current document's headings so that the table of contents follows the version being read. The navigation label and the overview label can be translated independently.

+

A longer section label should wrap naturally without pushing the article out of its column. On narrow screens, the compact menu uses the available row width and keeps the current section visible beside its trigger.

+

The same structure also supports right-to-left content. Logical spacing utilities follow the document direction, and native links continue to point to the same authored section identifiers.

+
+ +
+ +

Site navigation and article navigation remain independent. The site menu takes readers to another document. The table of contents moves within the current article and keeps their place visible while they read.

+

On a wide screen, the section list stays beside the article. Below the large breakpoint, a compact menu appears below the page header. The header remains available while the article scrolls, so readers can choose another section at any point.

+

Supporting content follows the article when the columns stack. It remains accessible without competing with the compact table of contents. The reading area uses the space left between the header and footer.

+

Try returning to Introduction, then switch between the desktop, tablet and mobile previews. Each mode displays the same document and section data. The active section stays synchronized when the visible presentation changes.

+ +

Choosing section headings

+

A heading should help a reader predict what comes next. Use the words that describe the task or subject, and keep the same wording in the section list. A concise label can still be specific: Content and configuration tells readers more than a general label such as Details. The heading remains part of the article even when the navigation is hidden.

+

Choose a stable identifier when a section is first published. If its wording changes later, keeping the identifier preserves links shared in messages, bookmarks and other pages. The visible title and the URL fragment have separate responsibilities: one explains the content, and the other provides a reliable destination. Section labels can change without breaking a reader's saved link.

+ +

Following the reading position

+

The active indicator follows the section at the top of the reading area. It changes when a reader scrolls naturally and when they follow a section link. The list and compact control describe the same position, so resizing the preview does not require selecting the section again. Move between Languages and Introduction to watch the indicator travel along the outline.

+

A fixed header sits outside the article's scrolling region. This leaves the navigation available without covering the target heading. The scrolling region owns the remaining page height, and the supplementary column remains alongside it on larger screens. The footer has its own place below the reading area and stays visible while the article moves between sections.

+ +

Moving between screen sizes

+

Desktop readers have enough space for a persistent outline beside the text. The tablet preview uses the compact control in the header, giving the article the full reading width. Mobile uses the same control and allows longer labels to wrap in the menu. These presentations share the document and the current section; they offer different ways to reach the same content.

+

Supporting information stays in the document when the layout changes. On narrow screens, it follows the article rather than occupying a second reading column. Scroll to the end of the mobile preview to find the page-information section. Returning to a heading through the compact menu brings the article back into view while the header and footer keep their positions.

+ +

Keyboard navigation and shared links

+

Native links remain usable with a keyboard. Tab reaches the section controls, and Enter follows the selected link. The compact control also uses the normal Dropdown keyboard behavior: open the menu, move between its options, choose a section, or close it with Escape. Focus and the active reading position serve different purposes, so each retains its own visible state.

+

The URL fragment identifies the selected destination. In the standalone document, a saved fragment can reopen the corresponding section, and browser history can return to an earlier destination. Within this preview, the document has its own history and scroll area. Exploring its section links leaves the surrounding documentation route, reading position and navigation controls available.

+ +

Keeping the document readable

+

The table of contents is an aid for longer articles. The main text should still be understandable when read from beginning to end. Give each section enough context, keep paragraphs focused on one idea, and use lists where readers benefit from a sequence. The outline then becomes a useful map of content that already has a clear reading order.

+

Readers who prefer reduced motion can move directly to a destination without animated scrolling. The same section links and current-section information remain available. Test both the reading flow and the section controls when publishing a guide, and check the narrow layout with realistic text so that long labels and supporting information receive the space they need.

+
+
+ +
+ {% elif region == "footer" %} +
Moo Starter · Placement guide
+ {% endif %} + {% endcall %} + {% endif %} + {% endcall %} +{%- endmacro %} + +{% block body %} + {{ toc_demo() }} +{% endblock %} diff --git a/site/src/pages/changelog.html.jinja b/site/src/pages/changelog.html.jinja index 29117391..2731d4ca 100644 --- a/site/src/pages/changelog.html.jinja +++ b/site/src/pages/changelog.html.jinja @@ -17,16 +17,47 @@ ) }}
+
+
+ {{ badge("v1.1.0", variant="dark") }} + Stable public contract +
+

Moo UI 1.1.0

+

+ This compatible minor release adds a shared table of contents and + description-only Alerts while preserving the 1.0.0 package contract. + Component certification remains preview. +

+
+
+ Added + Shared responsive Table of Contents with explicit lifecycle, smooth navigation and an animated current-section marker. +
+
+ Updated + Alerts accept a description without a title, retaining escaping and rejecting empty messages. +
+
+ Fixed + Compact TOC keyboard navigation continues from its selected section. Datepicker focus restoration preserves the scroll position. +
+
+ Development + Coverage is updated to 7.16.2 and Wrangler to 4.147.0. +
+
+
+
{{ badge("v1.1.0-dev.1", variant="dark") }} - Development preview + Superseded development preview

Shared table of contents

- This development preview adds a shared table of contents with native + This development preview introduced a shared table of contents with native fragment links, an optional current-section tracker and a compact - Bootstrap dropdown. It is not a published stable release. + Bootstrap dropdown. Stable 1.1.0 supersedes this preview.

@@ -695,6 +726,7 @@
{{ render_doc_toc([ + {"id": "release-1-1-0", "label": "v1.1.0"}, {"id": "release-1-1-0-dev-1", "label": "v1.1.0-dev.1"}, {"id": "release-1-0-0", "label": "v1.0.0"}, {"id": "release-1-0-0-rc-11", "label": "v1.0.0-rc.11"}, diff --git a/site/src/pages/components/table-of-contents.html.jinja b/site/src/pages/components/table-of-contents.html.jinja index d0016ecb..03585ca2 100644 --- a/site/src/pages/components/table-of-contents.html.jinja +++ b/site/src/pages/components/table-of-contents.html.jinja @@ -1,23 +1,9 @@ {% extends "layouts/catalog.html.jinja" %} -{% from "components/toc.html.jinja" import table_of_contents %} +{% from "includes/block-example.html.jinja" import render_block_example %} +{% from "includes/code-snippet.html.jinja" import render_code_snippet %} {% from "includes/documentation-reference.html.jinja" import render_reference with context %} -{% from "includes/example.html.jinja" import render_component_intro, render_component_example, render_rtl_example with context %} {% from "includes/page-header.html.jinja" import render_page_header with context %} - -{% macro section_example(prefix, presentation="list", label="On this page", overview_label="Overview", labels=["Introduction", "Content and configuration", "Languages"]) -%} - {% set items = [ - {"target_id": prefix ~ '-intro', "label": labels[0]}, - {"target_id": prefix ~ '-content', "label": labels[1]}, - {"target_id": prefix ~ '-languages', "label": labels[2]} - ] %} -
- {{ table_of_contents(prefix ~ '-toc', items, presentation=presentation, label=label, overview_label=overview_label, content_id=prefix ~ '-content-scope', scroll_root_id='main-content', extra_class="mb-3") }} - {% for item in items %} -

{{ item.label }}

-

The host supplies this native heading and its stable fragment ID.

- {% endfor %} -
-{%- endmacro %} +{% from "pages/blocks/previews/toc.html.jinja" import toc_demo with context %} {% block title %}Table of Contents — Moo UI{% endblock %} @@ -25,36 +11,48 @@ {{ render_page_header("Table of Contents", "Navigate sections of a document with a list or a compact control that shows the current section.") }}
- {% set intro %}{{ section_example('toc-intro') }}{% endset %} - {% set usage %} -

Supply ordered section IDs and plain-text labels. Moo UI renders native fragment links; the host owns headings, content and layout. Empty sections render nothing. A single section remains available.

-

Use the list beside a wide article or the compact presentation before narrow content. The compact menu spans its navigation row, wraps long labels and uses Bootstrap's Dropdown keyboard and dismissal behavior.

-

Initialize the optional TableOfContents module to synchronize the current section and active links. Importing the module alone does not initialize a document. For a JavaScript-disabled host, use the list.

+ {% set demo %}{{ toc_demo() }}{% endset %} + {{ render_block_example( + "toc-document", "Placement guide", "Explore one document at desktop, tablet and mobile widths.", demo, + preview_class="moo-example__preview--shell", + frame_src=site_href("blocks/previews/toc.html", root_path), + frame_width=1280, frame_height=900, + frame_viewports=[ + {"label": "Mobile", "icon": "smartphone", "width": 390, "height": 900}, + {"label": "Tablet", "icon": "tablet", "width": 768, "height": 900}, + {"label": "Desktop", "icon": "monitor", "width": 1280, "height": 900, "active": true} + ], + show_header=false + ) }} + +

Usage

+

Supply ordered section IDs and plain-text labels. Moo UI renders native fragment links; the host owns headings, content and layout. Empty sections render nothing. A single section remains available.

+

Use the viewport controls to resize the same live document. Choose a section or scroll inside the preview to follow its active indicator. The preview owns its URL fragments and scroll area independently from this documentation page.

+ +

Responsive placement

+

The desktop list stays beside the article. Below lg, the compact presentation moves into the header above the scrolling content. Both use the same section data with unique navigation IDs and complementary Bootstrap visibility utilities.

+

The compact menu spans its navigation row, wraps long labels and uses Bootstrap's Dropdown keyboard and dismissal behavior. Supporting blocks render once and follow the article when the columns stack. Translate the labels for each document; logical spacing follows its text direction.

+ +

Runtime and placement

+

Import TableOfContents from @wpmoo/ui/toc.js and initialize each supplied [data-toc] root. Use refresh() after replacing targets and dispose() when removing a view. The previous @wpmoo/ui/table-of-contents.js entrypoint remains a compatibility re-export of the same constructor.

+ {% set init_code %} + import TableOfContents from "@wpmoo/ui/toc.js"; + + const reader = document.getElementById("main-content"); + reader.classList.add("scroll-smooth"); + + document.querySelectorAll("[data-toc]").forEach(element => { + TableOfContents.getOrCreateInstance(element); + }); {% endset %} - {{ render_component_intro(intro, usage) }} - - {% set list_example %}{{ section_example('toc-list-example') }}{% endset %} - {{ render_component_example("table-of-contents", "Table of Contents", "list", "Section list", "Keep native section links visible in a content aside. The host chooses its column, breakpoint and position.", list_example) }} - - {% set compact_example %}{{ section_example('toc-compact-example', presentation='compact') }}{% endset %} - {{ render_component_example("table-of-contents", "Table of Contents", "compact", "Compact navigation", "Open the full-width menu to select a section. Overview is shown before the first tracked heading.", compact_example) }} + {{ render_code_snippet("toc-initialization", init_code, language="js") }} +

Importing the module alone does not initialize a document. For a JavaScript-disabled host, use the list.

+

content_id limits resolution to an explicit content region inside the same Moo owner. scroll_root_id identifies the scrolling ancestor of that content. Omitted values use the nearest Moo owner and document scrolling. A missing scope leaves the native links available and skips tracking.

+

The host owns fixed header geometry. With the App layout, place the compact navigation in the header above the scrolling main region, and supply that region's ID. For other layouts, a finite nonnegative activationOffset option sets the tracking line. The component does not change layout styles or override native scrolling.

+

For animated native fragment navigation, apply the Core scroll-smooth utility to the actual scroll container inside its Moo owner. The utility uses scroll-behavior and switches to immediate scrolling when the visitor prefers reduced motion. Links retain their native URL fragments and browser history.

+

Two presentations can share section data with unique IDs and complementary Bootstrap visibility utilities. Ordinary aside content renders once and can stack below the article on narrow screens.

- {% set arabic %} - {{ section_example('toc-arabic', presentation='compact', label='في هذه الصفحة', overview_label='نظرة عامة', labels=['مقدمة', 'المحتوى والإعدادات', 'اللغات']) }} - {% endset %} - {% set hebrew %} - {{ section_example('toc-hebrew', presentation='compact', label='בעמוד זה', overview_label='סקירה כללית', labels=['מבוא', 'תוכן והגדרות', 'שפות']) }} - {% endset %} - {% set english %}{{ section_example('toc-english', presentation='compact') }}{% endset %} - {{ render_rtl_example("table-of-contents", "Keep section labels readable and fragment navigation native in each direction.", arabic, hebrew, english) }}
-

Runtime and placement

-

Import TableOfContents from @wpmoo/ui/table-of-contents.js and call TableOfContents.getOrCreateInstance(element) for each supplied [data-toc] root. Use refresh() after replacing targets and dispose() when removing a view.

-

content_id limits resolution to an explicit content region inside the same Moo owner. scroll_root_id identifies the scrolling ancestor of that content. Omitted values use the nearest Moo owner and document scrolling. A missing scope leaves the native links available and skips tracking.

-

The host owns fixed header geometry. With the App layout, place the compact navigation in the header above the scrolling main region, and supply that region's ID. For other layouts, a finite nonnegative activationOffset option sets the tracking line. The component does not change layout styles or override native scrolling.

-

For animated native fragment navigation, apply the Core scroll-smooth utility to the actual scroll container inside its Moo owner. The utility uses scroll-behavior and switches to immediate scrolling when the visitor prefers reduced motion. Links retain their native URL fragments and browser history.

-

Two presentations can share section data with unique IDs and complementary Bootstrap visibility utilities. Ordinary aside content renders once and can stack below the article on narrow screens.

- {{ render_reference("For the compact control's native behavior, see the", "https://getbootstrap.com/docs/5.3/components/dropdowns/", "Bootstrap Dropdown documentation") }} {% endblock %} diff --git a/src/certification/api-freeze-1.1.0.json b/src/certification/api-freeze-1.1.0.json new file mode 100644 index 00000000..f28fdcaf --- /dev/null +++ b/src/certification/api-freeze-1.1.0.json @@ -0,0 +1,465 @@ +{ + "freezeVersion": "1.1.0", + "frozenAt": "2026-10-09", + "description": "API inventory for the Moo UI 1.1.0 stable public contract. It preserves the 1.0.0 surface and adds the shared TableOfContents module and aggregate export. The preferred standalone entrypoint is toc.js; table-of-contents.js remains a compatibility re-export of the same constructor. Component certification remains preview. The immutable 1.0.0 and release-candidate inventories remain removal guards; incompatible changes to a documented public contract require a major release.", + "packageExports": [ + "./moo-ui.css", + "./moo-ui.min.css", + "./moo.css", + "./moo.min.css", + "./moo-ui.js", + "./moo-ui.min.js", + "./combobox.js", + "./sidebar.js", + "./context-menu.js", + "./datatable.js", + "./slider.js", + "./toc.js", + "./table-of-contents.js", + "./sheet.js", + "./chart.js", + "./chart.min.js", + "./datepicker.js", + "./datepicker.min.js", + "./state.js", + "./release-manifest.json", + "./scss/config", + "./scss/moo-ui", + "./scss/moo-core", + "./scss/components", + "./scss/settings", + "./certification.json", + "./package.json" + ], + "packageFiles": [ + "dist/assets/css/moo-ui.css", + "dist/assets/css/moo-ui.min.css", + "dist/assets/css/moo.css", + "dist/assets/css/moo.min.css", + "dist/js/moo-ui.js", + "dist/js/moo-ui.min.js", + "dist/js/combobox.js", + "dist/js/sidebar.js", + "dist/js/context-menu.js", + "dist/js/datatable.js", + "dist/js/slider.js", + "dist/js/toc.js", + "dist/js/table-of-contents.js", + "dist/js/sheet.js", + "dist/js/chart.js", + "dist/js/chart.min.js", + "dist/js/datepicker.js", + "dist/js/datepicker.min.js", + "dist/js/state.js", + "dist/release-manifest.json", + "scss/*.scss", + "scss/**/*.scss", + "certification.json", + "README.md", + "LICENSE", + "ASSET_LICENSE.md" + ], + "sassFacadeAllowList": [ + "$primary", + "$secondary", + "$success", + "$info", + "$warning", + "$danger", + "$light", + "$dark", + "$body-color", + "$body-bg", + "$border-color", + "$border-radius", + "$border-radius-sm", + "$border-radius-lg", + "$font-size-base" + ], + "sassEntrypoints": [ + { + "export": "./scss/config", + "source": "scss/_config.scss", + "scope": "public variable allow-list" + }, + { + "export": "./scss/moo-ui", + "source": "scss/moo-ui.scss", + "scope": "full Bootstrap-compatible source entrypoint" + }, + { + "export": "./scss/moo-core", + "source": "scss/moo-core.scss", + "scope": "scoped .moo-ui source entrypoint" + }, + { + "export": "./scss/components", + "source": "scss/_components.scss", + "scope": "component layer source entrypoint" + }, + { + "export": "./scss/settings", + "source": "scss/_settings.scss", + "scope": "settings aggregate loaded before Bootstrap variables" + } + ], + "esmModules": [ + { + "module": "moo-ui.js", + "export": "./moo-ui.js", + "lifecycle": [ + "side-effect-free import", + "default namespace export", + "named lifecycle class exports", + "explicit asynchronous Chart loader" + ], + "source": "src/js/moo-ui.js", + "bundled": true, + "buildTool": "esbuild", + "note": "Side-effect-free convenience aggregate; loadChart() returns a promise for the adjacent chart.js default export. Both aggregate variants use the same Chart module; no Chart.js runtime is included in the aggregate.", + "defaultExport": "MooUI", + "namedExports": [ + "Combobox", + "ContextMenu", + "DataTable", + "Datepicker", + "MooCalendar", + "MooDateRangePicker", + "Sidebar", + "Slider", + "initSheets", + "loadChart", + "TableOfContents" + ], + "lazyModules": { + "loadChart": "./chart.js" + } + }, + { + "module": "combobox.js", + "export": "./combobox.js", + "lifecycle": [ + "constructor", + "getInstance", + "getOrCreateInstance", + "dispose" + ], + "source": "src/js/components/combobox.js" + }, + { + "module": "sidebar.js", + "export": "./sidebar.js", + "lifecycle": [ + "constructor", + "getInstance", + "getOrCreateInstance", + "dispose" + ], + "source": "src/js/components/sidebar.js" + }, + { + "module": "context-menu.js", + "export": "./context-menu.js", + "lifecycle": [ + "constructor", + "getInstance", + "getOrCreateInstance", + "dispose" + ], + "source": "src/js/components/context-menu.js" + }, + { + "module": "datatable.js", + "export": "./datatable.js", + "lifecycle": [ + "constructor", + "getInstance", + "getOrCreateInstance", + "dispose" + ], + "source": "src/js/components/datatable.js" + }, + { + "module": "sheet.js", + "export": "./sheet.js", + "source": "src/js/components/sheet.js", + "lifecycle": [ + "side-effect-free import", + "explicit initSheets(root)", + "Bootstrap Offcanvas instance lifecycle" + ], + "namedExports": [ + "initSheets" + ], + "note": "Explicitly opens data-sheet-open-on-load sheets through native Bootstrap; importing the module never scans the document." + }, + { + "module": "chart.js", + "export": "./chart.js", + "lifecycle": [ + "constructor", + "getInstance", + "getOrCreateInstance", + "dispose" + ], + "source": "src/js/components/chart.js", + "bundled": true, + "runtime": "chart.js@4.5.1", + "getters": [ + "chart", + "element" + ], + "markup": { + "rootClass": "chart", + "typeAttribute": "data-chart", + "dataAttribute": "data-chart-data", + "optionsAttribute": "data-chart-options", + "supportedTypes": [ + "area", + "line", + "bar", + "pie", + "doughnut", + "polarArea", + "radar", + "scatter", + "bubble" + ], + "canvas": ":scope > canvas", + "optionsContract": "JSON-object-only Chart.js options pass-through; functions/callbacks are not serializable and must use programmatic config" + } + }, + { + "module": "datepicker.js", + "export": "./datepicker.js", + "lifecycle": [ + "constructor", + "getInstance", + "getOrCreateInstance", + "dispose" + ], + "source": "src/js/components/datepicker.js", + "bundled": false, + "buildTool": "esbuild", + "note": "esbuild-produced canonical + minified artifact pair; the module is self-contained Moo UI ESM with no third-party runtime bundled", + "namedExports": [ + "MooCalendar", + "MooDateRangePicker" + ], + "publicMethods": { + "MooDatepicker": [ + "show", + "hide", + "toggle", + "isOpen", + "getDate", + "setDate", + "clear", + "dispose" + ], + "MooDateRangePicker": [ + "show", + "hide", + "toggle", + "isOpen", + "getDates", + "setDates", + "clear", + "dispose" + ], + "MooCalendar": [ + "getDate", + "getDates", + "setDate", + "setDates", + "clear", + "focusActiveDay", + "dispose" + ] + }, + "markup": { + "rootClass": "moo-datepicker", + "rootAttribute": "data-datepicker", + "triggerAttribute": "data-datepicker-trigger", + "popoverAttribute": "data-datepicker-popover", + "calendarAttribute": "data-calendar", + "labelAttribute": "data-datepicker-label" + } + }, + { + "module": "slider.js", + "export": "./slider.js", + "lifecycle": [ + "constructor", + "getInstance", + "getOrCreateInstance", + "dispose" + ], + "source": "src/js/components/slider.js", + "bundled": false, + "nativeElement": "input[type=\"range\"]", + "publicMethods": [ + "dispose" + ], + "markup": { + "rootClass": "slider", + "rootAttribute": "data-slider", + "trackAttribute": "data-slider-track", + "rangeAttribute": "data-slider-range", + "inputAttribute": "data-slider-input", + "outputAttribute": "data-slider-output" + } + }, + { + "module": "toc.js", + "export": "./toc.js", + "source": "src/js/components/toc.js", + "defaultExport": "TableOfContents", + "lifecycle": [ + "constructor", + "getInstance", + "getOrCreateInstance", + "refresh", + "dispose" + ] + }, + { + "module": "table-of-contents.js", + "export": "./table-of-contents.js", + "source": "src/js/components/table-of-contents.js", + "defaultExport": "TableOfContents", + "lifecycle": [ + "constructor", + "getInstance", + "getOrCreateInstance", + "refresh", + "dispose" + ] + } + ], + "artifactVariants": { + "moo-ui.min.js": { + "canonical": "moo-ui.js", + "relationship": "Minified artifact variant of moo-ui.js; shares aggregate exports and side-effect-free import behavior" + }, + "chart.min.js": { + "canonical": "chart.js", + "relationship": "Minified artifact variant of chart.js; shares source, exports, lifecycle, and runtime ownership" + }, + "datepicker.min.js": { + "canonical": "datepicker.js", + "relationship": "Minified artifact variant of datepicker.js; shares source, exports, lifecycle, and runtime ownership" + } + }, + "cssEntrypoints": [ + { + "export": "./moo-ui.css", + "purpose": "Full expanded Bootstrap-compatible CSS build", + "scope": "unscoped" + }, + { + "export": "./moo-ui.min.css", + "purpose": "Full minified CSS build", + "scope": "unscoped" + }, + { + "export": "./moo.css", + "purpose": "Scoped expanded component layer for .moo-ui", + "scope": "scoped" + }, + { + "export": "./moo.min.css", + "purpose": "Scoped minified component layer", + "scope": "scoped" + } + ], + "metadataEntrypoints": [ + { + "export": "./certification.json", + "purpose": "Versioned support/evidence manifest", + "scope": "metadata", + "source": "certification.json", + "key": "publicEntrypoints.metadata[0]" + }, + { + "export": "./package.json", + "purpose": "Package metadata", + "scope": "metadata", + "source": "package.json", + "key": "exports[\"./package.json\"]" + }, + { + "export": "./release-manifest.json", + "purpose": "SHA-256 manifest for the adapter-facing CSS and state bootstrap artifacts.", + "scope": "metadata", + "source": "dist/release-manifest.json", + "key": "publicEntrypoints.metadata[2]" + } + ], + "bootstrapSupport": { + "canonicalVersion": "5.3.3", + "targetRange": ">=5.3.0 <5.4", + "testedVersions": [ + "5.3.0", + "5.3.3", + "5.3.8" + ], + "source": "certification.json" + }, + "browserPolicy": { + "policy": "Certification is in progress; see SUPPORT.md for the intended browser policy.", + "exactEvidenceInAttestation": false, + "source": "certification.json" + }, + "certificationManifest": { + "schemaVersion": "0.1", + "requiredFields": [ + "schemaVersion", + "status", + "coreVersion", + "bootstrap", + "browserPolicy", + "certifiedComponents", + "publicEntrypoints", + "supportPolicy" + ], + "source": "src/certification/manifest.schema.json" + }, + "documentationSurfaces": [ + { + "file": "README.md", + "tracks": [ + "package exports", + "runtime ownership", + "installation paths" + ] + }, + { + "file": "SUPPORT.md", + "tracks": [ + "SemVer policy", + "breaking changes", + "Bootstrap compatibility", + "browser policy", + "maturity definitions" + ] + }, + { + "file": "site/public/llms.txt", + "tracks": [ + "package exports", + "installation facts", + "runtime ownership" + ] + } + ], + "browserEntrypoints": [ + { + "export": "./state.js", + "purpose": "Classic placement-dependent bootstrap restoring Theme, Direction, and keyed Sidebar state before owner content paints.", + "scope": "browser", + "source": "src/js/state.js", + "key": "publicEntrypoints.browser[0]" + } + ] +} diff --git a/src/js/components/table-of-contents.js b/src/js/components/table-of-contents.js index 3782ba8b..a0e257a8 100644 --- a/src/js/components/table-of-contents.js +++ b/src/js/components/table-of-contents.js @@ -1,202 +1 @@ -const instances = new WeakMap(); -const ownerSelector = '.moo-ui[data-bs-theme="light"], .moo-ui[data-bs-theme="dark"]'; - -function activationOffset(options, previous = 0) { - if (!options || typeof options !== "object" || Array.isArray(options)) { - throw new TypeError("TableOfContents options must be an object."); - } - const value = options.activationOffset === undefined ? previous : options.activationOffset; - if (typeof value !== "number" || !Number.isFinite(value) || value < 0) { - throw new TypeError("TableOfContents activationOffset must be a finite nonnegative number."); - } - return value; -} - -function fragmentId(href) { - if (!href?.startsWith("#") || href.length === 1) return null; - try { - return decodeURIComponent(href.slice(1)); - } catch { - return null; - } -} - -export default class TableOfContents { - static getInstance(element) { - return element?.nodeType === 1 ? instances.get(element) || null : null; - } - - static getOrCreateInstance(element, options = {}) { - return TableOfContents.getInstance(element) || new TableOfContents(element, options); - } - - constructor(element, options = {}) { - if (element?.nodeType !== 1 || !element.matches("[data-toc]")) { - throw new TypeError("TableOfContents requires a [data-toc] root element."); - } - const existing = instances.get(element); - if (existing) return existing; - this._element = element; - this._document = element.ownerDocument; - this._window = this._document.defaultView; - this._offset = activationOffset(options); - this._listeners = []; - this._originalLinks = new Map(); - this._originalStatus = new Map(); - this._originalMarker = { - attribute: element.getAttribute("data-toc-marker"), - properties: ["--moo-toc-marker-offset", "--moo-toc-marker-height"].map((name) => ({ - name, - value: element.style.getPropertyValue(name), - priority: element.style.getPropertyPriority(name), - })), - }; - this._links = []; - this._targets = []; - this._frame = 0; - this._disposed = false; - instances.set(element, this); - this.refresh(); - } - - refresh(options = {}) { - if (this._disposed) return this; - const offset = activationOffset(options, this._offset); - this._clearListeners(); - this._offset = offset; - const owner = this._element.closest(ownerSelector); - const contentId = this._element.getAttribute("data-toc-content"); - const scope = contentId ? this._document.getElementById(contentId) : owner; - const rootId = this._element.getAttribute("data-toc-scroll-root"); - this._scrollElement = rootId ? this._document.getElementById(rootId) : null; - this._status = Array.from(this._element.querySelectorAll("[data-toc-current]")) - .find((node) => node.closest("[data-toc]") === this._element) || null; - if (this._status && !this._originalStatus.has(this._status)) { - this._originalStatus.set(this._status, this._status.textContent); - } - this._overview = this._status ? this._originalStatus.get(this._status) : ""; - this._links = Array.from(this._element.querySelectorAll('a[href^="#"]')) - .filter((link) => link.closest("[data-toc]") === this._element) - .map((link) => ({ link, id: fragmentId(link.getAttribute("href")) })); - this._links.forEach(({ link }) => { - if (!this._originalLinks.has(link)) { - this._originalLinks.set(link, { - active: link.classList.contains("active"), - current: link.getAttribute("aria-current"), - }); - } - }); - this._targets = []; - this._activate(null); - if (!owner || !scope || scope.closest(ownerSelector) !== owner || - (rootId && (!this._scrollElement || !this._scrollElement.contains(scope)))) { - return this; - } - const seen = new Set(); - this._links.forEach(({ id, link }) => { - const target = id ? this._document.getElementById(id) : null; - if (!target || seen.has(id) || !scope.contains(target) || target.closest(ownerSelector) !== owner) return; - seen.add(id); - this._targets.push({ id, target, label: link.textContent.trim() }); - }); - if (!this._targets.length) return this; - const queue = () => this._requestUpdate(); - this._listen(this._scrollElement || this._window, "scroll", queue, { passive: true }); - this._listen(this._window, "resize", queue); - this._listen(this._window, "hashchange", queue); - this._listen(this._element, "click", queue); - this._update(); - return this; - } - - dispose() { - if (this._disposed) return; - this._disposed = true; - this._clearListeners(); - this._originalLinks.forEach(({ active, current }, link) => { - link.classList.toggle("active", active); - if (current === null) link.removeAttribute("aria-current"); - else link.setAttribute("aria-current", current); - }); - this._originalStatus.forEach((text, node) => { node.textContent = text; }); - const { attribute, properties } = this._originalMarker; - if (attribute === null) this._element.removeAttribute("data-toc-marker"); - else this._element.setAttribute("data-toc-marker", attribute); - properties.forEach(({ name, value, priority }) => { - if (value) this._element.style.setProperty(name, value, priority); - else this._element.style.removeProperty(name); - }); - this._originalLinks.clear(); - this._originalStatus.clear(); - this._targets = []; - this._links = []; - if (instances.get(this._element) === this) instances.delete(this._element); - } - - _listen(target, type, handler, options) { - target.addEventListener(type, handler, options); - this._listeners.push({ target, type, handler, options }); - } - - _clearListeners() { - this._listeners.forEach(({ target, type, handler, options }) => target.removeEventListener(type, handler, options)); - this._listeners = []; - if (this._frame) this._window.cancelAnimationFrame(this._frame); - this._frame = 0; - } - - _requestUpdate() { - if (this._disposed || this._frame) return; - this._frame = this._window.requestAnimationFrame(() => { - this._frame = 0; - this._update(); - }); - } - - _update() { - if (this._disposed) return; - const targets = this._targets.filter(({ target }) => target.isConnected && target.getClientRects().length > 0); - const scroller = this._scrollElement || this._document.scrollingElement; - const line = (this._scrollElement ? this._scrollElement.getBoundingClientRect().top + this._scrollElement.clientTop : 0) + this._offset; - let active = null; - targets.forEach((entry) => { - if (entry.target.getBoundingClientRect().top <= line + 1) active = entry; - }); - const canScroll = scroller && scroller.scrollHeight > scroller.clientHeight + 1; - if (!canScroll) { - const hashId = fragmentId(this._window.location.hash); - active = targets.find(({ id }) => id === hashId) || active; - } - if (targets.length && canScroll && - scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 1) { - active = targets[targets.length - 1]; - } - this._activate(active); - } - - _activate(active) { - let activeLink = null; - this._links.forEach(({ link, id }) => { - const current = active !== null && id === active.id; - link.classList.toggle("active", current); - if (current) { - link.setAttribute("aria-current", "location"); - activeLink ||= link; - } else link.removeAttribute("aria-current"); - }); - if (this._status) this._status.textContent = active ? active.label : this._overview; - this._updateMarker(activeLink); - } - - _updateMarker(link) { - const list = this._element.querySelector(":scope > .nav"); - const text = link?.querySelector("span"); - if (!list?.contains(link) || !text?.offsetHeight || !this._element.getClientRects().length) { - this._element.removeAttribute("data-toc-marker"); - return; - } - this._element.style.setProperty("--moo-toc-marker-offset", `${link.offsetTop + text.offsetTop}px`); - this._element.style.setProperty("--moo-toc-marker-height", `${text.offsetHeight}px`); - this._element.setAttribute("data-toc-marker", ""); - } -} +export { default } from "./toc.js"; diff --git a/src/js/components/toc.js b/src/js/components/toc.js new file mode 100644 index 00000000..3782ba8b --- /dev/null +++ b/src/js/components/toc.js @@ -0,0 +1,202 @@ +const instances = new WeakMap(); +const ownerSelector = '.moo-ui[data-bs-theme="light"], .moo-ui[data-bs-theme="dark"]'; + +function activationOffset(options, previous = 0) { + if (!options || typeof options !== "object" || Array.isArray(options)) { + throw new TypeError("TableOfContents options must be an object."); + } + const value = options.activationOffset === undefined ? previous : options.activationOffset; + if (typeof value !== "number" || !Number.isFinite(value) || value < 0) { + throw new TypeError("TableOfContents activationOffset must be a finite nonnegative number."); + } + return value; +} + +function fragmentId(href) { + if (!href?.startsWith("#") || href.length === 1) return null; + try { + return decodeURIComponent(href.slice(1)); + } catch { + return null; + } +} + +export default class TableOfContents { + static getInstance(element) { + return element?.nodeType === 1 ? instances.get(element) || null : null; + } + + static getOrCreateInstance(element, options = {}) { + return TableOfContents.getInstance(element) || new TableOfContents(element, options); + } + + constructor(element, options = {}) { + if (element?.nodeType !== 1 || !element.matches("[data-toc]")) { + throw new TypeError("TableOfContents requires a [data-toc] root element."); + } + const existing = instances.get(element); + if (existing) return existing; + this._element = element; + this._document = element.ownerDocument; + this._window = this._document.defaultView; + this._offset = activationOffset(options); + this._listeners = []; + this._originalLinks = new Map(); + this._originalStatus = new Map(); + this._originalMarker = { + attribute: element.getAttribute("data-toc-marker"), + properties: ["--moo-toc-marker-offset", "--moo-toc-marker-height"].map((name) => ({ + name, + value: element.style.getPropertyValue(name), + priority: element.style.getPropertyPriority(name), + })), + }; + this._links = []; + this._targets = []; + this._frame = 0; + this._disposed = false; + instances.set(element, this); + this.refresh(); + } + + refresh(options = {}) { + if (this._disposed) return this; + const offset = activationOffset(options, this._offset); + this._clearListeners(); + this._offset = offset; + const owner = this._element.closest(ownerSelector); + const contentId = this._element.getAttribute("data-toc-content"); + const scope = contentId ? this._document.getElementById(contentId) : owner; + const rootId = this._element.getAttribute("data-toc-scroll-root"); + this._scrollElement = rootId ? this._document.getElementById(rootId) : null; + this._status = Array.from(this._element.querySelectorAll("[data-toc-current]")) + .find((node) => node.closest("[data-toc]") === this._element) || null; + if (this._status && !this._originalStatus.has(this._status)) { + this._originalStatus.set(this._status, this._status.textContent); + } + this._overview = this._status ? this._originalStatus.get(this._status) : ""; + this._links = Array.from(this._element.querySelectorAll('a[href^="#"]')) + .filter((link) => link.closest("[data-toc]") === this._element) + .map((link) => ({ link, id: fragmentId(link.getAttribute("href")) })); + this._links.forEach(({ link }) => { + if (!this._originalLinks.has(link)) { + this._originalLinks.set(link, { + active: link.classList.contains("active"), + current: link.getAttribute("aria-current"), + }); + } + }); + this._targets = []; + this._activate(null); + if (!owner || !scope || scope.closest(ownerSelector) !== owner || + (rootId && (!this._scrollElement || !this._scrollElement.contains(scope)))) { + return this; + } + const seen = new Set(); + this._links.forEach(({ id, link }) => { + const target = id ? this._document.getElementById(id) : null; + if (!target || seen.has(id) || !scope.contains(target) || target.closest(ownerSelector) !== owner) return; + seen.add(id); + this._targets.push({ id, target, label: link.textContent.trim() }); + }); + if (!this._targets.length) return this; + const queue = () => this._requestUpdate(); + this._listen(this._scrollElement || this._window, "scroll", queue, { passive: true }); + this._listen(this._window, "resize", queue); + this._listen(this._window, "hashchange", queue); + this._listen(this._element, "click", queue); + this._update(); + return this; + } + + dispose() { + if (this._disposed) return; + this._disposed = true; + this._clearListeners(); + this._originalLinks.forEach(({ active, current }, link) => { + link.classList.toggle("active", active); + if (current === null) link.removeAttribute("aria-current"); + else link.setAttribute("aria-current", current); + }); + this._originalStatus.forEach((text, node) => { node.textContent = text; }); + const { attribute, properties } = this._originalMarker; + if (attribute === null) this._element.removeAttribute("data-toc-marker"); + else this._element.setAttribute("data-toc-marker", attribute); + properties.forEach(({ name, value, priority }) => { + if (value) this._element.style.setProperty(name, value, priority); + else this._element.style.removeProperty(name); + }); + this._originalLinks.clear(); + this._originalStatus.clear(); + this._targets = []; + this._links = []; + if (instances.get(this._element) === this) instances.delete(this._element); + } + + _listen(target, type, handler, options) { + target.addEventListener(type, handler, options); + this._listeners.push({ target, type, handler, options }); + } + + _clearListeners() { + this._listeners.forEach(({ target, type, handler, options }) => target.removeEventListener(type, handler, options)); + this._listeners = []; + if (this._frame) this._window.cancelAnimationFrame(this._frame); + this._frame = 0; + } + + _requestUpdate() { + if (this._disposed || this._frame) return; + this._frame = this._window.requestAnimationFrame(() => { + this._frame = 0; + this._update(); + }); + } + + _update() { + if (this._disposed) return; + const targets = this._targets.filter(({ target }) => target.isConnected && target.getClientRects().length > 0); + const scroller = this._scrollElement || this._document.scrollingElement; + const line = (this._scrollElement ? this._scrollElement.getBoundingClientRect().top + this._scrollElement.clientTop : 0) + this._offset; + let active = null; + targets.forEach((entry) => { + if (entry.target.getBoundingClientRect().top <= line + 1) active = entry; + }); + const canScroll = scroller && scroller.scrollHeight > scroller.clientHeight + 1; + if (!canScroll) { + const hashId = fragmentId(this._window.location.hash); + active = targets.find(({ id }) => id === hashId) || active; + } + if (targets.length && canScroll && + scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 1) { + active = targets[targets.length - 1]; + } + this._activate(active); + } + + _activate(active) { + let activeLink = null; + this._links.forEach(({ link, id }) => { + const current = active !== null && id === active.id; + link.classList.toggle("active", current); + if (current) { + link.setAttribute("aria-current", "location"); + activeLink ||= link; + } else link.removeAttribute("aria-current"); + }); + if (this._status) this._status.textContent = active ? active.label : this._overview; + this._updateMarker(activeLink); + } + + _updateMarker(link) { + const list = this._element.querySelector(":scope > .nav"); + const text = link?.querySelector("span"); + if (!list?.contains(link) || !text?.offsetHeight || !this._element.getClientRects().length) { + this._element.removeAttribute("data-toc-marker"); + return; + } + this._element.style.setProperty("--moo-toc-marker-offset", `${link.offsetTop + text.offsetTop}px`); + this._element.style.setProperty("--moo-toc-marker-height", `${text.offsetHeight}px`); + this._element.setAttribute("data-toc-marker", ""); + } +} diff --git a/src/js/moo-ui.js b/src/js/moo-ui.js index 40475b21..767a92b4 100644 --- a/src/js/moo-ui.js +++ b/src/js/moo-ui.js @@ -7,7 +7,7 @@ import Datepicker, { } from "./components/datepicker.js"; import Sidebar from "./components/sidebar.js"; import Slider from "./components/slider.js"; -import TableOfContents from "./components/table-of-contents.js"; +import TableOfContents from "./components/toc.js"; import { initSheets } from "./components/sheet.js"; async function loadChart() { diff --git a/tests/fixtures/boundary-baseline.json b/tests/fixtures/boundary-baseline.json index 55fed6de..0e481b25 100644 --- a/tests/fixtures/boundary-baseline.json +++ b/tests/fixtures/boundary-baseline.json @@ -1,24 +1,25 @@ { "coreOutputs": { - "dist/assets/css/moo-ui.css": "d6947a3a27a4315591cf9f6b92e1fd1d958a5fceb1523b709487d54fcbefc815", - "dist/assets/css/moo-ui.min.css": "e96d6d78ccb119f83aea7bd8e5d16b40bddf4251209f67abebb408f78f0bbcba", - "dist/assets/css/moo.css": "38bb85c89e2f1f2f043119a9290cb58dd74a1572090b5fc41930c7c57a90c894", - "dist/assets/css/moo.min.css": "2546a980bc41ef0c941b8c9cd8e65f6349ec37ff72d1506ba0a6ef6eec81f944", - "dist/js/chart.js": "0c3b2039d4b06993937da243c320cf8fa9fe48e2132935f76073879b0d21ee18", - "dist/js/chart.min.js": "3395911a9140f50cc5a8026682fea6151245492f421eb8660ec4938c8bbd6c2c", - "dist/js/combobox.js": "7e749aa39740349f12f95c2811b194a007f088c6c9c578f980c118958e80c07e", - "dist/js/context-menu.js": "27168c4b1ac42064338ad2746daaa602f22de717259aec5bfcf9d55d8e5d353c", - "dist/js/datatable.js": "730dfff81863801ede8f54e3810f6fd2219d533b4365ac9e1fb5fec3be9d886d", - "dist/js/datepicker.js": "570402e7d77598bfeccb5a929c1f4be6615196f287badc79ec13dd8b80300c5b", - "dist/js/datepicker.min.js": "c6c9057abbe41d774f23374f6c2e9f9f7e346f211ee7b61a8dc9129d056ff90d", - "dist/js/moo-ui.js": "9c498912d40b8b8a18bd515c5a53cb19f5546f5ab706ec6e8f4447b3f175e869", - "dist/js/moo-ui.min.js": "1dd4f3bfcc2effddb39b2fce0e00dbc00456aa3dc8a54595c8ce1f3822d064c6", - "dist/js/sheet.js": "35c31e3252b9510de4747a5a64e77dbec47f4cbb342337be21b75b6f46ff5b6a", - "dist/js/sidebar.js": "8fbb61af28a04e572c61f7df76d4c72f8048a92661dd9f129d41bcf3795c1bce", - "dist/js/slider.js": "cd61370895ce3d166aee99cea9772a59f1be4018c7114564e124c82fdf1caa26", - "dist/js/state.js": "3560c86c8623a3d523ec763456d984e009d8a7fa5834de168b153ba9d242f1c2", - "dist/js/table-of-contents.js": "b2322ad90148b91b75788e05f492b09065ba61257f934c74b916c1fdc5ce69e1", - "dist/release-manifest.json": "95ac88143922ba2a4b3f6e42406f0b50d0bf878187c14052d212ff8bb002cede" + "dist/assets/css/moo-ui.css": "39f8b612e0307052f250aa1df471f995f2008980e025beee96b8878771ef30d0", + "dist/assets/css/moo-ui.min.css": "9ba4af6aaa9394d530a5a43db4b775c2fbb2fce5a6ae9656e69e27a6358bbbb6", + "dist/assets/css/moo.css": "c3845a6391648eb2623bb03d6fe7ad37f03c6f481d4e5c9c7e0bb535e5047ae4", + "dist/assets/css/moo.min.css": "749a6e4d844bcdda6c449d724c20ed2661633158b83ef8eebf4716c43cd0d522", + "dist/js/chart.js": "73cb9136035a23ace9b5e8d832a61eae91edaa5524a53c569020a58937b7d0e3", + "dist/js/chart.min.js": "4bca338f3e379feb0fb239ae01469b6f514ad0490d921089a70e64e94035f7c5", + "dist/js/combobox.js": "421eea7f0947a64b11566bae5b2ce5ee2d0f5095b6fc8a1f24c56bf9ce11c6f6", + "dist/js/context-menu.js": "a76f810e50e8ab7b49ab7388e54c9ff54afe3560b29841c66dbb0191a4deb8d1", + "dist/js/datatable.js": "67d8a2f214644bccc1f5259410ff343be16d2b9560645f13e0de1915832200d8", + "dist/js/datepicker.js": "ed6c05db926acc46ef0925b90b3d6d9913f55a5b77aba7a5ed2a9939b5172e1c", + "dist/js/datepicker.min.js": "d2c74a960b79dae4c99c949aedef1613a086d94f06cf6c0011686df9f9caa845", + "dist/js/moo-ui.js": "4cd644ce29a39e0f0a0e35b0cd63993dc42df22cb8a5d05d4d2d9c26f95ed341", + "dist/js/moo-ui.min.js": "75944001ed86eb1641524e3915497d54f2c5110629ee49eed9352c589de9562d", + "dist/js/sheet.js": "5344b30c652bed676432c6b8b350b04061eeb7f0247c3d61bfc0a71c69402b63", + "dist/js/sidebar.js": "afb677b5289215ccfca0238f52a15b5a8869c57fdab427098ce7a26b014c5bbf", + "dist/js/slider.js": "209001958ceff89ee780fea0622de68c51861927866b85a121f1d757ef3d6fad", + "dist/js/state.js": "3c88bb64406688eb17abadd8b1f52bf096a1760a867d6f3ee9f0a317711f47bd", + "dist/js/table-of-contents.js": "89223fd9afd3fd1e6c5765a0ebf003b66833de5dc95b802cd5facc910d773831", + "dist/js/toc.js": "ce9ea72a162e3fb69784235a58878e532ef0841de901f000885a8bed689baddd", + "dist/release-manifest.json": "05d687ac31e8b207029a0aa4f446d93470d8e300f7034b9131b263a327ff8965" }, "distFiles": [ "dist/assets/css/moo-ui.css", @@ -39,6 +40,7 @@ "dist/js/slider.js", "dist/js/state.js", "dist/js/table-of-contents.js", + "dist/js/toc.js", "dist/release-manifest.json" ], "npmPackFiles": [ @@ -64,6 +66,7 @@ "dist/js/slider.js", "dist/js/state.js", "dist/js/table-of-contents.js", + "dist/js/toc.js", "dist/release-manifest.json", "package.json", "scss/_components.scss", @@ -171,7 +174,8 @@ "./sidebar.js": "./dist/js/sidebar.js", "./slider.js": "./dist/js/slider.js", "./state.js": "./dist/js/state.js", - "./table-of-contents.js": "./dist/js/table-of-contents.js" + "./table-of-contents.js": "./dist/js/table-of-contents.js", + "./toc.js": "./dist/js/toc.js" }, "files": [ "ASSET_LICENSE.md", @@ -196,6 +200,7 @@ "dist/js/slider.js", "dist/js/state.js", "dist/js/table-of-contents.js", + "dist/js/toc.js", "dist/release-manifest.json", "scss/**/*.scss", "scss/*.scss" @@ -207,7 +212,7 @@ "sideEffects": [ "dist/assets/css/*.css" ], - "version": "1.1.0-dev.1" + "version": "1.1.0" }, "siteDistFiles": [ "site-dist/acceptance/rc2/index.html", @@ -357,6 +362,7 @@ "site-dist/assets/js/components/sidebar.js", "site-dist/assets/js/components/slider.js", "site-dist/assets/js/components/table-of-contents.js", + "site-dist/assets/js/components/toc.js", "site-dist/assets/js/state.js", "site-dist/assets/js/theme-owner.js", "site-dist/blocks/index.html", @@ -366,6 +372,7 @@ "site-dist/blocks/previews/datatable-tasks/index.html", "site-dist/blocks/previews/sidebar-floating/index.html", "site-dist/blocks/previews/sidebar-inset/index.html", + "site-dist/blocks/previews/toc/index.html", "site-dist/blocks/sidebar-floating/index.html", "site-dist/blocks/sidebar-inset/index.html", "site-dist/changelog/index.html", @@ -414,6 +421,7 @@ "site-dist/components/tabs/index.html", "site-dist/components/textarea/index.html", "site-dist/components/toast/index.html", + "site-dist/components/toc/index.html", "site-dist/components/toggle-group/index.html", "site-dist/components/tooltip/index.html", "site-dist/components/typography/index.html", @@ -436,6 +444,7 @@ "site-dist/dist/js/slider.js", "site-dist/dist/js/state.js", "site-dist/dist/js/table-of-contents.js", + "site-dist/dist/js/toc.js", "site-dist/dist/release-manifest.json", "site-dist/examples/auth/forgot-password/index.html", "site-dist/examples/auth/sign-in/index.html", @@ -469,6 +478,7 @@ "site-dist/js/sidebar.js", "site-dist/js/slider.js", "site-dist/js/table-of-contents.js", + "site-dist/js/toc.js", "site-dist/layout/index.html", "site-dist/layouts/previews/app-none/index.html", "site-dist/layouts/previews/app-sidebar/index.html", @@ -544,21 +554,21 @@ "site-dist/vendor/bootstrap/dist/js/bootstrap.bundle.min.js.map" ], "siteOutputs": { - "site-dist/acceptance/rc2/index.html": "c9cdf1541e1186716644a1c83a5fd237560f84eb7fe8e61c7bc66986e9f8ee17", - "site-dist/acceptance/rc3/index.html": "d1cd3d122f23b1854fcad8c47a1f7a2d1bfa6cce369dba8f4c5ee7488f7dff79", - "site-dist/acceptance/rc4/index.html": "e69714edd14a8f475f38899533307624042d937d5db5668a525897549434f7bd", - "site-dist/acceptance/rc5/index.html": "f0b7deb47cff68f095ab44cba77a208839a60662d10e140aaa9de40e5f08b31c", - "site-dist/acceptance/rc6/index.html": "6d74e862cfecf2dec758abf22adf214faa77f45133a83785a74097b6a4a0b1bb", - "site-dist/acceptance/rc7/index.html": "8d5c6211be464284807c03d809703fb2a05a2941f5749eb6abda17a122249466", + "site-dist/acceptance/rc2/index.html": "3644e1c7e32228c60ef10425b0a423b318205e230a3cc0f5b6ffa4c4cd110c21", + "site-dist/acceptance/rc3/index.html": "07a4a66ce914f55b0f0755d670888f1e9c6b64c79f1103ba0be3e6c091548f17", + "site-dist/acceptance/rc4/index.html": "716813f6839c22db44b4b80dff57f7921be81b2e9044bd3e46492aa151668103", + "site-dist/acceptance/rc5/index.html": "c6e905a96c6c9457177411c85e16a9264e911f55d845ef226fca4230116d3591", + "site-dist/acceptance/rc6/index.html": "749a7c76ee87a16f4d0601c0e79347d8937b60de45cccaf6117f90d4274903eb", + "site-dist/acceptance/rc7/index.html": "978f69f1d664999d0ca48181857df1b8ee15de8d0f6a493e4f265fcbcb56d021", "site-dist/apple-touch-icon.png": "0369a32660a4679bbc8a2da846d38041ecffcea1266e07c7c688936b7f41713e", "site-dist/assets/css/catalog-state.css": "4563872a77fb4ed697b5fb08213b407a0db897da9e7de5163b6917f3c04494bb", "site-dist/assets/css/catalog.css": "4bfba3caaf6c675da27501d3275b72a4e9fbbc1659fc2060c72876ac0c852e6e", "site-dist/assets/css/catalog.min.css": "e7f4cf6b3424714131dd7372a8e8605670e5de6caafdaae47cb8ee6d7fdbfa98", "site-dist/assets/css/codepen-demo.css": "b6145a590303b6f118616a082471129155324476da22fbbe8f5dcc04844465c6", - "site-dist/assets/css/moo-ui.css": "d6947a3a27a4315591cf9f6b92e1fd1d958a5fceb1523b709487d54fcbefc815", - "site-dist/assets/css/moo-ui.min.css": "e96d6d78ccb119f83aea7bd8e5d16b40bddf4251209f67abebb408f78f0bbcba", - "site-dist/assets/css/moo.css": "38bb85c89e2f1f2f043119a9290cb58dd74a1572090b5fc41930c7c57a90c894", - "site-dist/assets/css/moo.min.css": "2546a980bc41ef0c941b8c9cd8e65f6349ec37ff72d1506ba0a6ef6eec81f944", + "site-dist/assets/css/moo-ui.css": "39f8b612e0307052f250aa1df471f995f2008980e025beee96b8878771ef30d0", + "site-dist/assets/css/moo-ui.min.css": "9ba4af6aaa9394d530a5a43db4b775c2fbb2fce5a6ae9656e69e27a6358bbbb6", + "site-dist/assets/css/moo.css": "c3845a6391648eb2623bb03d6fe7ad37f03c6f481d4e5c9c7e0bb535e5047ae4", + "site-dist/assets/css/moo.min.css": "749a6e4d844bcdda6c449d724c20ed2661633158b83ef8eebf4716c43cd0d522", "site-dist/assets/fonts/geist/Geist-Variable.woff2": "a369fcf5628ea2aa4e1b9e2ec6a5b3624e365bda588e1f0f2f12b564f728fbb8", "site-dist/assets/images/README.md": "31903daae7facf88dc4a5114081b42ae56be4b711ebecc9741571cd83b27641d", "site-dist/assets/images/avatars/bull.webp": "0eb5c3aed8fb08cf10cd94d7beaa1791333de2b6f399b88c780ffb771369b0a1", @@ -656,7 +666,7 @@ "site-dist/assets/js/bootstrap.bundle.min.js": "0833b2e9c3a26c258476c46266e6877fc75218625162e0460be9a3a098a61c6c", "site-dist/assets/js/bootstrap.bundle.min.js.map": "5e3e0763164143baaa1ca0706b6100ba0452f911d6ce9713b48e3dbe07b35125", "site-dist/assets/js/catalog-state.js": "6dc59bc3606d431ee62b38f679b5048b56e5568f72f1ac428f32ce4714878041", - "site-dist/assets/js/catalog-theme-state.js": "8d80abf4bb224dda0eeb25808b0f56c8fb069b807c161572477ee8103d07753e", + "site-dist/assets/js/catalog-theme-state.js": "b923c7f41051c087f52aaff9442e8deff30a9900d5052f6bc8ada595f64a2dac", "site-dist/assets/js/catalog/acceptance.js": "a4794396548080d0d91b6e038dfbbe4e9e1f16af8a471c57a3a343b7851eca2f", "site-dist/assets/js/catalog/block-frame.js": "71972d32ae738a99507ca17f931d4fbb74688bd3934aca201c7cc3a60e5a7a72", "site-dist/assets/js/catalog/bootstrap-preview.js": "b5e7ada504705f7cd31812e819042228504b1dcff5e9048e5efd4b78fffed58d", @@ -665,155 +675,160 @@ "site-dist/assets/js/catalog/catalog-view-toggle.js": "9212c34f419f1081ddadbcf45713fe343182f16b10e226f18a95fbdca924fc6a", "site-dist/assets/js/catalog/code-preview.js": "15d06605f6cfb6dde8b729aa44d6460e5ed06fe44aef400c747871236e8b4df0", "site-dist/assets/js/catalog/command.js": "79e8ddf096512706ee2e81d6453283c60a31cde605789f4716bb0ce01c170f28", - "site-dist/assets/js/catalog/examples-chart.js": "3112a4c99cca098d93c7ff1c02a1fe8a116a7407b3f2b4ebbc3269198c715a38", + "site-dist/assets/js/catalog/examples-chart.js": "f8d25d55998760f410de37da6ee9fab3dcd13eaa72a0551defc3a9669c5e8f14", "site-dist/assets/js/catalog/examples-forms.js": "7b3b408fa559336d4b7c8c077a161099f9f8bef1deecb44752b7d0b4e7098768", - "site-dist/assets/js/catalog/examples-tasks.js": "39ff4ae4b15d02510ab871862b931fe0d9e24c0e2f7c64b48c8fd84df9f4f180", - "site-dist/assets/js/catalog/examples-users.js": "922ad51762acbc528a60c8ecc76d9dfd12d80fdba3dfd7d528a4bdbb488a4687", + "site-dist/assets/js/catalog/examples-tasks.js": "433891c4ad941fddc33bca7ba2edc37771a15ea8a503570ba972e3e951aaeeac", + "site-dist/assets/js/catalog/examples-users.js": "ddd2423d2304b4f5f285c515e3a81204e460f55fb7c9f2e0c29109a4ecb4432c", "site-dist/assets/js/catalog/home-motion.js": "cfb6747563aee16049a71adff6f51c42eeafd9795db785f0dc537b423b234679", - "site-dist/assets/js/catalog/index.js": "a1b62300bd36b4120918769dcdba82f0e5990d561ba9253d58493641d0ac485a", + "site-dist/assets/js/catalog/index.js": "9cb2874b5a773d542d29677a333f4f9704ce39854762ae39a34bad07299c8401", "site-dist/assets/js/catalog/scroll-host.js": "905723ccf4fb885a84e9f5919137725d2a4c8c9ccc215369c2520edd2ae42f8d", "site-dist/assets/js/catalog/settings-panel.js": "5ae2f3ae84cabd19b42f3dbaf6338a2fa776bf102d1c2ca58644404e4b64f4e0", "site-dist/assets/js/catalog/theme-builder-export.js": "a6713d278217a81300b61806d4e5bd84ee0cd99314b68f561c03071b89dfe5cc", "site-dist/assets/js/catalog/theme-builder-schema.js": "c9b33dddc9d5ffab96b075dc1d68087a7055e020a66e44305462a3c6c7e9db57", "site-dist/assets/js/catalog/theme.js": "a961a18b6aa6f64a4b6ee39af041fe763bfc1176fad82c9cd344c7ff0d29f8ed", - "site-dist/assets/js/catalog/toc.js": "c6d6900f8a22cc1e9270dfabd414efcc61bd932ef158fdac650d0fb7535c2787", + "site-dist/assets/js/catalog/toc.js": "b6c13014e454f42ec0b1360a12fd166eda230a2b89a74785a2c616b9d05f0d7b", "site-dist/assets/js/codepen-demo.js": "61cc63865e1d072a01125ea0b6ce1be0cc9067d166e0e218526a97de1b752bd2", - "site-dist/assets/js/components/chart.js": "0c3b2039d4b06993937da243c320cf8fa9fe48e2132935f76073879b0d21ee18", - "site-dist/assets/js/components/chart.min.js": "3395911a9140f50cc5a8026682fea6151245492f421eb8660ec4938c8bbd6c2c", - "site-dist/assets/js/components/combobox.js": "7e749aa39740349f12f95c2811b194a007f088c6c9c578f980c118958e80c07e", - "site-dist/assets/js/components/context-menu.js": "27168c4b1ac42064338ad2746daaa602f22de717259aec5bfcf9d55d8e5d353c", - "site-dist/assets/js/components/datatable.js": "730dfff81863801ede8f54e3810f6fd2219d533b4365ac9e1fb5fec3be9d886d", - "site-dist/assets/js/components/datepicker.js": "570402e7d77598bfeccb5a929c1f4be6615196f287badc79ec13dd8b80300c5b", - "site-dist/assets/js/components/datepicker.min.js": "c6c9057abbe41d774f23374f6c2e9f9f7e346f211ee7b61a8dc9129d056ff90d", - "site-dist/assets/js/components/moo-ui.js": "9c498912d40b8b8a18bd515c5a53cb19f5546f5ab706ec6e8f4447b3f175e869", - "site-dist/assets/js/components/moo-ui.min.js": "1dd4f3bfcc2effddb39b2fce0e00dbc00456aa3dc8a54595c8ce1f3822d064c6", - "site-dist/assets/js/components/sheet.js": "35c31e3252b9510de4747a5a64e77dbec47f4cbb342337be21b75b6f46ff5b6a", - "site-dist/assets/js/components/sidebar.js": "8fbb61af28a04e572c61f7df76d4c72f8048a92661dd9f129d41bcf3795c1bce", - "site-dist/assets/js/components/slider.js": "cd61370895ce3d166aee99cea9772a59f1be4018c7114564e124c82fdf1caa26", - "site-dist/assets/js/components/table-of-contents.js": "b2322ad90148b91b75788e05f492b09065ba61257f934c74b916c1fdc5ce69e1", - "site-dist/assets/js/state.js": "3560c86c8623a3d523ec763456d984e009d8a7fa5834de168b153ba9d242f1c2", + "site-dist/assets/js/components/chart.js": "73cb9136035a23ace9b5e8d832a61eae91edaa5524a53c569020a58937b7d0e3", + "site-dist/assets/js/components/chart.min.js": "4bca338f3e379feb0fb239ae01469b6f514ad0490d921089a70e64e94035f7c5", + "site-dist/assets/js/components/combobox.js": "421eea7f0947a64b11566bae5b2ce5ee2d0f5095b6fc8a1f24c56bf9ce11c6f6", + "site-dist/assets/js/components/context-menu.js": "a76f810e50e8ab7b49ab7388e54c9ff54afe3560b29841c66dbb0191a4deb8d1", + "site-dist/assets/js/components/datatable.js": "67d8a2f214644bccc1f5259410ff343be16d2b9560645f13e0de1915832200d8", + "site-dist/assets/js/components/datepicker.js": "ed6c05db926acc46ef0925b90b3d6d9913f55a5b77aba7a5ed2a9939b5172e1c", + "site-dist/assets/js/components/datepicker.min.js": "d2c74a960b79dae4c99c949aedef1613a086d94f06cf6c0011686df9f9caa845", + "site-dist/assets/js/components/moo-ui.js": "4cd644ce29a39e0f0a0e35b0cd63993dc42df22cb8a5d05d4d2d9c26f95ed341", + "site-dist/assets/js/components/moo-ui.min.js": "75944001ed86eb1641524e3915497d54f2c5110629ee49eed9352c589de9562d", + "site-dist/assets/js/components/sheet.js": "5344b30c652bed676432c6b8b350b04061eeb7f0247c3d61bfc0a71c69402b63", + "site-dist/assets/js/components/sidebar.js": "afb677b5289215ccfca0238f52a15b5a8869c57fdab427098ce7a26b014c5bbf", + "site-dist/assets/js/components/slider.js": "209001958ceff89ee780fea0622de68c51861927866b85a121f1d757ef3d6fad", + "site-dist/assets/js/components/table-of-contents.js": "89223fd9afd3fd1e6c5765a0ebf003b66833de5dc95b802cd5facc910d773831", + "site-dist/assets/js/components/toc.js": "ce9ea72a162e3fb69784235a58878e532ef0841de901f000885a8bed689baddd", + "site-dist/assets/js/state.js": "3c88bb64406688eb17abadd8b1f52bf096a1760a867d6f3ee9f0a317711f47bd", "site-dist/assets/js/theme-owner.js": "7b7e9ca121536a8a8b5d0b596d1ceb14384b8bae195160e05e50e97a5da35b8c", - "site-dist/blocks/index.html": "3e12aac71f9bd84d0f135fafb91f3f43728e5dc853ed7b6614cc9d1b2b82b78e", - "site-dist/blocks/previews/datatable-release-review-responsive/index.html": "957a0fc7e3b5f0d2916260d8db7be08964c9c66aa6166fb463bd55f9c28d8802", - "site-dist/blocks/previews/datatable-release-review-toggle/index.html": "a9f933a8be6771ab5f3cfabfdd2cb3c5b270be1fc4c8769f9026032e8be60da1", - "site-dist/blocks/previews/datatable-release-review/index.html": "8338fe8128fad18a719efe1d0a24e03a6049916b8a6b7d1913b79b707d32affd", - "site-dist/blocks/previews/datatable-tasks/index.html": "b776256181fe70898d004798f262509dfca6843471a904ab13a16ec5aad1ed04", - "site-dist/blocks/previews/sidebar-floating/index.html": "83225b84734ed16cbe8e2be988804f7355e9943355fd07ffb4b084a33ba62dbf", - "site-dist/blocks/previews/sidebar-inset/index.html": "c603a097e825f81b50cb5e97e665716d33094249f976aefb7ca92448e9d4540b", - "site-dist/blocks/sidebar-floating/index.html": "72eeaedd362f04726902777119c023407be2c203f46199854f7e41722e25718c", - "site-dist/blocks/sidebar-inset/index.html": "8bb4fa31d7459abba1674433d8c1693b10ec5f7811ef3b04ae55c5826c258620", - "site-dist/changelog/index.html": "f6763b91bf7506a91ec96fdb4692b3f5270a10c2b50ed11686dfcf4a35cffc62", - "site-dist/charts/index.html": "41dddf970ed0044943c54d1692d82586238f5b68b6058e1410ca586a599c1c89", - "site-dist/components/accordion/index.html": "ebfbaca04a7ce021e9c76d98249b1d24be5cab80a5cdd5d6f7225502afa42c38", - "site-dist/components/alert-dialog/index.html": "4cf8751398d6edcb016df9a914db99510d45f71c06344e185aa3af23db925551", - "site-dist/components/alert/index.html": "a3f9c1ff83a593191b1a26465024e645ecc13d35515c0bd3edb32d324962318f", - "site-dist/components/avatar/index.html": "d716a54d1877b6628a833c1cd9a8ed7716bfb9c566af1ac809a428105bea4e25", - "site-dist/components/badge/index.html": "96ea5599b04f65519e27fb6b951badb4c5b478b84598dd8cf74c9c5baac2071f", - "site-dist/components/breadcrumb/index.html": "7f98a05ab9c3ae31605b58160d170bc24e81372af42e8b5ea80a552bf32bccb2", - "site-dist/components/button-group/index.html": "4e66de404c44b6f596910fa3f5ccdad2fbbe256ebc5ff352b8e641d1bdd2576c", - "site-dist/components/button/index.html": "b1c4b1ae6b670fb8af3b43443fff00a97dc0a951395cba0e58b301e204a1ac33", - "site-dist/components/card/index.html": "bea68d5879363047179442dc43757fb0511fe59c19a442a1bc810e0d7ee46ea2", - "site-dist/components/chart/index.html": "e458306effd4ee23df4fc77edf72f83dd4a122b4076af960e7005df51e3edfcb", - "site-dist/components/checkbox/index.html": "f393c68341f563b7339debf37df03e7a91560c8e3d4a9f1d6361f982fa8b08a7", - "site-dist/components/close-button/index.html": "709a6fc41feab5297d56fb16f87f059008c82cb847d0805615c0879087059698", - "site-dist/components/collapsible/index.html": "b3077168b89fbf8345410edcea83ed007f9f2e423f85bef10608726ed0044603", - "site-dist/components/combobox/index.html": "64f43873864b9d657790d274405d6ab1994a3f4dfc99eda54a30b1cc8d3fe7e3", - "site-dist/components/context-menu/index.html": "62dddcc6835e548b5b1ee500089f9e4ef516d61e7f30add211f1486dce711e1d", - "site-dist/components/datatable/index.html": "e4217160977fa278b99c35a57cfc5e55861a8606e007158a9fe12e06c80a95ef", - "site-dist/components/datepicker/index.html": "ae06bdf8ec1d4bd16d8087c467b3c80dcc41b39fa89416967449f30b1bc8f86b", - "site-dist/components/dialog/index.html": "ed9fc02652dcc2038a906399ff1a8038e4672e27c108ea6bfa82f0977ee4ece0", - "site-dist/components/dropdown-menu/index.html": "00cc2a93edbcc8035ab6f5a4eca6d7b04efcc0fc7af06c2e8d738c9f36592a97", - "site-dist/components/field/index.html": "d6017eec2c64ee5fda8e99494842734e1fb94e705b54cdd9a291ad928e019d8b", - "site-dist/components/form/index.html": "7df5336704fa85a5f53046254e5b3edf35cef36d63b21f83e43a729ce7d6ce7e", - "site-dist/components/index.html": "83b98407127f39354508c398768eb6412870796bcdc44c5a4713062d6827186a", - "site-dist/components/input-group/index.html": "c2db02d1ce0ec06a4f15f2b7dd042feb8e8a9a626cbab8d27f30b5b6085ba8db", - "site-dist/components/input/index.html": "d296c8a67fddefc23584e5e45b6459b729bd46feb5e53769d7e712533f92f333", - "site-dist/components/kbd/index.html": "71de9de725d80d9e6248839b6ce3f81950d85676ee24427542683229c71cf93d", - "site-dist/components/menubar/index.html": "fdf6675f65acfbf78f2a7f8087007daad2d36e9976aa13c307262f38d731ddd6", - "site-dist/components/navigation/index.html": "dd44b5c3e81ffa34c62c40474b4ce199df6b7cd4cbb6893f84b9f833c44abe73", - "site-dist/components/pagination/index.html": "1b0555daa76059aea942d40ccbf4655bd6f685ce370f68057f71dead103f9939", - "site-dist/components/popover/index.html": "c25b1afc37fa6b2824944559c65fbd7c7ed4a21f89c1e9cdfef2196c4f008b61", - "site-dist/components/progress/index.html": "5aca411452d7b488846b3250d5c3fea9c9b420579ae94850a7fc5b1338a6dc9c", - "site-dist/components/radio-group/index.html": "be22f8cbf9425e49c6523e82980c57cf6acdb6513670a21554ca3718fa1ef51e", - "site-dist/components/select/index.html": "42ba643037ff38b91aa38587d59edb5af3a2f1490908a32e803922ea324d906d", - "site-dist/components/separator/index.html": "e8385912ea13fcc6553607217fab8e64e4d32e8edec10c726cc0eda88887cc15", - "site-dist/components/sheet/index.html": "36099d158ecbd37953e8393a4ceca2e053d73f2179f1dfe893cee4ee8b489ec1", - "site-dist/components/sidebar/index.html": "e7c606191cae7cde245ee123f2618f89559944d5bdf7ee02217aa69dff7b71f3", - "site-dist/components/skeleton/index.html": "bd59f93d2174e2f1c516deb0b5ee0089540501db1a9ecdb20db30d514c93fa9c", - "site-dist/components/slider/index.html": "5befbe48b57951a952bbe3bc7da357589072a8a8ad8b5eb476c9a8ec006f555b", - "site-dist/components/spinner/index.html": "fd12194b7048229afd96db112bd7dec278ae25718dedb247792bbaf8b5f423c4", - "site-dist/components/switch/index.html": "f1010ccc94f4e7ea729f69dc96bc636548036dd950926d1e26113378088d23e0", - "site-dist/components/table-of-contents/index.html": "9147f8c7042360d1e04214f5b5da1d2c78191fe0ed990e3616001d1c6546fed4", - "site-dist/components/table/index.html": "dc0e1aaf76025d68f29b0945cf9b88ed399fe7b4159add3f711fe25fa946e214", - "site-dist/components/tabs/index.html": "66a3778645391b2cfc51912e71663d37d4fbc08eaabb96e7f75bbffef0f2e4ae", - "site-dist/components/textarea/index.html": "a6aa8d7b3486821c33d65f00fc253a88378a3c96e9631948974477182f2fd5d3", - "site-dist/components/toast/index.html": "0ce40b7bd30f720a061f277106ebc46c5c1d032c4dedcab82c980e791b6ed232", - "site-dist/components/toggle-group/index.html": "46b9be79dfcc9ca2504561489e189a91c7af6393586eb09275299c9e9513bfbe", - "site-dist/components/tooltip/index.html": "743f4535ff44cebb5fbef845a586473c105fa2e76e0caf558d64ea1b86549def", - "site-dist/components/typography/index.html": "efc6853144ffe1cef65cf94f9c3569225ae8237a6fbee863237ce4658addf667", - "site-dist/contributing/index.html": "c7dd26f30a0bf5a8a1c885a39199185496231a827c54e7160a856c37131fd099", - "site-dist/dist/assets/css/moo-ui.css": "d6947a3a27a4315591cf9f6b92e1fd1d958a5fceb1523b709487d54fcbefc815", - "site-dist/dist/assets/css/moo-ui.min.css": "e96d6d78ccb119f83aea7bd8e5d16b40bddf4251209f67abebb408f78f0bbcba", - "site-dist/dist/assets/css/moo.css": "38bb85c89e2f1f2f043119a9290cb58dd74a1572090b5fc41930c7c57a90c894", - "site-dist/dist/assets/css/moo.min.css": "2546a980bc41ef0c941b8c9cd8e65f6349ec37ff72d1506ba0a6ef6eec81f944", - "site-dist/dist/js/chart.js": "0c3b2039d4b06993937da243c320cf8fa9fe48e2132935f76073879b0d21ee18", - "site-dist/dist/js/chart.min.js": "3395911a9140f50cc5a8026682fea6151245492f421eb8660ec4938c8bbd6c2c", - "site-dist/dist/js/combobox.js": "7e749aa39740349f12f95c2811b194a007f088c6c9c578f980c118958e80c07e", - "site-dist/dist/js/context-menu.js": "27168c4b1ac42064338ad2746daaa602f22de717259aec5bfcf9d55d8e5d353c", - "site-dist/dist/js/datatable.js": "730dfff81863801ede8f54e3810f6fd2219d533b4365ac9e1fb5fec3be9d886d", - "site-dist/dist/js/datepicker.js": "570402e7d77598bfeccb5a929c1f4be6615196f287badc79ec13dd8b80300c5b", - "site-dist/dist/js/datepicker.min.js": "c6c9057abbe41d774f23374f6c2e9f9f7e346f211ee7b61a8dc9129d056ff90d", - "site-dist/dist/js/moo-ui.js": "9c498912d40b8b8a18bd515c5a53cb19f5546f5ab706ec6e8f4447b3f175e869", - "site-dist/dist/js/moo-ui.min.js": "1dd4f3bfcc2effddb39b2fce0e00dbc00456aa3dc8a54595c8ce1f3822d064c6", - "site-dist/dist/js/sheet.js": "35c31e3252b9510de4747a5a64e77dbec47f4cbb342337be21b75b6f46ff5b6a", - "site-dist/dist/js/sidebar.js": "8fbb61af28a04e572c61f7df76d4c72f8048a92661dd9f129d41bcf3795c1bce", - "site-dist/dist/js/slider.js": "cd61370895ce3d166aee99cea9772a59f1be4018c7114564e124c82fdf1caa26", - "site-dist/dist/js/state.js": "3560c86c8623a3d523ec763456d984e009d8a7fa5834de168b153ba9d242f1c2", - "site-dist/dist/js/table-of-contents.js": "b2322ad90148b91b75788e05f492b09065ba61257f934c74b916c1fdc5ce69e1", - "site-dist/dist/release-manifest.json": "95ac88143922ba2a4b3f6e42406f0b50d0bf878187c14052d212ff8bb002cede", - "site-dist/examples/auth/forgot-password/index.html": "fee080f01acdd6d58e9511486c4dad49a4ed25aa58c6820d42824c3c4cdc2156", - "site-dist/examples/auth/sign-in/index.html": "404649ea20fe4820fb653f32062e10010659fb33e3eda8715cab9a092b189579", - "site-dist/examples/auth/sign-up/index.html": "523b549753851b82b94a6a5715518a845f6a9a480cb45dcddb4e49e3bc5432ab", - "site-dist/examples/dashboard/overview/index.html": "9e534197d9e7082eb50ee1591f2b39cf19cc11c4c6e3f3fa31e1028cfc9d00a6", - "site-dist/examples/dashboard/tasks/index.html": "bc77b425ec140040072b5c4236e07eec4cd54ea5a4e3883f5a6d86a5139251e8", - "site-dist/examples/dashboard/users/index.html": "74ea36b20eec5eed041ea003039c38d5443393f8b5950fc30a3e3340f985a5bb", - "site-dist/examples/index.html": "1c2c8a07a6cef0dbb0718fd3933e2fac5284ec473bc59ca23e143b7cde9663cb", - "site-dist/examples/marketing/faq/index.html": "02e73419d34798da55f9e7547b0c8b60bfc5cfeec718f883b3b252992265d979", - "site-dist/examples/marketing/pricing/index.html": "56e5ba16a590536aca03393ed1d3e22fd1b42471b4a41088f58bc39300a0da2f", - "site-dist/examples/settings/account/index.html": "eaeacd17daef9afa94986de653ae1dca166b82f7f0e013883eaebe5e5cd72b68", - "site-dist/examples/settings/appearance/index.html": "7a406397804387c2314456dd6ecc90477b21181a1685c270e57871e5ae0bb0c8", - "site-dist/examples/settings/profile/index.html": "03f6f1dbb7d7ee67682dab3f52ce363a2cad32c49a13a36c8e3c45b08ae9dcb7", + "site-dist/blocks/index.html": "8b2fa2076233fd0f56341cdd3724f415d7ca645d1c5d3521b2499cbf90191f4b", + "site-dist/blocks/previews/datatable-release-review-responsive/index.html": "caf0ac6c0e22044d45ddd77d55d567dc42b87eb64916c7a0d788b783e7883c04", + "site-dist/blocks/previews/datatable-release-review-toggle/index.html": "666c454a451ff7802c692edf10bb3338301eb5283810ff9f83fb4f66f798ceec", + "site-dist/blocks/previews/datatable-release-review/index.html": "db00e6714ceb8797d3a1b02fb6d283b0b38ae7059fc5285ff3838afe8a5adc18", + "site-dist/blocks/previews/datatable-tasks/index.html": "81a767603ef6d4a31c028748a421d82ade34c9a3bf9f72c8591c858d4bef65d7", + "site-dist/blocks/previews/sidebar-floating/index.html": "846e36a71962e246fea740ed979653365e9a0a9c614a35e890e5dae6d07db2de", + "site-dist/blocks/previews/sidebar-inset/index.html": "a8997ab70f75d307fa1cfc99a17fc6c11bcf4d0957d68958170e44a53229f86c", + "site-dist/blocks/previews/toc/index.html": "1905933e1381153fe95280102e38540c8c9f8f39a3683b5fc06d7bb6b706d606", + "site-dist/blocks/sidebar-floating/index.html": "cbf7973faedff7534b7e93cde5f904d26a7b36c697b6129f01d0411c9f3afda6", + "site-dist/blocks/sidebar-inset/index.html": "ea5afb51a77923e6655ee4c4697969929fa40e8a9696210b33d775e6dc66fae4", + "site-dist/changelog/index.html": "f7a1dfe9319f6dcdffe6b372968e88a0c10a0ce3cc363b4d7395457bdbf0ac56", + "site-dist/charts/index.html": "5528b76215c27a1569e238f8231c9486403c79ac7202853af20ec5a5d82bb612", + "site-dist/components/accordion/index.html": "3fa15e5619d26e4ce69d12c7130b683b9eee034635a2058a2ac0a1b5f8f7eb20", + "site-dist/components/alert-dialog/index.html": "41782f1748db3180bbf8978bedb05bec765b2d832ec8d2ab409eb2cee83aa56a", + "site-dist/components/alert/index.html": "abd960052bd83a567b6f578f0c2ab0ae2405b4430bc8956b041f2a728eaf5622", + "site-dist/components/avatar/index.html": "04de2988fc33382096bf6187c2cdca624be8a70a098cd6ac2bce830ec4ebb2f7", + "site-dist/components/badge/index.html": "b551804a1620042893a01476a98ac2ab5c267809d07b6e110e50ed776dc46219", + "site-dist/components/breadcrumb/index.html": "72aa16392c6fd1320e4499d59a1b29d61fb9cc714c0c17356310195055a8ee35", + "site-dist/components/button-group/index.html": "237503dc983f5fd53b5b665b9ec6233e04e7b3419495efc77f7e1c3b5487d827", + "site-dist/components/button/index.html": "1c2bf8995b80970f420f487147a1db2511b5d008bd96c4fa9f265de4a2f241b7", + "site-dist/components/card/index.html": "823f38f3ad6952070d363db00f50f44ad18e8a08884b7da3ba2d239fe64d78dd", + "site-dist/components/chart/index.html": "5e90af1895d3ef587102dcce6b285fafc0665d632044abd6a7a5c9ce80adcb41", + "site-dist/components/checkbox/index.html": "f04ffc83c85df165e651629aeca2a6d55b4254168b5ade080394f5c7c270d375", + "site-dist/components/close-button/index.html": "4b79061139590d85ea196c9736de175e7f729cabc70fe57f8ce7eb73d8d21bb5", + "site-dist/components/collapsible/index.html": "b490ba43e5c716fa059b3b1ba54d1d256462ac82069351eef2a261d5e5a63ddb", + "site-dist/components/combobox/index.html": "08a5dfd8200c664eda33bacefb09884987387560c80f5022b272aa79009cb91a", + "site-dist/components/context-menu/index.html": "032c6645a910cdcbf5bb23e7b42e23c8245e5c0922abac8cbd6f7bcc8c2752d9", + "site-dist/components/datatable/index.html": "db8bad81e2730553b5847302e6138ef5aab614451f2e31a617e59a380cf726a8", + "site-dist/components/datepicker/index.html": "ae8ba49347297701a8831437c1122822a3fb9f706934abb157843f12ac40d26b", + "site-dist/components/dialog/index.html": "d4de64f6fd6e8da42135d2abf8661369cc406036b5b59fa123f2d459e36c2179", + "site-dist/components/dropdown-menu/index.html": "193f9056251a3158c9bb20624ee943edb9d2c6cb0526a2db149c5953f9dba757", + "site-dist/components/field/index.html": "bb0432e236761c7dd32cfd061fedee37d53c193738ab25db24cac1a7e0256363", + "site-dist/components/form/index.html": "1b9a3b16e533b610baeb997b0fe0c816b997fa2faa479a5a0a6af8be9a89c59f", + "site-dist/components/index.html": "129c925dc66420b5538806e915430bb52a54bf1413e4d8ee9435033eddfbe793", + "site-dist/components/input-group/index.html": "9793e6eea7a0f7680ef9cf779771ebd0175e826bf7e9b168263ad4ce966d6c43", + "site-dist/components/input/index.html": "4fd2053aaee4461074397156c8d28de3d5ebb7c7f1b7824d09b7ce64388f507e", + "site-dist/components/kbd/index.html": "3a6bfe48bf06c169746b2e29f25b41664f9f09bb80334ae4b7109208bbcd1f27", + "site-dist/components/menubar/index.html": "2e78b459526716547a7ddb56f2b60d85065e2942b2bda47025fa1b5b6e5c2dd5", + "site-dist/components/navigation/index.html": "e93fe4e1485be5c58a89ae656cdcff616dffc8ca00e4666f5345ee2c8f94b221", + "site-dist/components/pagination/index.html": "f73aa6019142cccec0ca3250cfdfefdd3116c0bf865b6f41774fa2f057fcec4b", + "site-dist/components/popover/index.html": "0b0526707240060563d28fc823b775004b0c1634a0eab82fa6418185d01fb326", + "site-dist/components/progress/index.html": "9e541cfc1708ae89bd1c3ddc6fc32db6b3af312bc9ed27852d6220eeb0143544", + "site-dist/components/radio-group/index.html": "c24d5944829d2a973eeb35167198b800177e0f1a1a5f87435c545f2be23cac95", + "site-dist/components/select/index.html": "5d5e0029e4fa0fb3fe9bc8749107f3191b78a54571d379739f8da46ab9f5175e", + "site-dist/components/separator/index.html": "36f8ae3b096b496e10461cc63014fe3cd8ae1ea6ff6abce38b68d812a2b16f81", + "site-dist/components/sheet/index.html": "0d45c7633f59e9957411078f2f29154df66a8e42b1e3b9a3502853b6e2b19b00", + "site-dist/components/sidebar/index.html": "4e4cdc708c1f356a07314b1290a93bd296a827583b7fa6e1523643da1bc5df44", + "site-dist/components/skeleton/index.html": "a62995f19454227b21a2cedee957b6621e61337c0b02d1ed442248e29975ac70", + "site-dist/components/slider/index.html": "9a839866592287b479285beea3006d87e1421c8cfd96667f4b40c81217e6126d", + "site-dist/components/spinner/index.html": "1595ce29b6b36d4aa961d7f06976479dd1561732855fa6f7442df91f716b0db0", + "site-dist/components/switch/index.html": "6a2b6f77bba2dcff57a45dbc5c9dc50152a31dc6d91f28bbcbde516bba38c80a", + "site-dist/components/table-of-contents/index.html": "502173a93a04caa61444b3ca98b98965dbe32bc44dc6f0e3170a3e554f396bb7", + "site-dist/components/table/index.html": "fe3fedc8c20d474e7fbca4924462e0439e8312bc0a38356a26f5498d425669d3", + "site-dist/components/tabs/index.html": "73a4c6fb7985fef7c8c644f575b8adc562205088732190d066e6af47acf70413", + "site-dist/components/textarea/index.html": "a09b2acb027656d1737dcc0067225e62eb6ce6e1a8e0879f1822978ea7b35b8c", + "site-dist/components/toast/index.html": "b5dd2a7aca77553207285a18db0bf7b1cfe934ce33fcf59948cd33c8697dda53", + "site-dist/components/toc/index.html": "502173a93a04caa61444b3ca98b98965dbe32bc44dc6f0e3170a3e554f396bb7", + "site-dist/components/toggle-group/index.html": "6f09011f8496755f3b92e9f4ec7231a78d5339c8270027bb429a3d34a35bd46e", + "site-dist/components/tooltip/index.html": "05834c3ab2c66da9cc819ff69f925f2c005a34688c2a6daccdf4fa397b4f4508", + "site-dist/components/typography/index.html": "b227d11a0e417a40c2c97716030c389d5ea8406c21da50904f2a2595c3b4d80e", + "site-dist/contributing/index.html": "f89132820e87a45bdbbbade88719272ecf5979c9e76deed269dbfc880da798cb", + "site-dist/dist/assets/css/moo-ui.css": "39f8b612e0307052f250aa1df471f995f2008980e025beee96b8878771ef30d0", + "site-dist/dist/assets/css/moo-ui.min.css": "9ba4af6aaa9394d530a5a43db4b775c2fbb2fce5a6ae9656e69e27a6358bbbb6", + "site-dist/dist/assets/css/moo.css": "c3845a6391648eb2623bb03d6fe7ad37f03c6f481d4e5c9c7e0bb535e5047ae4", + "site-dist/dist/assets/css/moo.min.css": "749a6e4d844bcdda6c449d724c20ed2661633158b83ef8eebf4716c43cd0d522", + "site-dist/dist/js/chart.js": "73cb9136035a23ace9b5e8d832a61eae91edaa5524a53c569020a58937b7d0e3", + "site-dist/dist/js/chart.min.js": "4bca338f3e379feb0fb239ae01469b6f514ad0490d921089a70e64e94035f7c5", + "site-dist/dist/js/combobox.js": "421eea7f0947a64b11566bae5b2ce5ee2d0f5095b6fc8a1f24c56bf9ce11c6f6", + "site-dist/dist/js/context-menu.js": "a76f810e50e8ab7b49ab7388e54c9ff54afe3560b29841c66dbb0191a4deb8d1", + "site-dist/dist/js/datatable.js": "67d8a2f214644bccc1f5259410ff343be16d2b9560645f13e0de1915832200d8", + "site-dist/dist/js/datepicker.js": "ed6c05db926acc46ef0925b90b3d6d9913f55a5b77aba7a5ed2a9939b5172e1c", + "site-dist/dist/js/datepicker.min.js": "d2c74a960b79dae4c99c949aedef1613a086d94f06cf6c0011686df9f9caa845", + "site-dist/dist/js/moo-ui.js": "4cd644ce29a39e0f0a0e35b0cd63993dc42df22cb8a5d05d4d2d9c26f95ed341", + "site-dist/dist/js/moo-ui.min.js": "75944001ed86eb1641524e3915497d54f2c5110629ee49eed9352c589de9562d", + "site-dist/dist/js/sheet.js": "5344b30c652bed676432c6b8b350b04061eeb7f0247c3d61bfc0a71c69402b63", + "site-dist/dist/js/sidebar.js": "afb677b5289215ccfca0238f52a15b5a8869c57fdab427098ce7a26b014c5bbf", + "site-dist/dist/js/slider.js": "209001958ceff89ee780fea0622de68c51861927866b85a121f1d757ef3d6fad", + "site-dist/dist/js/state.js": "3c88bb64406688eb17abadd8b1f52bf096a1760a867d6f3ee9f0a317711f47bd", + "site-dist/dist/js/table-of-contents.js": "89223fd9afd3fd1e6c5765a0ebf003b66833de5dc95b802cd5facc910d773831", + "site-dist/dist/js/toc.js": "ce9ea72a162e3fb69784235a58878e532ef0841de901f000885a8bed689baddd", + "site-dist/dist/release-manifest.json": "05d687ac31e8b207029a0aa4f446d93470d8e300f7034b9131b263a327ff8965", + "site-dist/examples/auth/forgot-password/index.html": "5fd6ee87570a83740f441aecfc7712e031407d58e2c7118ec8ddc81f164c0daa", + "site-dist/examples/auth/sign-in/index.html": "91625778cc4f73c16e5273b51397a0e5e511a09df3b180076efb7a3a05d4660f", + "site-dist/examples/auth/sign-up/index.html": "71b39a3fc0ff9a037a97d6b5587838a2b3977a4e3e2171b1f1db9f3500ec165c", + "site-dist/examples/dashboard/overview/index.html": "e788423767d6d89ba1ae1e81fa34384d712b10d8c18230962f3eb7a7b9be3489", + "site-dist/examples/dashboard/tasks/index.html": "8ef4627ae7a4a98aae9d1eefc44167bba57b538324224c5785cea438e170a6ae", + "site-dist/examples/dashboard/users/index.html": "2fee6abae835b119e38e283c0efca8b4c38d852e2c7f389e62a2d6efc79a0c3f", + "site-dist/examples/index.html": "7a7336007b828c23342900ceda04e7d8ee239be3600bc857b9f62fdc5a615ed0", + "site-dist/examples/marketing/faq/index.html": "1486c3f131761403a2577de59a05ad992aa9e834eeb85deb8d37e90d6e0ae736", + "site-dist/examples/marketing/pricing/index.html": "9487542fb43fbaa413d4dbb9563c2bd51237ff2c9d71250274060a6b6f3a5a04", + "site-dist/examples/settings/account/index.html": "29b07eb21ea5a7340376c241b961eb01935fbd5f7ec6e5cc6895abe7cc2377b1", + "site-dist/examples/settings/appearance/index.html": "8751525fb57962dff067ed36cdea235b4992b65a7b58c9f39e648e331524a774", + "site-dist/examples/settings/profile/index.html": "615875b9700563c34c4a5719557865fec37d290a3bb9b06ccd5eeb76ac29d31d", "site-dist/favicon.ico": "cb05ca1ff03f045852a46a2be53f931a76618323c8de2ed26bb03dd4b07c9893", "site-dist/favicon.svg": "faf1d513e3c7cdd2c17ee5fc2db6667fb7f99313562c6218cb8e2395aec0dc39", "site-dist/icon-192.png": "681080a5b14510b267344f67c6c0384a87480e6e919ad5d4003190d56c10050b", "site-dist/icon-512.png": "0f9e72886c967ba2a2e83bc68be51ce14a0967d641ec1f176ff850a1d9b91dec", - "site-dist/index.html": "237d341bc8400bfc78f69894167cee9ae3a0790d646a31342256751ac5406bf8", - "site-dist/installation/index.html": "28a5da36b4742b8c7f41e03e16105e685d5286cd876e7705bd8830082d7e74fe", - "site-dist/introduction/index.html": "8618bf45a2dcd6b88a4d9b7c8a74e01f59695887c8ea3168c95a368657fdf2d8", - "site-dist/js/chart.js": "0c3b2039d4b06993937da243c320cf8fa9fe48e2132935f76073879b0d21ee18", - "site-dist/js/chart.min.js": "3395911a9140f50cc5a8026682fea6151245492f421eb8660ec4938c8bbd6c2c", - "site-dist/js/combobox.js": "7e749aa39740349f12f95c2811b194a007f088c6c9c578f980c118958e80c07e", - "site-dist/js/context-menu.js": "27168c4b1ac42064338ad2746daaa602f22de717259aec5bfcf9d55d8e5d353c", - "site-dist/js/datatable.js": "730dfff81863801ede8f54e3810f6fd2219d533b4365ac9e1fb5fec3be9d886d", - "site-dist/js/datepicker.js": "570402e7d77598bfeccb5a929c1f4be6615196f287badc79ec13dd8b80300c5b", - "site-dist/js/datepicker.min.js": "c6c9057abbe41d774f23374f6c2e9f9f7e346f211ee7b61a8dc9129d056ff90d", - "site-dist/js/moo-ui.js": "9c498912d40b8b8a18bd515c5a53cb19f5546f5ab706ec6e8f4447b3f175e869", - "site-dist/js/moo-ui.min.js": "1dd4f3bfcc2effddb39b2fce0e00dbc00456aa3dc8a54595c8ce1f3822d064c6", - "site-dist/js/sheet.js": "35c31e3252b9510de4747a5a64e77dbec47f4cbb342337be21b75b6f46ff5b6a", - "site-dist/js/sidebar.js": "8fbb61af28a04e572c61f7df76d4c72f8048a92661dd9f129d41bcf3795c1bce", - "site-dist/js/slider.js": "cd61370895ce3d166aee99cea9772a59f1be4018c7114564e124c82fdf1caa26", - "site-dist/js/table-of-contents.js": "b2322ad90148b91b75788e05f492b09065ba61257f934c74b916c1fdc5ce69e1", - "site-dist/layout/index.html": "780889537fdc8738548bc5e03c11ebaf61ab64c06edc1531d7ed734d29f39e41", - "site-dist/layouts/previews/app-none/index.html": "c1a96a6790a06f3d1820f5f78f4f78dd2cf298bc3ea6bcd78fe5eee793a61b72", - "site-dist/layouts/previews/app-sidebar/index.html": "af56d5b93d8af7addf781104994ac13fe9522b464cdceb52d5ee8a0ec451c538", - "site-dist/layouts/previews/page/index.html": "1f6866b99e0cf2702f9554ee884c9b11015df9321a41cbf6e4cc63856262478b", - "site-dist/license/index.html": "277efdd1637b4c25174dd67d46b01efddd9f3f39cab66cf46b014c49b234e80c", - "site-dist/llms.txt": "d7f26db58c899611c2b04343c090645026f5a322bd236e27a61b1d68b71e4819", + "site-dist/index.html": "3eb71230ed9dee5187a4f81faa38704d806b65a225f6bc222125d63a311875d7", + "site-dist/installation/index.html": "1d8684958a2a2cd062021be39f3e399f3cc0328c854e9dadda4b68d417f00378", + "site-dist/introduction/index.html": "62110886f43c79f00f1700e7e4700e144721ac2456f998775999456d075b61f1", + "site-dist/js/chart.js": "73cb9136035a23ace9b5e8d832a61eae91edaa5524a53c569020a58937b7d0e3", + "site-dist/js/chart.min.js": "4bca338f3e379feb0fb239ae01469b6f514ad0490d921089a70e64e94035f7c5", + "site-dist/js/combobox.js": "421eea7f0947a64b11566bae5b2ce5ee2d0f5095b6fc8a1f24c56bf9ce11c6f6", + "site-dist/js/context-menu.js": "a76f810e50e8ab7b49ab7388e54c9ff54afe3560b29841c66dbb0191a4deb8d1", + "site-dist/js/datatable.js": "67d8a2f214644bccc1f5259410ff343be16d2b9560645f13e0de1915832200d8", + "site-dist/js/datepicker.js": "ed6c05db926acc46ef0925b90b3d6d9913f55a5b77aba7a5ed2a9939b5172e1c", + "site-dist/js/datepicker.min.js": "d2c74a960b79dae4c99c949aedef1613a086d94f06cf6c0011686df9f9caa845", + "site-dist/js/moo-ui.js": "4cd644ce29a39e0f0a0e35b0cd63993dc42df22cb8a5d05d4d2d9c26f95ed341", + "site-dist/js/moo-ui.min.js": "75944001ed86eb1641524e3915497d54f2c5110629ee49eed9352c589de9562d", + "site-dist/js/sheet.js": "5344b30c652bed676432c6b8b350b04061eeb7f0247c3d61bfc0a71c69402b63", + "site-dist/js/sidebar.js": "afb677b5289215ccfca0238f52a15b5a8869c57fdab427098ce7a26b014c5bbf", + "site-dist/js/slider.js": "209001958ceff89ee780fea0622de68c51861927866b85a121f1d757ef3d6fad", + "site-dist/js/table-of-contents.js": "89223fd9afd3fd1e6c5765a0ebf003b66833de5dc95b802cd5facc910d773831", + "site-dist/js/toc.js": "ce9ea72a162e3fb69784235a58878e532ef0841de901f000885a8bed689baddd", + "site-dist/layout/index.html": "9b0e810c6f96796d11f4a97476ea487dfc5016f4133a740dc11c7f6fdc18a35c", + "site-dist/layouts/previews/app-none/index.html": "23d94a74c42e24d676266edd95e04f2c99be0ac9cb0d081f92f9240e38af17b6", + "site-dist/layouts/previews/app-sidebar/index.html": "13b2f70cbbfed844918267e25d7fd5891badb13cf09a74cfae06eb95a1ada9bc", + "site-dist/layouts/previews/page/index.html": "de25669ee2310656e448bd83598e9d4b232639c414679b5268d2180d9eb0ed9e", + "site-dist/license/index.html": "66bb0eb234eef9b0f7806815114475d167eff402afef3e4ea5a5cb0ffe556cc8", + "site-dist/llms.txt": "e5084e4e5c10a5a96ce5d3b4bb284852203cdb4f2cb2c441f28868f584f56d64", "site-dist/robots.txt": "bf13521a8ca29a68a5ba125d21c65b84b1679a0aae915a9fd08794d1dc6d9fbf", "site-dist/site.webmanifest": "af971266a10396df62c991d9e1f4b86be68d7d2bb248b83386a7ba7f1820fbde", - "site-dist/sitemap.xml": "ebb2e72c1806e2951d65bae9f04add6922c3873405c50e1e1e3b01f942b57f56", - "site-dist/skills/index.html": "35f204b3c3d02d1a0a47f926225d6a4ab96888dea8787f072834ce25668cfc29", - "site-dist/support/index.html": "9c446a0cf2c0d7e0dc01c6c54294d02afb2186232d936929cbbf0673d9048e28", + "site-dist/sitemap.xml": "8595f67bb7b24e43463a9ba5c3610185044cbeb707006f30d2a044108660d79b", + "site-dist/skills/index.html": "39dde4fc94aa1fbd8f031d8ce44d80fc38716b16d9010a63588aa54025c88c07", + "site-dist/support/index.html": "30ce92abebc3b304a6a24caa96ed6d2d52acdd1bfd8164f26504ccc0e8935bcf", "site-dist/tests/fixtures/certification/accordion.html": "44b3319ff9e4ff52ae742d4bd31a5adeeaa790317f1ef2e856fa9c687d582896", "site-dist/tests/fixtures/certification/alert-dialog.html": "fcaa0b7d0c62cd2c56601a6b7d10d6a58a2f537bcdd6e93b8b1802167191c51e", "site-dist/tests/fixtures/certification/alert.html": "de8ac00fe4f60c3b691e8599a87da3c6b417a69b01030566f476b2f8c413e778", @@ -871,9 +886,9 @@ "site-dist/tests/fixtures/certification/toggle-group.html": "b7043e803a085f8b79eedff7774e6c47296a71498a871027a41181be93aca131", "site-dist/tests/fixtures/certification/tooltip.html": "c3b799391ba952efa243ee62ec42ff186dd2b6928a3dcb7944cc6541cf92fb3a", "site-dist/tests/fixtures/certification/typography.html": "999180358adbe189113f7c9338478056701683cafd6a6dd609b45637d3d534d6", - "site-dist/utils/border/index.html": "3ddb32978a0d164c389062b69b2e9c2efa295e8ec6c64297dbbe3944b06d0522", - "site-dist/utils/index.html": "57d870f74d2ccd0b1987c97d30e9e6dbb0298cc69d8bb54c8d9467e78b07c180", - "site-dist/utils/scroll-fade/index.html": "4ae471c5f849299e0ebc5bc4d0db356d4e957ce061056cc3e57387128d0bf5e7", + "site-dist/utils/border/index.html": "e808372cede7cbca91c30a4f339afcb5a556e31981338c51184c9203d109ed0e", + "site-dist/utils/index.html": "15553e3f284dd9184474e63fa8750a7ddd64533d6c2101dab069d079366de999", + "site-dist/utils/scroll-fade/index.html": "c4685757988a61d15ea76c636a349ee90bfecd8d5d0e55464960b0e62d2603e5", "site-dist/vendor/bootstrap/dist/js/bootstrap.bundle.min.js": "0833b2e9c3a26c258476c46266e6877fc75218625162e0460be9a3a098a61c6c", "site-dist/vendor/bootstrap/dist/js/bootstrap.bundle.min.js.map": "5e3e0763164143baaa1ca0706b6100ba0452f911d6ce9713b48e3dbe07b35125" } diff --git a/tests/fixtures/moo-core-baseline.sha256 b/tests/fixtures/moo-core-baseline.sha256 index 5142d32d..7aed38d0 100644 --- a/tests/fixtures/moo-core-baseline.sha256 +++ b/tests/fixtures/moo-core-baseline.sha256 @@ -1 +1 @@ -38dae5b4de52040bfe01083c37cb9bb07aacd06ff75c342cb7ef93568386d243 +6619afad2948af875d4d35685dd6b9d069ed76aaf6e68f4b50f6a6293e55182a diff --git a/tests/fixtures/moo-ui-baseline.sha256 b/tests/fixtures/moo-ui-baseline.sha256 index c1a208d8..c7470d9c 100644 --- a/tests/fixtures/moo-ui-baseline.sha256 +++ b/tests/fixtures/moo-ui-baseline.sha256 @@ -1 +1 @@ -fe98f06b700ab2b9a65b173c24372e3fe98a6a9597d8285256cc0bbb93089a01 +439f87271747381f41f58c45d1db14101040159fcbdbd66e254beabdfa6ed8fa diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 7734e568..3ba93ef9 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -1507,7 +1507,7 @@ def test_llms_txt_lists_ready_components_alphabetically(self) -> None: (ROOT / "src/registry/components.json").read_text(encoding="utf-8") ) expected = [ - f"- [{item['label']}](https://ui.wpmoo.org/components/{item['slug']}/)" + f"- [{item['label']}]({site_build.canonical_url('components/' + item['slug'] + '.html')})" for item in sorted( (item for item in catalog if item["status"] == "ready"), key=lambda item: item["label"].casefold(), @@ -3105,13 +3105,13 @@ def test_examples_catalog_pages_use_shell_footer_for_demo_meta(self) -> None: preview_link, learn_more = popover_content_parser.links self.assertEqual( preview_link.get("href"), - f"https://ui.wpmoo.org/components/{slug}/", + site_build.canonical_url(f"components/{slug}.html"), ) self.assertEqual(preview_link.get("target"), "_blank") self.assertEqual(preview_link.get("rel"), "noopener noreferrer") self.assertEqual( learn_more.get("href"), - f"https://ui.wpmoo.org/components/{slug}/", + site_build.canonical_url(f"components/{slug}.html"), ) self.assertEqual(learn_more.get("target"), "_blank") self.assertEqual(learn_more.get("rel"), "noopener noreferrer") @@ -3245,11 +3245,11 @@ def test_auth_example_component_references_use_popovers(self) -> None: preview_link, learn_more = popover_content_parser.links self.assertEqual( preview_link.get("href"), - f"https://ui.wpmoo.org/components/{slug}/", + site_build.canonical_url(f"components/{slug}.html"), ) self.assertEqual( learn_more.get("href"), - f"https://ui.wpmoo.org/components/{slug}/", + site_build.canonical_url(f"components/{slug}.html"), ) def test_compact_doc_toc_reuses_curated_labels_and_native_fragments(self) -> None: @@ -3274,6 +3274,18 @@ def test_catalog_toc_rejects_unsupported_presentations(self) -> None: with self.assertRaisesRegex(ValueError, "Unknown catalog TOC presentation"): template.render() + def test_toc_routes_resolve_to_the_short_canonical_address(self) -> None: + for route in ( + "components/table-of-contents.html", + "components/table-of-contents/", + "components/table-of-contents/index.html", + "components/toc.html", + "components/toc/", + ): + with self.subTest(route=route): + self.assertEqual(site_build.site_href(route), "components/toc/") + self.assertEqual(site_build.canonical_url(route), "https://ui.wpmoo.org/components/toc/") + def test_primary_docs_render_a_right_side_table_of_contents(self) -> None: result = self.run_build() @@ -4034,7 +4046,7 @@ def test_registered_components_have_pages_and_ownership_classification(self) -> (ROOT / "site/src/pages/components" / f"{slug}.html.jinja").is_file() ) self.assertTrue((DIST / "components" / slug / "index.html").is_file()) - self.assertIn(f'href="../components/{slug}/"', components_index) + self.assertIn(f'href="{site_build.site_href("components/" + slug + ".html", "../")}"', components_index) self.assertIn(component["label"], components_index) if slug == "datatable": self.assertIn(component["description"], components_index) diff --git a/tests/test_catalog_browser.py b/tests/test_catalog_browser.py index 673ec6e9..b902317b 100644 --- a/tests/test_catalog_browser.py +++ b/tests/test_catalog_browser.py @@ -40,6 +40,39 @@ def setUpClass(cls) -> None: cls.browser = launch_certification_browser(cls.playwright) cls.addClassCleanup(cls.browser.close) + def test_toc_document_preview_keeps_fragment_navigation_inside_its_frame(self) -> None: + context = self.browser.new_context(viewport={"width": 1540, "height": 1314}) + self.addCleanup(context.close) + page = context.new_page() + evidence = BrowserEvidence(page) + page.goto(f"{self.base_url}/site-dist/components/toc/index.html") + frame = page.frame_locator('iframe[data-moo-block-frame]') + expect(frame.locator('#demo-toc-list')).to_be_visible() + expect(frame.locator('#demo-toc-compact')).to_be_hidden() + expect(page.locator('iframe[data-moo-block-frame]')).to_have_count(1) + before = page.evaluate("() => ({url:location.href, y:scrollY, main:document.getElementById('main-content').scrollTop})") + for label, target in [('Languages', 'languages'), ('Navigation', 'navigation'), ('Introduction', 'introduction')]: + frame.locator('#demo-toc-list').get_by_role('link', name=label, exact=True).click() + expect(frame.locator(f'#demo-toc-list a[href="#{target}"]')).to_have_attribute('aria-current', 'location') + after = page.evaluate("() => ({url:location.href, y:scrollY, main:document.getElementById('main-content').scrollTop})") + self.assertEqual(after, before) + for label, width in [('Tablet', 768), ('Mobile', 390), ('Desktop', 1280)]: + page.get_by_role('button', name=f'{label} preview', exact=True).click() + expect(page.locator('iframe[data-moo-block-frame]')).to_have_attribute('width', str(width)) + if width < 992: + expect(frame.locator('#demo-toc-list')).to_be_hidden() + expect(frame.locator('#demo-toc-compact')).to_be_visible() + frame.locator('#demo-toc-compact').get_by_role('button', name='On this page').click() + frame.locator('#demo-toc-compact').get_by_role('link', name='Languages', exact=True).click() + expect(frame.locator('#demo-toc-compact [data-toc-current]')).to_have_text('Languages') + expect(frame.locator('#demo-toc-compact .dropdown-menu')).to_be_hidden() + else: + expect(frame.locator('#demo-toc-list')).to_be_visible() + expect(frame.locator('#demo-toc-compact')).to_be_hidden() + after = page.evaluate("() => ({url:location.href, y:scrollY, main:document.getElementById('main-content').scrollTop})") + self.assertEqual(after, before) + evidence.assert_clean() + def test_external_state_restores_the_first_content_frame_and_sidebar_reload(self) -> None: for case in CERTIFICATION_CASES: with self.subTest(case=case.name): diff --git a/tests/test_certification_contract.py b/tests/test_certification_contract.py index 8c69680f..6f22132b 100644 --- a/tests/test_certification_contract.py +++ b/tests/test_certification_contract.py @@ -1177,6 +1177,34 @@ def test_rc11_api_freeze_remains_a_historical_removal_guard(self) -> None: self.assertTrue(set(freeze["packageExports"]).issubset(package["exports"])) self.assertTrue(set(freeze["packageFiles"]).issubset(package["files"])) + def test_1_1_api_freeze_preserves_the_released_toc_surface(self) -> None: + freeze = self._read_json("src/certification/api-freeze-1.1.0.json") + package = self._read_json("package.json") + self.assertEqual(freeze["freezeVersion"], "1.1.0") + self.assertEqual(freeze["freezeVersion"], package["version"]) + self.assertEqual(set(freeze["packageExports"]), set(package["exports"])) + self.assertEqual(set(freeze["packageFiles"]), set(package["files"])) + aggregate = next(record for record in freeze["esmModules"] if record["module"] == "moo-ui.js") + self.assertIn("TableOfContents", aggregate["namedExports"]) + toc = next(record for record in freeze["esmModules"] if record["module"] == "table-of-contents.js") + self.assertEqual(toc["export"], "./table-of-contents.js") + self.assertEqual( + toc["lifecycle"], + ["constructor", "getInstance", "getOrCreateInstance", "refresh", "dispose"], + ) + imported = subprocess.run( + [ + "node", "--input-type=module", "--eval", + 'import Toc from "./src/js/components/table-of-contents.js"; ' + + 'const instance = Object.getOwnPropertyNames(Toc.prototype).filter(key => !key.startsWith("_")); ' + + 'const statics = ["getInstance", "getOrCreateInstance"].filter(key => typeof Toc[key] === "function"); ' + + 'process.stdout.write(JSON.stringify([...instance, ...statics]));', + ], + cwd=ROOT, capture_output=True, text=True, check=False, env=npm_env(), + ) + self.assertEqual(imported.returncode, 0, imported.stderr) + self.assertTrue(set(toc["lifecycle"]).issubset(set(json.loads(imported.stdout)))) + def test_stable_api_freeze_matches_current_package_and_runtime_exports(self) -> None: """The unchanged 1.0.0 inventory remains a removal guard for additive development.""" self.assertTrue((CERTIFICATION_ROOT / "api-freeze-1.0.0.json").is_file()) diff --git a/tests/test_code_examples.py b/tests/test_code_examples.py index 7293f144..dc5f2b63 100644 --- a/tests/test_code_examples.py +++ b/tests/test_code_examples.py @@ -615,10 +615,12 @@ def test_render_component_intro_centralizes_hero_and_usage_layout(self) -> None: self.assertIn('aria-labelledby="usage"', template) self.assertIn('

Usage

', template) - # These pages own bespoke intro layouts instead of the shared - # render_component_intro surface. Removing an entry means the page - # adopted the shared macro. - excluded_component_intro_pages = {"chart", "datatable", "sidebar"} + # These pages use framed block previews or chart-specific layouts + # instead of the shared render_component_intro surface. Removing an + # entry means the page adopted the component intro macro. + excluded_component_intro_pages = { + "chart", "datatable", "sidebar", "table-of-contents", + } catalog = json.loads( (ROOT / "src/registry/components.json").read_text(encoding="utf-8") ) diff --git a/tests/test_core_docs_boundary.py b/tests/test_core_docs_boundary.py index f87fb46b..200a9a07 100644 --- a/tests/test_core_docs_boundary.py +++ b/tests/test_core_docs_boundary.py @@ -30,6 +30,7 @@ "dist/js/sidebar.js", "dist/js/slider.js", "dist/js/sheet.js", + "dist/js/toc.js", "dist/js/table-of-contents.js", "dist/js/moo-ui.js", "dist/js/moo-ui.min.js", @@ -164,6 +165,7 @@ def test_package_and_site_outputs_are_separate(self) -> None: "js/sidebar.js", "js/slider.js", "js/sheet.js", + "js/toc.js", "js/table-of-contents.js", "js/moo-ui.js", "js/moo-ui.min.js", @@ -453,7 +455,7 @@ def test_public_asset_prose_uses_site_static_source_path(self) -> None: def test_public_policy_docs_track_current_release_line(self) -> None: package = json.loads((ROOT / "package.json").read_text(encoding="utf-8")) - self.assertEqual(package["version"], "1.1.0-dev.1") + self.assertEqual(package["version"], "1.1.0") published_version = package["version"] if "-dev." in published_version: freeze = json.loads((ROOT / "src/certification/api-freeze-1.0.0.json").read_text(encoding="utf-8")) diff --git a/tests/test_package.py b/tests/test_package.py index d4efc33d..20c1c4be 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -104,6 +104,7 @@ "dist/js/datatable.js", "dist/js/slider.js", "dist/js/sheet.js", + "dist/js/toc.js", "dist/js/table-of-contents.js", "dist/js/moo-ui.js", "dist/js/moo-ui.min.js", @@ -131,6 +132,7 @@ "./datatable.js": "./dist/js/datatable.js", "./slider.js": "./dist/js/slider.js", "./sheet.js": "./dist/js/sheet.js", + "./toc.js": "./dist/js/toc.js", "./table-of-contents.js": "./dist/js/table-of-contents.js", "./moo-ui.js": "./dist/js/moo-ui.js", "./moo-ui.min.js": "./dist/js/moo-ui.min.js", @@ -254,7 +256,7 @@ def test_root_package_publishes_canonical_wpmoo_scope(self) -> None: def test_stable_package_declares_the_state_artifact_surface(self) -> None: package = self._read_package() - self.assertEqual(package["version"], "1.1.0-dev.1") + self.assertEqual(package["version"], "1.1.0") self.assertEqual( package["exports"]["./state.js"], "./dist/js/state.js", @@ -480,6 +482,7 @@ def test_public_component_module_surface_is_explicit(self) -> None: "datatable.js", "slider.js", "sheet.js", + "toc.js", "table-of-contents.js", "chart.js", "datepicker.js", @@ -670,6 +673,8 @@ def test_real_tarball_resolves_from_a_clean_consumer(self) -> None: import ContextMenu from "@wpmoo/ui/context-menu.js"; import DataTable from "@wpmoo/ui/datatable.js"; import Slider from "@wpmoo/ui/slider.js"; +import Toc from "@wpmoo/ui/toc.js"; +import LegacyToc from "@wpmoo/ui/table-of-contents.js"; import { initSheets } from "@wpmoo/ui/sheet.js"; import MooUI, { loadChart as AggregateLoadChart, @@ -702,6 +707,8 @@ def test_real_tarball_resolves_from_a_clean_consumer(self) -> None: ContextMenu.name !== "ContextMenu" || DataTable.name !== "DataTable" || Slider.name !== "MooSlider" || + Toc !== LegacyToc || + typeof Toc.getOrCreateInstance !== "function" || typeof AggregateCombobox.getOrCreateInstance !== "function" || typeof AggregateSidebar.getOrCreateInstance !== "function" || typeof AggregateContextMenu.getOrCreateInstance !== "function" || @@ -939,6 +946,8 @@ def test_component_module_imports_have_no_document_side_effect(self) -> None: "datatable.js", "sheet.js", "slider.js", + "toc.js", + "table-of-contents.js", "src/js/moo-ui.js", "chart.js", "datepicker.js", diff --git a/tests/test_table_of_contents.py b/tests/test_table_of_contents.py index ec7cfbd1..d02dced8 100644 --- a/tests/test_table_of_contents.py +++ b/tests/test_table_of_contents.py @@ -1,6 +1,8 @@ from __future__ import annotations +import subprocess import unittest +from pathlib import Path from markupsafe import Markup @@ -8,6 +10,19 @@ class TableOfContentsTests(unittest.TestCase): + def test_short_runtime_entrypoint_preserves_the_existing_constructor(self) -> None: + result = subprocess.run( + [ + "node", "--input-type=module", "--eval", + 'import Toc from "./src/js/components/toc.js"; ' + + 'import Legacy from "./src/js/components/table-of-contents.js"; ' + + 'process.stdout.write(JSON.stringify(Toc === Legacy));', + ], + cwd=Path(__file__).resolve().parents[1], capture_output=True, text=True, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout, "true") + def render_toc(self, **props: object) -> str: options = { "id": "page-toc",