@moni-labs/moni-ui 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -194
- package/custom-elements.json +1709 -338
- package/dist/actions/index.d.ts +6 -0
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +6 -0
- package/dist/components/_base/field-styles.d.ts +51 -16
- package/dist/components/_base/field-styles.d.ts.map +1 -1
- package/dist/components/_base/field-styles.js +164 -36
- package/dist/components/_base/index.d.ts +25 -0
- package/dist/components/_base/index.d.ts.map +1 -1
- package/dist/components/_base/index.js +25 -0
- package/dist/components/_base/interaction-styles.d.ts +39 -12
- package/dist/components/_base/interaction-styles.d.ts.map +1 -1
- package/dist/components/_base/interaction-styles.js +85 -33
- package/dist/components/_base/moni-element.d.ts +43 -8
- package/dist/components/_base/moni-element.d.ts.map +1 -1
- package/dist/components/_base/moni-element.js +43 -8
- package/dist/components/_base/shared-styles.d.ts +41 -17
- package/dist/components/_base/shared-styles.d.ts.map +1 -1
- package/dist/components/_base/shared-styles.js +113 -21
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +6 -0
- package/dist/components/loading-shapes.d.ts +6 -0
- package/dist/components/loading-shapes.d.ts.map +1 -1
- package/dist/components/loading-shapes.js +6 -0
- package/dist/components/moni-app-bar.d.ts +128 -33
- package/dist/components/moni-app-bar.d.ts.map +1 -1
- package/dist/components/moni-app-bar.js +121 -26
- package/dist/components/moni-badge.d.ts +122 -14
- package/dist/components/moni-badge.d.ts.map +1 -1
- package/dist/components/moni-badge.js +122 -14
- package/dist/components/moni-bottom-sheet.d.ts +120 -15
- package/dist/components/moni-bottom-sheet.d.ts.map +1 -1
- package/dist/components/moni-bottom-sheet.js +116 -12
- package/dist/components/moni-button-group.d.ts +53 -27
- package/dist/components/moni-button-group.d.ts.map +1 -1
- package/dist/components/moni-button-group.js +49 -23
- package/dist/components/moni-button-segment.d.ts +28 -8
- package/dist/components/moni-button-segment.d.ts.map +1 -1
- package/dist/components/moni-button-segment.js +27 -7
- package/dist/components/moni-button.d.ts +51 -32
- package/dist/components/moni-button.d.ts.map +1 -1
- package/dist/components/moni-button.js +50 -31
- package/dist/components/moni-card.d.ts +91 -31
- package/dist/components/moni-card.d.ts.map +1 -1
- package/dist/components/moni-card.js +86 -26
- package/dist/components/moni-carousel.d.ts +80 -17
- package/dist/components/moni-carousel.d.ts.map +1 -1
- package/dist/components/moni-carousel.js +258 -34
- package/dist/components/moni-checkbox.d.ts +122 -17
- package/dist/components/moni-checkbox.d.ts.map +1 -1
- package/dist/components/moni-checkbox.js +118 -14
- package/dist/components/moni-chip.d.ts +56 -30
- package/dist/components/moni-chip.d.ts.map +1 -1
- package/dist/components/moni-chip.js +51 -25
- package/dist/components/moni-color-field.d.ts +44 -6
- package/dist/components/moni-color-field.d.ts.map +1 -1
- package/dist/components/moni-color-field.js +43 -5
- package/dist/components/moni-context-menu.d.ts +44 -22
- package/dist/components/moni-context-menu.d.ts.map +1 -1
- package/dist/components/moni-context-menu.js +43 -21
- package/dist/components/moni-dialog.d.ts +107 -15
- package/dist/components/moni-dialog.d.ts.map +1 -1
- package/dist/components/moni-dialog.js +105 -14
- package/dist/components/moni-divider.d.ts +50 -15
- package/dist/components/moni-divider.d.ts.map +1 -1
- package/dist/components/moni-divider.js +49 -14
- package/dist/components/moni-expansion.d.ts +44 -8
- package/dist/components/moni-expansion.d.ts.map +1 -1
- package/dist/components/moni-expansion.js +43 -7
- package/dist/components/moni-fab-menu.d.ts +39 -20
- package/dist/components/moni-fab-menu.d.ts.map +1 -1
- package/dist/components/moni-fab-menu.js +38 -19
- package/dist/components/moni-fab.d.ts +49 -23
- package/dist/components/moni-fab.d.ts.map +1 -1
- package/dist/components/moni-fab.js +46 -20
- package/dist/components/moni-file-field.d.ts +54 -14
- package/dist/components/moni-file-field.d.ts.map +1 -1
- package/dist/components/moni-file-field.js +53 -13
- package/dist/components/moni-icon.d.ts +78 -11
- package/dist/components/moni-icon.d.ts.map +1 -1
- package/dist/components/moni-icon.js +77 -10
- package/dist/components/moni-list-item.d.ts +61 -30
- package/dist/components/moni-list-item.d.ts.map +1 -1
- package/dist/components/moni-list-item.js +55 -24
- package/dist/components/moni-list.d.ts +37 -13
- package/dist/components/moni-list.d.ts.map +1 -1
- package/dist/components/moni-list.js +36 -12
- package/dist/components/moni-loading-indicator.d.ts +38 -11
- package/dist/components/moni-loading-indicator.d.ts.map +1 -1
- package/dist/components/moni-loading-indicator.js +37 -10
- package/dist/components/moni-menu-item.d.ts +31 -8
- package/dist/components/moni-menu-item.d.ts.map +1 -1
- package/dist/components/moni-menu-item.js +30 -7
- package/dist/components/moni-menu.d.ts +58 -33
- package/dist/components/moni-menu.d.ts.map +1 -1
- package/dist/components/moni-menu.js +51 -26
- package/dist/components/moni-morph-modal.d.ts +7 -1
- package/dist/components/moni-morph-modal.d.ts.map +1 -1
- package/dist/components/moni-morph-modal.js +46 -24
- package/dist/components/moni-nav-item.d.ts +50 -10
- package/dist/components/moni-nav-item.d.ts.map +1 -1
- package/dist/components/moni-nav-item.js +48 -8
- package/dist/components/moni-nav.d.ts +57 -22
- package/dist/components/moni-nav.d.ts.map +1 -1
- package/dist/components/moni-nav.js +53 -18
- package/dist/components/moni-progress.d.ts +108 -20
- package/dist/components/moni-progress.d.ts.map +1 -1
- package/dist/components/moni-progress.js +104 -16
- package/dist/components/moni-radio.d.ts +106 -14
- package/dist/components/moni-radio.d.ts.map +1 -1
- package/dist/components/moni-radio.js +104 -13
- package/dist/components/moni-ripple.d.ts +121 -10
- package/dist/components/moni-ripple.d.ts.map +1 -1
- package/dist/components/moni-ripple.js +120 -9
- package/dist/components/moni-segmented-button.d.ts +31 -11
- package/dist/components/moni-segmented-button.d.ts.map +1 -1
- package/dist/components/moni-segmented-button.js +30 -10
- package/dist/components/moni-select-option.d.ts +43 -9
- package/dist/components/moni-select-option.d.ts.map +1 -1
- package/dist/components/moni-select-option.js +41 -7
- package/dist/components/moni-select.d.ts +59 -2
- package/dist/components/moni-select.d.ts.map +1 -1
- package/dist/components/moni-select.js +58 -1
- package/dist/components/moni-shape.d.ts +1 -1
- package/dist/components/moni-side-sheet.d.ts +56 -19
- package/dist/components/moni-side-sheet.d.ts.map +1 -1
- package/dist/components/moni-side-sheet.js +53 -16
- package/dist/components/moni-slider.d.ts +56 -25
- package/dist/components/moni-slider.d.ts.map +1 -1
- package/dist/components/moni-slider.js +55 -24
- package/dist/components/moni-snackbar.d.ts +86 -17
- package/dist/components/moni-snackbar.d.ts.map +1 -1
- package/dist/components/moni-snackbar.js +85 -16
- package/dist/components/moni-split-button.d.ts +38 -9
- package/dist/components/moni-split-button.d.ts.map +1 -1
- package/dist/components/moni-split-button.js +37 -8
- package/dist/components/moni-step.d.ts +42 -9
- package/dist/components/moni-step.d.ts.map +1 -1
- package/dist/components/moni-step.js +41 -8
- package/dist/components/moni-stepper.d.ts +43 -6
- package/dist/components/moni-stepper.d.ts.map +1 -1
- package/dist/components/moni-stepper.js +42 -5
- package/dist/components/moni-switch.d.ts +103 -16
- package/dist/components/moni-switch.d.ts.map +1 -1
- package/dist/components/moni-switch.js +99 -13
- package/dist/components/moni-tab.d.ts +35 -8
- package/dist/components/moni-tab.d.ts.map +1 -1
- package/dist/components/moni-tab.js +34 -7
- package/dist/components/moni-tabs.d.ts +51 -13
- package/dist/components/moni-tabs.d.ts.map +1 -1
- package/dist/components/moni-tabs.js +48 -10
- package/dist/components/moni-text-field.d.ts +55 -10
- package/dist/components/moni-text-field.d.ts.map +1 -1
- package/dist/components/moni-text-field.js +54 -9
- package/dist/components/moni-textarea.d.ts +51 -21
- package/dist/components/moni-textarea.d.ts.map +1 -1
- package/dist/components/moni-textarea.js +48 -18
- package/dist/components/moni-time-picker.d.ts +41 -11
- package/dist/components/moni-time-picker.d.ts.map +1 -1
- package/dist/components/moni-time-picker.js +40 -10
- package/dist/components/moni-toolbar.d.ts +43 -15
- package/dist/components/moni-toolbar.d.ts.map +1 -1
- package/dist/components/moni-toolbar.js +42 -14
- package/dist/components/moni-tooltip.d.ts +55 -25
- package/dist/components/moni-tooltip.d.ts.map +1 -1
- package/dist/components/moni-tooltip.js +54 -24
- package/dist/components/moni-typography.d.ts +43 -18
- package/dist/components/moni-typography.d.ts.map +1 -1
- package/dist/components/moni-typography.js +42 -17
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +59 -2
- package/dist/styles/tailwind.css +67 -0
- package/dist/styles/tokens.css +111 -99
- package/dist/utils/color.d.ts +181 -2
- package/dist/utils/color.d.ts.map +1 -1
- package/dist/utils/color.js +182 -4
- package/dist/utils/theme.svelte.d.ts +305 -2
- package/dist/utils/theme.svelte.d.ts.map +1 -1
- package/dist/utils/theme.svelte.js +331 -2
- package/dist/web-components.d.ts +28 -0
- package/dist/web-components.d.ts.map +1 -1
- package/dist/web-components.js +29 -2
- package/package.json +1 -1
- package/src/actions/index.ts +7 -0
- package/src/components/_base/field-styles.ts +165 -37
- package/src/components/_base/index.ts +27 -0
- package/src/components/_base/interaction-styles.ts +86 -33
- package/src/components/_base/moni-element.ts +44 -8
- package/src/components/_base/shared-styles.ts +114 -21
- package/src/components/index.ts +7 -0
- package/src/components/loading-shapes.ts +7 -0
- package/src/components/moni-app-bar.ts +127 -26
- package/src/components/moni-badge.ts +128 -14
- package/src/components/moni-bottom-sheet.ts +125 -13
- package/src/components/moni-button-group.ts +50 -23
- package/src/components/moni-button-segment.ts +28 -7
- package/src/components/moni-button.ts +51 -31
- package/src/components/moni-card.ts +90 -26
- package/src/components/moni-carousel.ts +282 -34
- package/src/components/moni-checkbox.ts +125 -14
- package/src/components/moni-chip.ts +52 -25
- package/src/components/moni-color-field.ts +44 -5
- package/src/components/moni-context-menu.ts +44 -21
- package/src/components/moni-dialog.ts +111 -14
- package/src/components/moni-divider.ts +50 -14
- package/src/components/moni-expansion.ts +44 -7
- package/src/components/moni-fab-menu.ts +39 -19
- package/src/components/moni-fab.ts +47 -20
- package/src/components/moni-file-field.ts +54 -13
- package/src/components/moni-icon.ts +80 -10
- package/src/components/moni-list-item.ts +56 -24
- package/src/components/moni-list.ts +37 -12
- package/src/components/moni-loading-indicator.ts +38 -10
- package/src/components/moni-menu-item.ts +31 -7
- package/src/components/moni-menu.ts +52 -26
- package/src/components/moni-morph-modal.ts +58 -24
- package/src/components/moni-nav-item.ts +49 -8
- package/src/components/moni-nav.ts +54 -18
- package/src/components/moni-progress.ts +109 -16
- package/src/components/moni-radio.ts +111 -13
- package/src/components/moni-ripple.ts +126 -9
- package/src/components/moni-segmented-button.ts +31 -10
- package/src/components/moni-select-option.ts +42 -7
- package/src/components/moni-select.ts +79 -1
- package/src/components/moni-side-sheet.ts +54 -16
- package/src/components/moni-slider.ts +56 -24
- package/src/components/moni-snackbar.ts +90 -16
- package/src/components/moni-split-button.ts +38 -8
- package/src/components/moni-step.ts +42 -8
- package/src/components/moni-stepper.ts +43 -5
- package/src/components/moni-switch.ts +106 -13
- package/src/components/moni-tab.ts +35 -7
- package/src/components/moni-tabs.ts +49 -10
- package/src/components/moni-text-field.ts +55 -9
- package/src/components/moni-textarea.ts +49 -18
- package/src/components/moni-time-picker.ts +41 -10
- package/src/components/moni-toolbar.ts +43 -14
- package/src/components/moni-tooltip.ts +55 -24
- package/src/components/moni-typography.ts +43 -17
- package/src/index.ts +67 -3
- package/src/styles/tailwind.css +67 -0
- package/src/styles/tokens.css +111 -99
- package/src/types/svelte-runes.d.ts +64 -2
- package/src/utils/color.ts +286 -5
- package/src/utils/theme.svelte.ts +411 -2
- package/src/web-components.ts +31 -2
- package/dist/assets/shapes/arch.svg +0 -1
- package/dist/assets/shapes/arrow.svg +0 -1
- package/dist/assets/shapes/boom.svg +0 -1
- package/dist/assets/shapes/burst.svg +0 -1
- package/dist/assets/shapes/circle.svg +0 -1
- package/dist/assets/shapes/clamshell.svg +0 -1
- package/dist/assets/shapes/diamond.svg +0 -1
- package/dist/assets/shapes/fan.svg +0 -1
- package/dist/assets/shapes/flower.svg +0 -1
- package/dist/assets/shapes/gem.svg +0 -1
- package/dist/assets/shapes/ghost-ish.svg +0 -1
- package/dist/assets/shapes/heart.svg +0 -1
- package/dist/assets/shapes/leaf-clover4.svg +0 -1
- package/dist/assets/shapes/leaf-clover8.svg +0 -1
- package/dist/assets/shapes/loading-indicator.svg +0 -1
- package/dist/assets/shapes/oval.svg +0 -1
- package/dist/assets/shapes/pentagon.svg +0 -1
- package/dist/assets/shapes/pill.svg +0 -1
- package/dist/assets/shapes/pixel-circle.svg +0 -1
- package/dist/assets/shapes/pixel-triangle.svg +0 -1
- package/dist/assets/shapes/puffy-diamond.svg +0 -1
- package/dist/assets/shapes/puffy.svg +0 -1
- package/dist/assets/shapes/semicircle.svg +0 -1
- package/dist/assets/shapes/sided-cookie12.svg +0 -1
- package/dist/assets/shapes/sided-cookie4.svg +0 -1
- package/dist/assets/shapes/sided-cookie6.svg +0 -1
- package/dist/assets/shapes/sided-cookie7.svg +0 -1
- package/dist/assets/shapes/sided-cookie9.svg +0 -1
- package/dist/assets/shapes/slanted.svg +0 -1
- package/dist/assets/shapes/soft-boom.svg +0 -1
- package/dist/assets/shapes/soft-burst.svg +0 -1
- package/dist/assets/shapes/square.svg +0 -1
- package/dist/assets/shapes/sunny.svg +0 -1
- package/dist/assets/shapes/triangle.svg +0 -1
- package/dist/assets/shapes/very-sunny.svg +0 -1
- package/dist/assets/shapes/wavy-circle.svg +0 -1
- package/dist/assets/shapes/wavy.svg +0 -1
- package/src/assets/shapes/arch.svg +0 -1
- package/src/assets/shapes/arrow.svg +0 -1
- package/src/assets/shapes/boom.svg +0 -1
- package/src/assets/shapes/burst.svg +0 -1
- package/src/assets/shapes/circle.svg +0 -1
- package/src/assets/shapes/clamshell.svg +0 -1
- package/src/assets/shapes/diamond.svg +0 -1
- package/src/assets/shapes/fan.svg +0 -1
- package/src/assets/shapes/flower.svg +0 -1
- package/src/assets/shapes/gem.svg +0 -1
- package/src/assets/shapes/ghost-ish.svg +0 -1
- package/src/assets/shapes/heart.svg +0 -1
- package/src/assets/shapes/leaf-clover4.svg +0 -1
- package/src/assets/shapes/leaf-clover8.svg +0 -1
- package/src/assets/shapes/loading-indicator.svg +0 -1
- package/src/assets/shapes/oval.svg +0 -1
- package/src/assets/shapes/pentagon.svg +0 -1
- package/src/assets/shapes/pill.svg +0 -1
- package/src/assets/shapes/pixel-circle.svg +0 -1
- package/src/assets/shapes/pixel-triangle.svg +0 -1
- package/src/assets/shapes/puffy-diamond.svg +0 -1
- package/src/assets/shapes/puffy.svg +0 -1
- package/src/assets/shapes/semicircle.svg +0 -1
- package/src/assets/shapes/sided-cookie12.svg +0 -1
- package/src/assets/shapes/sided-cookie4.svg +0 -1
- package/src/assets/shapes/sided-cookie6.svg +0 -1
- package/src/assets/shapes/sided-cookie7.svg +0 -1
- package/src/assets/shapes/sided-cookie9.svg +0 -1
- package/src/assets/shapes/slanted.svg +0 -1
- package/src/assets/shapes/soft-boom.svg +0 -1
- package/src/assets/shapes/soft-burst.svg +0 -1
- package/src/assets/shapes/square.svg +0 -1
- package/src/assets/shapes/sunny.svg +0 -1
- package/src/assets/shapes/triangle.svg +0 -1
- package/src/assets/shapes/very-sunny.svg +0 -1
- package/src/assets/shapes/wavy-circle.svg +0 -1
- package/src/assets/shapes/wavy.svg +0 -1
|
@@ -1,34 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file components/moni-carousel.ts
|
|
3
|
+
* @package @moni-labs/moni-ui
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @contributors Moni Labs & Contributors
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import { html, css } from 'lit';
|
|
2
9
|
import { customElement, property, query, state, queryAll } from 'lit/decorators.js';
|
|
3
10
|
import { MoniElement, sharedStyles } from './_base/index.js';
|
|
4
11
|
import { gsap } from 'gsap';
|
|
5
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Data model for a single item in a `<moni-carousel>`.
|
|
15
|
+
*/
|
|
6
16
|
export interface CarouselItem {
|
|
17
|
+
/** Display title overlaid on the item. */
|
|
7
18
|
title: string;
|
|
19
|
+
/** URL or path to the background image. */
|
|
8
20
|
img: string;
|
|
21
|
+
/** Optional link target. If provided, the item renders as an `<a>` element. */
|
|
9
22
|
href?: string;
|
|
23
|
+
/** Link target attribute (e.g. `'_blank'`). Only applies if `href` is set. */
|
|
10
24
|
target?: string;
|
|
11
25
|
}
|
|
12
26
|
|
|
13
27
|
/**
|
|
14
28
|
* Material Design 3 Expressive Carousel component.
|
|
15
|
-
* Supports three layouts:
|
|
16
|
-
* - `multi-browse`: Shows a collection of items (large, medium, and small items).
|
|
17
|
-
* - `hero`: Focuses on one large item with a smaller item peeking.
|
|
18
|
-
* - `uncontained`: Standard same-sized layout bleeding off the edges.
|
|
19
29
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
30
|
+
* Carousels display a collection of related items in a scrollable, horizontal list.
|
|
31
|
+
* They allow users to quickly browse through items like images, cards, or products.
|
|
32
|
+
*
|
|
33
|
+
* **M3 spec reference:** `m3-docs/components/carousel/specs.md`
|
|
34
|
+
*
|
|
35
|
+
* **Layout variants:**
|
|
36
|
+
* - `multi-browse` (default) — Shows a mix of large, medium, and small (peeking)
|
|
37
|
+
* items. Best for exploring a large number of items.
|
|
38
|
+
* - `hero` — Focuses on one large primary item while showing a sliver of the
|
|
39
|
+
* next item. Best for featuring important content.
|
|
40
|
+
* - `uncontained` — Standard layout where all items have the same width and
|
|
41
|
+
* bleed off the edges of the container.
|
|
42
|
+
*
|
|
43
|
+
* **Animation & Gestures:**
|
|
44
|
+
* This component uses GSAP for smooth drag, flick, and snap animations,
|
|
45
|
+
* mirroring the high-fidelity M3 Expressive motion specs. It handles touch
|
|
46
|
+
* gestures for mobile and mouse-drag for desktop.
|
|
47
|
+
*
|
|
48
|
+
* **Auto-sizing (`auto` mode):**
|
|
49
|
+
* When `auto=true` (default), the carousel measures its own width and
|
|
50
|
+
* dynamically calculates the optimal sizes for large, medium, and small items
|
|
51
|
+
* based on the active `layout` to ensure they fit perfectly without awkward gaps
|
|
52
|
+
* or clipping at the edges.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```html
|
|
56
|
+
* <!-- Declarative usage via DOM properties (recommended) -->
|
|
57
|
+
* <moni-carousel layout="hero"></moni-carousel>
|
|
58
|
+
* <script>
|
|
59
|
+
* const carousel = document.querySelector('moni-carousel');
|
|
60
|
+
* carousel.items = [
|
|
61
|
+
* { title: 'Item 1', img: '/img1.jpg', href: '/link1' },
|
|
62
|
+
* { title: 'Item 2', img: '/img2.jpg' }
|
|
63
|
+
* ];
|
|
64
|
+
* </script>
|
|
65
|
+
*
|
|
66
|
+
* <!-- Slot-based usage (for SSR or simple static content) -->
|
|
67
|
+
* <moni-carousel layout="uncontained">
|
|
68
|
+
* <div slot="item">
|
|
69
|
+
* <img src="/img1.jpg" />
|
|
70
|
+
* <h3>Static Item</h3>
|
|
71
|
+
* </div>
|
|
72
|
+
* </moni-carousel>
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* @slot item - Alternative to the `items` property. Slot individual HTML elements
|
|
76
|
+
* instead of passing data objects.
|
|
77
|
+
*
|
|
78
|
+
* @csspart carousel - The outer wrapper.
|
|
79
|
+
* @csspart track - The scrolling track element.
|
|
80
|
+
* @csspart item - Individual carousel item containers.
|
|
81
|
+
* @csspart img - The image elements inside the items.
|
|
82
|
+
* @csspart title - The title text elements inside the items.
|
|
32
83
|
*/
|
|
33
84
|
@customElement('moni-carousel')
|
|
34
85
|
export class MoniCarousel extends MoniElement {
|
|
@@ -45,6 +96,9 @@ export class MoniCarousel extends MoniElement {
|
|
|
45
96
|
@property({ attribute: 'show-all-text' }) showAllText = 'Show all';
|
|
46
97
|
@property({ attribute: 'header-text' }) headerText = '';
|
|
47
98
|
@property({ type: Boolean, attribute: 'hide-nav' }) hideNav = false;
|
|
99
|
+
@property({ type: Boolean, reflect: true }) infinite = false;
|
|
100
|
+
@property({ type: Boolean, reflect: true }) autoplay = false;
|
|
101
|
+
@property({ type: Number, attribute: 'autoplay-interval' }) autoplayInterval = 3000;
|
|
48
102
|
|
|
49
103
|
@state() private _containerWidth = 0;
|
|
50
104
|
@state() private _slottedItems: CarouselItem[] = [];
|
|
@@ -73,6 +127,10 @@ export class MoniCarousel extends MoniElement {
|
|
|
73
127
|
private _tickerCurrent = 0;
|
|
74
128
|
private _isTicking = false;
|
|
75
129
|
|
|
130
|
+
// Infinite & Autoplay state
|
|
131
|
+
private _infiniteInitialized = false;
|
|
132
|
+
private _autoplayTimer: any = null;
|
|
133
|
+
|
|
76
134
|
// quickSetter caches — avoid gsap.set() overhead per frame
|
|
77
135
|
private _cardSetX: ((value: number) => void)[] = [];
|
|
78
136
|
private _cardSetWidth: ((value: number) => void)[] = [];
|
|
@@ -361,6 +419,10 @@ export class MoniCarousel extends MoniElement {
|
|
|
361
419
|
}
|
|
362
420
|
this._buildQuickSetters();
|
|
363
421
|
this.updateLayout(true);
|
|
422
|
+
|
|
423
|
+
if (this.autoplay) {
|
|
424
|
+
this._startAutoplay();
|
|
425
|
+
}
|
|
364
426
|
}
|
|
365
427
|
|
|
366
428
|
override updated(changedProperties: Map<string, any>) {
|
|
@@ -374,16 +436,32 @@ export class MoniCarousel extends MoniElement {
|
|
|
374
436
|
changedProperties.has('padding') ||
|
|
375
437
|
changedProperties.has('items') ||
|
|
376
438
|
changedProperties.has('hideNav') ||
|
|
439
|
+
changedProperties.has('infinite') ||
|
|
440
|
+
changedProperties.has('autoplay') ||
|
|
441
|
+
changedProperties.has('autoplayInterval') ||
|
|
377
442
|
changedProperties.has('_containerWidth') ||
|
|
378
443
|
changedProperties.has('_slottedItems') ||
|
|
379
444
|
changedProperties.has('_hasSlottedShowAll')
|
|
380
445
|
) {
|
|
446
|
+
if (changedProperties.has('infinite') || changedProperties.has('items') || changedProperties.has('_slottedItems')) {
|
|
447
|
+
this._infiniteInitialized = false;
|
|
448
|
+
}
|
|
449
|
+
|
|
381
450
|
if (changedProperties.has('layout') && this._scrollContainer) {
|
|
382
451
|
this._scrollContainer.scrollLeft = 0;
|
|
452
|
+
this._infiniteInitialized = false;
|
|
383
453
|
this._scrollContainer.style.scrollSnapType =
|
|
384
454
|
this.layout === 'uncontained' ? 'none' : 'x mandatory';
|
|
385
455
|
}
|
|
386
456
|
this.updateLayout(true);
|
|
457
|
+
|
|
458
|
+
if (changedProperties.has('autoplay') || changedProperties.has('autoplayInterval') || changedProperties.has('items') || changedProperties.has('_slottedItems')) {
|
|
459
|
+
if (this.autoplay) {
|
|
460
|
+
this._startAutoplay();
|
|
461
|
+
} else {
|
|
462
|
+
this._stopAutoplay();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
387
465
|
}
|
|
388
466
|
}
|
|
389
467
|
|
|
@@ -586,6 +664,7 @@ export class MoniCarousel extends MoniElement {
|
|
|
586
664
|
this._tickerCurrent = this._tickerTarget;
|
|
587
665
|
gsap.ticker.remove(this._tick);
|
|
588
666
|
this._isTicking = false;
|
|
667
|
+
this._checkInfiniteWrap();
|
|
589
668
|
}
|
|
590
669
|
|
|
591
670
|
this._applyLayout(this._tickerCurrent);
|
|
@@ -608,6 +687,34 @@ export class MoniCarousel extends MoniElement {
|
|
|
608
687
|
this._buildQuickSetters();
|
|
609
688
|
}
|
|
610
689
|
|
|
690
|
+
if (this.infinite && !this._infiniteInitialized && this.effectiveItems.length > 0) {
|
|
691
|
+
const C = this.effectiveItems.length;
|
|
692
|
+
const V = C * this._visualCardsMultiplier;
|
|
693
|
+
const K = this._snapSetsCount;
|
|
694
|
+
const centerSetIndex = Math.floor(K / 2);
|
|
695
|
+
|
|
696
|
+
// Wait for the browser to render the new items so scrollWidth is updated
|
|
697
|
+
// A double-rAF or setTimeout ensures the layout engine has processed the new snap-items
|
|
698
|
+
requestAnimationFrame(() => {
|
|
699
|
+
setTimeout(() => {
|
|
700
|
+
if (this._scrollContainer) {
|
|
701
|
+
// Force layout calculation
|
|
702
|
+
this._scrollContainer.scrollWidth;
|
|
703
|
+
this._scrollContainer.scrollLeft = centerSetIndex * V * this.itemSize;
|
|
704
|
+
|
|
705
|
+
// If the browser still clamped it, it means it's not ready.
|
|
706
|
+
// But with setTimeout it should be fully ready.
|
|
707
|
+
|
|
708
|
+
// Force the visual ticker to match instantly to avoid an animation zoom from 0
|
|
709
|
+
this._tickerTarget = this._scrollContainer.scrollLeft / this.itemSize;
|
|
710
|
+
this._tickerCurrent = this._tickerTarget;
|
|
711
|
+
this._applyLayout(this._tickerCurrent);
|
|
712
|
+
}
|
|
713
|
+
}, 20);
|
|
714
|
+
});
|
|
715
|
+
this._infiniteInitialized = true;
|
|
716
|
+
}
|
|
717
|
+
|
|
611
718
|
this._tickerTarget = this._scrollContainer.scrollLeft / this.itemSize;
|
|
612
719
|
|
|
613
720
|
if (instant || (!this._isTicking && this._tickerCurrent === 0)) {
|
|
@@ -625,7 +732,21 @@ export class MoniCarousel extends MoniElement {
|
|
|
625
732
|
const isMobile = this._isMobile;
|
|
626
733
|
const cardCount = this._cards.length;
|
|
627
734
|
for (let i = 0; i < cardCount; i++) {
|
|
628
|
-
|
|
735
|
+
let p = i - t;
|
|
736
|
+
if (this.infinite) {
|
|
737
|
+
// Modulo wrap so the cards perfectly loop around the center
|
|
738
|
+
p = i - (t % cardCount);
|
|
739
|
+
if (p > cardCount / 2) p -= cardCount;
|
|
740
|
+
if (p < -cardCount / 2) p += cardCount;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Optimization: if the card is far off-screen, hide it to save rendering work
|
|
744
|
+
if (p < -15 || p > 15) {
|
|
745
|
+
this._cardSetX[i](-9999);
|
|
746
|
+
this._titleSetOpacity[i](0);
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
|
|
629
750
|
const layout = this._getCardLayout(p);
|
|
630
751
|
|
|
631
752
|
// Use quickSetters — ~3x faster than gsap.set() per call
|
|
@@ -642,8 +763,112 @@ export class MoniCarousel extends MoniElement {
|
|
|
642
763
|
}
|
|
643
764
|
}
|
|
644
765
|
|
|
766
|
+
private get _visualCardsMultiplier() {
|
|
767
|
+
if (!this.infinite || this.effectiveItems.length === 0) return 1;
|
|
768
|
+
const C = this.effectiveItems.length;
|
|
769
|
+
// We need at least ~15 cards to fill the screen seamlessly for small datasets
|
|
770
|
+
return Math.max(1, Math.ceil(15 / C));
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
private get _snapSetsCount() {
|
|
774
|
+
if (!this.infinite || this.effectiveItems.length === 0) return 1;
|
|
775
|
+
const C = this.effectiveItems.length;
|
|
776
|
+
const V = C * this._visualCardsMultiplier;
|
|
777
|
+
// We want enough snap items so the user NEVER reaches the end in a single fast swipe.
|
|
778
|
+
// A total of ~1000 items is extremely cheap as empty divs, and provides a massive buffer.
|
|
779
|
+
let K = Math.ceil(1000 / V);
|
|
780
|
+
if (K % 2 === 0) K += 1; // Keep it odd for a perfect center
|
|
781
|
+
return Math.max(3, K);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
private get _snapItemsCount() {
|
|
785
|
+
if (this.infinite) {
|
|
786
|
+
const C = this.effectiveItems.length;
|
|
787
|
+
const V_mult = this._visualCardsMultiplier;
|
|
788
|
+
const K = this._snapSetsCount;
|
|
789
|
+
return K * (C * V_mult);
|
|
790
|
+
}
|
|
791
|
+
return this.effectiveItems.length;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
private _checkInfiniteWrap() {
|
|
795
|
+
if (!this.infinite || this.effectiveItems.length === 0 || !this._scrollContainer) return;
|
|
796
|
+
|
|
797
|
+
const C = this.effectiveItems.length;
|
|
798
|
+
const V = C * this._visualCardsMultiplier;
|
|
799
|
+
const K = this._snapSetsCount;
|
|
800
|
+
const centerSetIndex = Math.floor(K / 2);
|
|
801
|
+
|
|
802
|
+
// The scroll track is divided into K sets, each containing V items.
|
|
803
|
+
const setWidth = V * this.itemSize;
|
|
804
|
+
|
|
805
|
+
const currentScroll = this._scrollContainer.scrollLeft;
|
|
806
|
+
const currentSet = Math.floor(currentScroll / setWidth);
|
|
807
|
+
|
|
808
|
+
if (currentSet !== centerSetIndex) {
|
|
809
|
+
const delta = (centerSetIndex - currentSet) * setWidth;
|
|
810
|
+
|
|
811
|
+
const oldSnap = this._scrollContainer.style.scrollSnapType;
|
|
812
|
+
const oldBehavior = this._scrollContainer.style.scrollBehavior;
|
|
813
|
+
|
|
814
|
+
// Temporarily disable snap and smooth behavior so the wrap is instantaneous
|
|
815
|
+
this._scrollContainer.style.scrollSnapType = 'none';
|
|
816
|
+
this._scrollContainer.style.scrollBehavior = 'auto';
|
|
817
|
+
|
|
818
|
+
this._scrollContainer.scrollLeft += delta;
|
|
819
|
+
|
|
820
|
+
// INSTANTLY update the GSAP visual ticker so it doesn't animate the jump
|
|
821
|
+
this._tickerTarget = this._scrollContainer.scrollLeft / this.itemSize;
|
|
822
|
+
this._tickerCurrent = this._tickerTarget;
|
|
823
|
+
this._applyLayout(this._tickerCurrent);
|
|
824
|
+
|
|
825
|
+
// Restore original scroll behavior
|
|
826
|
+
this._scrollContainer.style.scrollBehavior = oldBehavior;
|
|
827
|
+
|
|
828
|
+
if (this._isDown) {
|
|
829
|
+
this._scrollLeftStart += delta;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (oldSnap && oldSnap !== 'none') {
|
|
833
|
+
// Re-enable snap on the next frame to avoid jumping
|
|
834
|
+
requestAnimationFrame(() => {
|
|
835
|
+
if (this._scrollContainer && !this._isDown) {
|
|
836
|
+
this._scrollContainer.style.scrollSnapType = oldSnap;
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
private _startAutoplay() {
|
|
844
|
+
this._stopAutoplay();
|
|
845
|
+
if (this.autoplay && this.effectiveItems.length > 0) {
|
|
846
|
+
this._autoplayTimer = setInterval(() => {
|
|
847
|
+
// We removed _isInteracting to make autoplay more robust. It only pauses on active drag.
|
|
848
|
+
if (!this._isDown) {
|
|
849
|
+
this._scrollNext();
|
|
850
|
+
}
|
|
851
|
+
}, this.autoplayInterval) as unknown as number;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
private _stopAutoplay() {
|
|
856
|
+
if (this._autoplayTimer) {
|
|
857
|
+
clearInterval(this._autoplayTimer);
|
|
858
|
+
this._autoplayTimer = null;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private _handleMouseEnter() {
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
private _handleMouseLeave() {
|
|
866
|
+
this._handleMouseUp();
|
|
867
|
+
}
|
|
868
|
+
|
|
645
869
|
private _handleMouseDown(e: MouseEvent) {
|
|
646
870
|
gsap.killTweensOf(this._scrollContainer);
|
|
871
|
+
this._checkInfiniteWrap();
|
|
647
872
|
this._isDown = true;
|
|
648
873
|
this._draggedDistance = 0;
|
|
649
874
|
this._scrollContainer.style.scrollBehavior = 'auto';
|
|
@@ -685,13 +910,22 @@ export class MoniCarousel extends MoniElement {
|
|
|
685
910
|
|
|
686
911
|
// Dynamic momentum: stronger swipes have a much higher multiplier
|
|
687
912
|
const momentumMultiplier = 350 + Math.abs(this._velocity) * 300;
|
|
688
|
-
|
|
913
|
+
let projectedScrollLeft = this._scrollContainer.scrollLeft - this._velocity * momentumMultiplier;
|
|
914
|
+
|
|
915
|
+
// Cap momentum throw to prevent hitting infinite wrap edges on fast swipes
|
|
916
|
+
const maxThrowDist = 15 * this.itemSize;
|
|
917
|
+
if (projectedScrollLeft < this._scrollContainer.scrollLeft - maxThrowDist) {
|
|
918
|
+
projectedScrollLeft = this._scrollContainer.scrollLeft - maxThrowDist;
|
|
919
|
+
} else if (projectedScrollLeft > this._scrollContainer.scrollLeft + maxThrowDist) {
|
|
920
|
+
projectedScrollLeft = this._scrollContainer.scrollLeft + maxThrowDist;
|
|
921
|
+
}
|
|
689
922
|
|
|
690
923
|
if (this.layout !== 'uncontained') {
|
|
691
924
|
this._scrollContainer.style.scrollSnapType = 'none';
|
|
692
925
|
|
|
693
926
|
let targetIndex = Math.round(projectedScrollLeft / this.itemSize);
|
|
694
|
-
|
|
927
|
+
const maxIndex = this.effectiveItems.length > 0 ? this._snapItemsCount - 1 : 0;
|
|
928
|
+
targetIndex = Math.max(0, Math.min(maxIndex, targetIndex));
|
|
695
929
|
const snapPoint = targetIndex * this.itemSize;
|
|
696
930
|
|
|
697
931
|
// Dynamic duration: longer distance = more time, max 0.85s for faster feeling
|
|
@@ -716,6 +950,7 @@ export class MoniCarousel extends MoniElement {
|
|
|
716
950
|
|
|
717
951
|
private _handleTouchStart(e: TouchEvent) {
|
|
718
952
|
gsap.killTweensOf(this._scrollContainer);
|
|
953
|
+
this._checkInfiniteWrap();
|
|
719
954
|
this._isDown = true;
|
|
720
955
|
this._draggedDistance = 0;
|
|
721
956
|
this._scrollContainer.style.scrollBehavior = 'auto';
|
|
@@ -780,7 +1015,7 @@ export class MoniCarousel extends MoniElement {
|
|
|
780
1015
|
// Restablecer el snap después de un pequeño retraso
|
|
781
1016
|
if ((this as any)._wheelTimeout) clearTimeout((this as any)._wheelTimeout);
|
|
782
1017
|
(this as any)._wheelTimeout = setTimeout(() => {
|
|
783
|
-
if (this.layout !== 'uncontained') {
|
|
1018
|
+
if (this.layout !== 'uncontained' && !this._isDown) {
|
|
784
1019
|
this._scrollContainer.style.scrollSnapType = 'x mandatory';
|
|
785
1020
|
}
|
|
786
1021
|
}, 150);
|
|
@@ -794,9 +1029,10 @@ export class MoniCarousel extends MoniElement {
|
|
|
794
1029
|
return;
|
|
795
1030
|
}
|
|
796
1031
|
|
|
1032
|
+
const originalIndex = index % this.effectiveItems.length;
|
|
797
1033
|
this.dispatchEvent(
|
|
798
1034
|
new CustomEvent('item-click', {
|
|
799
|
-
detail: { item, index },
|
|
1035
|
+
detail: { item, index: originalIndex },
|
|
800
1036
|
bubbles: true,
|
|
801
1037
|
composed: true
|
|
802
1038
|
})
|
|
@@ -804,14 +1040,13 @@ export class MoniCarousel extends MoniElement {
|
|
|
804
1040
|
}
|
|
805
1041
|
|
|
806
1042
|
private _scrollPrevious() {
|
|
807
|
-
const target =
|
|
1043
|
+
const target = this._scrollContainer.scrollLeft - this.itemSize;
|
|
808
1044
|
this._scrollContainer.style.scrollBehavior = 'smooth';
|
|
809
1045
|
this._scrollContainer.scrollTo({ left: target, behavior: 'smooth' });
|
|
810
1046
|
}
|
|
811
1047
|
|
|
812
1048
|
private _scrollNext() {
|
|
813
|
-
const
|
|
814
|
-
const target = Math.min(maxScroll, this._scrollContainer.scrollLeft + this.itemSize);
|
|
1049
|
+
const target = this._scrollContainer.scrollLeft + this.itemSize;
|
|
815
1050
|
this._scrollContainer.style.scrollBehavior = 'smooth';
|
|
816
1051
|
this._scrollContainer.scrollTo({ left: target, behavior: 'smooth' });
|
|
817
1052
|
}
|
|
@@ -908,15 +1143,27 @@ export class MoniCarousel extends MoniElement {
|
|
|
908
1143
|
rightPadding = minRightPaddingNeeded;
|
|
909
1144
|
}
|
|
910
1145
|
|
|
1146
|
+
let snapItemsCount = hasItems ? this._snapItemsCount : 0;
|
|
1147
|
+
let visualItems = this.effectiveItems;
|
|
1148
|
+
|
|
1149
|
+
if (this.infinite && hasItems) {
|
|
1150
|
+
const V_mult = this._visualCardsMultiplier;
|
|
1151
|
+
|
|
1152
|
+
visualItems = [];
|
|
1153
|
+
for (let i = 0; i < V_mult; i++) {
|
|
1154
|
+
visualItems = visualItems.concat(this.effectiveItems);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
911
1158
|
const trackWidth = hasItems
|
|
912
|
-
?
|
|
1159
|
+
? snapItemsCount * L + (snapItemsCount - 1) * this.gap
|
|
913
1160
|
: 0;
|
|
914
1161
|
|
|
915
1162
|
const isScrollable = hasItems && this._scrollContainer &&
|
|
916
1163
|
(this._scrollContainer.scrollWidth > this._scrollContainer.clientWidth);
|
|
917
|
-
const showPrevArrow = isScrollable && this._scrollContainer.scrollLeft > 5;
|
|
918
|
-
const showNextArrow = isScrollable &&
|
|
919
|
-
(this._scrollContainer.scrollWidth - this._scrollContainer.scrollLeft - this._scrollContainer.clientWidth > 5);
|
|
1164
|
+
const showPrevArrow = this.infinite || (isScrollable && this._scrollContainer.scrollLeft > 5);
|
|
1165
|
+
const showNextArrow = this.infinite || (isScrollable &&
|
|
1166
|
+
(this._scrollContainer.scrollWidth - this._scrollContainer.scrollLeft - this._scrollContainer.clientWidth > 5));
|
|
920
1167
|
|
|
921
1168
|
return html`
|
|
922
1169
|
<div class="carousel-container" style="
|
|
@@ -964,18 +1211,19 @@ export class MoniCarousel extends MoniElement {
|
|
|
964
1211
|
@mousedown=${this._handleMouseDown}
|
|
965
1212
|
@mousemove=${this._handleMouseMove}
|
|
966
1213
|
@mouseup=${this._handleMouseUp}
|
|
967
|
-
@
|
|
1214
|
+
@mouseenter=${this._handleMouseEnter}
|
|
1215
|
+
@mouseleave=${this._handleMouseLeave}
|
|
968
1216
|
@touchstart=${this._handleTouchStart}
|
|
969
1217
|
@touchmove=${this._handleTouchMove}
|
|
970
1218
|
@touchend=${this._handleTouchEnd}
|
|
971
1219
|
@wheel=${this._handleWheel}
|
|
972
1220
|
>
|
|
973
1221
|
<div class="snap-track">
|
|
974
|
-
${hasItems ?
|
|
1222
|
+
${hasItems ? Array.from({ length: snapItemsCount }).map(() => html`<div class="snap-item"></div>`) : ''}
|
|
975
1223
|
</div>
|
|
976
1224
|
<div class="visual-track">
|
|
977
1225
|
${hasItems
|
|
978
|
-
?
|
|
1226
|
+
? visualItems.map(
|
|
979
1227
|
(item, idx) => html`
|
|
980
1228
|
<div
|
|
981
1229
|
class="card"
|
|
@@ -983,16 +1231,16 @@ export class MoniCarousel extends MoniElement {
|
|
|
983
1231
|
>
|
|
984
1232
|
${item.href
|
|
985
1233
|
? html`
|
|
986
|
-
<a href="${item.href}" target="${item.target || '_self'}" style="text-decoration:none; color:inherit;">
|
|
1234
|
+
<a href="${item.href}" target="${item.target || '_self'}" style="text-decoration:none; color:inherit;" draggable="false">
|
|
987
1235
|
<div class="img-parallax-container">
|
|
988
|
-
<img src="${item.img}" alt="${item.title}" draggable="false" />
|
|
1236
|
+
<img src="${item.img}" alt="${item.title}" draggable="false" loading="lazy" />
|
|
989
1237
|
</div>
|
|
990
1238
|
<h2 class="card-title">${item.title}</h2>
|
|
991
1239
|
</a>
|
|
992
1240
|
`
|
|
993
1241
|
: html`
|
|
994
1242
|
<div class="img-parallax-container">
|
|
995
|
-
<img src="${item.img}" alt="${item.title}" draggable="false" />
|
|
1243
|
+
<img src="${item.img}" alt="${item.title}" draggable="false" loading="lazy" />
|
|
996
1244
|
</div>
|
|
997
1245
|
<h2 class="card-title">${item.title}</h2>
|
|
998
1246
|
`}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file components/moni-checkbox.ts
|
|
3
|
+
* @package @moni-labs/moni-ui
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @contributors Moni Labs & Contributors
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import { html, css } from 'lit';
|
|
2
9
|
import { customElement, property, query } from 'lit/decorators.js';
|
|
3
10
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
@@ -5,35 +12,122 @@ import { live } from 'lit/directives/live.js';
|
|
|
5
12
|
import { MoniElement, sharedStyles } from './_base/index.js';
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
|
-
*
|
|
15
|
+
* Material Design 3 Checkbox component.
|
|
16
|
+
*
|
|
17
|
+
* Checkboxes allow users to select one or more items from a set, or toggle
|
|
18
|
+
* a single binary option. They are visual-only shells — the consumer is
|
|
19
|
+
* responsible for wiring up form submission and validation logic.
|
|
20
|
+
*
|
|
21
|
+
* **Visual architecture (BeerCSS pattern):**
|
|
22
|
+
* The native `<input type="checkbox">` occupies real layout space (16×16 minimum)
|
|
23
|
+
* but is visually hidden via `opacity: 0`. A `<span>` sibling rendered after
|
|
24
|
+
* the input holds two pseudo-elements:
|
|
25
|
+
* - `::before` — the visible checkbox icon (Material Symbols ligature).
|
|
26
|
+
* - `::after` — the hover/focus state layer ripple ring.
|
|
27
|
+
*
|
|
28
|
+
* The `::before` content switches between:
|
|
29
|
+
* - `'check_box_outline_blank'` (unchecked)
|
|
30
|
+
* - `'check_box'` (checked)
|
|
31
|
+
* - `'indeterminate_check_box'` (native indeterminate state)
|
|
32
|
+
*
|
|
33
|
+
* **Form integration:**
|
|
34
|
+
* Setting `name` and `value` passes them to the native `<input>` element,
|
|
35
|
+
* enabling participation in HTML form submissions.
|
|
9
36
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* - `input` has width/height = --_size, opacity: 0 (occupies real space, no absolute!)
|
|
13
|
-
* - `span::before` is the visible checkbox box (position: absolute, inset from the input)
|
|
14
|
-
* - `span::after` is the ripple hover effect
|
|
15
|
-
* - Content is set via CSS: "check_box_outline_blank" / "check_box" (Material Symbols font)
|
|
37
|
+
* @fires change - Bubbles and is composed. Fired when the checkbox is toggled.
|
|
38
|
+
* The consumer can read `element.checked` for the new state.
|
|
16
39
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```html
|
|
42
|
+
* <moni-checkbox label="Accept terms" name="terms" value="yes"></moni-checkbox>
|
|
43
|
+
*
|
|
44
|
+
* <script>
|
|
45
|
+
* document.querySelector('moni-checkbox').addEventListener('change', (e) => {
|
|
46
|
+
* console.log('checked:', e.target.checked);
|
|
47
|
+
* });
|
|
48
|
+
* </script>
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @csspart checkbox - The outer `<label>` element.
|
|
24
52
|
*/
|
|
25
53
|
@customElement('moni-checkbox')
|
|
26
54
|
export class MoniCheckbox extends MoniElement {
|
|
55
|
+
/**
|
|
56
|
+
* Text label displayed to the right of the checkbox icon.
|
|
57
|
+
*
|
|
58
|
+
* When non-empty, the label is rendered as a text node inside the `<span>`.
|
|
59
|
+
* When empty, the default slot is rendered instead, allowing slotted HTML.
|
|
60
|
+
*
|
|
61
|
+
* @default ''
|
|
62
|
+
*/
|
|
27
63
|
@property({ reflect: true }) label = '';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Whether the checkbox is currently checked.
|
|
67
|
+
*
|
|
68
|
+
* Reflected as an attribute so CSS attribute selectors and external state
|
|
69
|
+
* readers can observe the checked state without accessing the JS property.
|
|
70
|
+
* Synced to the native input via `updated()`.
|
|
71
|
+
*
|
|
72
|
+
* @default false
|
|
73
|
+
*/
|
|
28
74
|
@property({ type: Boolean, reflect: true }) checked = false;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* When `true`, the native input is disabled: the checkbox is not interactive
|
|
78
|
+
* and renders at 50% opacity.
|
|
79
|
+
*
|
|
80
|
+
* @default false
|
|
81
|
+
*/
|
|
29
82
|
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Visual size of the checkbox icon.
|
|
86
|
+
*
|
|
87
|
+
* Maps to the `--_size` custom property which controls both the invisible
|
|
88
|
+
* input's hit area and the visible `::before` icon size.
|
|
89
|
+
*
|
|
90
|
+
* | Value | `--_size` |
|
|
91
|
+
* |------------|-----------|
|
|
92
|
+
* | `'small'` | 1rem |
|
|
93
|
+
* | `'medium'` | 1.5rem |
|
|
94
|
+
* | `'large'` | 2rem |
|
|
95
|
+
* | `'extra'` | 2.5rem |
|
|
96
|
+
*
|
|
97
|
+
* @default 'medium'
|
|
98
|
+
*/
|
|
30
99
|
@property({ reflect: true })
|
|
31
100
|
size: 'small' | 'medium' | 'large' | 'extra' = 'medium';
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Forwarded to the native `<input name>` attribute.
|
|
104
|
+
* Required for grouping checkboxes within a form.
|
|
105
|
+
*
|
|
106
|
+
* @default ''
|
|
107
|
+
*/
|
|
32
108
|
@property({ reflect: true }) name = '';
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Forwarded to the native `<input value>` attribute.
|
|
112
|
+
* The value submitted in a form when this checkbox is checked.
|
|
113
|
+
*
|
|
114
|
+
* @default ''
|
|
115
|
+
*/
|
|
33
116
|
@property({ reflect: true }) value = '';
|
|
34
117
|
|
|
118
|
+
/** Direct reference to the native input element for programmatic access. */
|
|
35
119
|
@query('input') private _input!: HTMLInputElement;
|
|
36
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Syncs `checked` and `disabled` back to the native input element after
|
|
123
|
+
* Lit's render cycle, ensuring the DOM stays in sync with component state.
|
|
124
|
+
*
|
|
125
|
+
* This is necessary because Lit's `.property=${value}` binding updates the
|
|
126
|
+
* DOM property, but the `live()` directive and direct property assignment
|
|
127
|
+
* are more reliable for boolean inputs across browser implementations.
|
|
128
|
+
*
|
|
129
|
+
* @param changed - Map of changed property names to their previous values.
|
|
130
|
+
*/
|
|
37
131
|
override updated(changed: Map<string, unknown>) {
|
|
38
132
|
if (this._input) {
|
|
39
133
|
if (changed.has('checked')) this._input.checked = this.checked;
|
|
@@ -159,11 +253,28 @@ export class MoniCheckbox extends MoniElement {
|
|
|
159
253
|
`
|
|
160
254
|
];
|
|
161
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Handles the native input's `change` event.
|
|
258
|
+
*
|
|
259
|
+
* Reads the new checked state from the input and re-dispatches a composed
|
|
260
|
+
* `'change'` event so the signal crosses shadow DOM boundaries and can be
|
|
261
|
+
* heard by parent elements in the light DOM.
|
|
262
|
+
*
|
|
263
|
+
* @param e - The native `change` event from the hidden `<input>`.
|
|
264
|
+
*/
|
|
162
265
|
private _onChange(e: Event) {
|
|
163
266
|
this.checked = (e.target as HTMLInputElement).checked;
|
|
164
267
|
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
|
|
165
268
|
}
|
|
166
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Renders the accessible checkbox label structure.
|
|
272
|
+
*
|
|
273
|
+
* The `<label>` wraps the hidden `<input>` and a `<span>` so that clicking
|
|
274
|
+
* anywhere on the label area (including the text) toggles the checkbox.
|
|
275
|
+
* The `has-label` class on the span adds left padding to separate the icon
|
|
276
|
+
* from the label text.
|
|
277
|
+
*/
|
|
167
278
|
override render() {
|
|
168
279
|
return html`<label part="checkbox">
|
|
169
280
|
<input
|