@moni-labs/moni-ui 0.4.2 → 0.4.4

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.
Files changed (92) hide show
  1. package/README.md +6 -6
  2. package/custom-elements.json +61 -16
  3. package/dist/browser/moni-ui.iife.min.js +155 -6
  4. package/dist/browser/moni-ui.iife.min.js.br +0 -0
  5. package/dist/browser/moni-ui.iife.min.js.gz +0 -0
  6. package/dist/browser/moni-ui.iife.min.js.map +1 -1
  7. package/dist/browser/moni-ui.min.css +1 -1
  8. package/dist/browser/moni-ui.min.css.br +0 -0
  9. package/dist/browser/moni-ui.min.css.gz +0 -0
  10. package/dist/browser/moni-ui.min.css.map +1 -1
  11. package/dist/browser/moni-ui.min.js +189 -30
  12. package/dist/browser/moni-ui.min.js.br +0 -0
  13. package/dist/browser/moni-ui.min.js.gz +0 -0
  14. package/dist/browser/moni-ui.min.js.map +1 -1
  15. package/dist/cdn/chunks/{class-map-DBljv-_e.js → class-map-EVPcxmRq.js} +1 -1
  16. package/dist/cdn/chunks/{decorate-DxX3e27g.js → decorate-D4jKxi6H.js} +56 -0
  17. package/dist/cdn/chunks/if-defined-DlAUOFi9.js +1 -0
  18. package/dist/cdn/chunks/{live-B7Rjg1kt.js → live-DdUrWiUp.js} +1 -1
  19. package/dist/cdn/components/moni-app-bar.js +1 -1
  20. package/dist/cdn/components/moni-badge.js +1 -1
  21. package/dist/cdn/components/moni-bottom-sheet.js +1 -1
  22. package/dist/cdn/components/moni-button-group.js +72 -3
  23. package/dist/cdn/components/moni-button-segment.js +1 -1
  24. package/dist/cdn/components/moni-button.js +2 -1
  25. package/dist/cdn/components/moni-card.js +1 -1
  26. package/dist/cdn/components/moni-carousel.js +1 -1
  27. package/dist/cdn/components/moni-checkbox.js +1 -1
  28. package/dist/cdn/components/moni-chip.js +1 -1
  29. package/dist/cdn/components/moni-color-field.js +1 -1
  30. package/dist/cdn/components/moni-context-menu.js +1 -1
  31. package/dist/cdn/components/moni-dialog.js +1 -1
  32. package/dist/cdn/components/moni-divider.js +1 -1
  33. package/dist/cdn/components/moni-expansion.js +1 -1
  34. package/dist/cdn/components/moni-fab-menu.js +1 -1
  35. package/dist/cdn/components/moni-fab.js +1 -1
  36. package/dist/cdn/components/moni-file-field.js +1 -1
  37. package/dist/cdn/components/moni-icon.js +1 -1
  38. package/dist/cdn/components/moni-list-item.js +1 -1
  39. package/dist/cdn/components/moni-list.js +1 -1
  40. package/dist/cdn/components/moni-loading-indicator.js +22 -3
  41. package/dist/cdn/components/moni-menu-item.js +1 -1
  42. package/dist/cdn/components/moni-menu.js +1 -1
  43. package/dist/cdn/components/moni-morph-modal.js +1 -1
  44. package/dist/cdn/components/moni-nav-item.js +1 -1
  45. package/dist/cdn/components/moni-nav.js +1 -1
  46. package/dist/cdn/components/moni-progress.js +1 -1
  47. package/dist/cdn/components/moni-radio.js +1 -1
  48. package/dist/cdn/components/moni-ripple.js +1 -1
  49. package/dist/cdn/components/moni-segmented-button.js +1 -1
  50. package/dist/cdn/components/moni-select-option.js +1 -1
  51. package/dist/cdn/components/moni-select.js +1 -1
  52. package/dist/cdn/components/moni-shape.js +1 -1
  53. package/dist/cdn/components/moni-side-sheet.js +1 -1
  54. package/dist/cdn/components/moni-slider.js +1 -1
  55. package/dist/cdn/components/moni-snackbar.js +1 -1
  56. package/dist/cdn/components/moni-split-button.js +1 -1
  57. package/dist/cdn/components/moni-step.js +1 -1
  58. package/dist/cdn/components/moni-stepper.js +1 -1
  59. package/dist/cdn/components/moni-switch.js +1 -1
  60. package/dist/cdn/components/moni-tab.js +1 -1
  61. package/dist/cdn/components/moni-tabs.js +1 -1
  62. package/dist/cdn/components/moni-text-field.js +1 -1
  63. package/dist/cdn/components/moni-textarea.js +1 -1
  64. package/dist/cdn/components/moni-time-picker.js +6 -2
  65. package/dist/cdn/components/moni-toolbar.js +1 -1
  66. package/dist/cdn/components/moni-tooltip.js +1 -1
  67. package/dist/cdn/components/moni-typography.js +1 -1
  68. package/dist/cdn/importmap.json +98 -98
  69. package/dist/cdn/moni-ui.min.css +1 -1
  70. package/dist/components/_base/shared-styles.d.ts.map +1 -1
  71. package/dist/components/_base/shared-styles.js +56 -0
  72. package/dist/components/moni-button-group.d.ts +14 -0
  73. package/dist/components/moni-button-group.d.ts.map +1 -1
  74. package/dist/components/moni-button-group.js +130 -25
  75. package/dist/components/moni-button.d.ts.map +1 -1
  76. package/dist/components/moni-button.js +1 -0
  77. package/dist/components/moni-loading-indicator.d.ts.map +1 -1
  78. package/dist/components/moni-loading-indicator.js +21 -2
  79. package/dist/components/moni-time-picker.d.ts.map +1 -1
  80. package/dist/components/moni-time-picker.js +5 -1
  81. package/dist/styles/base.css +27 -7
  82. package/package.json +1 -1
  83. package/src/components/_base/shared-styles.test.ts +15 -0
  84. package/src/components/_base/shared-styles.ts +56 -0
  85. package/src/components/moni-button-group.test.ts +76 -0
  86. package/src/components/moni-button-group.ts +130 -34
  87. package/src/components/moni-button.ts +1 -0
  88. package/src/components/moni-loading-indicator.test.ts +9 -0
  89. package/src/components/moni-loading-indicator.ts +21 -2
  90. package/src/components/moni-time-picker.ts +5 -1
  91. package/src/styles/base.css +27 -7
  92. package/dist/cdn/chunks/if-defined-CuEAJKpp.js +0 -1
@@ -1377,7 +1377,7 @@
1377
1377
  {
1378
1378
  "kind": "variable",
1379
1379
  "name": "MoniButtonGroup",
1380
- "default": "class MoniButtonGroup extends MoniElement { constructor() { super(...arguments); /** * Variante visual del grupo de botones. * - `standard`: Los elementos se espacian normalmente. * - `connected`: Los elementos se unen con bordes colapsados y radios internos aplanados. * @type {'standard' | 'connected'} * @default 'standard' */ this.variant = 'standard'; /** * Tamaño de los botones en el grupo. Si se especifica, se propaga hacia abajo a los hijos. * @type {'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'extra'} * @default 'medium' */ this.size = 'small'; /** * Permite que múltiples botones estén activos a la vez (solo aplica a grupos seleccionables). * @type {boolean} */ this.multi = false; /** * Espacio CSS personalizado entre botones (ej., '1rem'). * Solo se aplica cuando la variante es 'standard'. * @type {string} */ this.gap = ''; /** * Rol ARIA del contenedor del grupo. * @type {'group' | 'toolbar' | 'radiogroup'} * @default 'group' */ this.role = 'group'; /** * Una etiqueta accesible para el grupo (`aria-label`). * @type {string} */ this.label = ''; /** * ID de un elemento que etiqueta este grupo (`aria-labelledby`). * @type {string} */ this.labelledBy = ''; } static { this.styles = [ sharedStyles, css ` :host { display: inline-flex; align-items: center; vertical-align: middle; } .group-container { display: inline-flex; align-items: center; width: 100%; } slot { display: contents; } :host([variant='standard']) .group-container { gap: 0.5rem; } :host([variant='standard'][size='xsmall']) .group-container { gap: 1.125rem; /* 18dp */ } :host([variant='standard'][size='small']) .group-container { gap: 0.75rem; /* 12dp */ } :host([variant='standard'][size='medium']) .group-container { gap: 0.5rem; /* 8dp */ } :host([variant='standard'][size='large']) .group-container { gap: 0.5rem; /* 8dp */ } :host([variant='standard'][size='xlarge']) .group-container, :host([variant='standard'][size='extra']) .group-container { gap: 0.5rem; /* 8dp */ } :host([variant='connected']) .group-container { gap: 0.125rem; /* 2dp */ } ` ]; } /** * Hook del ciclo de vida reactivo (Lit). * Detecta alteraciones estructurales en el propio grupo (como `variant`, `size` o `gap`) * y fuerza una re-sincronización de todos los botones hijos para aplicar las físicas CSS. */ updated(changedProperties) { super.updated(changedProperties); if (changedProperties.has('variant') || changedProperties.has('size') || changedProperties.has('gap')) { this.updateChildren(); } } /** * Manejador del slot interno (`<slot>`). * Cuando el desarrollador inyecta dinámicamente o remueve botones en runtime, * re-sincroniza las formas (shapes) y tamaños de todo el listado. */ handleSlotChange() { this.updateChildren(); } getGapValue(gap) { if (!gap) return ''; const preset = gap.toLowerCase(); if (preset === 'xs' || preset === 'xsmall') return '1.125rem'; if (preset === 's' || preset === 'small') return '0.75rem'; if (preset === 'm' || preset === 'medium') return '0.5rem'; if (preset === 'l' || preset === 'large') return '0.5rem'; if (preset === 'xl' || preset === 'xlarge') return '0.5rem'; return gap; } /** * Sincronizador de Propiedades (Engine interno). * Itera sobre cada botón hijo para transferirle jerárquicamente las configuraciones * del grupo. Si el grupo es del tipo `connected` (botones pegados), evalúa matemáticamente * la posición del índice actual (primero, en medio o último) e inyecta la propiedad `shape` * correspondiente (`left-round-flat`, `inner-round`, etc.) para formar una cápsula unificada. */ updateChildren() { const buttons = this.slottedButtons.filter((el) => el.tagName.toLowerCase() === 'moni-button' || el.tagName.toLowerCase() === 'moni-icon-button'); const normalizedGap = this.gap.trim().toLowerCase(); const hasConnectedGap = !['0', '0px', '0rem', '0em'].includes(normalizedGap); buttons.forEach((btn, index) => { // Propagate size btn.setAttribute('size', this.size); // Propagate shape for connected variant if (this.variant === 'connected') { if (hasConnectedGap) { if (buttons.length === 1) { btn.setAttribute('shape', 'round'); } else if (index === 0) { btn.setAttribute('shape', 'left-round'); } else if (index === buttons.length - 1) { btn.setAttribute('shape', 'right-round'); } else { btn.setAttribute('shape', 'inner-round'); } } else { if (buttons.length === 1) { btn.setAttribute('shape', 'round'); } else if (index === 0) { btn.setAttribute('shape', 'left-round-flat'); } else if (index === buttons.length - 1) { btn.setAttribute('shape', 'right-round-flat'); } else { btn.setAttribute('shape', 'no-round'); } } } else { // Standard variant: buttons retain their own shapes or default round const currentShape = btn.getAttribute('shape'); if (!currentShape || ['left-round-flat', 'right-round-flat', 'no-round', 'left-round', 'right-round', 'inner-round'].includes(currentShape)) { btn.setAttribute('shape', 'round'); } } }); } /** * Maneja la interacción táctil o de mouse (Pointer Down). * Genera un micro-efecto físico (Squish effect). Cuando el usuario presiona un botón * dentro de un listado estándar, empuja físicamente (`scaleX` / `translateX`) a los * botones adyacentes para simular presión material y desplazamiento. */ handlePointerDown(e) { if (this.variant !== 'standard') return; const target = e.target; const button = target.closest('moni-button, moni-icon-button'); if (!button || button.hasAttribute('disabled')) return; const buttons = this.slottedButtons.filter((el) => el.tagName.toLowerCase() === 'moni-button' || el.tagName.toLowerCase() === 'moni-icon-button'); const index = buttons.indexOf(button); if (index === -1) return; const prev = buttons[index - 1]; const next = buttons[index + 1]; if (prev) { prev.style.transition = 'transform 200ms cubic-bezier(0.2, 0, 0, 1)'; prev.style.transform = 'translateX(-6px) scaleX(0.92)'; prev.style.transformOrigin = 'right center'; } if (next) { next.style.transition = 'transform 200ms cubic-bezier(0.2, 0, 0, 1)'; next.style.transform = 'translateX(6px) scaleX(0.92)'; next.style.transformOrigin = 'left center'; } } /** * Conclusión de la interacción táctil (Pointer Up). * Restaura todas las transformaciones elásticas aplicadas a los botones adyacentes * a su estado nativo neutro. */ handlePointerUp() { const buttons = this.slottedButtons.filter((el) => el.tagName.toLowerCase() === 'moni-button' || el.tagName.toLowerCase() === 'moni-icon-button'); buttons.forEach((btn) => { btn.style.transform = ''; btn.style.transformOrigin = ''; }); } /** * Interceptor de clics de delegación. * En grupos de tipo 'segment', garantiza que solo un botón tenga la clase activa * o el atributo encendido de forma excluyente, actuando como un gestor de radio-buttons. */ handleClick(e) { const target = e.target; const clickedButton = target.closest('moni-button, moni-icon-button'); if (!clickedButton || clickedButton.hasAttribute('disabled')) { return; } const buttons = this.slottedButtons.filter((el) => el.tagName.toLowerCase() === 'moni-button' || el.tagName.toLowerCase() === 'moni-icon-button'); if (!this.multi) { buttons.forEach((btn) => { if (btn !== clickedButton) { btn.removeAttribute('active'); btn.active = false; } }); } // Toggle clicked button active state const wasActive = clickedButton.hasAttribute('active'); if (wasActive) { clickedButton.removeAttribute('active'); clickedButton.active = false; } else { clickedButton.setAttribute('active', ''); clickedButton.active = true; } this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true, detail: { button: clickedButton, active: !wasActive } })); } /** * Renders the button group container with `role=\"group\"` semantics. * * The group is a flex container that distributes its slotted `<moni-button>` * or `<moni-button-segment>` children in a row or column (based on `orientation`). * CSS handles the shared-border collapse between adjacent segments: * - Middle segments use `border-radius: 0` and `margin-inline-start: -1px` * to prevent doubled borders. * - `_syncSegments()` in `firstUpdated()` injects `left-round` / `right-round` * shape attributes into the first and last children for correct pill termination. */ render() { const resolvedGap = this.getGapValue(this.gap); const normalizedGap = this.gap.trim().toLowerCase(); const isConnected = ['0', '0px', '0rem', '0em'].includes(normalizedGap); const inlineStyles = resolvedGap ? `gap: ${resolvedGap};` : isConnected ? 'gap: 0;' : ''; // M3 connected button group a11y: role=\"group\" by default. Consumers // can override via the `role` attribute (e.g. \"toolbar\" for app // actions) and provide an aria-label or aria-labelledby. return html ` <div class=\"group-container\" style=${inlineStyles} part=\"container\" role=${this.role} aria-label=${this.label || nothing} aria-labelledby=${this.labelledBy || nothing} @pointerdown=${this.handlePointerDown} @pointerup=${this.handlePointerUp} @pointercancel=${this.handlePointerUp} @pointerleave=${this.handlePointerUp} > <slot @slotchange=${this.handleSlotChange} @click=${this.handleClick}></slot> </div> `; } }",
1380
+ "default": "class MoniButtonGroup extends MoniElement { constructor() { super(...arguments); this._activeObserver = new MutationObserver(() => this.updateSelectionNeighbors()); /** * Variante visual del grupo de botones. * - `standard`: Los elementos se espacian normalmente. * - `connected`: Los elementos se unen con bordes colapsados y radios internos aplanados. * @type {'standard' | 'connected'} * @default 'standard' */ this.variant = 'standard'; /** * Tamaño de los botones en el grupo. Si se especifica, se propaga hacia abajo a los hijos. * @type {'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'extra'} * @default 'medium' */ this.size = 'small'; /** * Permite que múltiples botones estén activos a la vez (solo aplica a grupos seleccionables). * @type {boolean} */ this.multi = false; /** Impide que el grupo quede sin selección. */ this.selectionRequired = false; /** Forma base común de los botones. La selección invierte round ↔ square. */ this.shape = 'round'; /** Controla si los botones conservan su ancho intrínseco o llenan la superficie. */ this.resizing = 'fixed'; /** * Espacio CSS personalizado entre botones (ej., '1rem'). * Solo se aplica cuando la variante es 'standard'. * @type {string} */ this.gap = ''; /** * Rol ARIA del contenedor del grupo. * @type {'group' | 'toolbar' | 'radiogroup'} * @default 'group' */ this.role = 'group'; /** * Una etiqueta accesible para el grupo (`aria-label`). * @type {string} */ this.label = ''; /** * ID de un elemento que etiqueta este grupo (`aria-labelledby`). * @type {string} */ this.labelledBy = ''; } connectedCallback() { super.connectedCallback(); this._activeObserver.observe(this, { subtree: true, attributes: true, attributeFilter: ['active'] }); } disconnectedCallback() { this._activeObserver.disconnect(); super.disconnectedCallback(); } static { this.styles = [ sharedStyles, css ` :host { display: inline-flex; align-items: center; vertical-align: middle; max-inline-size: 100%; } :host([variant='connected']), :host([resizing='flexible']) { inline-size: 100%; } .group-container { display: inline-flex; align-items: center; width: 100%; flex-wrap: nowrap; } slot { display: contents; } :host([variant='standard']) .group-container { gap: 0.5rem; } :host([variant='standard'][size='xsmall']) .group-container { gap: 1.125rem; /* 18dp */ } :host([variant='standard'][size='small']) .group-container { gap: 0.75rem; /* 12dp */ } :host([variant='standard'][size='medium']) .group-container { gap: 0.5rem; /* 8dp */ } :host([variant='standard'][size='large']) .group-container { gap: 0.5rem; /* 8dp */ } :host([variant='standard'][size='xlarge']) .group-container, :host([variant='standard'][size='extra']) .group-container { gap: 0.5rem; /* 8dp */ } :host([variant='connected']) .group-container { gap: 0.125rem; /* 2dp */ } :host([variant='connected']) ::slotted(moni-button), :host([variant='connected']) ::slotted(moni-icon-button), :host([resizing='flexible']) ::slotted(moni-button) { flex: 1 1 0; } :host([variant='connected']) ::slotted(moni-button) { --moni-button-inline-size: 100%; min-inline-size: 0; } :host([variant='connected'][size='xsmall']) ::slotted(moni-button), :host([variant='connected'][size='xsmall']) ::slotted(moni-icon-button), :host([variant='connected'][size='small']) ::slotted(moni-button), :host([variant='connected'][size='small']) ::slotted(moni-icon-button) { min-inline-size: 3rem; } /* M3 Expressive selection emphasis. Increasing the button's internal * space (instead of scaling the host) keeps labels and icons crisp and * lets the existing padding transition transfer width smoothly. */ :host([variant='standard'][size='xsmall']) ::slotted(moni-button[active]), :host([variant='standard'][size='xsmall']) ::slotted(moni-button[data-group-pressed]) { --moni-button-padding: 0 1.25rem; } :host([variant='standard'][size='small']) ::slotted(moni-button[active]), :host([variant='standard'][size='small']) ::slotted(moni-button[data-group-pressed]) { --moni-button-padding: 0 1.75rem; } :host([variant='standard'][size='medium']) ::slotted(moni-button[active]), :host([variant='standard'][size='medium']) ::slotted(moni-button[data-group-pressed]) { --moni-button-padding: 0 2.375rem; } :host([variant='standard'][size='large']) ::slotted(moni-button[active]), :host([variant='standard'][size='large']) ::slotted(moni-button[data-group-pressed]) { --moni-button-padding: 0 4rem; } :host([variant='standard'][size='xlarge']) ::slotted(moni-button[active]), :host([variant='standard'][size='extra']) ::slotted(moni-button[active]), :host([variant='standard'][size='xlarge']) ::slotted(moni-button[data-group-pressed]), :host([variant='standard'][size='extra']) ::slotted(moni-button[data-group-pressed]) { --moni-button-padding: 0 5.25rem; } :host([variant='standard'][size='xsmall']) ::slotted(moni-button[data-group-adjacent-pressed]) { --moni-button-padding: 0 .5rem; } :host([variant='standard'][size='small']) ::slotted(moni-button[data-group-adjacent-pressed]) { --moni-button-padding: 0 .75rem; } :host([variant='standard'][size='medium']) ::slotted(moni-button[data-group-adjacent-pressed]) { --moni-button-padding: 0 1.25rem; } :host([variant='standard'][size='large']) ::slotted(moni-button[data-group-adjacent-pressed]) { --moni-button-padding: 0 2.75rem; } :host([variant='standard'][size='xlarge']) ::slotted(moni-button[data-group-adjacent-pressed]), :host([variant='standard'][size='extra']) ::slotted(moni-button[data-group-adjacent-pressed]) { --moni-button-padding: 0 3.75rem; } /* A selected standard button borrows space from its immediate * neighbours, keeping the group's outside footprint stable. */ :host([variant='standard'][size='xsmall']) ::slotted(moni-button[data-group-adjacent-selected='shared']) { --moni-button-padding: 0 .5rem; } :host([variant='standard'][size='xsmall']) ::slotted(moni-button[data-group-adjacent-selected='sole']) { --moni-button-padding: 0 .25rem; } :host([variant='standard'][size='small']) ::slotted(moni-button[data-group-adjacent-selected='shared']) { --moni-button-padding: 0 .625rem; } :host([variant='standard'][size='small']) ::slotted(moni-button[data-group-adjacent-selected='sole']) { --moni-button-padding: 0 .25rem; } :host([variant='standard'][size='medium']) ::slotted(moni-button[data-group-adjacent-selected='shared']) { --moni-button-padding: 0 1.0625rem; } :host([variant='standard'][size='medium']) ::slotted(moni-button[data-group-adjacent-selected='sole']) { --moni-button-padding: 0 .625rem; } :host([variant='standard'][size='large']) ::slotted(moni-button[data-group-adjacent-selected='shared']) { --moni-button-padding: 0 2.5rem; } :host([variant='standard'][size='large']) ::slotted(moni-button[data-group-adjacent-selected='sole']) { --moni-button-padding: 0 2rem; } :host([variant='standard'][size='xlarge']) ::slotted(moni-button[data-group-adjacent-selected='shared']), :host([variant='standard'][size='extra']) ::slotted(moni-button[data-group-adjacent-selected='shared']) { --moni-button-padding: 0 3.375rem; } :host([variant='standard'][size='xlarge']) ::slotted(moni-button[data-group-adjacent-selected='sole']), :host([variant='standard'][size='extra']) ::slotted(moni-button[data-group-adjacent-selected='sole']) { --moni-button-padding: 0 2.75rem; } ` ]; } /** * Hook del ciclo de vida reactivo (Lit). * Detecta alteraciones estructurales en el propio grupo (como `variant`, `size` o `gap`) * y fuerza una re-sincronización de todos los botones hijos para aplicar las físicas CSS. */ updated(changedProperties) { super.updated(changedProperties); if (changedProperties.has('variant') || changedProperties.has('size') || changedProperties.has('gap') || changedProperties.has('shape')) { this.updateChildren(); } } /** * Manejador del slot interno (`<slot>`). * Cuando el desarrollador inyecta dinámicamente o remueve botones en runtime, * re-sincroniza las formas (shapes) y tamaños de todo el listado. */ handleSlotChange() { this.updateChildren(); } getButtons() { return (this.slottedButtons ?? []).filter((el) => el.tagName.toLowerCase() === 'moni-button' || el.tagName.toLowerCase() === 'moni-icon-button'); } /** Keeps selection emphasis inside the group's original visual footprint. * The selected width is funded evenly by every unselected button so edge * selections remain visually balanced instead of collapsing one neighbour. */ updateSelectionNeighbors() { const buttons = this.getButtons(); buttons.forEach((button) => button.removeAttribute('data-group-adjacent-selected')); if (this.variant !== 'standard') return; const selected = buttons.filter((button) => button.hasAttribute('active')); if (selected.length === 0) return; const unselected = buttons.filter((button) => !button.hasAttribute('active')); const weight = unselected.length === 1 ? 'sole' : 'shared'; unselected.forEach((button) => button.setAttribute('data-group-adjacent-selected', weight)); } getGapValue(gap) { if (!gap) return ''; const preset = gap.toLowerCase(); if (preset === 'xs' || preset === 'xsmall') return '1.125rem'; if (preset === 's' || preset === 'small') return '0.75rem'; if (preset === 'm' || preset === 'medium') return '0.5rem'; if (preset === 'l' || preset === 'large') return '0.5rem'; if (preset === 'xl' || preset === 'xlarge') return '0.5rem'; return gap; } /** * Sincronizador de Propiedades (Engine interno). * Itera sobre cada botón hijo para transferirle jerárquicamente las configuraciones * del grupo. Si el grupo es del tipo `connected` (botones pegados), evalúa matemáticamente * la posición del índice actual (primero, en medio o último) e inyecta la propiedad `shape` * correspondiente (`left-round-flat`, `inner-round`, etc.) para formar una cápsula unificada. */ updateChildren() { const buttons = this.getButtons(); const normalizedGap = this.gap.trim().toLowerCase(); const hasConnectedGap = !['0', '0px', '0rem', '0em'].includes(normalizedGap); buttons.forEach((btn, index) => { // Propagate size btn.setAttribute('size', this.size); // Propagate shape for connected variant if (this.variant === 'connected' && this.shape === 'square') { btn.setAttribute('shape', 'square'); } else if (this.variant === 'connected') { if (hasConnectedGap) { if (buttons.length === 1) { btn.setAttribute('shape', 'round'); } else if (index === 0) { btn.setAttribute('shape', 'left-round'); } else if (index === buttons.length - 1) { btn.setAttribute('shape', 'right-round'); } else { btn.setAttribute('shape', 'inner-round'); } } else { if (buttons.length === 1) { btn.setAttribute('shape', 'round'); } else if (index === 0) { btn.setAttribute('shape', 'left-round-flat'); } else if (index === buttons.length - 1) { btn.setAttribute('shape', 'right-round-flat'); } else { btn.setAttribute('shape', 'no-round'); } } } else { btn.setAttribute('shape', this.shape); } }); this.updateSelectionNeighbors(); } /** * Maneja la interacción táctil o de mouse (Pointer Down). * Genera un micro-efecto físico (Squish effect). Cuando el usuario presiona un botón * dentro de un listado estándar, empuja físicamente (`scaleX` / `translateX`) a los * botones adyacentes para simular presión material y desplazamiento. */ handlePointerDown(e) { if (this.variant !== 'standard') return; const target = e.target; const button = target.closest('moni-button, moni-icon-button'); if (!button || button.hasAttribute('disabled')) return; const buttons = this.getButtons(); const index = buttons.indexOf(button); if (index === -1) return; button.setAttribute('data-group-pressed', ''); buttons[index - 1]?.setAttribute('data-group-adjacent-pressed', ''); buttons[index + 1]?.setAttribute('data-group-adjacent-pressed', ''); } /** * Conclusión de la interacción táctil (Pointer Up). * Restaura todas las transformaciones elásticas aplicadas a los botones adyacentes * a su estado nativo neutro. */ handlePointerUp() { const buttons = this.getButtons(); buttons.forEach((btn) => { btn.removeAttribute('data-group-pressed'); btn.removeAttribute('data-group-adjacent-pressed'); }); } /** * Interceptor de clics de delegación. * En grupos de tipo 'segment', garantiza que solo un botón tenga la clase activa * o el atributo encendido de forma excluyente, actuando como un gestor de radio-buttons. */ handleClick(e) { const target = e.target; const clickedButton = target.closest('moni-button, moni-icon-button'); if (!clickedButton || clickedButton.hasAttribute('disabled')) { return; } const buttons = this.getButtons(); if (!this.multi) { buttons.forEach((btn) => { if (btn !== clickedButton) { btn.removeAttribute('active'); btn.active = false; } }); } // Toggle clicked button active state const wasActive = clickedButton.hasAttribute('active'); const activeCount = buttons.filter((btn) => btn.hasAttribute('active')).length; if (wasActive && this.selectionRequired && activeCount <= 1) return; if (wasActive) { clickedButton.removeAttribute('active'); clickedButton.active = false; } else { clickedButton.setAttribute('active', ''); clickedButton.active = true; } this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true, detail: { button: clickedButton, active: !wasActive } })); } /** * Renders the button group container with `role=\"group\"` semantics. * * The group is a flex container that distributes its slotted `<moni-button>` * or `<moni-button-segment>` children in a row or column (based on `orientation`). * CSS handles the shared-border collapse between adjacent segments: * - Middle segments use `border-radius: 0` and `margin-inline-start: -1px` * to prevent doubled borders. * - `_syncSegments()` in `firstUpdated()` injects `left-round` / `right-round` * shape attributes into the first and last children for correct pill termination. */ render() { const resolvedGap = this.getGapValue(this.gap); const normalizedGap = this.gap.trim().toLowerCase(); const isConnected = ['0', '0px', '0rem', '0em'].includes(normalizedGap); const inlineStyles = resolvedGap ? `gap: ${resolvedGap};` : isConnected ? 'gap: 0;' : ''; // M3 connected button group a11y: role=\"group\" by default. Consumers // can override via the `role` attribute (e.g. \"toolbar\" for app // actions) and provide an aria-label or aria-labelledby. return html ` <div class=\"group-container\" style=${inlineStyles} part=\"container\" role=${this.role} aria-label=${this.label || nothing} aria-labelledby=${this.labelledBy || nothing} @pointerdown=${this.handlePointerDown} @pointerup=${this.handlePointerUp} @pointercancel=${this.handlePointerUp} @pointerleave=${this.handlePointerUp} > <slot @slotchange=${this.handleSlotChange} @click=${this.handleClick}></slot> </div> `; } }",
1381
1381
  "description": "Componente Material Design 3 Button Group.\n\nOrganiza múltiples componentes `<moni-button>` o `<moni-icon-button>`\nen una sola fila.\n\n**Variantes:**\n- `standard` (por defecto) — Una fila flex simple con un espacio entre botones.\n- `connected` — El reemplazo de M3 Expressive para los botones segmentados. En este\n modo, los botones comparten bordes y forman una sola forma de píldora continua. El\n grupo gestiona el estado de selección única/múltiple de sus hijos.\n\n**Detalles de la variante `connected`:**\n- **Propagación de forma:** El grupo propaga automáticamente las clases de forma M3\n (`left-round-flat`, `no-round`, `right-round-flat`) a sus hijos para\n que se entrelacen sin problemas.\n- **Gestión del interruptor:** El grupo escucha los clics de los hijos y cambia sus\n atributos `active`. Cuando `multi=false` (por defecto), solo un botón puede estar\n activo a la vez (comportamiento de radio button). Cuando `multi=true`, múltiples botones\n pueden estar activos (comportamiento de checkbox).\n- **Propagación de eventos:** Dispara un evento `'change'` cuando la selección cambia.\n\n**Accesibilidad:**\n- Renderiza con `role=\"group\"` (puede ser sobrescrito a `toolbar` o `radiogroup`).\n- Los consumidores deben proporcionar un atributo `aria-label` o `aria-labelledby`\n para identificar el propósito del grupo para las tecnologías de asistencia."
1382
1382
  }
1383
1383
  ],
@@ -1438,7 +1438,7 @@
1438
1438
  {
1439
1439
  "kind": "variable",
1440
1440
  "name": "MoniButton",
1441
- "default": "class MoniButton extends MoniElement { constructor() { super(...arguments); this._hasLabel = false; /** * Variante visual del botón. * @type {'filled' | 'tonal' | 'outlined' | 'text' | 'fill' | 'elevated'} * @default 'filled' */ this.variant = 'filled'; /** * Tamaños de M3 Expressive: xsmall, small, medium, large, xlarge. * `extra` es un alias heredado de Moni para `xlarge`, obsoleto en v0.3.1; se * eliminará en v1.0. Ver `m3-docs/components/buttons/overview.md` § Sizes. * @type {'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'extra'} * @default 'medium' */ this.size = 'small'; /** * Forma de las esquinas del botón. * @type {'round' | 'no-round' | 'square' | 'circle' | 'left-round' | 'right-round' | 'top-round' | 'bottom-round' | 'left-round-flat' | 'right-round-flat' | 'top-round-flat' | 'bottom-round-flat' | 'inner-round'} * @default 'round' */ this.shape = 'round'; /** * Deshabilita el botón. * @type {boolean} */ this.disabled = false; /** * Si es verdadero, muestra un indicador de carga y deshabilita el botón. * @type {boolean} */ this.loading = false; /** * Si es verdadero, establece el botón a un estado activo/seleccionado. * @type {boolean} */ this.active = false; /** * Nombre del icono inicial (Material Symbols). * @type {string} */ this.icon = ''; /** * Nombre del icono final (Material Symbols). * @type {string} */ this.iconTrailing = ''; /** * Tipo nativo del botón (cuando se renderiza como un `<button>`). * @type {'button' | 'submit' | 'reset'} * @default 'button' */ this.type = 'button'; /** * Si está establecido, renderiza el botón como un elemento `<a>` con esta URL. * @type {string} */ this.href = ''; /** * Atributo target para los botones tipo enlace. * @type {string} */ this.target = ''; } static { MoniButton_1 = this; } static { this._deprecationWarned = false; } connectedCallback() { super.connectedCallback(); this._hasLabel = this._hasDefaultSlotContent(this.childNodes); if (MoniButton_1._deprecationWarned) return; if (this.size === 'extra') { MoniButton_1._deprecationWarned = true; console.warn('[moni-ui] <moni-button size=\"extra\"> is deprecated since v0.3.1. ' + 'Material Design 3 Expressive uses \"xlarge\". ' + 'See m3-docs/components/buttons/overview.md § Sizes.'); } } _hasDefaultSlotContent(nodes) { return Array.from(nodes).some((node) => { if (node.nodeType === Node.TEXT_NODE) return Boolean(node.textContent?.trim()); return node instanceof Element && !node.hasAttribute('slot'); }); } _handleLabelSlotChange(event) { const slot = event.currentTarget; this._hasLabel = this._hasDefaultSlotContent(slot.assignedNodes({ flatten: true })); } static { this.styles = [ sharedStyles, css ` :host { display: inline-flex; vertical-align: middle; font-family: var(--font); position: relative; user-select: none; } .button { box-sizing: var(--moni-button-box-sizing, border-box); display: inline-flex; --_button-visual-height: 3.5rem; align-items: center; justify-content: center; --_button-icon-size: var(--moni-button-icon-size, 1.5rem); block-size: var(--moni-button-height, 3.5rem); font-size: var(--moni-button-font-size, 1rem); font-weight: 500; color: var(--on-primary); padding: var(--moni-button-padding, 0 1.5rem); background-color: var(--primary); border: 0.0625rem solid transparent; border-radius: 999px; transition: transform 250ms cubic-bezier(0.2, 0, 0, 1), border-radius 200ms cubic-bezier(0.2, 0, 0, 1), padding 250ms cubic-bezier(0.2, 0, 0, 1), background-color 200ms cubic-bezier(0.2, 0, 0, 1), box-shadow 200ms cubic-bezier(0.2, 0, 0, 1); user-select: none; gap: var(--moni-button-gap, 0.5rem); line-height: normal; font-family: inherit; cursor: pointer; text-decoration: none; box-shadow: none; position: relative; } .button::after { content: ''; position: absolute; top: 50%; left: 50%; min-width: 3rem; /* 48px minimum touch target */ min-height: 3rem; /* 48px minimum touch target */ width: 100%; height: 100%; transform: translate(-50%, -50%); } /* Sizes */ .button.xsmall { --_button-visual-height: 2rem; --_button-icon-size: var(--moni-button-icon-size, 1.25rem); block-size: var(--moni-button-height, 2rem); min-inline-size: var(--moni-button-min-inline-size, 0); font-size: var(--moni-button-font-size, 0.875rem); padding: var(--moni-button-padding, 0 0.75rem); /* 12dp; keeps 48dp touch target */ gap: var(--moni-button-gap, 0.25rem); } .button.small { --_button-visual-height: 2.5rem; --_button-icon-size: var(--moni-button-icon-size, 1.25rem); block-size: var(--moni-button-height, 2.5rem); min-inline-size: var(--moni-button-min-inline-size, 0); padding: var(--moni-button-padding, 0 1rem); gap: var(--moni-button-gap, 0.5rem); } .button.large { --_button-visual-height: 6rem; --_button-icon-size: var(--moni-button-icon-size, 2rem); block-size: var(--moni-button-height, 6rem); font-size: var(--moni-button-font-size, 1.5rem); padding: var(--moni-button-padding, 0 3rem); gap: var(--moni-button-gap, 0.75rem); } .button.xlarge, .button.extra { --_button-visual-height: 8.5rem; --_button-icon-size: var(--moni-button-icon-size, 2.5rem); block-size: var(--moni-button-height, 8.5rem); font-size: var(--moni-button-font-size, 2rem); padding: var(--moni-button-padding, 0 4rem); gap: var(--moni-button-gap, 1rem); } /* Shapes */ /* Square keeps horizontal padding (like a normal button) but drops border-radius. Only the circle removes all padding. */ .button.circle { padding: 0 !important; border-radius: 50%; inline-size: var(--moni-button-height, 3.5rem); min-inline-size: var(--moni-button-height, 3.5rem); aspect-ratio: 1; } .button.round.icon-only { inline-size: var(--moni-button-height, var(--_button-visual-height)); min-inline-size: var(--moni-button-height, var(--_button-visual-height)); padding-inline: 0; } .button.no-round { border-radius: 0; } .button.square { border-radius: 1rem; } .button.square.xsmall, .button.square.small { border-radius: 0.75rem; } .button.square.large, .button.square.xlarge, .button.square.extra { border-radius: 1.75rem; } .button.left-round { border-radius: 1.25rem 0.5rem 0.5rem 1.25rem; } .button.left-round.xsmall { border-radius: 0.875rem 0.25rem 0.25rem 0.875rem; } .button.left-round.small { border-radius: 1rem 0.5rem 0.5rem 1rem; } .button.left-round.large { border-radius: 1.5rem 1rem 1rem 1.5rem; } .button.left-round.xlarge, .button.left-round.extra { border-radius: 1.75rem 1.25rem 1.25rem 1.75rem; } .button.right-round { border-radius: 0.5rem 1.25rem 1.25rem 0.5rem; } .button.right-round.xsmall { border-radius: 0.25rem 0.875rem 0.875rem 0.25rem; } .button.right-round.small { border-radius: 0.5rem 1rem 1rem 0.5rem; } .button.right-round.large { border-radius: 1rem 1.5rem 1.5rem 1rem; } .button.right-round.xlarge, .button.right-round.extra { border-radius: 1.25rem 1.75rem 1.75rem 1.25rem; } .button.top-round { border-radius: 1.25rem 1.25rem 0.5rem 0.5rem; } .button.top-round.xsmall { border-radius: 0.875rem 0.875rem 0.25rem 0.25rem; } .button.top-round.small { border-radius: 1rem 1rem 0.5rem 0.5rem; } .button.top-round.large { border-radius: 1.5rem 1.5rem 1rem 1rem; } .button.top-round.xlarge, .button.top-round.extra { border-radius: 1.75rem 1.75rem 1.25rem 1.25rem; } .button.bottom-round { border-radius: 0.5rem 0.5rem 1.25rem 1.25rem; } .button.bottom-round.xsmall { border-radius: 0.25rem 0.25rem 0.875rem 0.875rem; } .button.bottom-round.small { border-radius: 0.5rem 0.5rem 1rem 1rem; } .button.bottom-round.large { border-radius: 1rem 1rem 1.5rem 1.5rem; } .button.bottom-round.xlarge, .button.bottom-round.extra { border-radius: 1.25rem 1.25rem 1.75rem 1.75rem; } /* Morph active states (square-ish equivalents) */ .button.left-square { border-radius: 0.75rem 0.5rem 0.5rem 0.75rem; } .button.left-square.xsmall { border-radius: 0.5rem 0.25rem 0.25rem 0.5rem; } .button.left-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.left-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.left-square.xlarge, .button.left-square.extra { border-radius: 1rem 1.25rem 1.25rem 1rem; } .button.right-square { border-radius: 0.5rem 0.75rem 0.75rem 0.5rem; } .button.right-square.xsmall { border-radius: 0.25rem 0.5rem 0.5rem 0.25rem; } .button.right-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.right-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.right-square.xlarge, .button.right-square.extra { border-radius: 1.25rem 1rem 1rem 1.25rem; } .button.top-square { border-radius: 0.75rem 0.75rem 0.5rem 0.5rem; } .button.top-square.xsmall { border-radius: 0.5rem 0.5rem 0.25rem 0.25rem; } .button.top-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.top-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.top-square.xlarge, .button.top-square.extra { border-radius: 1rem 1rem 1.25rem 1.25rem; } .button.bottom-square { border-radius: 0.5rem 0.5rem 0.75rem 0.75rem; } .button.bottom-square.xsmall { border-radius: 0.25rem 0.25rem 0.5rem 0.5rem; } .button.bottom-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.bottom-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.bottom-square.xlarge, .button.bottom-square.extra { border-radius: 1.25rem 1.25rem 1rem 1rem; } .button.inner-round { border-radius: 0.5rem; } .button.inner-round.xsmall { border-radius: 0.25rem; } .button.inner-round.small { border-radius: 0.5rem; } .button.inner-round.large { border-radius: 1rem; } .button.inner-round.xlarge, .button.inner-round.extra { border-radius: 1.25rem; } /* Flat variants (completely 0px on flat sides) */ .button.left-round-flat { border-radius: 1.25rem 0 0 1.25rem; } .button.left-round-flat.xsmall { border-radius: 0.875rem 0 0 0.875rem; } .button.left-round-flat.small { border-radius: 1rem 0 0 1rem; } .button.left-round-flat.large { border-radius: 1.5rem 0 0 1.5rem; } .button.left-round-flat.xlarge, .button.left-round-flat.extra { border-radius: 1.75rem 0 0 1.75rem; } .button.right-round-flat { border-radius: 0 1.25rem 1.25rem 0; } .button.right-round-flat.xsmall { border-radius: 0 0.875rem 0.875rem 0; } .button.right-round-flat.small { border-radius: 0 1rem 1rem 0; } .button.right-round-flat.large { border-radius: 0 1.5rem 1.5rem 0; } .button.right-round-flat.xlarge, .button.right-round-flat.extra { border-radius: 0 1.75rem 1.75rem 0; } .button.top-round-flat { border-radius: 1.25rem 1.25rem 0 0; } .button.top-round-flat.xsmall { border-radius: 0.875rem 0.875rem 0 0; } .button.top-round-flat.small { border-radius: 1rem 1rem 0 0; } .button.top-round-flat.large { border-radius: 1.5rem 1.5rem 0 0; } .button.top-round-flat.xlarge, .button.top-round-flat.extra { border-radius: 1.75rem 1.75rem 0 0; } .button.bottom-round-flat { border-radius: 0 0 1.25rem 1.25rem; } .button.bottom-round-flat.xsmall { border-radius: 0 0 0.875rem 0.875rem; } .button.bottom-round-flat.small { border-radius: 0 0 1rem 1rem; } .button.bottom-round-flat.large { border-radius: 0 0 1.5rem 1.5rem; } .button.bottom-round-flat.xlarge, .button.bottom-round-flat.extra { border-radius: 0 0 1.75rem 1.75rem; } /* Morph active states for flat variants */ .button.left-square-flat { border-radius: 0.75rem 0 0 0.75rem; } .button.left-square-flat.xsmall, .button.left-square-flat.small { border-radius: 0.5rem 0 0 0.5rem; } .button.left-square-flat.large, .button.left-square-flat.xlarge, .button.left-square-flat.extra { border-radius: 1rem 0 0 1rem; } .button.right-square-flat { border-radius: 0 0.75rem 0.75rem 0; } .button.right-square-flat.xsmall, .button.right-square-flat.small { border-radius: 0 0.5rem 0.5rem 0; } .button.right-square-flat.large, .button.right-square-flat.xlarge, .button.right-square-flat.extra { border-radius: 0 1rem 1rem 0; } .button.top-square-flat { border-radius: 0.75rem 0.75rem 0 0; } .button.top-square-flat.xsmall, .button.top-square-flat.small { border-radius: 0.5rem 0.5rem 0 0; } .button.top-square-flat.large, .button.top-square-flat.xlarge, .button.top-square-flat.extra { border-radius: 1rem 1rem 0 0; } .button.bottom-square-flat { border-radius: 0 0 0.75rem 0.75rem; } .button.bottom-square-flat.xsmall, .button.bottom-square-flat.small { border-radius: 0 0 0.5rem 0.5rem; } .button.bottom-square-flat.large, .button.bottom-square-flat.xlarge, .button.bottom-square-flat.extra { border-radius: 0 0 1rem 1rem; } /* Circle hides the label visually (slot still present for a11y and SSR fallback). */ :host([shape='circle']) [part='label'] { display: none; } /* Variants */ .button.border { border: 0.0625rem solid var(--outline-variant); color: var(--primary); background-color: transparent; } .button.fill { background-color: var(--secondary-container); color: var(--on-secondary-container); } .button.transparent { color: var(--primary); background-color: transparent; } .button.elevated { background-color: var(--surface-container-low, var(--surface)); color: var(--primary); box-shadow: var(--elevate1); } .button.error { background-color: var(--error); color: var(--on-error); } .button.error:hover:not(:disabled) { background-image: linear-gradient(color-mix(in srgb, currentColor 8%, transparent), color-mix(in srgb, currentColor 8%, transparent)); } .button.error:focus-visible { outline-color: var(--error); } /* Disabled / active */ .button:disabled, .button[aria-disabled='true'] { opacity: 0.5; cursor: not-allowed; pointer-events: none; } .button:hover:not(:disabled) { transform: translateY(-0.0625rem); background-image: linear-gradient(color-mix(in srgb, currentColor 8%, transparent), color-mix(in srgb, currentColor 8%, transparent)); } .button.elevated:hover:not(:disabled) { box-shadow: var(--elevate2); } .button:active:not(:disabled) { border-radius: 0.75rem; transform: translateY(0.0625rem) scale(0.97); background-image: linear-gradient(color-mix(in srgb, currentColor 12%, transparent), color-mix(in srgb, currentColor 12%, transparent)); } .button.elevated:active:not(:disabled) { box-shadow: none; } .button.xsmall:active:not(:disabled) { border-radius: 0.5rem; } .button.small:active:not(:disabled) { border-radius: 0.5rem; } .button.large:active:not(:disabled) { border-radius: 1rem; } .button.xlarge:active:not(:disabled), .button.extra:active:not(:disabled) { border-radius: 1rem; } .button.active:not(:disabled) { background-color: var(--primary-container); color: var(--on-primary-container); } .button.fill.active:not(:disabled) { background-color: var(--secondary); color: var(--on-secondary); } .button.border.active:not(:disabled) { background-color: var(--inverse-surface); color: var(--inverse-on-surface); border-color: var(--inverse-surface); } .button.elevated.active:not(:disabled) { background-color: var(--primary-container); color: var(--on-primary-container); box-shadow: var(--elevate2); } .button:focus-visible { outline: 0.125rem solid var(--primary); outline-offset: 0.125rem; } /* Icon helper */ .icon, ::slotted([slot='icon']), ::slotted([slot='icon-trailing']) { display: inline-flex; align-items: center; justify-content: center; font-size: var(--_button-icon-size); inline-size: var(--_button-icon-size); block-size: var(--_button-icon-size); flex: none; color: inherit; } .icon { --moni-icon-size: var(--_button-icon-size); font-size: var(--_button-icon-size); inline-size: var(--_button-icon-size); block-size: var(--_button-icon-size); transform: translateX(var(--moni-button-icon-offset, 0)); } .button.left-round { border-radius: 999px var(--_button-square-radius, 1rem) var(--_button-square-radius, 1rem) 999px; } .button.right-round { border-radius: var(--_button-square-radius, 1rem) 999px 999px var(--_button-square-radius, 1rem); } .button.top-round { border-radius: 999px 999px var(--_button-square-radius, 1rem) var(--_button-square-radius, 1rem); } .button.bottom-round { border-radius: var(--_button-square-radius, 1rem) var(--_button-square-radius, 1rem) 999px 999px; } .button.left-round-flat { border-radius: 999px 0 0 999px; } .button.right-round-flat { border-radius: 0 999px 999px 0; } .button.top-round-flat { border-radius: 999px 999px 0 0; } .button.bottom-round-flat { border-radius: 0 0 999px 999px; } .button.xsmall, .button.small { --_button-square-radius: 0.75rem; } .button.large, .button.xlarge, .button.extra { --_button-square-radius: 1.75rem; } :host([split-position]) .button { inline-size: var(--moni-button-inline-size); min-inline-size: var(--moni-button-min-inline-size); border-radius: var(--moni-button-border-radius); } slot { display: contents; } [part='label'][hidden] { display: none; } /* Loading spinner */ .loading-spinner { inline-size: 1.125rem; block-size: 1.125rem; color: currentColor; } .loading-spinner::part(progress) { border-width: 0.1875rem; } ` ]; } /** * Intercepta los clicks en botones tipo enlace (`href=\"...\"`). * Si el botón está desactivado (`disabled`), previene agresivamente * tanto la navegación por defecto del navegador como la burbuja de eventos. */ handleLinkClick(e) { if (this.disabled) { e.preventDefault(); e.stopPropagation(); } } /** * Assembles the button's Shadow DOM structure for each Lit render cycle. * * **Variant → CSS class mapping:** * | `variant` | CSS class | Visual result | * |--------------|---------------|-----------------------------| * | `'filled'` | `''` (empty) | Primary filled button | * | `'outlined'` | `'border'` | Outlined/stroked button | * | `'tonal'` | `'fill'` | Secondary container fill | * | `'text'` | `'transparent'` | Ghost/text button | * | `'elevated'` | `'elevated'` | Tonal surface + shadow | * * **Shape morphing:** * When `active=true`, the button inverts its corner-radius shape to create * a micro-animation where clicking \"morphs\" the shape (round → square, etc.). * This is used in toggle-button patterns (e.g., a nav-item that morphs to * indicate the current page). * * **Icon resolution priority:** * 1. `loading=true` → renders a `<moni-progress>` spinner (hides label at 50% opacity). * 2. `icon` attribute → renders a `<moni-icon>` in the leading icon slot. * 3. Otherwise → renders the `icon` named slot for slotted content. * * **Link vs. button:** * When `href` is set, the root element is an `<a>` tag (with disabled click * guard) for proper browser navigation semantics. Otherwise it's a `<button>` * with `type=\"button\"` to prevent accidental form submission. */ render() { let variantClass = ''; if (this.variant === 'outlined') variantClass = 'border'; else if (this.variant === 'tonal' || this.variant === 'fill') variantClass = 'fill'; else if (this.variant === 'text') variantClass = 'transparent'; else if (this.variant === 'elevated') variantClass = 'elevated'; else if (this.variant === 'error') variantClass = 'error'; let activeShape = this.shape; if (this.active) { if (this.shape === 'round') activeShape = 'square'; else if (this.shape === 'square') activeShape = 'round'; else if (this.shape === 'left-round') activeShape = 'left-square'; else if (this.shape === 'right-round') activeShape = 'right-square'; else if (this.shape === 'top-round') activeShape = 'top-square'; else if (this.shape === 'bottom-round') activeShape = 'bottom-square'; else if (this.shape === 'left-round-flat') activeShape = 'left-square-flat'; else if (this.shape === 'right-round-flat') activeShape = 'right-square-flat'; else if (this.shape === 'top-round-flat') activeShape = 'top-square-flat'; else if (this.shape === 'bottom-round-flat') activeShape = 'bottom-square-flat'; else if (this.shape === 'inner-round') activeShape = 'inner-round'; // remains inner-round or standard shape morphing } const classes = [ 'button', variantClass, this.size, activeShape, this.active ? 'active' : '', !this._hasLabel && (this.icon || this.iconTrailing || this.loading) ? 'icon-only' : '' ] .filter(Boolean) .join(' '); const iconEl = this.loading ? html `<moni-progress class=\"loading-spinner\" part=\"loading\" variant=\"circular\" indeterminate size=\"small\" ></moni-progress>` : this.icon ? html `<span class=\"icon\" part=\"icon\" ><moni-icon name=\"${this.icon}\"></moni-icon ></span>` : html `<slot name=\"icon\" part=\"icon\"></slot>`; const trailingIconEl = this.iconTrailing ? html `<span class=\"icon\" part=\"icon-trailing\" ><moni-icon name=\"${this.iconTrailing}\"></moni-icon ></span>` : html `<slot name=\"icon-trailing\" part=\"icon-trailing\"></slot>`; const content = html `${iconEl} <span part=\"label\" ?hidden=${!this._hasLabel} style=${this.loading ? 'opacity: 0.5;' : ''}><slot @slotchange=${this._handleLabelSlotChange}></slot></span> ${trailingIconEl}`; if (this.href) { return html `<a class=${classes} part=\"button\" href=${this.disabled ? undefined : this.href} target=${ifDefined(this.target || undefined)} aria-disabled=${this.disabled ? 'true' : 'false'} aria-busy=${this.loading ? 'true' : 'false'} @click=${this.handleLinkClick} > ${content} </a>`; } return html `<button class=${classes} part=\"button\" type=${this.type} ?disabled=${this.disabled} aria-disabled=${this.disabled} aria-busy=${this.loading ? 'true' : 'false'} > ${content} </button>`; } }",
1441
+ "default": "class MoniButton extends MoniElement { constructor() { super(...arguments); this._hasLabel = false; /** * Variante visual del botón. * @type {'filled' | 'tonal' | 'outlined' | 'text' | 'fill' | 'elevated'} * @default 'filled' */ this.variant = 'filled'; /** * Tamaños de M3 Expressive: xsmall, small, medium, large, xlarge. * `extra` es un alias heredado de Moni para `xlarge`, obsoleto en v0.3.1; se * eliminará en v1.0. Ver `m3-docs/components/buttons/overview.md` § Sizes. * @type {'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'extra'} * @default 'medium' */ this.size = 'small'; /** * Forma de las esquinas del botón. * @type {'round' | 'no-round' | 'square' | 'circle' | 'left-round' | 'right-round' | 'top-round' | 'bottom-round' | 'left-round-flat' | 'right-round-flat' | 'top-round-flat' | 'bottom-round-flat' | 'inner-round'} * @default 'round' */ this.shape = 'round'; /** * Deshabilita el botón. * @type {boolean} */ this.disabled = false; /** * Si es verdadero, muestra un indicador de carga y deshabilita el botón. * @type {boolean} */ this.loading = false; /** * Si es verdadero, establece el botón a un estado activo/seleccionado. * @type {boolean} */ this.active = false; /** * Nombre del icono inicial (Material Symbols). * @type {string} */ this.icon = ''; /** * Nombre del icono final (Material Symbols). * @type {string} */ this.iconTrailing = ''; /** * Tipo nativo del botón (cuando se renderiza como un `<button>`). * @type {'button' | 'submit' | 'reset'} * @default 'button' */ this.type = 'button'; /** * Si está establecido, renderiza el botón como un elemento `<a>` con esta URL. * @type {string} */ this.href = ''; /** * Atributo target para los botones tipo enlace. * @type {string} */ this.target = ''; } static { MoniButton_1 = this; } static { this._deprecationWarned = false; } connectedCallback() { super.connectedCallback(); this._hasLabel = this._hasDefaultSlotContent(this.childNodes); if (MoniButton_1._deprecationWarned) return; if (this.size === 'extra') { MoniButton_1._deprecationWarned = true; console.warn('[moni-ui] <moni-button size=\"extra\"> is deprecated since v0.3.1. ' + 'Material Design 3 Expressive uses \"xlarge\". ' + 'See m3-docs/components/buttons/overview.md § Sizes.'); } } _hasDefaultSlotContent(nodes) { return Array.from(nodes).some((node) => { if (node.nodeType === Node.TEXT_NODE) return Boolean(node.textContent?.trim()); return node instanceof Element && !node.hasAttribute('slot'); }); } _handleLabelSlotChange(event) { const slot = event.currentTarget; this._hasLabel = this._hasDefaultSlotContent(slot.assignedNodes({ flatten: true })); } static { this.styles = [ sharedStyles, css ` :host { display: inline-flex; vertical-align: middle; font-family: var(--font); position: relative; user-select: none; } .button { box-sizing: var(--moni-button-box-sizing, border-box); display: inline-flex; inline-size: var(--moni-button-inline-size, auto); --_button-visual-height: 3.5rem; align-items: center; justify-content: center; --_button-icon-size: var(--moni-button-icon-size, 1.5rem); block-size: var(--moni-button-height, 3.5rem); font-size: var(--moni-button-font-size, 1rem); font-weight: 500; color: var(--on-primary); padding: var(--moni-button-padding, 0 1.5rem); background-color: var(--primary); border: 0.0625rem solid transparent; border-radius: 999px; transition: transform 250ms cubic-bezier(0.2, 0, 0, 1), border-radius 200ms cubic-bezier(0.2, 0, 0, 1), padding 250ms cubic-bezier(0.2, 0, 0, 1), background-color 200ms cubic-bezier(0.2, 0, 0, 1), box-shadow 200ms cubic-bezier(0.2, 0, 0, 1); user-select: none; gap: var(--moni-button-gap, 0.5rem); line-height: normal; font-family: inherit; cursor: pointer; text-decoration: none; box-shadow: none; position: relative; } .button::after { content: ''; position: absolute; top: 50%; left: 50%; min-width: 3rem; /* 48px minimum touch target */ min-height: 3rem; /* 48px minimum touch target */ width: 100%; height: 100%; transform: translate(-50%, -50%); } /* Sizes */ .button.xsmall { --_button-visual-height: 2rem; --_button-icon-size: var(--moni-button-icon-size, 1.25rem); block-size: var(--moni-button-height, 2rem); min-inline-size: var(--moni-button-min-inline-size, 0); font-size: var(--moni-button-font-size, 0.875rem); padding: var(--moni-button-padding, 0 0.75rem); /* 12dp; keeps 48dp touch target */ gap: var(--moni-button-gap, 0.25rem); } .button.small { --_button-visual-height: 2.5rem; --_button-icon-size: var(--moni-button-icon-size, 1.25rem); block-size: var(--moni-button-height, 2.5rem); min-inline-size: var(--moni-button-min-inline-size, 0); padding: var(--moni-button-padding, 0 1rem); gap: var(--moni-button-gap, 0.5rem); } .button.large { --_button-visual-height: 6rem; --_button-icon-size: var(--moni-button-icon-size, 2rem); block-size: var(--moni-button-height, 6rem); font-size: var(--moni-button-font-size, 1.5rem); padding: var(--moni-button-padding, 0 3rem); gap: var(--moni-button-gap, 0.75rem); } .button.xlarge, .button.extra { --_button-visual-height: 8.5rem; --_button-icon-size: var(--moni-button-icon-size, 2.5rem); block-size: var(--moni-button-height, 8.5rem); font-size: var(--moni-button-font-size, 2rem); padding: var(--moni-button-padding, 0 4rem); gap: var(--moni-button-gap, 1rem); } /* Shapes */ /* Square keeps horizontal padding (like a normal button) but drops border-radius. Only the circle removes all padding. */ .button.circle { padding: 0 !important; border-radius: 50%; inline-size: var(--moni-button-height, 3.5rem); min-inline-size: var(--moni-button-height, 3.5rem); aspect-ratio: 1; } .button.round.icon-only { inline-size: var(--moni-button-height, var(--_button-visual-height)); min-inline-size: var(--moni-button-height, var(--_button-visual-height)); padding-inline: 0; } .button.no-round { border-radius: 0; } .button.square { border-radius: 1rem; } .button.square.xsmall, .button.square.small { border-radius: 0.75rem; } .button.square.large, .button.square.xlarge, .button.square.extra { border-radius: 1.75rem; } .button.left-round { border-radius: 1.25rem 0.5rem 0.5rem 1.25rem; } .button.left-round.xsmall { border-radius: 0.875rem 0.25rem 0.25rem 0.875rem; } .button.left-round.small { border-radius: 1rem 0.5rem 0.5rem 1rem; } .button.left-round.large { border-radius: 1.5rem 1rem 1rem 1.5rem; } .button.left-round.xlarge, .button.left-round.extra { border-radius: 1.75rem 1.25rem 1.25rem 1.75rem; } .button.right-round { border-radius: 0.5rem 1.25rem 1.25rem 0.5rem; } .button.right-round.xsmall { border-radius: 0.25rem 0.875rem 0.875rem 0.25rem; } .button.right-round.small { border-radius: 0.5rem 1rem 1rem 0.5rem; } .button.right-round.large { border-radius: 1rem 1.5rem 1.5rem 1rem; } .button.right-round.xlarge, .button.right-round.extra { border-radius: 1.25rem 1.75rem 1.75rem 1.25rem; } .button.top-round { border-radius: 1.25rem 1.25rem 0.5rem 0.5rem; } .button.top-round.xsmall { border-radius: 0.875rem 0.875rem 0.25rem 0.25rem; } .button.top-round.small { border-radius: 1rem 1rem 0.5rem 0.5rem; } .button.top-round.large { border-radius: 1.5rem 1.5rem 1rem 1rem; } .button.top-round.xlarge, .button.top-round.extra { border-radius: 1.75rem 1.75rem 1.25rem 1.25rem; } .button.bottom-round { border-radius: 0.5rem 0.5rem 1.25rem 1.25rem; } .button.bottom-round.xsmall { border-radius: 0.25rem 0.25rem 0.875rem 0.875rem; } .button.bottom-round.small { border-radius: 0.5rem 0.5rem 1rem 1rem; } .button.bottom-round.large { border-radius: 1rem 1rem 1.5rem 1.5rem; } .button.bottom-round.xlarge, .button.bottom-round.extra { border-radius: 1.25rem 1.25rem 1.75rem 1.75rem; } /* Morph active states (square-ish equivalents) */ .button.left-square { border-radius: 0.75rem 0.5rem 0.5rem 0.75rem; } .button.left-square.xsmall { border-radius: 0.5rem 0.25rem 0.25rem 0.5rem; } .button.left-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.left-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.left-square.xlarge, .button.left-square.extra { border-radius: 1rem 1.25rem 1.25rem 1rem; } .button.right-square { border-radius: 0.5rem 0.75rem 0.75rem 0.5rem; } .button.right-square.xsmall { border-radius: 0.25rem 0.5rem 0.5rem 0.25rem; } .button.right-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.right-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.right-square.xlarge, .button.right-square.extra { border-radius: 1.25rem 1rem 1rem 1.25rem; } .button.top-square { border-radius: 0.75rem 0.75rem 0.5rem 0.5rem; } .button.top-square.xsmall { border-radius: 0.5rem 0.5rem 0.25rem 0.25rem; } .button.top-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.top-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.top-square.xlarge, .button.top-square.extra { border-radius: 1rem 1rem 1.25rem 1.25rem; } .button.bottom-square { border-radius: 0.5rem 0.5rem 0.75rem 0.75rem; } .button.bottom-square.xsmall { border-radius: 0.25rem 0.25rem 0.5rem 0.5rem; } .button.bottom-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.bottom-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.bottom-square.xlarge, .button.bottom-square.extra { border-radius: 1.25rem 1.25rem 1rem 1rem; } .button.inner-round { border-radius: 0.5rem; } .button.inner-round.xsmall { border-radius: 0.25rem; } .button.inner-round.small { border-radius: 0.5rem; } .button.inner-round.large { border-radius: 1rem; } .button.inner-round.xlarge, .button.inner-round.extra { border-radius: 1.25rem; } /* Flat variants (completely 0px on flat sides) */ .button.left-round-flat { border-radius: 1.25rem 0 0 1.25rem; } .button.left-round-flat.xsmall { border-radius: 0.875rem 0 0 0.875rem; } .button.left-round-flat.small { border-radius: 1rem 0 0 1rem; } .button.left-round-flat.large { border-radius: 1.5rem 0 0 1.5rem; } .button.left-round-flat.xlarge, .button.left-round-flat.extra { border-radius: 1.75rem 0 0 1.75rem; } .button.right-round-flat { border-radius: 0 1.25rem 1.25rem 0; } .button.right-round-flat.xsmall { border-radius: 0 0.875rem 0.875rem 0; } .button.right-round-flat.small { border-radius: 0 1rem 1rem 0; } .button.right-round-flat.large { border-radius: 0 1.5rem 1.5rem 0; } .button.right-round-flat.xlarge, .button.right-round-flat.extra { border-radius: 0 1.75rem 1.75rem 0; } .button.top-round-flat { border-radius: 1.25rem 1.25rem 0 0; } .button.top-round-flat.xsmall { border-radius: 0.875rem 0.875rem 0 0; } .button.top-round-flat.small { border-radius: 1rem 1rem 0 0; } .button.top-round-flat.large { border-radius: 1.5rem 1.5rem 0 0; } .button.top-round-flat.xlarge, .button.top-round-flat.extra { border-radius: 1.75rem 1.75rem 0 0; } .button.bottom-round-flat { border-radius: 0 0 1.25rem 1.25rem; } .button.bottom-round-flat.xsmall { border-radius: 0 0 0.875rem 0.875rem; } .button.bottom-round-flat.small { border-radius: 0 0 1rem 1rem; } .button.bottom-round-flat.large { border-radius: 0 0 1.5rem 1.5rem; } .button.bottom-round-flat.xlarge, .button.bottom-round-flat.extra { border-radius: 0 0 1.75rem 1.75rem; } /* Morph active states for flat variants */ .button.left-square-flat { border-radius: 0.75rem 0 0 0.75rem; } .button.left-square-flat.xsmall, .button.left-square-flat.small { border-radius: 0.5rem 0 0 0.5rem; } .button.left-square-flat.large, .button.left-square-flat.xlarge, .button.left-square-flat.extra { border-radius: 1rem 0 0 1rem; } .button.right-square-flat { border-radius: 0 0.75rem 0.75rem 0; } .button.right-square-flat.xsmall, .button.right-square-flat.small { border-radius: 0 0.5rem 0.5rem 0; } .button.right-square-flat.large, .button.right-square-flat.xlarge, .button.right-square-flat.extra { border-radius: 0 1rem 1rem 0; } .button.top-square-flat { border-radius: 0.75rem 0.75rem 0 0; } .button.top-square-flat.xsmall, .button.top-square-flat.small { border-radius: 0.5rem 0.5rem 0 0; } .button.top-square-flat.large, .button.top-square-flat.xlarge, .button.top-square-flat.extra { border-radius: 1rem 1rem 0 0; } .button.bottom-square-flat { border-radius: 0 0 0.75rem 0.75rem; } .button.bottom-square-flat.xsmall, .button.bottom-square-flat.small { border-radius: 0 0 0.5rem 0.5rem; } .button.bottom-square-flat.large, .button.bottom-square-flat.xlarge, .button.bottom-square-flat.extra { border-radius: 0 0 1rem 1rem; } /* Circle hides the label visually (slot still present for a11y and SSR fallback). */ :host([shape='circle']) [part='label'] { display: none; } /* Variants */ .button.border { border: 0.0625rem solid var(--outline-variant); color: var(--primary); background-color: transparent; } .button.fill { background-color: var(--secondary-container); color: var(--on-secondary-container); } .button.transparent { color: var(--primary); background-color: transparent; } .button.elevated { background-color: var(--surface-container-low, var(--surface)); color: var(--primary); box-shadow: var(--elevate1); } .button.error { background-color: var(--error); color: var(--on-error); } .button.error:hover:not(:disabled) { background-image: linear-gradient(color-mix(in srgb, currentColor 8%, transparent), color-mix(in srgb, currentColor 8%, transparent)); } .button.error:focus-visible { outline-color: var(--error); } /* Disabled / active */ .button:disabled, .button[aria-disabled='true'] { opacity: 0.5; cursor: not-allowed; pointer-events: none; } .button:hover:not(:disabled) { transform: translateY(-0.0625rem); background-image: linear-gradient(color-mix(in srgb, currentColor 8%, transparent), color-mix(in srgb, currentColor 8%, transparent)); } .button.elevated:hover:not(:disabled) { box-shadow: var(--elevate2); } .button:active:not(:disabled) { border-radius: 0.75rem; transform: translateY(0.0625rem) scale(0.97); background-image: linear-gradient(color-mix(in srgb, currentColor 12%, transparent), color-mix(in srgb, currentColor 12%, transparent)); } .button.elevated:active:not(:disabled) { box-shadow: none; } .button.xsmall:active:not(:disabled) { border-radius: 0.5rem; } .button.small:active:not(:disabled) { border-radius: 0.5rem; } .button.large:active:not(:disabled) { border-radius: 1rem; } .button.xlarge:active:not(:disabled), .button.extra:active:not(:disabled) { border-radius: 1rem; } .button.active:not(:disabled) { background-color: var(--primary-container); color: var(--on-primary-container); } .button.fill.active:not(:disabled) { background-color: var(--secondary); color: var(--on-secondary); } .button.border.active:not(:disabled) { background-color: var(--inverse-surface); color: var(--inverse-on-surface); border-color: var(--inverse-surface); } .button.elevated.active:not(:disabled) { background-color: var(--primary-container); color: var(--on-primary-container); box-shadow: var(--elevate2); } .button:focus-visible { outline: 0.125rem solid var(--primary); outline-offset: 0.125rem; } /* Icon helper */ .icon, ::slotted([slot='icon']), ::slotted([slot='icon-trailing']) { display: inline-flex; align-items: center; justify-content: center; font-size: var(--_button-icon-size); inline-size: var(--_button-icon-size); block-size: var(--_button-icon-size); flex: none; color: inherit; } .icon { --moni-icon-size: var(--_button-icon-size); font-size: var(--_button-icon-size); inline-size: var(--_button-icon-size); block-size: var(--_button-icon-size); transform: translateX(var(--moni-button-icon-offset, 0)); } .button.left-round { border-radius: 999px var(--_button-square-radius, 1rem) var(--_button-square-radius, 1rem) 999px; } .button.right-round { border-radius: var(--_button-square-radius, 1rem) 999px 999px var(--_button-square-radius, 1rem); } .button.top-round { border-radius: 999px 999px var(--_button-square-radius, 1rem) var(--_button-square-radius, 1rem); } .button.bottom-round { border-radius: var(--_button-square-radius, 1rem) var(--_button-square-radius, 1rem) 999px 999px; } .button.left-round-flat { border-radius: 999px 0 0 999px; } .button.right-round-flat { border-radius: 0 999px 999px 0; } .button.top-round-flat { border-radius: 999px 999px 0 0; } .button.bottom-round-flat { border-radius: 0 0 999px 999px; } .button.xsmall, .button.small { --_button-square-radius: 0.75rem; } .button.large, .button.xlarge, .button.extra { --_button-square-radius: 1.75rem; } :host([split-position]) .button { inline-size: var(--moni-button-inline-size); min-inline-size: var(--moni-button-min-inline-size); border-radius: var(--moni-button-border-radius); } slot { display: contents; } [part='label'][hidden] { display: none; } /* Loading spinner */ .loading-spinner { inline-size: 1.125rem; block-size: 1.125rem; color: currentColor; } .loading-spinner::part(progress) { border-width: 0.1875rem; } ` ]; } /** * Intercepta los clicks en botones tipo enlace (`href=\"...\"`). * Si el botón está desactivado (`disabled`), previene agresivamente * tanto la navegación por defecto del navegador como la burbuja de eventos. */ handleLinkClick(e) { if (this.disabled) { e.preventDefault(); e.stopPropagation(); } } /** * Assembles the button's Shadow DOM structure for each Lit render cycle. * * **Variant → CSS class mapping:** * | `variant` | CSS class | Visual result | * |--------------|---------------|-----------------------------| * | `'filled'` | `''` (empty) | Primary filled button | * | `'outlined'` | `'border'` | Outlined/stroked button | * | `'tonal'` | `'fill'` | Secondary container fill | * | `'text'` | `'transparent'` | Ghost/text button | * | `'elevated'` | `'elevated'` | Tonal surface + shadow | * * **Shape morphing:** * When `active=true`, the button inverts its corner-radius shape to create * a micro-animation where clicking \"morphs\" the shape (round → square, etc.). * This is used in toggle-button patterns (e.g., a nav-item that morphs to * indicate the current page). * * **Icon resolution priority:** * 1. `loading=true` → renders a `<moni-progress>` spinner (hides label at 50% opacity). * 2. `icon` attribute → renders a `<moni-icon>` in the leading icon slot. * 3. Otherwise → renders the `icon` named slot for slotted content. * * **Link vs. button:** * When `href` is set, the root element is an `<a>` tag (with disabled click * guard) for proper browser navigation semantics. Otherwise it's a `<button>` * with `type=\"button\"` to prevent accidental form submission. */ render() { let variantClass = ''; if (this.variant === 'outlined') variantClass = 'border'; else if (this.variant === 'tonal' || this.variant === 'fill') variantClass = 'fill'; else if (this.variant === 'text') variantClass = 'transparent'; else if (this.variant === 'elevated') variantClass = 'elevated'; else if (this.variant === 'error') variantClass = 'error'; let activeShape = this.shape; if (this.active) { if (this.shape === 'round') activeShape = 'square'; else if (this.shape === 'square') activeShape = 'round'; else if (this.shape === 'left-round') activeShape = 'left-square'; else if (this.shape === 'right-round') activeShape = 'right-square'; else if (this.shape === 'top-round') activeShape = 'top-square'; else if (this.shape === 'bottom-round') activeShape = 'bottom-square'; else if (this.shape === 'left-round-flat') activeShape = 'left-square-flat'; else if (this.shape === 'right-round-flat') activeShape = 'right-square-flat'; else if (this.shape === 'top-round-flat') activeShape = 'top-square-flat'; else if (this.shape === 'bottom-round-flat') activeShape = 'bottom-square-flat'; else if (this.shape === 'inner-round') activeShape = 'inner-round'; // remains inner-round or standard shape morphing } const classes = [ 'button', variantClass, this.size, activeShape, this.active ? 'active' : '', !this._hasLabel && (this.icon || this.iconTrailing || this.loading) ? 'icon-only' : '' ] .filter(Boolean) .join(' '); const iconEl = this.loading ? html `<moni-progress class=\"loading-spinner\" part=\"loading\" variant=\"circular\" indeterminate size=\"small\" ></moni-progress>` : this.icon ? html `<span class=\"icon\" part=\"icon\" ><moni-icon name=\"${this.icon}\"></moni-icon ></span>` : html `<slot name=\"icon\" part=\"icon\"></slot>`; const trailingIconEl = this.iconTrailing ? html `<span class=\"icon\" part=\"icon-trailing\" ><moni-icon name=\"${this.iconTrailing}\"></moni-icon ></span>` : html `<slot name=\"icon-trailing\" part=\"icon-trailing\"></slot>`; const content = html `${iconEl} <span part=\"label\" ?hidden=${!this._hasLabel} style=${this.loading ? 'opacity: 0.5;' : ''}><slot @slotchange=${this._handleLabelSlotChange}></slot></span> ${trailingIconEl}`; if (this.href) { return html `<a class=${classes} part=\"button\" href=${this.disabled ? undefined : this.href} target=${ifDefined(this.target || undefined)} aria-disabled=${this.disabled ? 'true' : 'false'} aria-busy=${this.loading ? 'true' : 'false'} @click=${this.handleLinkClick} > ${content} </a>`; } return html `<button class=${classes} part=\"button\" type=${this.type} ?disabled=${this.disabled} aria-disabled=${this.disabled} aria-busy=${this.loading ? 'true' : 'false'} > ${content} </button>`; } }",
1442
1442
  "description": "Componente Material Design 3 Button.\n\nLos botones permiten a los usuarios realizar acciones y hacer elecciones con un solo toque.\nEste componente proporciona todas las variantes de botones de M3, tamaños y capacidades\nde transformación de formas (ej. cambiar a una forma de píldora al presionar o alternar).\n\n**Referencia de la especificación M3:** `m3-docs/components/buttons/specs.md`\n\n**Variantes:**\n- `filled` (por defecto) — Énfasis alto. Úsalo para acciones principales.\n- `tonal` — Énfasis medio. Acciones secundarias que aún necesitan destacar.\n- `elevated` — Énfasis medio con sombra. Úsalo al sentarse sobre fondos con patrones.\n- `outlined` — Énfasis medio, sin relleno. Acciones secundarias o terciarias.\n- `text` — Énfasis bajo. Acciones terciarias (ej. botón de cancelar diálogo).\n\n**Transformación de forma (Característica M3 Expressive):**\n- Al presionar (estado activo): Los botones redondos y cuadrados se transforman a una forma\n \"presionada\" ligeramente más cuadrada con radios de esquina específicos de M3 (ej. XS/S 8dp, M 12dp).\n- Al alternar (atributo `active`): La forma de reposo se invierte (ej. redondo ↔ cuadrado).\n\n**Renderizando como un enlace:**\nCuando se proporciona el atributo `href`, el componente se renderiza internamente como\nun elemento `<a>` en lugar de un `<button>`, permitiendo enrutamiento nativo y comportamientos\nde clic central (abrir en nueva pestaña) mientras mantiene los visuales del botón."
1443
1443
  }
1444
1444
  ],
@@ -1910,7 +1910,7 @@
1910
1910
  {
1911
1911
  "kind": "variable",
1912
1912
  "name": "MoniLoadingIndicator",
1913
- "default": "class MoniLoadingIndicator extends MoniElement { constructor() { super(...arguments); this.variant = 'uncontained'; } /** * Hook de inicialización (Lit). * Configura roles ARIA (progressbar) por defecto y asegura * el encendido inicial de la animación infinita en el Shadow DOM. */ connectedCallback() { super.connectedCallback(); this.ariaValueMin = this.ariaValueMin || '0'; this.ariaValueMax = this.ariaValueMax || '100'; this.role = this.role || 'progressbar'; this._toggleAnimation(true); } /** * Hook de destrucción (Lit). * Apaga formalmente la animación CSS (`.animate`) para frenar el GPU repaint * cuando el componente es destruido, reduciendo el consumo de batería. */ disconnectedCallback() { super.disconnectedCallback(); this._toggleAnimation(false); } /** * Hook de primera actualización (Lit). * Dispara explícitamente el estado de animación una vez que el DOM * interno (`this._container`) ha sido montado y es seleccionable. */ firstUpdated() { this._toggleAnimation(true); } /** * Controlador imperativo de las clases de animación. * Permite pausar/reanudar el loader mediante CSS, muy útil cuando se integra * con observadores de visibilidad (IntersectionObserver) para mejorar el rendimiento. */ _toggleAnimation(enable) { if (this._container) { this._container.classList.toggle('animate', enable); } } static { this.styles = [ sharedStyles, css ` :host { display: inline-block; aspect-ratio: 1 / 1; contain: strict; vertical-align: middle; } :host([variant='uncontained']) { width: var(--moni-loading-indicator-size, 2.375rem); } :host([variant='contained']) { width: var(--moni-loading-indicator-container-size, 3rem); } :host([variant='uncontained']) .active-indicator { background-color: var(--moni-loading-indicator-active-color, var(--primary)); } :host([variant='contained']) .active-indicator { background-color: var(--moni-loading-indicator-contained-active-color, var(--on-primary-container)); } :host([variant='contained']) .container { background-color: var(--moni-loading-indicator-contained-container-color, var(--secondary-container)); } .container { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; border-radius: var(--moni-loading-indicator-container-shape, 9999px); box-sizing: border-box; } .active-indicator { margin: auto; aspect-ratio: 1 / 1; width: calc(var(--moni-loading-indicator-size, 2.375rem) * 0.842); transform-origin: center; transition: clip-path 500ms cubic-bezier(0.2, 0, 0, 1); will-change: transform, clip-path; --_polygon-soft-burst: polygon(${unsafeCSS(loadingIndicatorPolygons['soft-burst'])}); --_polygon-9-sided-cookie: polygon(${unsafeCSS(loadingIndicatorPolygons['9-sided-cookie'])}); --_polygon-pentagon: polygon(${unsafeCSS(loadingIndicatorPolygons['pentagon'])}); --_polygon-pill: polygon(${unsafeCSS(loadingIndicatorPolygons['pill'])}); --_polygon-sunny: polygon(${unsafeCSS(loadingIndicatorPolygons['sunny'])}); --_polygon-4-sided-cookie: polygon(${unsafeCSS(loadingIndicatorPolygons['4-sided-cookie'])}); --_polygon-oval: polygon(${unsafeCSS(loadingIndicatorPolygons['oval'])}); } .container.animate .active-indicator-wrapper { animation: rotate-outer 4666ms linear infinite; transform-origin: center; display: flex; align-items: center; justify-content: center; will-change: transform; } @keyframes rotate-outer { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .container.animate .active-indicator { animation: rotate-inner 4666ms cubic-bezier(0.34, 0.88, 0.34, 1) infinite; } @keyframes rotate-inner { 0% { clip-path: var(--_polygon-soft-burst); transform: rotate(0deg); } 14% { clip-path: var(--_polygon-9-sided-cookie); transform: rotate(154deg) scale(1); } 29% { clip-path: var(--_polygon-pentagon); transform: rotate(309deg) scale(1); } 43% { clip-path: var(--_polygon-pill); transform: rotate(463deg) scale(1); } 57% { clip-path: var(--_polygon-sunny); transform: rotate(617deg) scale(1); } 71% { clip-path: var(--_polygon-4-sided-cookie); transform: rotate(771deg) scale(1); } 83% { clip-path: var(--_polygon-oval); transform: rotate(926deg) scale(1); } 100% { clip-path: var(--_polygon-soft-burst); transform: rotate(1080deg) scale(1); } } @media (forced-colors: active) { .active-indicator { background-color: CanvasText !important; } } @media (prefers-reduced-motion: reduce) { .container.animate .active-indicator-wrapper, .container.animate .active-indicator { animation: none; } .active-indicator { clip-path: var(--_polygon-soft-burst); } } ` ]; } /** * Renderiza la superposición de carga de pantalla completa con un spinner centrado y una etiqueta opcional. * * La superposición utiliza `position: fixed; inset: 0` para cubrir toda la ventana gráfica, * y `z-index` para aparecer por encima del resto del contenido. El spinner `<moni-progress>` * se centra mediante CSS flexbox en el elemento `:host`. * * **Región ARIA en vivo:** * `aria-live=\"polite\"` y `aria-label` en el contenedor anuncian los cambios de estado * de carga a los lectores de pantalla sin interrumpir el flujo de lectura del usuario. * El `role=\"status\"` da a los lectores de pantalla un punto de referencia implícito similar a una alerta. */ render() { return html ` <div class=\"container animate\" aria-hidden=\"true\"> <div class=\"active-indicator-wrapper\"> <div class=\"active-indicator\"></div> </div> </div> `; } }",
1913
+ "default": "class MoniLoadingIndicator extends MoniElement { constructor() { super(...arguments); this.variant = 'uncontained'; } /** * Hook de inicialización (Lit). * Configura roles ARIA (progressbar) por defecto y asegura * el encendido inicial de la animación infinita en el Shadow DOM. */ connectedCallback() { super.connectedCallback(); this.ariaValueMin = this.ariaValueMin || '0'; this.ariaValueMax = this.ariaValueMax || '100'; this.role = this.role || 'progressbar'; this._toggleAnimation(true); } /** * Hook de destrucción (Lit). * Apaga formalmente la animación CSS (`.animate`) para frenar el GPU repaint * cuando el componente es destruido, reduciendo el consumo de batería. */ disconnectedCallback() { super.disconnectedCallback(); this._toggleAnimation(false); } /** * Hook de primera actualización (Lit). * Dispara explícitamente el estado de animación una vez que el DOM * interno (`this._container`) ha sido montado y es seleccionable. */ firstUpdated() { this._toggleAnimation(true); } /** * Controlador imperativo de las clases de animación. * Permite pausar/reanudar el loader mediante CSS, muy útil cuando se integra * con observadores de visibilidad (IntersectionObserver) para mejorar el rendimiento. */ _toggleAnimation(enable) { if (this._container) { this._container.classList.toggle('animate', enable); } } static { this.styles = [ sharedStyles, css ` :host { display: inline-block; aspect-ratio: 1 / 1; /* Paint containment clipped oversized morphs in the contained * variant. Keep layout/style isolation while allowing the active * indicator to animate beyond the container surface. */ contain: layout style; overflow: visible; vertical-align: middle; } :host([variant='uncontained']) { width: var(--moni-loading-indicator-size, 2.375rem); } :host([variant='contained']) { width: var(--moni-loading-indicator-container-size, 3rem); } :host([variant='uncontained']) .active-indicator { background-color: var(--moni-loading-indicator-active-color, var(--primary)); } :host([variant='contained']) .active-indicator { background-color: var(--moni-loading-indicator-contained-active-color, var(--on-primary-container)); } :host([variant='contained']) .container { background-color: var(--moni-loading-indicator-contained-container-color, var(--secondary-container)); } .container { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; border-radius: var(--moni-loading-indicator-container-shape, 9999px); box-sizing: border-box; overflow: visible; } .active-indicator-wrapper { overflow: visible; } .active-indicator-scale { display: flex; align-items: center; justify-content: center; overflow: visible; transform-origin: center; } .active-indicator { margin: auto; aspect-ratio: 1 / 1; width: calc(var(--moni-loading-indicator-size, 2.375rem) * 0.842); transform-origin: center; transition: clip-path 500ms cubic-bezier(0.2, 0, 0, 1); will-change: transform, clip-path; --_polygon-soft-burst: polygon(${unsafeCSS(loadingIndicatorPolygons['soft-burst'])}); --_polygon-9-sided-cookie: polygon(${unsafeCSS(loadingIndicatorPolygons['9-sided-cookie'])}); --_polygon-pentagon: polygon(${unsafeCSS(loadingIndicatorPolygons['pentagon'])}); --_polygon-pill: polygon(${unsafeCSS(loadingIndicatorPolygons['pill'])}); --_polygon-sunny: polygon(${unsafeCSS(loadingIndicatorPolygons['sunny'])}); --_polygon-4-sided-cookie: polygon(${unsafeCSS(loadingIndicatorPolygons['4-sided-cookie'])}); --_polygon-oval: polygon(${unsafeCSS(loadingIndicatorPolygons['oval'])}); } .container.animate .active-indicator-wrapper { animation: rotate-outer 4666ms linear infinite; transform-origin: center; display: flex; align-items: center; justify-content: center; will-change: transform; } @keyframes rotate-outer { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .container.animate .active-indicator { animation: rotate-inner 4666ms cubic-bezier(0.34, 0.88, 0.34, 1) infinite; } @keyframes rotate-inner { 0% { clip-path: var(--_polygon-soft-burst); transform: rotate(0deg); } 14% { clip-path: var(--_polygon-9-sided-cookie); transform: rotate(154deg) scale(1); } 29% { clip-path: var(--_polygon-pentagon); transform: rotate(309deg) scale(1); } 43% { clip-path: var(--_polygon-pill); transform: rotate(463deg) scale(1); } 57% { clip-path: var(--_polygon-sunny); transform: rotate(617deg) scale(1); } 71% { clip-path: var(--_polygon-4-sided-cookie); transform: rotate(771deg) scale(1); } 83% { clip-path: var(--_polygon-oval); transform: rotate(926deg) scale(1); } 100% { clip-path: var(--_polygon-soft-burst); transform: rotate(1080deg) scale(1); } } @media (forced-colors: active) { .active-indicator { background-color: CanvasText !important; } } @media (prefers-reduced-motion: reduce) { .container.animate .active-indicator-wrapper, .container.animate .active-indicator { animation: none; } .active-indicator { clip-path: var(--_polygon-soft-burst); } } ` ]; } /** * Renderiza la superposición de carga de pantalla completa con un spinner centrado y una etiqueta opcional. * * La superposición utiliza `position: fixed; inset: 0` para cubrir toda la ventana gráfica, * y `z-index` para aparecer por encima del resto del contenido. El spinner `<moni-progress>` * se centra mediante CSS flexbox en el elemento `:host`. * * **Región ARIA en vivo:** * `aria-live=\"polite\"` y `aria-label` en el contenedor anuncian los cambios de estado * de carga a los lectores de pantalla sin interrumpir el flujo de lectura del usuario. * El `role=\"status\"` da a los lectores de pantalla un punto de referencia implícito similar a una alerta. */ render() { return html ` <div class=\"container animate\" aria-hidden=\"true\"> <div class=\"active-indicator-wrapper\"> <div class=\"active-indicator-scale\"> <div class=\"active-indicator\"></div> </div> </div> </div> `; } }",
1914
1914
  "description": "Componente Material Design 3 Loading Indicator (Indicador de carga).\n\nUn indicador de carga indeterminado que representa visualmente un tiempo de espera\nno especificado. A diferencia de los spinners circulares estándar, este componente usa una animación\nde polígono cambiante que cambia entre formas (círculo, cuadrado redondeado, etc.)\nde acuerdo con las especificaciones de movimiento de alta fidelidad M3 Expressive.\n\n**Variantes:**\n- `uncontained` (por defecto) — Una forma cambiante independiente que hereda el color\n de su contexto de texto (o por defecto es `primary`).\n- `contained` — La forma cambiante se coloca dentro de un contenedor circular\n con un color de fondo distinto, útil para estados de carga de alto contraste\n o superposición de imágenes.\n\n**Animación y Accesibilidad:**\nEl componente gestiona sus propias etiquetas `<animate>` SVG. La animación se\ninicia/detiene automáticamente a través de `connectedCallback`/`disconnectedCallback`\npara ahorrar ciclos de CPU cuando el elemento está fuera de la pantalla. Aplica los atributos de valor\ny roles ARIA estándar (`role=\"progressbar\"`) para asegurar que los lectores de pantalla\nlo identifiquen correctamente como un estado de carga indeterminado."
1915
1915
  }
1916
1916
  ],
@@ -2630,7 +2630,7 @@
2630
2630
  {
2631
2631
  "kind": "variable",
2632
2632
  "name": "MoniTimePicker",
2633
- "default": "class MoniTimePicker extends MoniElement { constructor() { super(...arguments); this.value = '00:00'; this.use24Hour = false; this.mode = 'dial'; this.orientation = 'auto'; this.hideModeToggle = false; this.hideHeadline = false; this.hourLabel = 'Hora'; this.minuteLabel = 'Minuto'; this.activeSelection = 'hour'; this.period = 'AM'; this.currentHour = 0; // 0-23 this.currentMinute = 0; // 0-59 this.isDragging = false; } /** * Hook del ciclo de vida (Lit) previo al renderizado. * * Si la propiedad externa `value` o el modo `use24Hour` cambian (vía binding o atributo HTML), * dispara el `parseValue()` de inmediato para mantener sincronizado el estado interno de * horas y minutos (y recalcular AM/PM) antes de mutar el DOM real. */ willUpdate(changedProperties) { if (changedProperties.has('value') || changedProperties.has('use24Hour')) { this.parseValue(); } } /** * Interpreta el string `value` (formato \"HH:MM\") y lo descompone en enteros para * el estado interno. Automáticamente corrige valores fuera de rango y determina * si estamos en AM o PM (cuando no se usa formato de 24 horas). */ parseValue() { const parts = this.value.split(':'); let h = parseInt(parts[0], 10); let m = parseInt(parts[1], 10); if (isNaN(h) || h < 0 || h > 23) h = 0; if (isNaN(m) || m < 0 || m > 59) m = 0; this.currentHour = h; this.currentMinute = m; if (!this.use24Hour) { if (h >= 12) { this.period = 'PM'; } else { this.period = 'AM'; } } } /** * Reconstruye el string `value` a partir del estado interno (`currentHour`, `currentMinute`). * Formatea con ceros a la izquierda e incluye la lógica de conversión AM/PM a 24hrs * para garantizar que el valor expuesto hacia afuera sea siempre un estándar ISO (\"14:30\"). */ updateValue() { let h = this.currentHour; const m = this.currentMinute; if (!this.use24Hour) { if (this.period === 'PM') { if (h < 12) h += 12; } else { if (h === 12) h = 0; else if (h > 12) h -= 12; // Ajuste por si quedó fuera de límites } } const formattedHour = String(h).padStart(2, '0'); const formattedMinute = String(m).padStart(2, '0'); this.value = `${formattedHour}:${formattedMinute}`; this.dispatchEvent(new CustomEvent('change', { detail: { value: this.value }, bubbles: true, composed: true })); } /** * Alterna el modo de interacción visual entre el Reloj Analógico (dial) * y el modo de Entrada de Texto manual (input). */ toggleMode() { this.mode = this.mode === 'dial' ? 'input' : 'dial'; } /** * Define el nivel de selección activo ('hour' o 'minute'). * Cambia dinámicamente qué anillo o qué matemáticas (12 vs 60 items) * utilizará el componente principal y el reloj. */ setSelection(type) { this.activeSelection = type; } /** * Cambia forzosamente el periodo del día ('AM' o 'PM') cuando el usuario * presiona los conmutadores laterales (sólo visible en formato 12h). * Inmediatamente propaga el cambio hacia `updateValue()` para recalcular * y despachar la hora formateada. */ setPeriod(p) { this.period = p; this.updateValue(); } // Clock Math and Interaction /** * Inicia el proceso de arrastre en la carátula del reloj (Pointer Down). * Utiliza `setPointerCapture` nativo para asegurar que el movimiento (drag) siga * siendo rastreado aunque el puntero del usuario salga físicamente del contenedor, * garantizando una interacción \"Touch\" a prueba de errores. */ handleClockPointerDown(e) { if (!this.clockFaceEl) return; this.isDragging = true; this.clockFaceEl.classList.add('dragging'); this.clockFaceEl.setPointerCapture(e.pointerId); this.handleClockPointerMove(e); } /** * Corazón matemático del reloj radial (Time Picker analógico). * Convierte las coordenadas cartesianas del ratón/dedo en un ángulo polar (0-360º), * y dependiendo si medimos horas o minutos, lo mapea al número correspondiente. */ handleClockPointerMove(e) { if (!this.isDragging || !this.clockFaceEl) return; const rect = this.clockFaceEl.getBoundingClientRect(); // Encontramos el centro geométrico absoluto del reloj const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const dx = e.clientX - centerX; const dy = e.clientY - centerY; // Teorema de Pitágoras para saber a qué distancia del centro está apuntando el usuario const distance = Math.sqrt(dx * dx + dy * dy); // Calculamos el ángulo en grados partiendo desde las 12 (sentido horario). // Math.atan2 devuelve de -PI a PI, sumamos 90º para desfasar el origen arriba (eje Y). let angle = Math.atan2(dy, dx) * (180 / Math.PI) + 90; if (angle < 0) angle += 360; if (this.activeSelection === 'hour') { if (this.use24Hour) { // Reloj de 24 horas M3: Un círculo externo (13-00) y uno interno (1-12) // El umbral (0.32) fue medido visualmente para coincidir con el diseño radial interno. const isInner = distance < rect.width * 0.32; let hour = Math.round(angle / 30) % 12; // 360 / 12 horas = 30 grados por hora if (hour === 0) hour = 12; if (isInner) { // Círculo interno: 1 a 12 (AM/PM) this.currentHour = hour; } else { // Círculo externo: 13 a 24 (donde las 24 = 0) this.currentHour = hour === 12 ? 0 : hour + 12; } } else { // Reloj estándar de 12 horas: solo 1 a 12 let hour = Math.round(angle / 30) % 12; if (hour === 0) hour = 12; this.currentHour = hour; } } else { // Modo minutos: 360 / 60 minutos = 6 grados por minuto const minute = Math.round(angle / 6) % 60; this.currentMinute = minute; } this.updateValue(); } /** * Finaliza la interacción de arrastre en la carátula del reloj (Pointer Up). * * Como heurística de usabilidad, si el usuario acaba de seleccionar la Hora, * el componente espera 300ms y automáticamente transiciona a la vista de Minutos. * Esto reduce los clics necesarios en interacciones de Time Pickers de un solo toque. */ handleClockPointerUp() { if (this.isDragging) { this.isDragging = false; this.clockFaceEl?.classList.remove('dragging'); if (this.activeSelection === 'hour') { // Cambiar automáticamente a la selección de minutos después de elegir la hora setTimeout(() => { this.activeSelection = 'minute'; }, 300); } } } /** * Handler para entradas directas desde el `<input>` de horas (Modo 'input' texto). * Implementa clamping (límites mínimos y máximos) para asegurar que el usuario * nunca pueda introducir valores erróneos mayores a 23h o menores a 0h. */ handleHourInputChange(e) { const target = e.target; let val = parseInt(target.value, 10); if (isNaN(val)) val = 0; if (this.use24Hour) { if (val < 0) val = 0; if (val > 23) val = 23; this.currentHour = val; } else { if (val < 1) val = 1; if (val > 12) val = 12; this.currentHour = val; } this.updateValue(); } /** * Handler para entradas directas desde el `<input>` de minutos (Modo 'input' texto). * Trunca cualquier input alfanumérico a enteros y realiza un clamp a 0-59. */ handleMinuteInputChange(e) { const target = e.target; let val = parseInt(target.value, 10); if (isNaN(val)) val = 0; if (val < 0) val = 0; if (val > 59) val = 59; this.currentMinute = val; this.updateValue(); } static { this.styles = [ sharedStyles, css ` :host { --_display-size: 6rem; display: inline-flex; flex-direction: column; background-color: transparent; inline-size: min(20.5rem, 100%); max-inline-size: 100%; box-sizing: border-box; container-type: inline-size; color: var(--on-surface); font-family: var(--font); user-select: none; } :host([use-24-hour]) { --_display-size: 7.125rem; } /* Forced orientations */ :host([orientation=\"vertical\"]) { inline-size: min(20.5rem, 100%); } :host([orientation=\"vertical\"]) .main-content { flex-direction: column; } :host([orientation=\"horizontal\"]) { inline-size: auto; } :host([orientation=\"horizontal\"]) .main-content { flex-direction: row; align-items: center; gap: 2rem; } :host([orientation=\"horizontal\"]) .left-pane { display: flex; flex-direction: column; justify-content: center; } /* Main Content (Default vertical) */ .main-content { display: flex; flex-direction: column; gap: 1rem; min-inline-size: 0; } :host([mode='input']) .main-content { margin-block: auto; justify-content: center; align-items: center; } .main-content > * { min-inline-size: 0; } .clock-container { flex: none; } /* Auto stays vertical because the component can live inside a narrow dialog even when the browser viewport itself is wide. Consumers that reserve enough inline space can opt into horizontal explicitly. */ @media (min-width: 48rem) { :host([orientation='auto']), :host(:not([orientation])) { inline-size: min(40rem, 100%); } :host([orientation='auto']) .main-content, :host(:not([orientation])) .main-content { flex-direction: row; align-items: center; gap: 2rem; } :host([orientation='auto']) .left-pane, :host(:not([orientation])) .left-pane { display: flex; flex-direction: column; justify-content: center; } } @keyframes fadeInScale { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } } .clock-container, .time-display-container, .input-labels, .display-box { animation: fadeInScale var(--speed3) var(--ease-standard) forwards; } .picker-header { font-size: 0.75rem; font-weight: 500; margin-bottom: 1.25rem; color: var(--on-surface-variant); text-transform: uppercase; letter-spacing: 0.1em; } /* Display Panel (Time box inputs/views) */ .time-display-container { display: flex; align-items: center; justify-content: center; margin-bottom: 1.5rem; gap: 0.75rem; } .time-display { display: grid; grid-template-columns: var(--_display-size) 1.5rem var(--_display-size); align-items: center; justify-content: center; flex: none; background-color: transparent; } .display-box { display: flex; align-items: center; justify-content: center; width: 100%; height: 5rem; border-radius: 0.5rem; background-color: var(--surface-container-highest); font-size: 3.5rem; font-weight: 400; color: var(--on-surface); cursor: pointer; transition: background-color var(--speed2); border: 0.125rem solid transparent; text-align: center; } :host([use-24-hour]) .display-box { width: 100%; } .display-box:hover:not(.active) { background-color: var(--active); } .display-box.active { background-color: var(--primary-container); color: var(--on-primary-container); border-color: var(--primary); } .display-box input { width: 100%; height: 100%; border: none; background: transparent; text-align: center; font-size: inherit; font-family: inherit; color: inherit; outline: none; padding: 0; } /* Remove input spinners */ .display-box input::-webkit-outer-spin-button, .display-box input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .display-box input[type=number] { -moz-appearance: textfield; } .separator { font-size: 3rem; font-weight: 400; color: var(--on-surface); width: 1.5rem; text-align: center; line-height: 5rem; } /* AM/PM Toggle */ .ampm-toggle { display: flex; flex-direction: column; flex: 0 0 3.25rem; border: 0.0625rem solid var(--outline); border-radius: 0.5rem; overflow: hidden; height: 5rem; width: 3.25rem; } .ampm-btn { flex: 1; background-color: transparent; border: none; font-size: 0.875rem; font-weight: 500; color: var(--on-surface-variant); cursor: pointer; transition: background-color var(--speed1), color var(--speed1); } .ampm-btn.active { background-color: var(--tertiary-container); color: var(--on-tertiary-container); } .ampm-btn:first-child { border-bottom: 0.0625rem solid var(--outline); } /* Clock Face */ .clock-container { display: flex; justify-content: center; align-items: center; margin-block: 0.5rem 1.5rem; } .clock-face { position: relative; width: 16rem; height: 16rem; border-radius: 50%; background-color: var(--surface-container-highest); touch-action: none; cursor: pointer; } @container (max-width: 18rem) { :host { --_display-size: clamp(4.5rem, 35cqi, 6rem); } :host([use-24-hour]) { --_display-size: clamp(4.75rem, 38cqi, 7.125rem); } .time-display-container { gap: .375rem; margin-block-end: 1rem; } .display-box { block-size: 4rem; font-size: 2.75rem; } .separator { inline-size: 1rem; font-size: 2.25rem; line-height: 4rem; } .clock-container { margin-block: .25rem 1rem; } } /* Center dot */ .clock-center { position: absolute; left: calc(50% - 0.25rem); top: calc(50% - 0.25rem); width: 0.5rem; height: 0.5rem; border-radius: 50%; background-color: var(--primary); } /* Clock hand line */ .clock-hand { position: absolute; left: calc(50% - 0.0625rem); bottom: 50%; width: 0.125rem; background-color: var(--primary); transform-origin: bottom center; z-index: 1; pointer-events: none; transition: transform var(--speed3) var(--ease-standard), height var(--speed3) var(--ease-standard); } /* Hand tip circle selection inside the hand */ .clock-hand-selector { position: absolute; top: 0; left: 50%; width: 3rem; height: 3rem; border-radius: 50%; background-color: var(--primary); opacity: 0.3; transform: translate(-50%, -50%) scale(1); z-index: 1; pointer-events: none; transition: transform var(--speed2) var(--ease-standard); } /* Disable transition during active drag but keep scale transform animation */ .clock-face.dragging .clock-hand, .clock-face:active .clock-hand { transition: none !important; } /* Selector handle expands/grows when pressed or dragging */ .clock-face.dragging .clock-hand-selector, .clock-face:active .clock-hand-selector { transform: translate(-50%, -50%) scale(1.2); } .clock-hand-dot { position: absolute; left: calc(50% - 0.25rem); top: -2rem; width: 0.5rem; height: 0.5rem; border-radius: 50%; background-color: var(--on-primary); z-index: 2; pointer-events: none; } @keyframes numberEntrance { from { opacity: 0; transform: translate(-50%, -50%) scale(0.85); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } /* Clock dial numbers */ .clock-number { position: absolute; width: 2.25rem; height: 2.25rem; display: flex; align-items: center; justify-content: center; font-size: 0.875rem; font-weight: 500; color: var(--on-surface); border-radius: 50%; transform: translate(-50%, -50%); transition: background-color var(--speed2), color var(--speed2); z-index: 2; pointer-events: auto; cursor: pointer; animation: numberEntrance var(--speed3) var(--ease-standard) forwards; } .clock-number:hover { background-color: var(--active); } .clock-number.selected { background-color: var(--primary); color: var(--on-primary); font-weight: bold; } .clock-number.selected:hover { background-color: var(--primary); } /* Manual Input Mode specific styling */ .input-labels { display: grid; grid-template-columns: var(--_display-size) 1.5rem var(--_display-size); justify-content: center; margin-top: -1rem; margin-bottom: 1.5rem; color: var(--on-surface-variant); font-size: 0.75rem; font-weight: 400; padding: 0; inline-size: 100%; } .input-labels > span { text-align: center; } .input-labels > span:first-child { grid-column: 1; } .input-labels > span:last-child { grid-column: 3; } :host([mode='input']) .input-labels { margin-block-end: 0; } /* Bottom bar */ .bottom-actions { display: flex; justify-content: space-between; align-items: center; } .mode-toggle-btn { background: none; border: none; color: var(--primary); cursor: pointer; padding: 0.5rem; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: background-color var(--speed1); } .mode-toggle-btn:hover { background-color: var(--active); } .action-buttons { display: flex; gap: 0.5rem; } ` ]; } /** * Renderizador de la carátula circular (Dial Face). * * Utiliza trigonometría básica (`sin` y `cos`) para distribuir los números equitativamente * a lo largo del perímetro del círculo en CSS absoluto. En formato 24h, renderiza * dos anillos concéntricos manipulando el radio relativo (`50%` vs `32%`). */ renderDialClockFace() { const isHour = this.activeSelection === 'hour'; let degrees = 0; let handLength = '6.25rem'; if (isHour) { let h = this.currentHour; if (!this.use24Hour) { if (h > 12) h -= 12; if (h === 0) h = 12; degrees = h * 30; } else { // 24 hour mode if (h > 12 || h === 0) { // Outer circle: 13-00 const outerHour = h === 0 ? 12 : h > 12 ? h - 12 : h; degrees = outerHour * 30; handLength = '6.5rem'; } else { // Inner circle: 1-12 degrees = h * 30; handLength = '4.5rem'; } } } else { degrees = this.currentMinute * 6; } // Calculate numbers placement const numbers = []; const r = 16 * 16; // 16rem = 256px const center = r / 2; if (isHour) { if (this.use24Hour) { // Outer circle: 13-00 const outerRadius = center - 24; for (let i = 1; i <= 12; i++) { const hVal = i === 12 ? 0 : i + 12; const angle = (i * 30 - 90) * (Math.PI / 180); const x = center + outerRadius * Math.cos(angle); const y = center + outerRadius * Math.sin(angle); numbers.push({ val: hVal, label: String(hVal === 0 ? '00' : hVal), x: `${x / 16}rem`, y: `${y / 16}rem`, selected: this.currentHour === hVal }); } // Inner circle: 1-12 const innerRadius = center - 56; for (let i = 1; i <= 12; i++) { const angle = (i * 30 - 90) * (Math.PI / 180); const x = center + innerRadius * Math.cos(angle); const y = center + innerRadius * Math.sin(angle); numbers.push({ val: i, label: String(i), x: `${x / 16}rem`, y: `${y / 16}rem`, selected: this.currentHour === i }); } } else { // 12-hour: 1-12 const radius = center - 28; for (let i = 1; i <= 12; i++) { const angle = (i * 30 - 90) * (Math.PI / 180); const x = center + radius * Math.cos(angle); const y = center + radius * Math.sin(angle); let hVal = this.currentHour; if (hVal > 12) hVal -= 12; if (hVal === 0) hVal = 12; numbers.push({ val: i, label: String(i), x: `${x / 16}rem`, y: `${y / 16}rem`, selected: hVal === i }); } } } else { // Minutes: 0, 5, 10... 55 const radius = center - 28; for (let i = 0; i < 12; i++) { const mVal = i * 5; const angle = (i * 30 - 90) * (Math.PI / 180); const x = center + radius * Math.cos(angle); const y = center + radius * Math.sin(angle); numbers.push({ val: mVal, label: String(mVal).padStart(2, '0'), x: `${x / 16}rem`, y: `${y / 16}rem`, selected: this.currentMinute === mVal }); } } return html ` <div class=\"clock-container\"> <div class=\"clock-face\" @pointerdown=${this.handleClockPointerDown} @pointermove=${this.handleClockPointerMove} @pointerup=${this.handleClockPointerUp} @pointercancel=${this.handleClockPointerUp} > <div class=\"clock-center\"></div> <div class=\"clock-hand\" style=\"transform: rotate(${degrees}deg); height: ${handLength};\" > <div class=\"clock-hand-selector\"></div> ${isHour && this.use24Hour && this.currentHour <= 12 && this.currentHour > 0 ? html `<div class=\"clock-hand-dot\"></div>` : ''} </div> ${numbers.map((n) => html ` <div class=\"clock-number ${n.selected ? 'selected' : ''}\" style=\"left: ${n.x}; top: ${n.y};\" > ${n.label} </div> `)} </div> </div> `; } /** * Renderiza el selector de hora (time picker) en modo `dial` o `input`. * * **Visualización 12 vs 24 horas:** * `hourVal` convierte `currentHour` (0–23) al formato de visualización: * - Modo 24 horas: string con ceros a la izquierda (ej. `'09'`, `'14'`). * - Modo 12 horas: `0` → `12`, `13` → `1`, preservando la visualización AM/PM. * `minuteVal` siempre tiene ceros a la izquierda para tener 2 dígitos. * * **Modo dial:** * Renderiza una carátula de reloj DOM (HTML/CSS) con números distribuidos * circularmente para cada hora/minuto. El sub-método `renderDialClockFace()` * maneja la geometría y estilos que mueven la manecilla selectora mientras * el usuario arrastra. * * **Modo input:** * Renderiza dos elementos `<input type=\"number\">` para la hora y el minuto con * un selector AM/PM. El modo input es la alternativa (fallback) para accesibilidad * y entornos no táctiles. * * **Alternador de modo:** * Un icono de teclado cambia de modo `dial` a `input`; un icono de reloj * cambia de vuelta. */ render() { const hourVal = !this.use24Hour ? String(this.currentHour > 12 ? this.currentHour - 12 : this.currentHour === 0 ? 12 : this.currentHour).padStart(2, '0') : String(this.currentHour).padStart(2, '0'); const minuteVal = String(this.currentMinute).padStart(2, '0'); return html ` <div class=\"main-content\"> <div class=\"left-pane\"> ${this.hideHeadline ? '' : html `<div class=\"picker-header\"> ${this.mode === 'dial' ? 'Select time' : 'Enter time'} </div>`} <div class=\"time-display-container\"> <div class=\"time-display\"> <div class=\"display-box ${this.activeSelection === 'hour' ? 'active' : ''}\" @click=${() => this.setSelection('hour')} > ${this.mode === 'input' ? html `<input type=\"number\" .value=${hourVal} @change=${this.handleHourInputChange} min=${this.use24Hour ? 0 : 1} max=${this.use24Hour ? 23 : 12} />` : hourVal} </div> <div class=\"separator\">:</div> <div class=\"display-box ${this.activeSelection === 'minute' ? 'active' : ''}\" @click=${() => this.setSelection('minute')} > ${this.mode === 'input' ? html `<input type=\"number\" .value=${minuteVal} @change=${this.handleMinuteInputChange} min=\"0\" max=\"59\" />` : minuteVal} </div> </div> ${!this.use24Hour ? html ` <div class=\"ampm-toggle\"> <button class=\"ampm-btn ${this.period === 'AM' ? 'active' : ''}\" @click=${() => this.setPeriod('AM')} > AM </button> <button class=\"ampm-btn ${this.period === 'PM' ? 'active' : ''}\" @click=${() => this.setPeriod('PM')} > PM </button> </div> ` : ''} </div> ${this.mode === 'input' ? html ` <div class=\"input-labels\"> <span>${this.hourLabel}</span> <span>${this.minuteLabel}</span> </div> ` : ''} </div> ${this.mode === 'dial' ? this.renderDialClockFace() : ''} </div> ${this.hideModeToggle ? '' : html `<div class=\"bottom-actions\"> <button class=\"mode-toggle-btn\" @click=${this.toggleMode} aria-label=\"Toggle input mode\"> <moni-icon name=${this.mode === 'dial' ? 'keyboard' : 'schedule'}></moni-icon> </button> </div>`} `; } }",
2633
+ "default": "class MoniTimePicker extends MoniElement { constructor() { super(...arguments); this.value = '00:00'; this.use24Hour = false; this.mode = 'dial'; this.orientation = 'auto'; this.hideModeToggle = false; this.hideHeadline = false; this.hourLabel = 'Hora'; this.minuteLabel = 'Minuto'; this.activeSelection = 'hour'; this.period = 'AM'; this.currentHour = 0; // 0-23 this.currentMinute = 0; // 0-59 this.isDragging = false; } /** * Hook del ciclo de vida (Lit) previo al renderizado. * * Si la propiedad externa `value` o el modo `use24Hour` cambian (vía binding o atributo HTML), * dispara el `parseValue()` de inmediato para mantener sincronizado el estado interno de * horas y minutos (y recalcular AM/PM) antes de mutar el DOM real. */ willUpdate(changedProperties) { if (changedProperties.has('value') || changedProperties.has('use24Hour')) { this.parseValue(); } } /** * Interpreta el string `value` (formato \"HH:MM\") y lo descompone en enteros para * el estado interno. Automáticamente corrige valores fuera de rango y determina * si estamos en AM o PM (cuando no se usa formato de 24 horas). */ parseValue() { const parts = this.value.split(':'); let h = parseInt(parts[0], 10); let m = parseInt(parts[1], 10); if (isNaN(h) || h < 0 || h > 23) h = 0; if (isNaN(m) || m < 0 || m > 59) m = 0; this.currentHour = h; this.currentMinute = m; if (!this.use24Hour) { if (h >= 12) { this.period = 'PM'; } else { this.period = 'AM'; } } } /** * Reconstruye el string `value` a partir del estado interno (`currentHour`, `currentMinute`). * Formatea con ceros a la izquierda e incluye la lógica de conversión AM/PM a 24hrs * para garantizar que el valor expuesto hacia afuera sea siempre un estándar ISO (\"14:30\"). */ updateValue() { let h = this.currentHour; const m = this.currentMinute; if (!this.use24Hour) { if (this.period === 'PM') { if (h < 12) h += 12; } else { if (h === 12) h = 0; else if (h > 12) h -= 12; // Ajuste por si quedó fuera de límites } } const formattedHour = String(h).padStart(2, '0'); const formattedMinute = String(m).padStart(2, '0'); this.value = `${formattedHour}:${formattedMinute}`; this.dispatchEvent(new CustomEvent('change', { detail: { value: this.value }, bubbles: true, composed: true })); } /** * Alterna el modo de interacción visual entre el Reloj Analógico (dial) * y el modo de Entrada de Texto manual (input). */ toggleMode() { this.mode = this.mode === 'dial' ? 'input' : 'dial'; } /** * Define el nivel de selección activo ('hour' o 'minute'). * Cambia dinámicamente qué anillo o qué matemáticas (12 vs 60 items) * utilizará el componente principal y el reloj. */ setSelection(type) { this.activeSelection = type; } /** * Cambia forzosamente el periodo del día ('AM' o 'PM') cuando el usuario * presiona los conmutadores laterales (sólo visible en formato 12h). * Inmediatamente propaga el cambio hacia `updateValue()` para recalcular * y despachar la hora formateada. */ setPeriod(p) { this.period = p; this.updateValue(); } // Clock Math and Interaction /** * Inicia el proceso de arrastre en la carátula del reloj (Pointer Down). * Utiliza `setPointerCapture` nativo para asegurar que el movimiento (drag) siga * siendo rastreado aunque el puntero del usuario salga físicamente del contenedor, * garantizando una interacción \"Touch\" a prueba de errores. */ handleClockPointerDown(e) { if (!this.clockFaceEl) return; this.isDragging = true; this.clockFaceEl.classList.add('dragging'); this.clockFaceEl.setPointerCapture(e.pointerId); this.handleClockPointerMove(e); } /** * Corazón matemático del reloj radial (Time Picker analógico). * Convierte las coordenadas cartesianas del ratón/dedo en un ángulo polar (0-360º), * y dependiendo si medimos horas o minutos, lo mapea al número correspondiente. */ handleClockPointerMove(e) { if (!this.isDragging || !this.clockFaceEl) return; const rect = this.clockFaceEl.getBoundingClientRect(); // Encontramos el centro geométrico absoluto del reloj const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; const dx = e.clientX - centerX; const dy = e.clientY - centerY; // Teorema de Pitágoras para saber a qué distancia del centro está apuntando el usuario const distance = Math.sqrt(dx * dx + dy * dy); // Calculamos el ángulo en grados partiendo desde las 12 (sentido horario). // Math.atan2 devuelve de -PI a PI, sumamos 90º para desfasar el origen arriba (eje Y). let angle = Math.atan2(dy, dx) * (180 / Math.PI) + 90; if (angle < 0) angle += 360; if (this.activeSelection === 'hour') { if (this.use24Hour) { // Reloj de 24 horas M3: Un círculo externo (13-00) y uno interno (1-12) // El umbral (0.32) fue medido visualmente para coincidir con el diseño radial interno. const isInner = distance < rect.width * 0.32; let hour = Math.round(angle / 30) % 12; // 360 / 12 horas = 30 grados por hora if (hour === 0) hour = 12; if (isInner) { // Círculo interno: 1 a 12 (AM/PM) this.currentHour = hour; } else { // Círculo externo: 13 a 24 (donde las 24 = 0) this.currentHour = hour === 12 ? 0 : hour + 12; } } else { // Reloj estándar de 12 horas: solo 1 a 12 let hour = Math.round(angle / 30) % 12; if (hour === 0) hour = 12; this.currentHour = hour; } } else { // Modo minutos: 360 / 60 minutos = 6 grados por minuto const minute = Math.round(angle / 6) % 60; this.currentMinute = minute; } this.updateValue(); } /** * Finaliza la interacción de arrastre en la carátula del reloj (Pointer Up). * * Como heurística de usabilidad, si el usuario acaba de seleccionar la Hora, * el componente espera 300ms y automáticamente transiciona a la vista de Minutos. * Esto reduce los clics necesarios en interacciones de Time Pickers de un solo toque. */ handleClockPointerUp() { if (this.isDragging) { this.isDragging = false; this.clockFaceEl?.classList.remove('dragging'); if (this.activeSelection === 'hour') { // Cambiar automáticamente a la selección de minutos después de elegir la hora setTimeout(() => { this.activeSelection = 'minute'; }, 300); } } } /** * Handler para entradas directas desde el `<input>` de horas (Modo 'input' texto). * Implementa clamping (límites mínimos y máximos) para asegurar que el usuario * nunca pueda introducir valores erróneos mayores a 23h o menores a 0h. */ handleHourInputChange(e) { const target = e.target; let val = parseInt(target.value, 10); if (isNaN(val)) val = 0; if (this.use24Hour) { if (val < 0) val = 0; if (val > 23) val = 23; this.currentHour = val; } else { if (val < 1) val = 1; if (val > 12) val = 12; this.currentHour = val; } this.updateValue(); } /** * Handler para entradas directas desde el `<input>` de minutos (Modo 'input' texto). * Trunca cualquier input alfanumérico a enteros y realiza un clamp a 0-59. */ handleMinuteInputChange(e) { const target = e.target; let val = parseInt(target.value, 10); if (isNaN(val)) val = 0; if (val < 0) val = 0; if (val > 59) val = 59; this.currentMinute = val; this.updateValue(); } static { this.styles = [ sharedStyles, css ` :host { --_display-size: 6rem; display: inline-flex; flex-direction: column; background-color: transparent; inline-size: min(20.5rem, 100%); max-inline-size: 100%; box-sizing: border-box; container-type: inline-size; color: var(--on-surface); font-family: var(--font); user-select: none; } :host([use-24-hour]) { --_display-size: 7.125rem; } /* Forced orientations */ :host([orientation=\"vertical\"]) { inline-size: min(20.5rem, 100%); } :host([orientation=\"vertical\"]) .main-content { flex-direction: column; } :host([orientation=\"horizontal\"]) { inline-size: min(40rem, 100%); } :host([orientation=\"horizontal\"]) .main-content { flex-direction: row; align-items: center; justify-content: center; gap: 2rem; } :host([orientation=\"horizontal\"]) .left-pane { display: flex; flex-direction: column; justify-content: center; flex: none; } /* Main Content (Default vertical) */ .main-content { display: flex; flex-direction: column; gap: 1rem; min-inline-size: 0; } :host([mode='input']) .main-content { margin-block: auto; justify-content: center; align-items: center; } .main-content > * { min-inline-size: 0; } .clock-container { flex: none; } /* Auto stays vertical because the component can live inside a narrow dialog even when the browser viewport itself is wide. Consumers that reserve enough inline space can opt into horizontal explicitly. */ @media (min-width: 48rem) { :host([orientation='auto']), :host(:not([orientation])) { inline-size: min(40rem, 100%); } :host([orientation='auto']) .main-content, :host(:not([orientation])) .main-content { flex-direction: row; align-items: center; justify-content: center; gap: 2rem; } :host([orientation='auto']) .left-pane, :host(:not([orientation])) .left-pane { display: flex; flex-direction: column; justify-content: center; flex: none; } } @keyframes fadeInScale { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } } .clock-container, .time-display-container, .input-labels, .display-box { animation: fadeInScale var(--speed3) var(--ease-standard) forwards; } .picker-header { font-size: 0.75rem; font-weight: 500; margin-bottom: 1.25rem; color: var(--on-surface-variant); text-transform: uppercase; letter-spacing: 0.1em; } /* Display Panel (Time box inputs/views) */ .time-display-container { display: flex; align-items: center; justify-content: center; margin-bottom: 1.5rem; gap: 0.75rem; } .time-display { display: grid; grid-template-columns: var(--_display-size) 1.5rem var(--_display-size); align-items: center; justify-content: center; flex: none; background-color: transparent; } .display-box { display: flex; align-items: center; justify-content: center; width: 100%; height: 5rem; border-radius: 0.5rem; background-color: var(--surface-container-highest); font-size: 3.5rem; font-weight: 400; color: var(--on-surface); cursor: pointer; transition: background-color var(--speed2); border: 0.125rem solid transparent; text-align: center; } :host([use-24-hour]) .display-box { width: 100%; } .display-box:hover:not(.active) { background-color: var(--active); } .display-box.active { background-color: var(--primary-container); color: var(--on-primary-container); border-color: var(--primary); } .display-box input { width: 100%; height: 100%; border: none; background: transparent; text-align: center; font-size: inherit; font-family: inherit; color: inherit; outline: none; padding: 0; } /* Remove input spinners */ .display-box input::-webkit-outer-spin-button, .display-box input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .display-box input[type=number] { -moz-appearance: textfield; } .separator { font-size: 3rem; font-weight: 400; color: var(--on-surface); width: 1.5rem; text-align: center; line-height: 5rem; } /* AM/PM Toggle */ .ampm-toggle { display: flex; flex-direction: column; flex: 0 0 3.25rem; border: 0.0625rem solid var(--outline); border-radius: 0.5rem; overflow: hidden; height: 5rem; width: 3.25rem; } .ampm-btn { flex: 1; background-color: transparent; border: none; font-size: 0.875rem; font-weight: 500; color: var(--on-surface-variant); cursor: pointer; transition: background-color var(--speed1), color var(--speed1); } .ampm-btn.active { background-color: var(--tertiary-container); color: var(--on-tertiary-container); } .ampm-btn:first-child { border-bottom: 0.0625rem solid var(--outline); } /* Clock Face */ .clock-container { display: flex; justify-content: center; align-items: center; margin-block: 0.5rem 1.5rem; } .clock-face { position: relative; width: 16rem; height: 16rem; border-radius: 50%; background-color: var(--surface-container-highest); touch-action: none; cursor: pointer; } @container (max-width: 18rem) { :host { --_display-size: clamp(4.5rem, 35cqi, 6rem); } :host([use-24-hour]) { --_display-size: clamp(4.75rem, 38cqi, 7.125rem); } .time-display-container { gap: .375rem; margin-block-end: 1rem; } .display-box { block-size: 4rem; font-size: 2.75rem; } .separator { inline-size: 1rem; font-size: 2.25rem; line-height: 4rem; } .clock-container { margin-block: .25rem 1rem; } } /* Center dot */ .clock-center { position: absolute; left: calc(50% - 0.25rem); top: calc(50% - 0.25rem); width: 0.5rem; height: 0.5rem; border-radius: 50%; background-color: var(--primary); } /* Clock hand line */ .clock-hand { position: absolute; left: calc(50% - 0.0625rem); bottom: 50%; width: 0.125rem; background-color: var(--primary); transform-origin: bottom center; z-index: 1; pointer-events: none; transition: transform var(--speed3) var(--ease-standard), height var(--speed3) var(--ease-standard); } /* Hand tip circle selection inside the hand */ .clock-hand-selector { position: absolute; top: 0; left: 50%; width: 3rem; height: 3rem; border-radius: 50%; background-color: var(--primary); opacity: 0.3; transform: translate(-50%, -50%) scale(1); z-index: 1; pointer-events: none; transition: transform var(--speed2) var(--ease-standard); } /* Disable transition during active drag but keep scale transform animation */ .clock-face.dragging .clock-hand, .clock-face:active .clock-hand { transition: none !important; } /* Selector handle expands/grows when pressed or dragging */ .clock-face.dragging .clock-hand-selector, .clock-face:active .clock-hand-selector { transform: translate(-50%, -50%) scale(1.2); } .clock-hand-dot { position: absolute; left: calc(50% - 0.25rem); top: -2rem; width: 0.5rem; height: 0.5rem; border-radius: 50%; background-color: var(--on-primary); z-index: 2; pointer-events: none; } @keyframes numberEntrance { from { opacity: 0; transform: translate(-50%, -50%) scale(0.85); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } /* Clock dial numbers */ .clock-number { position: absolute; width: 2.25rem; height: 2.25rem; display: flex; align-items: center; justify-content: center; font-size: 0.875rem; font-weight: 500; color: var(--on-surface); border-radius: 50%; transform: translate(-50%, -50%); transition: background-color var(--speed2), color var(--speed2); z-index: 2; pointer-events: auto; cursor: pointer; animation: numberEntrance var(--speed3) var(--ease-standard) forwards; } .clock-number:hover { background-color: var(--active); } .clock-number.selected { background-color: var(--primary); color: var(--on-primary); font-weight: bold; } .clock-number.selected:hover { background-color: var(--primary); } /* Manual Input Mode specific styling */ .input-labels { display: grid; grid-template-columns: var(--_display-size) 1.5rem var(--_display-size); justify-content: center; margin-top: -1rem; margin-bottom: 1.5rem; color: var(--on-surface-variant); font-size: 0.75rem; font-weight: 400; padding: 0; inline-size: 100%; } .input-labels > span { text-align: center; } .input-labels > span:first-child { grid-column: 1; } .input-labels > span:last-child { grid-column: 3; } :host([mode='input']) .input-labels { margin-block-end: 0; } /* Bottom bar */ .bottom-actions { display: flex; justify-content: space-between; align-items: center; } .mode-toggle-btn { background: none; border: none; color: var(--primary); cursor: pointer; padding: 0.5rem; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: background-color var(--speed1); } .mode-toggle-btn:hover { background-color: var(--active); } .action-buttons { display: flex; gap: 0.5rem; } ` ]; } /** * Renderizador de la carátula circular (Dial Face). * * Utiliza trigonometría básica (`sin` y `cos`) para distribuir los números equitativamente * a lo largo del perímetro del círculo en CSS absoluto. En formato 24h, renderiza * dos anillos concéntricos manipulando el radio relativo (`50%` vs `32%`). */ renderDialClockFace() { const isHour = this.activeSelection === 'hour'; let degrees = 0; let handLength = '6.25rem'; if (isHour) { let h = this.currentHour; if (!this.use24Hour) { if (h > 12) h -= 12; if (h === 0) h = 12; degrees = h * 30; } else { // 24 hour mode if (h > 12 || h === 0) { // Outer circle: 13-00 const outerHour = h === 0 ? 12 : h > 12 ? h - 12 : h; degrees = outerHour * 30; handLength = '6.5rem'; } else { // Inner circle: 1-12 degrees = h * 30; handLength = '4.5rem'; } } } else { degrees = this.currentMinute * 6; } // Calculate numbers placement const numbers = []; const r = 16 * 16; // 16rem = 256px const center = r / 2; if (isHour) { if (this.use24Hour) { // Outer circle: 13-00 const outerRadius = center - 24; for (let i = 1; i <= 12; i++) { const hVal = i === 12 ? 0 : i + 12; const angle = (i * 30 - 90) * (Math.PI / 180); const x = center + outerRadius * Math.cos(angle); const y = center + outerRadius * Math.sin(angle); numbers.push({ val: hVal, label: String(hVal === 0 ? '00' : hVal), x: `${x / 16}rem`, y: `${y / 16}rem`, selected: this.currentHour === hVal }); } // Inner circle: 1-12 const innerRadius = center - 56; for (let i = 1; i <= 12; i++) { const angle = (i * 30 - 90) * (Math.PI / 180); const x = center + innerRadius * Math.cos(angle); const y = center + innerRadius * Math.sin(angle); numbers.push({ val: i, label: String(i), x: `${x / 16}rem`, y: `${y / 16}rem`, selected: this.currentHour === i }); } } else { // 12-hour: 1-12 const radius = center - 28; for (let i = 1; i <= 12; i++) { const angle = (i * 30 - 90) * (Math.PI / 180); const x = center + radius * Math.cos(angle); const y = center + radius * Math.sin(angle); let hVal = this.currentHour; if (hVal > 12) hVal -= 12; if (hVal === 0) hVal = 12; numbers.push({ val: i, label: String(i), x: `${x / 16}rem`, y: `${y / 16}rem`, selected: hVal === i }); } } } else { // Minutes: 0, 5, 10... 55 const radius = center - 28; for (let i = 0; i < 12; i++) { const mVal = i * 5; const angle = (i * 30 - 90) * (Math.PI / 180); const x = center + radius * Math.cos(angle); const y = center + radius * Math.sin(angle); numbers.push({ val: mVal, label: String(mVal).padStart(2, '0'), x: `${x / 16}rem`, y: `${y / 16}rem`, selected: this.currentMinute === mVal }); } } return html ` <div class=\"clock-container\"> <div class=\"clock-face\" @pointerdown=${this.handleClockPointerDown} @pointermove=${this.handleClockPointerMove} @pointerup=${this.handleClockPointerUp} @pointercancel=${this.handleClockPointerUp} > <div class=\"clock-center\"></div> <div class=\"clock-hand\" style=\"transform: rotate(${degrees}deg); height: ${handLength};\" > <div class=\"clock-hand-selector\"></div> ${isHour && this.use24Hour && this.currentHour <= 12 && this.currentHour > 0 ? html `<div class=\"clock-hand-dot\"></div>` : ''} </div> ${numbers.map((n) => html ` <div class=\"clock-number ${n.selected ? 'selected' : ''}\" style=\"left: ${n.x}; top: ${n.y};\" > ${n.label} </div> `)} </div> </div> `; } /** * Renderiza el selector de hora (time picker) en modo `dial` o `input`. * * **Visualización 12 vs 24 horas:** * `hourVal` convierte `currentHour` (0–23) al formato de visualización: * - Modo 24 horas: string con ceros a la izquierda (ej. `'09'`, `'14'`). * - Modo 12 horas: `0` → `12`, `13` → `1`, preservando la visualización AM/PM. * `minuteVal` siempre tiene ceros a la izquierda para tener 2 dígitos. * * **Modo dial:** * Renderiza una carátula de reloj DOM (HTML/CSS) con números distribuidos * circularmente para cada hora/minuto. El sub-método `renderDialClockFace()` * maneja la geometría y estilos que mueven la manecilla selectora mientras * el usuario arrastra. * * **Modo input:** * Renderiza dos elementos `<input type=\"number\">` para la hora y el minuto con * un selector AM/PM. El modo input es la alternativa (fallback) para accesibilidad * y entornos no táctiles. * * **Alternador de modo:** * Un icono de teclado cambia de modo `dial` a `input`; un icono de reloj * cambia de vuelta. */ render() { const hourVal = !this.use24Hour ? String(this.currentHour > 12 ? this.currentHour - 12 : this.currentHour === 0 ? 12 : this.currentHour).padStart(2, '0') : String(this.currentHour).padStart(2, '0'); const minuteVal = String(this.currentMinute).padStart(2, '0'); return html ` <div class=\"main-content\"> <div class=\"left-pane\"> ${this.hideHeadline ? '' : html `<div class=\"picker-header\"> ${this.mode === 'dial' ? 'Select time' : 'Enter time'} </div>`} <div class=\"time-display-container\"> <div class=\"time-display\"> <div class=\"display-box ${this.activeSelection === 'hour' ? 'active' : ''}\" @click=${() => this.setSelection('hour')} > ${this.mode === 'input' ? html `<input type=\"number\" .value=${hourVal} @change=${this.handleHourInputChange} min=${this.use24Hour ? 0 : 1} max=${this.use24Hour ? 23 : 12} />` : hourVal} </div> <div class=\"separator\">:</div> <div class=\"display-box ${this.activeSelection === 'minute' ? 'active' : ''}\" @click=${() => this.setSelection('minute')} > ${this.mode === 'input' ? html `<input type=\"number\" .value=${minuteVal} @change=${this.handleMinuteInputChange} min=\"0\" max=\"59\" />` : minuteVal} </div> </div> ${!this.use24Hour ? html ` <div class=\"ampm-toggle\"> <button class=\"ampm-btn ${this.period === 'AM' ? 'active' : ''}\" @click=${() => this.setPeriod('AM')} > AM </button> <button class=\"ampm-btn ${this.period === 'PM' ? 'active' : ''}\" @click=${() => this.setPeriod('PM')} > PM </button> </div> ` : ''} </div> ${this.mode === 'input' ? html ` <div class=\"input-labels\"> <span>${this.hourLabel}</span> <span>${this.minuteLabel}</span> </div> ` : ''} </div> ${this.mode === 'dial' ? this.renderDialClockFace() : ''} </div> ${this.hideModeToggle ? '' : html `<div class=\"bottom-actions\"> <button class=\"mode-toggle-btn\" @click=${this.toggleMode} aria-label=\"Toggle input mode\"> <moni-icon name=${this.mode === 'dial' ? 'keyboard' : 'schedule'}></moni-icon> </button> </div>`} `; } }",
2634
2634
  "description": "Componente Material Design 3 Time Picker (Selector de hora).\n\nUn control altamente interactivo que permite a los usuarios seleccionar una hora específica.\nProporciona dos modos de entrada distintos:\n1. **Modo dial (analógico):** Una carátula de reloj interactiva donde los usuarios pueden arrastrar o hacer clic\n para seleccionar horas y minutos.\n2. **Modo input (texto):** Entradas de texto estándar para una introducción precisa por teclado.\n\n**Referencia a la especificación M3:** `m3-docs/components/time-pickers/specs.md`\n\n**Formatos de hora y modos:**\n- La propiedad `value` espera y siempre emite un string en formato de 24 horas\n (`HH:MM`, ej. `\"14:30\"`).\n- Establecer `use-24-hour` configura la presentación visual para usar un\n reloj de 24 horas (anillos interior y exterior) y elimina el selector AM/PM. De lo contrario,\n utiliza un dial estándar de 12 horas con un selector AM/PM.\n\n**Diseño responsivo:**\nEl atributo `orientation` configura el diseño. `vertical` apila la\nvisualización de la hora sobre la carátula del reloj, `horizontal` los coloca lado a lado,\ny `auto` responde automáticamente al ancho del contenedor/viewport."
2635
2635
  }
2636
2636
  ],
@@ -3135,12 +3135,6 @@
3135
3135
  "declarations": [],
3136
3136
  "exports": []
3137
3137
  },
3138
- {
3139
- "kind": "javascript-module",
3140
- "path": "src/types/events.ts",
3141
- "declarations": [],
3142
- "exports": []
3143
- },
3144
3138
  {
3145
3139
  "kind": "javascript-module",
3146
3140
  "path": "src/components/index.ts",
@@ -4383,6 +4377,13 @@
4383
4377
  }
4384
4378
  ],
4385
4379
  "members": [
4380
+ {
4381
+ "kind": "field",
4382
+ "name": "_activeObserver",
4383
+ "privacy": "private",
4384
+ "readonly": true,
4385
+ "default": "new MutationObserver(() => this.updateSelectionNeighbors())"
4386
+ },
4386
4387
  {
4387
4388
  "kind": "field",
4388
4389
  "name": "variant",
@@ -4410,6 +4411,33 @@
4410
4411
  "default": "false",
4411
4412
  "description": "Permite que múltiples botones estén activos a la vez (solo aplica a grupos seleccionables)."
4412
4413
  },
4414
+ {
4415
+ "kind": "field",
4416
+ "name": "selectionRequired",
4417
+ "type": {
4418
+ "text": "boolean"
4419
+ },
4420
+ "default": "false",
4421
+ "description": "Impide que el grupo quede sin selección."
4422
+ },
4423
+ {
4424
+ "kind": "field",
4425
+ "name": "shape",
4426
+ "type": {
4427
+ "text": "'round' | 'square'"
4428
+ },
4429
+ "default": "'round'",
4430
+ "description": "Forma base común de los botones. La selección invierte round ↔ square."
4431
+ },
4432
+ {
4433
+ "kind": "field",
4434
+ "name": "resizing",
4435
+ "type": {
4436
+ "text": "'fixed' | 'flexible'"
4437
+ },
4438
+ "default": "'fixed'",
4439
+ "description": "Controla si los botones conservan su ancho intrínseco o llenan la superficie."
4440
+ },
4413
4441
  {
4414
4442
  "kind": "field",
4415
4443
  "name": "gap",
@@ -4461,7 +4489,7 @@
4461
4489
  "text": "array"
4462
4490
  },
4463
4491
  "static": true,
4464
- "default": "[ sharedStyles, css` :host { display: inline-flex; align-items: center; vertical-align: middle; } .group-container { display: inline-flex; align-items: center; width: 100%; } slot { display: contents; } :host([variant='standard']) .group-container { gap: 0.5rem; } :host([variant='standard'][size='xsmall']) .group-container { gap: 1.125rem; /* 18dp */ } :host([variant='standard'][size='small']) .group-container { gap: 0.75rem; /* 12dp */ } :host([variant='standard'][size='medium']) .group-container { gap: 0.5rem; /* 8dp */ } :host([variant='standard'][size='large']) .group-container { gap: 0.5rem; /* 8dp */ } :host([variant='standard'][size='xlarge']) .group-container, :host([variant='standard'][size='extra']) .group-container { gap: 0.5rem; /* 8dp */ } :host([variant='connected']) .group-container { gap: 0.125rem; /* 2dp */ } ` ]"
4492
+ "default": "[ sharedStyles, css` :host { display: inline-flex; align-items: center; vertical-align: middle; max-inline-size: 100%; } :host([variant='connected']), :host([resizing='flexible']) { inline-size: 100%; } .group-container { display: inline-flex; align-items: center; width: 100%; flex-wrap: nowrap; } slot { display: contents; } :host([variant='standard']) .group-container { gap: 0.5rem; } :host([variant='standard'][size='xsmall']) .group-container { gap: 1.125rem; /* 18dp */ } :host([variant='standard'][size='small']) .group-container { gap: 0.75rem; /* 12dp */ } :host([variant='standard'][size='medium']) .group-container { gap: 0.5rem; /* 8dp */ } :host([variant='standard'][size='large']) .group-container { gap: 0.5rem; /* 8dp */ } :host([variant='standard'][size='xlarge']) .group-container, :host([variant='standard'][size='extra']) .group-container { gap: 0.5rem; /* 8dp */ } :host([variant='connected']) .group-container { gap: 0.125rem; /* 2dp */ } :host([variant='connected']) ::slotted(moni-button), :host([variant='connected']) ::slotted(moni-icon-button), :host([resizing='flexible']) ::slotted(moni-button) { flex: 1 1 0; } :host([variant='connected']) ::slotted(moni-button) { --moni-button-inline-size: 100%; min-inline-size: 0; } :host([variant='connected'][size='xsmall']) ::slotted(moni-button), :host([variant='connected'][size='xsmall']) ::slotted(moni-icon-button), :host([variant='connected'][size='small']) ::slotted(moni-button), :host([variant='connected'][size='small']) ::slotted(moni-icon-button) { min-inline-size: 3rem; } /* M3 Expressive selection emphasis. Increasing the button's internal * space (instead of scaling the host) keeps labels and icons crisp and * lets the existing padding transition transfer width smoothly. */ :host([variant='standard'][size='xsmall']) ::slotted(moni-button[active]), :host([variant='standard'][size='xsmall']) ::slotted(moni-button[data-group-pressed]) { --moni-button-padding: 0 1.25rem; } :host([variant='standard'][size='small']) ::slotted(moni-button[active]), :host([variant='standard'][size='small']) ::slotted(moni-button[data-group-pressed]) { --moni-button-padding: 0 1.75rem; } :host([variant='standard'][size='medium']) ::slotted(moni-button[active]), :host([variant='standard'][size='medium']) ::slotted(moni-button[data-group-pressed]) { --moni-button-padding: 0 2.375rem; } :host([variant='standard'][size='large']) ::slotted(moni-button[active]), :host([variant='standard'][size='large']) ::slotted(moni-button[data-group-pressed]) { --moni-button-padding: 0 4rem; } :host([variant='standard'][size='xlarge']) ::slotted(moni-button[active]), :host([variant='standard'][size='extra']) ::slotted(moni-button[active]), :host([variant='standard'][size='xlarge']) ::slotted(moni-button[data-group-pressed]), :host([variant='standard'][size='extra']) ::slotted(moni-button[data-group-pressed]) { --moni-button-padding: 0 5.25rem; } :host([variant='standard'][size='xsmall']) ::slotted(moni-button[data-group-adjacent-pressed]) { --moni-button-padding: 0 .5rem; } :host([variant='standard'][size='small']) ::slotted(moni-button[data-group-adjacent-pressed]) { --moni-button-padding: 0 .75rem; } :host([variant='standard'][size='medium']) ::slotted(moni-button[data-group-adjacent-pressed]) { --moni-button-padding: 0 1.25rem; } :host([variant='standard'][size='large']) ::slotted(moni-button[data-group-adjacent-pressed]) { --moni-button-padding: 0 2.75rem; } :host([variant='standard'][size='xlarge']) ::slotted(moni-button[data-group-adjacent-pressed]), :host([variant='standard'][size='extra']) ::slotted(moni-button[data-group-adjacent-pressed]) { --moni-button-padding: 0 3.75rem; } /* A selected standard button borrows space from its immediate * neighbours, keeping the group's outside footprint stable. */ :host([variant='standard'][size='xsmall']) ::slotted(moni-button[data-group-adjacent-selected='shared']) { --moni-button-padding: 0 .5rem; } :host([variant='standard'][size='xsmall']) ::slotted(moni-button[data-group-adjacent-selected='sole']) { --moni-button-padding: 0 .25rem; } :host([variant='standard'][size='small']) ::slotted(moni-button[data-group-adjacent-selected='shared']) { --moni-button-padding: 0 .625rem; } :host([variant='standard'][size='small']) ::slotted(moni-button[data-group-adjacent-selected='sole']) { --moni-button-padding: 0 .25rem; } :host([variant='standard'][size='medium']) ::slotted(moni-button[data-group-adjacent-selected='shared']) { --moni-button-padding: 0 1.0625rem; } :host([variant='standard'][size='medium']) ::slotted(moni-button[data-group-adjacent-selected='sole']) { --moni-button-padding: 0 .625rem; } :host([variant='standard'][size='large']) ::slotted(moni-button[data-group-adjacent-selected='shared']) { --moni-button-padding: 0 2.5rem; } :host([variant='standard'][size='large']) ::slotted(moni-button[data-group-adjacent-selected='sole']) { --moni-button-padding: 0 2rem; } :host([variant='standard'][size='xlarge']) ::slotted(moni-button[data-group-adjacent-selected='shared']), :host([variant='standard'][size='extra']) ::slotted(moni-button[data-group-adjacent-selected='shared']) { --moni-button-padding: 0 3.375rem; } :host([variant='standard'][size='xlarge']) ::slotted(moni-button[data-group-adjacent-selected='sole']), :host([variant='standard'][size='extra']) ::slotted(moni-button[data-group-adjacent-selected='sole']) { --moni-button-padding: 0 2.75rem; } ` ]"
4465
4493
  },
4466
4494
  {
4467
4495
  "kind": "method",
@@ -4483,6 +4511,17 @@
4483
4511
  "privacy": "private",
4484
4512
  "description": "Manejador del slot interno (`<slot>`).\nCuando el desarrollador inyecta dinámicamente o remueve botones en runtime,\nre-sincroniza las formas (shapes) y tamaños de todo el listado."
4485
4513
  },
4514
+ {
4515
+ "kind": "method",
4516
+ "name": "getButtons",
4517
+ "privacy": "private"
4518
+ },
4519
+ {
4520
+ "kind": "method",
4521
+ "name": "updateSelectionNeighbors",
4522
+ "privacy": "private",
4523
+ "description": "Keeps selection emphasis inside the group's original visual footprint.\nThe selected width is funded evenly by every unselected button so edge\nselections remain visually balanced instead of collapsing one neighbour."
4524
+ },
4486
4525
  {
4487
4526
  "kind": "method",
4488
4527
  "name": "getGapValue",
@@ -5047,7 +5086,7 @@
5047
5086
  "text": "array"
5048
5087
  },
5049
5088
  "static": true,
5050
- "default": "[ sharedStyles, css` :host { display: inline-flex; vertical-align: middle; font-family: var(--font); position: relative; user-select: none; } .button { box-sizing: var(--moni-button-box-sizing, border-box); display: inline-flex; --_button-visual-height: 3.5rem; align-items: center; justify-content: center; --_button-icon-size: var(--moni-button-icon-size, 1.5rem); block-size: var(--moni-button-height, 3.5rem); font-size: var(--moni-button-font-size, 1rem); font-weight: 500; color: var(--on-primary); padding: var(--moni-button-padding, 0 1.5rem); background-color: var(--primary); border: 0.0625rem solid transparent; border-radius: 999px; transition: transform 250ms cubic-bezier(0.2, 0, 0, 1), border-radius 200ms cubic-bezier(0.2, 0, 0, 1), padding 250ms cubic-bezier(0.2, 0, 0, 1), background-color 200ms cubic-bezier(0.2, 0, 0, 1), box-shadow 200ms cubic-bezier(0.2, 0, 0, 1); user-select: none; gap: var(--moni-button-gap, 0.5rem); line-height: normal; font-family: inherit; cursor: pointer; text-decoration: none; box-shadow: none; position: relative; } .button::after { content: ''; position: absolute; top: 50%; left: 50%; min-width: 3rem; /* 48px minimum touch target */ min-height: 3rem; /* 48px minimum touch target */ width: 100%; height: 100%; transform: translate(-50%, -50%); } /* Sizes */ .button.xsmall { --_button-visual-height: 2rem; --_button-icon-size: var(--moni-button-icon-size, 1.25rem); block-size: var(--moni-button-height, 2rem); min-inline-size: var(--moni-button-min-inline-size, 0); font-size: var(--moni-button-font-size, 0.875rem); padding: var(--moni-button-padding, 0 0.75rem); /* 12dp; keeps 48dp touch target */ gap: var(--moni-button-gap, 0.25rem); } .button.small { --_button-visual-height: 2.5rem; --_button-icon-size: var(--moni-button-icon-size, 1.25rem); block-size: var(--moni-button-height, 2.5rem); min-inline-size: var(--moni-button-min-inline-size, 0); padding: var(--moni-button-padding, 0 1rem); gap: var(--moni-button-gap, 0.5rem); } .button.large { --_button-visual-height: 6rem; --_button-icon-size: var(--moni-button-icon-size, 2rem); block-size: var(--moni-button-height, 6rem); font-size: var(--moni-button-font-size, 1.5rem); padding: var(--moni-button-padding, 0 3rem); gap: var(--moni-button-gap, 0.75rem); } .button.xlarge, .button.extra { --_button-visual-height: 8.5rem; --_button-icon-size: var(--moni-button-icon-size, 2.5rem); block-size: var(--moni-button-height, 8.5rem); font-size: var(--moni-button-font-size, 2rem); padding: var(--moni-button-padding, 0 4rem); gap: var(--moni-button-gap, 1rem); } /* Shapes */ /* Square keeps horizontal padding (like a normal button) but drops border-radius. Only the circle removes all padding. */ .button.circle { padding: 0 !important; border-radius: 50%; inline-size: var(--moni-button-height, 3.5rem); min-inline-size: var(--moni-button-height, 3.5rem); aspect-ratio: 1; } .button.round.icon-only { inline-size: var(--moni-button-height, var(--_button-visual-height)); min-inline-size: var(--moni-button-height, var(--_button-visual-height)); padding-inline: 0; } .button.no-round { border-radius: 0; } .button.square { border-radius: 1rem; } .button.square.xsmall, .button.square.small { border-radius: 0.75rem; } .button.square.large, .button.square.xlarge, .button.square.extra { border-radius: 1.75rem; } .button.left-round { border-radius: 1.25rem 0.5rem 0.5rem 1.25rem; } .button.left-round.xsmall { border-radius: 0.875rem 0.25rem 0.25rem 0.875rem; } .button.left-round.small { border-radius: 1rem 0.5rem 0.5rem 1rem; } .button.left-round.large { border-radius: 1.5rem 1rem 1rem 1.5rem; } .button.left-round.xlarge, .button.left-round.extra { border-radius: 1.75rem 1.25rem 1.25rem 1.75rem; } .button.right-round { border-radius: 0.5rem 1.25rem 1.25rem 0.5rem; } .button.right-round.xsmall { border-radius: 0.25rem 0.875rem 0.875rem 0.25rem; } .button.right-round.small { border-radius: 0.5rem 1rem 1rem 0.5rem; } .button.right-round.large { border-radius: 1rem 1.5rem 1.5rem 1rem; } .button.right-round.xlarge, .button.right-round.extra { border-radius: 1.25rem 1.75rem 1.75rem 1.25rem; } .button.top-round { border-radius: 1.25rem 1.25rem 0.5rem 0.5rem; } .button.top-round.xsmall { border-radius: 0.875rem 0.875rem 0.25rem 0.25rem; } .button.top-round.small { border-radius: 1rem 1rem 0.5rem 0.5rem; } .button.top-round.large { border-radius: 1.5rem 1.5rem 1rem 1rem; } .button.top-round.xlarge, .button.top-round.extra { border-radius: 1.75rem 1.75rem 1.25rem 1.25rem; } .button.bottom-round { border-radius: 0.5rem 0.5rem 1.25rem 1.25rem; } .button.bottom-round.xsmall { border-radius: 0.25rem 0.25rem 0.875rem 0.875rem; } .button.bottom-round.small { border-radius: 0.5rem 0.5rem 1rem 1rem; } .button.bottom-round.large { border-radius: 1rem 1rem 1.5rem 1.5rem; } .button.bottom-round.xlarge, .button.bottom-round.extra { border-radius: 1.25rem 1.25rem 1.75rem 1.75rem; } /* Morph active states (square-ish equivalents) */ .button.left-square { border-radius: 0.75rem 0.5rem 0.5rem 0.75rem; } .button.left-square.xsmall { border-radius: 0.5rem 0.25rem 0.25rem 0.5rem; } .button.left-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.left-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.left-square.xlarge, .button.left-square.extra { border-radius: 1rem 1.25rem 1.25rem 1rem; } .button.right-square { border-radius: 0.5rem 0.75rem 0.75rem 0.5rem; } .button.right-square.xsmall { border-radius: 0.25rem 0.5rem 0.5rem 0.25rem; } .button.right-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.right-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.right-square.xlarge, .button.right-square.extra { border-radius: 1.25rem 1rem 1rem 1.25rem; } .button.top-square { border-radius: 0.75rem 0.75rem 0.5rem 0.5rem; } .button.top-square.xsmall { border-radius: 0.5rem 0.5rem 0.25rem 0.25rem; } .button.top-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.top-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.top-square.xlarge, .button.top-square.extra { border-radius: 1rem 1rem 1.25rem 1.25rem; } .button.bottom-square { border-radius: 0.5rem 0.5rem 0.75rem 0.75rem; } .button.bottom-square.xsmall { border-radius: 0.25rem 0.25rem 0.5rem 0.5rem; } .button.bottom-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.bottom-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.bottom-square.xlarge, .button.bottom-square.extra { border-radius: 1.25rem 1.25rem 1rem 1rem; } .button.inner-round { border-radius: 0.5rem; } .button.inner-round.xsmall { border-radius: 0.25rem; } .button.inner-round.small { border-radius: 0.5rem; } .button.inner-round.large { border-radius: 1rem; } .button.inner-round.xlarge, .button.inner-round.extra { border-radius: 1.25rem; } /* Flat variants (completely 0px on flat sides) */ .button.left-round-flat { border-radius: 1.25rem 0 0 1.25rem; } .button.left-round-flat.xsmall { border-radius: 0.875rem 0 0 0.875rem; } .button.left-round-flat.small { border-radius: 1rem 0 0 1rem; } .button.left-round-flat.large { border-radius: 1.5rem 0 0 1.5rem; } .button.left-round-flat.xlarge, .button.left-round-flat.extra { border-radius: 1.75rem 0 0 1.75rem; } .button.right-round-flat { border-radius: 0 1.25rem 1.25rem 0; } .button.right-round-flat.xsmall { border-radius: 0 0.875rem 0.875rem 0; } .button.right-round-flat.small { border-radius: 0 1rem 1rem 0; } .button.right-round-flat.large { border-radius: 0 1.5rem 1.5rem 0; } .button.right-round-flat.xlarge, .button.right-round-flat.extra { border-radius: 0 1.75rem 1.75rem 0; } .button.top-round-flat { border-radius: 1.25rem 1.25rem 0 0; } .button.top-round-flat.xsmall { border-radius: 0.875rem 0.875rem 0 0; } .button.top-round-flat.small { border-radius: 1rem 1rem 0 0; } .button.top-round-flat.large { border-radius: 1.5rem 1.5rem 0 0; } .button.top-round-flat.xlarge, .button.top-round-flat.extra { border-radius: 1.75rem 1.75rem 0 0; } .button.bottom-round-flat { border-radius: 0 0 1.25rem 1.25rem; } .button.bottom-round-flat.xsmall { border-radius: 0 0 0.875rem 0.875rem; } .button.bottom-round-flat.small { border-radius: 0 0 1rem 1rem; } .button.bottom-round-flat.large { border-radius: 0 0 1.5rem 1.5rem; } .button.bottom-round-flat.xlarge, .button.bottom-round-flat.extra { border-radius: 0 0 1.75rem 1.75rem; } /* Morph active states for flat variants */ .button.left-square-flat { border-radius: 0.75rem 0 0 0.75rem; } .button.left-square-flat.xsmall, .button.left-square-flat.small { border-radius: 0.5rem 0 0 0.5rem; } .button.left-square-flat.large, .button.left-square-flat.xlarge, .button.left-square-flat.extra { border-radius: 1rem 0 0 1rem; } .button.right-square-flat { border-radius: 0 0.75rem 0.75rem 0; } .button.right-square-flat.xsmall, .button.right-square-flat.small { border-radius: 0 0.5rem 0.5rem 0; } .button.right-square-flat.large, .button.right-square-flat.xlarge, .button.right-square-flat.extra { border-radius: 0 1rem 1rem 0; } .button.top-square-flat { border-radius: 0.75rem 0.75rem 0 0; } .button.top-square-flat.xsmall, .button.top-square-flat.small { border-radius: 0.5rem 0.5rem 0 0; } .button.top-square-flat.large, .button.top-square-flat.xlarge, .button.top-square-flat.extra { border-radius: 1rem 1rem 0 0; } .button.bottom-square-flat { border-radius: 0 0 0.75rem 0.75rem; } .button.bottom-square-flat.xsmall, .button.bottom-square-flat.small { border-radius: 0 0 0.5rem 0.5rem; } .button.bottom-square-flat.large, .button.bottom-square-flat.xlarge, .button.bottom-square-flat.extra { border-radius: 0 0 1rem 1rem; } /* Circle hides the label visually (slot still present for a11y and SSR fallback). */ :host([shape='circle']) [part='label'] { display: none; } /* Variants */ .button.border { border: 0.0625rem solid var(--outline-variant); color: var(--primary); background-color: transparent; } .button.fill { background-color: var(--secondary-container); color: var(--on-secondary-container); } .button.transparent { color: var(--primary); background-color: transparent; } .button.elevated { background-color: var(--surface-container-low, var(--surface)); color: var(--primary); box-shadow: var(--elevate1); } .button.error { background-color: var(--error); color: var(--on-error); } .button.error:hover:not(:disabled) { background-image: linear-gradient(color-mix(in srgb, currentColor 8%, transparent), color-mix(in srgb, currentColor 8%, transparent)); } .button.error:focus-visible { outline-color: var(--error); } /* Disabled / active */ .button:disabled, .button[aria-disabled='true'] { opacity: 0.5; cursor: not-allowed; pointer-events: none; } .button:hover:not(:disabled) { transform: translateY(-0.0625rem); background-image: linear-gradient(color-mix(in srgb, currentColor 8%, transparent), color-mix(in srgb, currentColor 8%, transparent)); } .button.elevated:hover:not(:disabled) { box-shadow: var(--elevate2); } .button:active:not(:disabled) { border-radius: 0.75rem; transform: translateY(0.0625rem) scale(0.97); background-image: linear-gradient(color-mix(in srgb, currentColor 12%, transparent), color-mix(in srgb, currentColor 12%, transparent)); } .button.elevated:active:not(:disabled) { box-shadow: none; } .button.xsmall:active:not(:disabled) { border-radius: 0.5rem; } .button.small:active:not(:disabled) { border-radius: 0.5rem; } .button.large:active:not(:disabled) { border-radius: 1rem; } .button.xlarge:active:not(:disabled), .button.extra:active:not(:disabled) { border-radius: 1rem; } .button.active:not(:disabled) { background-color: var(--primary-container); color: var(--on-primary-container); } .button.fill.active:not(:disabled) { background-color: var(--secondary); color: var(--on-secondary); } .button.border.active:not(:disabled) { background-color: var(--inverse-surface); color: var(--inverse-on-surface); border-color: var(--inverse-surface); } .button.elevated.active:not(:disabled) { background-color: var(--primary-container); color: var(--on-primary-container); box-shadow: var(--elevate2); } .button:focus-visible { outline: 0.125rem solid var(--primary); outline-offset: 0.125rem; } /* Icon helper */ .icon, ::slotted([slot='icon']), ::slotted([slot='icon-trailing']) { display: inline-flex; align-items: center; justify-content: center; font-size: var(--_button-icon-size); inline-size: var(--_button-icon-size); block-size: var(--_button-icon-size); flex: none; color: inherit; } .icon { --moni-icon-size: var(--_button-icon-size); font-size: var(--_button-icon-size); inline-size: var(--_button-icon-size); block-size: var(--_button-icon-size); transform: translateX(var(--moni-button-icon-offset, 0)); } .button.left-round { border-radius: 999px var(--_button-square-radius, 1rem) var(--_button-square-radius, 1rem) 999px; } .button.right-round { border-radius: var(--_button-square-radius, 1rem) 999px 999px var(--_button-square-radius, 1rem); } .button.top-round { border-radius: 999px 999px var(--_button-square-radius, 1rem) var(--_button-square-radius, 1rem); } .button.bottom-round { border-radius: var(--_button-square-radius, 1rem) var(--_button-square-radius, 1rem) 999px 999px; } .button.left-round-flat { border-radius: 999px 0 0 999px; } .button.right-round-flat { border-radius: 0 999px 999px 0; } .button.top-round-flat { border-radius: 999px 999px 0 0; } .button.bottom-round-flat { border-radius: 0 0 999px 999px; } .button.xsmall, .button.small { --_button-square-radius: 0.75rem; } .button.large, .button.xlarge, .button.extra { --_button-square-radius: 1.75rem; } :host([split-position]) .button { inline-size: var(--moni-button-inline-size); min-inline-size: var(--moni-button-min-inline-size); border-radius: var(--moni-button-border-radius); } slot { display: contents; } [part='label'][hidden] { display: none; } /* Loading spinner */ .loading-spinner { inline-size: 1.125rem; block-size: 1.125rem; color: currentColor; } .loading-spinner::part(progress) { border-width: 0.1875rem; } ` ]"
5089
+ "default": "[ sharedStyles, css` :host { display: inline-flex; vertical-align: middle; font-family: var(--font); position: relative; user-select: none; } .button { box-sizing: var(--moni-button-box-sizing, border-box); display: inline-flex; inline-size: var(--moni-button-inline-size, auto); --_button-visual-height: 3.5rem; align-items: center; justify-content: center; --_button-icon-size: var(--moni-button-icon-size, 1.5rem); block-size: var(--moni-button-height, 3.5rem); font-size: var(--moni-button-font-size, 1rem); font-weight: 500; color: var(--on-primary); padding: var(--moni-button-padding, 0 1.5rem); background-color: var(--primary); border: 0.0625rem solid transparent; border-radius: 999px; transition: transform 250ms cubic-bezier(0.2, 0, 0, 1), border-radius 200ms cubic-bezier(0.2, 0, 0, 1), padding 250ms cubic-bezier(0.2, 0, 0, 1), background-color 200ms cubic-bezier(0.2, 0, 0, 1), box-shadow 200ms cubic-bezier(0.2, 0, 0, 1); user-select: none; gap: var(--moni-button-gap, 0.5rem); line-height: normal; font-family: inherit; cursor: pointer; text-decoration: none; box-shadow: none; position: relative; } .button::after { content: ''; position: absolute; top: 50%; left: 50%; min-width: 3rem; /* 48px minimum touch target */ min-height: 3rem; /* 48px minimum touch target */ width: 100%; height: 100%; transform: translate(-50%, -50%); } /* Sizes */ .button.xsmall { --_button-visual-height: 2rem; --_button-icon-size: var(--moni-button-icon-size, 1.25rem); block-size: var(--moni-button-height, 2rem); min-inline-size: var(--moni-button-min-inline-size, 0); font-size: var(--moni-button-font-size, 0.875rem); padding: var(--moni-button-padding, 0 0.75rem); /* 12dp; keeps 48dp touch target */ gap: var(--moni-button-gap, 0.25rem); } .button.small { --_button-visual-height: 2.5rem; --_button-icon-size: var(--moni-button-icon-size, 1.25rem); block-size: var(--moni-button-height, 2.5rem); min-inline-size: var(--moni-button-min-inline-size, 0); padding: var(--moni-button-padding, 0 1rem); gap: var(--moni-button-gap, 0.5rem); } .button.large { --_button-visual-height: 6rem; --_button-icon-size: var(--moni-button-icon-size, 2rem); block-size: var(--moni-button-height, 6rem); font-size: var(--moni-button-font-size, 1.5rem); padding: var(--moni-button-padding, 0 3rem); gap: var(--moni-button-gap, 0.75rem); } .button.xlarge, .button.extra { --_button-visual-height: 8.5rem; --_button-icon-size: var(--moni-button-icon-size, 2.5rem); block-size: var(--moni-button-height, 8.5rem); font-size: var(--moni-button-font-size, 2rem); padding: var(--moni-button-padding, 0 4rem); gap: var(--moni-button-gap, 1rem); } /* Shapes */ /* Square keeps horizontal padding (like a normal button) but drops border-radius. Only the circle removes all padding. */ .button.circle { padding: 0 !important; border-radius: 50%; inline-size: var(--moni-button-height, 3.5rem); min-inline-size: var(--moni-button-height, 3.5rem); aspect-ratio: 1; } .button.round.icon-only { inline-size: var(--moni-button-height, var(--_button-visual-height)); min-inline-size: var(--moni-button-height, var(--_button-visual-height)); padding-inline: 0; } .button.no-round { border-radius: 0; } .button.square { border-radius: 1rem; } .button.square.xsmall, .button.square.small { border-radius: 0.75rem; } .button.square.large, .button.square.xlarge, .button.square.extra { border-radius: 1.75rem; } .button.left-round { border-radius: 1.25rem 0.5rem 0.5rem 1.25rem; } .button.left-round.xsmall { border-radius: 0.875rem 0.25rem 0.25rem 0.875rem; } .button.left-round.small { border-radius: 1rem 0.5rem 0.5rem 1rem; } .button.left-round.large { border-radius: 1.5rem 1rem 1rem 1.5rem; } .button.left-round.xlarge, .button.left-round.extra { border-radius: 1.75rem 1.25rem 1.25rem 1.75rem; } .button.right-round { border-radius: 0.5rem 1.25rem 1.25rem 0.5rem; } .button.right-round.xsmall { border-radius: 0.25rem 0.875rem 0.875rem 0.25rem; } .button.right-round.small { border-radius: 0.5rem 1rem 1rem 0.5rem; } .button.right-round.large { border-radius: 1rem 1.5rem 1.5rem 1rem; } .button.right-round.xlarge, .button.right-round.extra { border-radius: 1.25rem 1.75rem 1.75rem 1.25rem; } .button.top-round { border-radius: 1.25rem 1.25rem 0.5rem 0.5rem; } .button.top-round.xsmall { border-radius: 0.875rem 0.875rem 0.25rem 0.25rem; } .button.top-round.small { border-radius: 1rem 1rem 0.5rem 0.5rem; } .button.top-round.large { border-radius: 1.5rem 1.5rem 1rem 1rem; } .button.top-round.xlarge, .button.top-round.extra { border-radius: 1.75rem 1.75rem 1.25rem 1.25rem; } .button.bottom-round { border-radius: 0.5rem 0.5rem 1.25rem 1.25rem; } .button.bottom-round.xsmall { border-radius: 0.25rem 0.25rem 0.875rem 0.875rem; } .button.bottom-round.small { border-radius: 0.5rem 0.5rem 1rem 1rem; } .button.bottom-round.large { border-radius: 1rem 1rem 1.5rem 1.5rem; } .button.bottom-round.xlarge, .button.bottom-round.extra { border-radius: 1.25rem 1.25rem 1.75rem 1.75rem; } /* Morph active states (square-ish equivalents) */ .button.left-square { border-radius: 0.75rem 0.5rem 0.5rem 0.75rem; } .button.left-square.xsmall { border-radius: 0.5rem 0.25rem 0.25rem 0.5rem; } .button.left-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.left-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.left-square.xlarge, .button.left-square.extra { border-radius: 1rem 1.25rem 1.25rem 1rem; } .button.right-square { border-radius: 0.5rem 0.75rem 0.75rem 0.5rem; } .button.right-square.xsmall { border-radius: 0.25rem 0.5rem 0.5rem 0.25rem; } .button.right-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.right-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.right-square.xlarge, .button.right-square.extra { border-radius: 1.25rem 1rem 1rem 1.25rem; } .button.top-square { border-radius: 0.75rem 0.75rem 0.5rem 0.5rem; } .button.top-square.xsmall { border-radius: 0.5rem 0.5rem 0.25rem 0.25rem; } .button.top-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.top-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.top-square.xlarge, .button.top-square.extra { border-radius: 1rem 1rem 1.25rem 1.25rem; } .button.bottom-square { border-radius: 0.5rem 0.5rem 0.75rem 0.75rem; } .button.bottom-square.xsmall { border-radius: 0.25rem 0.25rem 0.5rem 0.5rem; } .button.bottom-square.small { border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; } .button.bottom-square.large { border-radius: 1rem 1rem 1rem 1rem; } .button.bottom-square.xlarge, .button.bottom-square.extra { border-radius: 1.25rem 1.25rem 1rem 1rem; } .button.inner-round { border-radius: 0.5rem; } .button.inner-round.xsmall { border-radius: 0.25rem; } .button.inner-round.small { border-radius: 0.5rem; } .button.inner-round.large { border-radius: 1rem; } .button.inner-round.xlarge, .button.inner-round.extra { border-radius: 1.25rem; } /* Flat variants (completely 0px on flat sides) */ .button.left-round-flat { border-radius: 1.25rem 0 0 1.25rem; } .button.left-round-flat.xsmall { border-radius: 0.875rem 0 0 0.875rem; } .button.left-round-flat.small { border-radius: 1rem 0 0 1rem; } .button.left-round-flat.large { border-radius: 1.5rem 0 0 1.5rem; } .button.left-round-flat.xlarge, .button.left-round-flat.extra { border-radius: 1.75rem 0 0 1.75rem; } .button.right-round-flat { border-radius: 0 1.25rem 1.25rem 0; } .button.right-round-flat.xsmall { border-radius: 0 0.875rem 0.875rem 0; } .button.right-round-flat.small { border-radius: 0 1rem 1rem 0; } .button.right-round-flat.large { border-radius: 0 1.5rem 1.5rem 0; } .button.right-round-flat.xlarge, .button.right-round-flat.extra { border-radius: 0 1.75rem 1.75rem 0; } .button.top-round-flat { border-radius: 1.25rem 1.25rem 0 0; } .button.top-round-flat.xsmall { border-radius: 0.875rem 0.875rem 0 0; } .button.top-round-flat.small { border-radius: 1rem 1rem 0 0; } .button.top-round-flat.large { border-radius: 1.5rem 1.5rem 0 0; } .button.top-round-flat.xlarge, .button.top-round-flat.extra { border-radius: 1.75rem 1.75rem 0 0; } .button.bottom-round-flat { border-radius: 0 0 1.25rem 1.25rem; } .button.bottom-round-flat.xsmall { border-radius: 0 0 0.875rem 0.875rem; } .button.bottom-round-flat.small { border-radius: 0 0 1rem 1rem; } .button.bottom-round-flat.large { border-radius: 0 0 1.5rem 1.5rem; } .button.bottom-round-flat.xlarge, .button.bottom-round-flat.extra { border-radius: 0 0 1.75rem 1.75rem; } /* Morph active states for flat variants */ .button.left-square-flat { border-radius: 0.75rem 0 0 0.75rem; } .button.left-square-flat.xsmall, .button.left-square-flat.small { border-radius: 0.5rem 0 0 0.5rem; } .button.left-square-flat.large, .button.left-square-flat.xlarge, .button.left-square-flat.extra { border-radius: 1rem 0 0 1rem; } .button.right-square-flat { border-radius: 0 0.75rem 0.75rem 0; } .button.right-square-flat.xsmall, .button.right-square-flat.small { border-radius: 0 0.5rem 0.5rem 0; } .button.right-square-flat.large, .button.right-square-flat.xlarge, .button.right-square-flat.extra { border-radius: 0 1rem 1rem 0; } .button.top-square-flat { border-radius: 0.75rem 0.75rem 0 0; } .button.top-square-flat.xsmall, .button.top-square-flat.small { border-radius: 0.5rem 0.5rem 0 0; } .button.top-square-flat.large, .button.top-square-flat.xlarge, .button.top-square-flat.extra { border-radius: 1rem 1rem 0 0; } .button.bottom-square-flat { border-radius: 0 0 0.75rem 0.75rem; } .button.bottom-square-flat.xsmall, .button.bottom-square-flat.small { border-radius: 0 0 0.5rem 0.5rem; } .button.bottom-square-flat.large, .button.bottom-square-flat.xlarge, .button.bottom-square-flat.extra { border-radius: 0 0 1rem 1rem; } /* Circle hides the label visually (slot still present for a11y and SSR fallback). */ :host([shape='circle']) [part='label'] { display: none; } /* Variants */ .button.border { border: 0.0625rem solid var(--outline-variant); color: var(--primary); background-color: transparent; } .button.fill { background-color: var(--secondary-container); color: var(--on-secondary-container); } .button.transparent { color: var(--primary); background-color: transparent; } .button.elevated { background-color: var(--surface-container-low, var(--surface)); color: var(--primary); box-shadow: var(--elevate1); } .button.error { background-color: var(--error); color: var(--on-error); } .button.error:hover:not(:disabled) { background-image: linear-gradient(color-mix(in srgb, currentColor 8%, transparent), color-mix(in srgb, currentColor 8%, transparent)); } .button.error:focus-visible { outline-color: var(--error); } /* Disabled / active */ .button:disabled, .button[aria-disabled='true'] { opacity: 0.5; cursor: not-allowed; pointer-events: none; } .button:hover:not(:disabled) { transform: translateY(-0.0625rem); background-image: linear-gradient(color-mix(in srgb, currentColor 8%, transparent), color-mix(in srgb, currentColor 8%, transparent)); } .button.elevated:hover:not(:disabled) { box-shadow: var(--elevate2); } .button:active:not(:disabled) { border-radius: 0.75rem; transform: translateY(0.0625rem) scale(0.97); background-image: linear-gradient(color-mix(in srgb, currentColor 12%, transparent), color-mix(in srgb, currentColor 12%, transparent)); } .button.elevated:active:not(:disabled) { box-shadow: none; } .button.xsmall:active:not(:disabled) { border-radius: 0.5rem; } .button.small:active:not(:disabled) { border-radius: 0.5rem; } .button.large:active:not(:disabled) { border-radius: 1rem; } .button.xlarge:active:not(:disabled), .button.extra:active:not(:disabled) { border-radius: 1rem; } .button.active:not(:disabled) { background-color: var(--primary-container); color: var(--on-primary-container); } .button.fill.active:not(:disabled) { background-color: var(--secondary); color: var(--on-secondary); } .button.border.active:not(:disabled) { background-color: var(--inverse-surface); color: var(--inverse-on-surface); border-color: var(--inverse-surface); } .button.elevated.active:not(:disabled) { background-color: var(--primary-container); color: var(--on-primary-container); box-shadow: var(--elevate2); } .button:focus-visible { outline: 0.125rem solid var(--primary); outline-offset: 0.125rem; } /* Icon helper */ .icon, ::slotted([slot='icon']), ::slotted([slot='icon-trailing']) { display: inline-flex; align-items: center; justify-content: center; font-size: var(--_button-icon-size); inline-size: var(--_button-icon-size); block-size: var(--_button-icon-size); flex: none; color: inherit; } .icon { --moni-icon-size: var(--_button-icon-size); font-size: var(--_button-icon-size); inline-size: var(--_button-icon-size); block-size: var(--_button-icon-size); transform: translateX(var(--moni-button-icon-offset, 0)); } .button.left-round { border-radius: 999px var(--_button-square-radius, 1rem) var(--_button-square-radius, 1rem) 999px; } .button.right-round { border-radius: var(--_button-square-radius, 1rem) 999px 999px var(--_button-square-radius, 1rem); } .button.top-round { border-radius: 999px 999px var(--_button-square-radius, 1rem) var(--_button-square-radius, 1rem); } .button.bottom-round { border-radius: var(--_button-square-radius, 1rem) var(--_button-square-radius, 1rem) 999px 999px; } .button.left-round-flat { border-radius: 999px 0 0 999px; } .button.right-round-flat { border-radius: 0 999px 999px 0; } .button.top-round-flat { border-radius: 999px 999px 0 0; } .button.bottom-round-flat { border-radius: 0 0 999px 999px; } .button.xsmall, .button.small { --_button-square-radius: 0.75rem; } .button.large, .button.xlarge, .button.extra { --_button-square-radius: 1.75rem; } :host([split-position]) .button { inline-size: var(--moni-button-inline-size); min-inline-size: var(--moni-button-min-inline-size); border-radius: var(--moni-button-border-radius); } slot { display: contents; } [part='label'][hidden] { display: none; } /* Loading spinner */ .loading-spinner { inline-size: 1.125rem; block-size: 1.125rem; color: currentColor; } .loading-spinner::part(progress) { border-width: 0.1875rem; } ` ]"
5051
5090
  },
5052
5091
  {
5053
5092
  "kind": "method",
@@ -9425,7 +9464,7 @@
9425
9464
  "text": "array"
9426
9465
  },
9427
9466
  "static": true,
9428
- "default": "[ sharedStyles, css` :host { display: inline-block; aspect-ratio: 1 / 1; contain: strict; vertical-align: middle; } :host([variant='uncontained']) { width: var(--moni-loading-indicator-size, 2.375rem); } :host([variant='contained']) { width: var(--moni-loading-indicator-container-size, 3rem); } :host([variant='uncontained']) .active-indicator { background-color: var(--moni-loading-indicator-active-color, var(--primary)); } :host([variant='contained']) .active-indicator { background-color: var(--moni-loading-indicator-contained-active-color, var(--on-primary-container)); } :host([variant='contained']) .container { background-color: var(--moni-loading-indicator-contained-container-color, var(--secondary-container)); } .container { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; border-radius: var(--moni-loading-indicator-container-shape, 9999px); box-sizing: border-box; } .active-indicator { margin: auto; aspect-ratio: 1 / 1; width: calc(var(--moni-loading-indicator-size, 2.375rem) * 0.842); transform-origin: center; transition: clip-path 500ms cubic-bezier(0.2, 0, 0, 1); will-change: transform, clip-path; --_polygon-soft-burst: polygon(${unsafeCSS(loadingIndicatorPolygons['soft-burst'])}); --_polygon-9-sided-cookie: polygon(${unsafeCSS(loadingIndicatorPolygons['9-sided-cookie'])}); --_polygon-pentagon: polygon(${unsafeCSS(loadingIndicatorPolygons['pentagon'])}); --_polygon-pill: polygon(${unsafeCSS(loadingIndicatorPolygons['pill'])}); --_polygon-sunny: polygon(${unsafeCSS(loadingIndicatorPolygons['sunny'])}); --_polygon-4-sided-cookie: polygon(${unsafeCSS(loadingIndicatorPolygons['4-sided-cookie'])}); --_polygon-oval: polygon(${unsafeCSS(loadingIndicatorPolygons['oval'])}); } .container.animate .active-indicator-wrapper { animation: rotate-outer 4666ms linear infinite; transform-origin: center; display: flex; align-items: center; justify-content: center; will-change: transform; } @keyframes rotate-outer { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .container.animate .active-indicator { animation: rotate-inner 4666ms cubic-bezier(0.34, 0.88, 0.34, 1) infinite; } @keyframes rotate-inner { 0% { clip-path: var(--_polygon-soft-burst); transform: rotate(0deg); } 14% { clip-path: var(--_polygon-9-sided-cookie); transform: rotate(154deg) scale(1); } 29% { clip-path: var(--_polygon-pentagon); transform: rotate(309deg) scale(1); } 43% { clip-path: var(--_polygon-pill); transform: rotate(463deg) scale(1); } 57% { clip-path: var(--_polygon-sunny); transform: rotate(617deg) scale(1); } 71% { clip-path: var(--_polygon-4-sided-cookie); transform: rotate(771deg) scale(1); } 83% { clip-path: var(--_polygon-oval); transform: rotate(926deg) scale(1); } 100% { clip-path: var(--_polygon-soft-burst); transform: rotate(1080deg) scale(1); } } @media (forced-colors: active) { .active-indicator { background-color: CanvasText !important; } } @media (prefers-reduced-motion: reduce) { .container.animate .active-indicator-wrapper, .container.animate .active-indicator { animation: none; } .active-indicator { clip-path: var(--_polygon-soft-burst); } } ` ]"
9467
+ "default": "[ sharedStyles, css` :host { display: inline-block; aspect-ratio: 1 / 1; /* Paint containment clipped oversized morphs in the contained * variant. Keep layout/style isolation while allowing the active * indicator to animate beyond the container surface. */ contain: layout style; overflow: visible; vertical-align: middle; } :host([variant='uncontained']) { width: var(--moni-loading-indicator-size, 2.375rem); } :host([variant='contained']) { width: var(--moni-loading-indicator-container-size, 3rem); } :host([variant='uncontained']) .active-indicator { background-color: var(--moni-loading-indicator-active-color, var(--primary)); } :host([variant='contained']) .active-indicator { background-color: var(--moni-loading-indicator-contained-active-color, var(--on-primary-container)); } :host([variant='contained']) .container { background-color: var(--moni-loading-indicator-contained-container-color, var(--secondary-container)); } .container { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; border-radius: var(--moni-loading-indicator-container-shape, 9999px); box-sizing: border-box; overflow: visible; } .active-indicator-wrapper { overflow: visible; } .active-indicator-scale { display: flex; align-items: center; justify-content: center; overflow: visible; transform-origin: center; } .active-indicator { margin: auto; aspect-ratio: 1 / 1; width: calc(var(--moni-loading-indicator-size, 2.375rem) * 0.842); transform-origin: center; transition: clip-path 500ms cubic-bezier(0.2, 0, 0, 1); will-change: transform, clip-path; --_polygon-soft-burst: polygon(${unsafeCSS(loadingIndicatorPolygons['soft-burst'])}); --_polygon-9-sided-cookie: polygon(${unsafeCSS(loadingIndicatorPolygons['9-sided-cookie'])}); --_polygon-pentagon: polygon(${unsafeCSS(loadingIndicatorPolygons['pentagon'])}); --_polygon-pill: polygon(${unsafeCSS(loadingIndicatorPolygons['pill'])}); --_polygon-sunny: polygon(${unsafeCSS(loadingIndicatorPolygons['sunny'])}); --_polygon-4-sided-cookie: polygon(${unsafeCSS(loadingIndicatorPolygons['4-sided-cookie'])}); --_polygon-oval: polygon(${unsafeCSS(loadingIndicatorPolygons['oval'])}); } .container.animate .active-indicator-wrapper { animation: rotate-outer 4666ms linear infinite; transform-origin: center; display: flex; align-items: center; justify-content: center; will-change: transform; } @keyframes rotate-outer { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .container.animate .active-indicator { animation: rotate-inner 4666ms cubic-bezier(0.34, 0.88, 0.34, 1) infinite; } @keyframes rotate-inner { 0% { clip-path: var(--_polygon-soft-burst); transform: rotate(0deg); } 14% { clip-path: var(--_polygon-9-sided-cookie); transform: rotate(154deg) scale(1); } 29% { clip-path: var(--_polygon-pentagon); transform: rotate(309deg) scale(1); } 43% { clip-path: var(--_polygon-pill); transform: rotate(463deg) scale(1); } 57% { clip-path: var(--_polygon-sunny); transform: rotate(617deg) scale(1); } 71% { clip-path: var(--_polygon-4-sided-cookie); transform: rotate(771deg) scale(1); } 83% { clip-path: var(--_polygon-oval); transform: rotate(926deg) scale(1); } 100% { clip-path: var(--_polygon-soft-burst); transform: rotate(1080deg) scale(1); } } @media (forced-colors: active) { .active-indicator { background-color: CanvasText !important; } } @media (prefers-reduced-motion: reduce) { .container.animate .active-indicator-wrapper, .container.animate .active-indicator { animation: none; } .active-indicator { clip-path: var(--_polygon-soft-burst); } } ` ]"
9429
9468
  },
9430
9469
  {
9431
9470
  "kind": "method",
@@ -17351,7 +17390,7 @@
17351
17390
  "text": "array"
17352
17391
  },
17353
17392
  "static": true,
17354
- "default": "[ sharedStyles, css` :host { --_display-size: 6rem; display: inline-flex; flex-direction: column; background-color: transparent; inline-size: min(20.5rem, 100%); max-inline-size: 100%; box-sizing: border-box; container-type: inline-size; color: var(--on-surface); font-family: var(--font); user-select: none; } :host([use-24-hour]) { --_display-size: 7.125rem; } /* Forced orientations */ :host([orientation=\"vertical\"]) { inline-size: min(20.5rem, 100%); } :host([orientation=\"vertical\"]) .main-content { flex-direction: column; } :host([orientation=\"horizontal\"]) { inline-size: auto; } :host([orientation=\"horizontal\"]) .main-content { flex-direction: row; align-items: center; gap: 2rem; } :host([orientation=\"horizontal\"]) .left-pane { display: flex; flex-direction: column; justify-content: center; } /* Main Content (Default vertical) */ .main-content { display: flex; flex-direction: column; gap: 1rem; min-inline-size: 0; } :host([mode='input']) .main-content { margin-block: auto; justify-content: center; align-items: center; } .main-content > * { min-inline-size: 0; } .clock-container { flex: none; } /* Auto stays vertical because the component can live inside a narrow dialog even when the browser viewport itself is wide. Consumers that reserve enough inline space can opt into horizontal explicitly. */ @media (min-width: 48rem) { :host([orientation='auto']), :host(:not([orientation])) { inline-size: min(40rem, 100%); } :host([orientation='auto']) .main-content, :host(:not([orientation])) .main-content { flex-direction: row; align-items: center; gap: 2rem; } :host([orientation='auto']) .left-pane, :host(:not([orientation])) .left-pane { display: flex; flex-direction: column; justify-content: center; } } @keyframes fadeInScale { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } } .clock-container, .time-display-container, .input-labels, .display-box { animation: fadeInScale var(--speed3) var(--ease-standard) forwards; } .picker-header { font-size: 0.75rem; font-weight: 500; margin-bottom: 1.25rem; color: var(--on-surface-variant); text-transform: uppercase; letter-spacing: 0.1em; } /* Display Panel (Time box inputs/views) */ .time-display-container { display: flex; align-items: center; justify-content: center; margin-bottom: 1.5rem; gap: 0.75rem; } .time-display { display: grid; grid-template-columns: var(--_display-size) 1.5rem var(--_display-size); align-items: center; justify-content: center; flex: none; background-color: transparent; } .display-box { display: flex; align-items: center; justify-content: center; width: 100%; height: 5rem; border-radius: 0.5rem; background-color: var(--surface-container-highest); font-size: 3.5rem; font-weight: 400; color: var(--on-surface); cursor: pointer; transition: background-color var(--speed2); border: 0.125rem solid transparent; text-align: center; } :host([use-24-hour]) .display-box { width: 100%; } .display-box:hover:not(.active) { background-color: var(--active); } .display-box.active { background-color: var(--primary-container); color: var(--on-primary-container); border-color: var(--primary); } .display-box input { width: 100%; height: 100%; border: none; background: transparent; text-align: center; font-size: inherit; font-family: inherit; color: inherit; outline: none; padding: 0; } /* Remove input spinners */ .display-box input::-webkit-outer-spin-button, .display-box input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .display-box input[type=number] { -moz-appearance: textfield; } .separator { font-size: 3rem; font-weight: 400; color: var(--on-surface); width: 1.5rem; text-align: center; line-height: 5rem; } /* AM/PM Toggle */ .ampm-toggle { display: flex; flex-direction: column; flex: 0 0 3.25rem; border: 0.0625rem solid var(--outline); border-radius: 0.5rem; overflow: hidden; height: 5rem; width: 3.25rem; } .ampm-btn { flex: 1; background-color: transparent; border: none; font-size: 0.875rem; font-weight: 500; color: var(--on-surface-variant); cursor: pointer; transition: background-color var(--speed1), color var(--speed1); } .ampm-btn.active { background-color: var(--tertiary-container); color: var(--on-tertiary-container); } .ampm-btn:first-child { border-bottom: 0.0625rem solid var(--outline); } /* Clock Face */ .clock-container { display: flex; justify-content: center; align-items: center; margin-block: 0.5rem 1.5rem; } .clock-face { position: relative; width: 16rem; height: 16rem; border-radius: 50%; background-color: var(--surface-container-highest); touch-action: none; cursor: pointer; } @container (max-width: 18rem) { :host { --_display-size: clamp(4.5rem, 35cqi, 6rem); } :host([use-24-hour]) { --_display-size: clamp(4.75rem, 38cqi, 7.125rem); } .time-display-container { gap: .375rem; margin-block-end: 1rem; } .display-box { block-size: 4rem; font-size: 2.75rem; } .separator { inline-size: 1rem; font-size: 2.25rem; line-height: 4rem; } .clock-container { margin-block: .25rem 1rem; } } /* Center dot */ .clock-center { position: absolute; left: calc(50% - 0.25rem); top: calc(50% - 0.25rem); width: 0.5rem; height: 0.5rem; border-radius: 50%; background-color: var(--primary); } /* Clock hand line */ .clock-hand { position: absolute; left: calc(50% - 0.0625rem); bottom: 50%; width: 0.125rem; background-color: var(--primary); transform-origin: bottom center; z-index: 1; pointer-events: none; transition: transform var(--speed3) var(--ease-standard), height var(--speed3) var(--ease-standard); } /* Hand tip circle selection inside the hand */ .clock-hand-selector { position: absolute; top: 0; left: 50%; width: 3rem; height: 3rem; border-radius: 50%; background-color: var(--primary); opacity: 0.3; transform: translate(-50%, -50%) scale(1); z-index: 1; pointer-events: none; transition: transform var(--speed2) var(--ease-standard); } /* Disable transition during active drag but keep scale transform animation */ .clock-face.dragging .clock-hand, .clock-face:active .clock-hand { transition: none !important; } /* Selector handle expands/grows when pressed or dragging */ .clock-face.dragging .clock-hand-selector, .clock-face:active .clock-hand-selector { transform: translate(-50%, -50%) scale(1.2); } .clock-hand-dot { position: absolute; left: calc(50% - 0.25rem); top: -2rem; width: 0.5rem; height: 0.5rem; border-radius: 50%; background-color: var(--on-primary); z-index: 2; pointer-events: none; } @keyframes numberEntrance { from { opacity: 0; transform: translate(-50%, -50%) scale(0.85); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } /* Clock dial numbers */ .clock-number { position: absolute; width: 2.25rem; height: 2.25rem; display: flex; align-items: center; justify-content: center; font-size: 0.875rem; font-weight: 500; color: var(--on-surface); border-radius: 50%; transform: translate(-50%, -50%); transition: background-color var(--speed2), color var(--speed2); z-index: 2; pointer-events: auto; cursor: pointer; animation: numberEntrance var(--speed3) var(--ease-standard) forwards; } .clock-number:hover { background-color: var(--active); } .clock-number.selected { background-color: var(--primary); color: var(--on-primary); font-weight: bold; } .clock-number.selected:hover { background-color: var(--primary); } /* Manual Input Mode specific styling */ .input-labels { display: grid; grid-template-columns: var(--_display-size) 1.5rem var(--_display-size); justify-content: center; margin-top: -1rem; margin-bottom: 1.5rem; color: var(--on-surface-variant); font-size: 0.75rem; font-weight: 400; padding: 0; inline-size: 100%; } .input-labels > span { text-align: center; } .input-labels > span:first-child { grid-column: 1; } .input-labels > span:last-child { grid-column: 3; } :host([mode='input']) .input-labels { margin-block-end: 0; } /* Bottom bar */ .bottom-actions { display: flex; justify-content: space-between; align-items: center; } .mode-toggle-btn { background: none; border: none; color: var(--primary); cursor: pointer; padding: 0.5rem; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: background-color var(--speed1); } .mode-toggle-btn:hover { background-color: var(--active); } .action-buttons { display: flex; gap: 0.5rem; } ` ]"
17393
+ "default": "[ sharedStyles, css` :host { --_display-size: 6rem; display: inline-flex; flex-direction: column; background-color: transparent; inline-size: min(20.5rem, 100%); max-inline-size: 100%; box-sizing: border-box; container-type: inline-size; color: var(--on-surface); font-family: var(--font); user-select: none; } :host([use-24-hour]) { --_display-size: 7.125rem; } /* Forced orientations */ :host([orientation=\"vertical\"]) { inline-size: min(20.5rem, 100%); } :host([orientation=\"vertical\"]) .main-content { flex-direction: column; } :host([orientation=\"horizontal\"]) { inline-size: min(40rem, 100%); } :host([orientation=\"horizontal\"]) .main-content { flex-direction: row; align-items: center; justify-content: center; gap: 2rem; } :host([orientation=\"horizontal\"]) .left-pane { display: flex; flex-direction: column; justify-content: center; flex: none; } /* Main Content (Default vertical) */ .main-content { display: flex; flex-direction: column; gap: 1rem; min-inline-size: 0; } :host([mode='input']) .main-content { margin-block: auto; justify-content: center; align-items: center; } .main-content > * { min-inline-size: 0; } .clock-container { flex: none; } /* Auto stays vertical because the component can live inside a narrow dialog even when the browser viewport itself is wide. Consumers that reserve enough inline space can opt into horizontal explicitly. */ @media (min-width: 48rem) { :host([orientation='auto']), :host(:not([orientation])) { inline-size: min(40rem, 100%); } :host([orientation='auto']) .main-content, :host(:not([orientation])) .main-content { flex-direction: row; align-items: center; justify-content: center; gap: 2rem; } :host([orientation='auto']) .left-pane, :host(:not([orientation])) .left-pane { display: flex; flex-direction: column; justify-content: center; flex: none; } } @keyframes fadeInScale { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } } .clock-container, .time-display-container, .input-labels, .display-box { animation: fadeInScale var(--speed3) var(--ease-standard) forwards; } .picker-header { font-size: 0.75rem; font-weight: 500; margin-bottom: 1.25rem; color: var(--on-surface-variant); text-transform: uppercase; letter-spacing: 0.1em; } /* Display Panel (Time box inputs/views) */ .time-display-container { display: flex; align-items: center; justify-content: center; margin-bottom: 1.5rem; gap: 0.75rem; } .time-display { display: grid; grid-template-columns: var(--_display-size) 1.5rem var(--_display-size); align-items: center; justify-content: center; flex: none; background-color: transparent; } .display-box { display: flex; align-items: center; justify-content: center; width: 100%; height: 5rem; border-radius: 0.5rem; background-color: var(--surface-container-highest); font-size: 3.5rem; font-weight: 400; color: var(--on-surface); cursor: pointer; transition: background-color var(--speed2); border: 0.125rem solid transparent; text-align: center; } :host([use-24-hour]) .display-box { width: 100%; } .display-box:hover:not(.active) { background-color: var(--active); } .display-box.active { background-color: var(--primary-container); color: var(--on-primary-container); border-color: var(--primary); } .display-box input { width: 100%; height: 100%; border: none; background: transparent; text-align: center; font-size: inherit; font-family: inherit; color: inherit; outline: none; padding: 0; } /* Remove input spinners */ .display-box input::-webkit-outer-spin-button, .display-box input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .display-box input[type=number] { -moz-appearance: textfield; } .separator { font-size: 3rem; font-weight: 400; color: var(--on-surface); width: 1.5rem; text-align: center; line-height: 5rem; } /* AM/PM Toggle */ .ampm-toggle { display: flex; flex-direction: column; flex: 0 0 3.25rem; border: 0.0625rem solid var(--outline); border-radius: 0.5rem; overflow: hidden; height: 5rem; width: 3.25rem; } .ampm-btn { flex: 1; background-color: transparent; border: none; font-size: 0.875rem; font-weight: 500; color: var(--on-surface-variant); cursor: pointer; transition: background-color var(--speed1), color var(--speed1); } .ampm-btn.active { background-color: var(--tertiary-container); color: var(--on-tertiary-container); } .ampm-btn:first-child { border-bottom: 0.0625rem solid var(--outline); } /* Clock Face */ .clock-container { display: flex; justify-content: center; align-items: center; margin-block: 0.5rem 1.5rem; } .clock-face { position: relative; width: 16rem; height: 16rem; border-radius: 50%; background-color: var(--surface-container-highest); touch-action: none; cursor: pointer; } @container (max-width: 18rem) { :host { --_display-size: clamp(4.5rem, 35cqi, 6rem); } :host([use-24-hour]) { --_display-size: clamp(4.75rem, 38cqi, 7.125rem); } .time-display-container { gap: .375rem; margin-block-end: 1rem; } .display-box { block-size: 4rem; font-size: 2.75rem; } .separator { inline-size: 1rem; font-size: 2.25rem; line-height: 4rem; } .clock-container { margin-block: .25rem 1rem; } } /* Center dot */ .clock-center { position: absolute; left: calc(50% - 0.25rem); top: calc(50% - 0.25rem); width: 0.5rem; height: 0.5rem; border-radius: 50%; background-color: var(--primary); } /* Clock hand line */ .clock-hand { position: absolute; left: calc(50% - 0.0625rem); bottom: 50%; width: 0.125rem; background-color: var(--primary); transform-origin: bottom center; z-index: 1; pointer-events: none; transition: transform var(--speed3) var(--ease-standard), height var(--speed3) var(--ease-standard); } /* Hand tip circle selection inside the hand */ .clock-hand-selector { position: absolute; top: 0; left: 50%; width: 3rem; height: 3rem; border-radius: 50%; background-color: var(--primary); opacity: 0.3; transform: translate(-50%, -50%) scale(1); z-index: 1; pointer-events: none; transition: transform var(--speed2) var(--ease-standard); } /* Disable transition during active drag but keep scale transform animation */ .clock-face.dragging .clock-hand, .clock-face:active .clock-hand { transition: none !important; } /* Selector handle expands/grows when pressed or dragging */ .clock-face.dragging .clock-hand-selector, .clock-face:active .clock-hand-selector { transform: translate(-50%, -50%) scale(1.2); } .clock-hand-dot { position: absolute; left: calc(50% - 0.25rem); top: -2rem; width: 0.5rem; height: 0.5rem; border-radius: 50%; background-color: var(--on-primary); z-index: 2; pointer-events: none; } @keyframes numberEntrance { from { opacity: 0; transform: translate(-50%, -50%) scale(0.85); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } /* Clock dial numbers */ .clock-number { position: absolute; width: 2.25rem; height: 2.25rem; display: flex; align-items: center; justify-content: center; font-size: 0.875rem; font-weight: 500; color: var(--on-surface); border-radius: 50%; transform: translate(-50%, -50%); transition: background-color var(--speed2), color var(--speed2); z-index: 2; pointer-events: auto; cursor: pointer; animation: numberEntrance var(--speed3) var(--ease-standard) forwards; } .clock-number:hover { background-color: var(--active); } .clock-number.selected { background-color: var(--primary); color: var(--on-primary); font-weight: bold; } .clock-number.selected:hover { background-color: var(--primary); } /* Manual Input Mode specific styling */ .input-labels { display: grid; grid-template-columns: var(--_display-size) 1.5rem var(--_display-size); justify-content: center; margin-top: -1rem; margin-bottom: 1.5rem; color: var(--on-surface-variant); font-size: 0.75rem; font-weight: 400; padding: 0; inline-size: 100%; } .input-labels > span { text-align: center; } .input-labels > span:first-child { grid-column: 1; } .input-labels > span:last-child { grid-column: 3; } :host([mode='input']) .input-labels { margin-block-end: 0; } /* Bottom bar */ .bottom-actions { display: flex; justify-content: space-between; align-items: center; } .mode-toggle-btn { background: none; border: none; color: var(--primary); cursor: pointer; padding: 0.5rem; display: flex; align-items: center; justify-content: center; border-radius: 50%; transition: background-color var(--speed1); } .mode-toggle-btn:hover { background-color: var(--active); } .action-buttons { display: flex; gap: 0.5rem; } ` ]"
17355
17394
  },
17356
17395
  {
17357
17396
  "kind": "method",
@@ -18113,6 +18152,12 @@
18113
18152
  }
18114
18153
  ]
18115
18154
  },
18155
+ {
18156
+ "kind": "javascript-module",
18157
+ "path": "src/types/events.ts",
18158
+ "declarations": [],
18159
+ "exports": []
18160
+ },
18116
18161
  {
18117
18162
  "kind": "javascript-module",
18118
18163
  "path": "src/utils/color.ts",
@@ -18739,7 +18784,7 @@
18739
18784
  {
18740
18785
  "kind": "variable",
18741
18786
  "name": "sharedStyles",
18742
- "default": "css ` /* ─── 1. Box-sizing reset ─────────────────────────────────────────────── */ :host { box-sizing: border-box; font-family: var(--font); font-style: normal; position: relative; color: var(--on-surface); } *, *::before, *::after { box-sizing: border-box; font-style: normal; } ::slotted(*) { font-style: normal; } /* ─── 2. CSS custom property bridge ──────────────────────────────────── */ /* Each token resolves in order: Moni namespace → MD3 spec → M3 default. */ :host { /* ─── Primary palette ─── */ --primary: var(--moni-primary, var(--md-sys-color-primary, #6750A4)); --on-primary: var(--moni-on-primary, var(--md-sys-color-on-primary, #ffffff)); --primary-container: var(--moni-primary-container, var(--md-sys-color-primary-container, #eaddff)); --on-primary-container: var(--moni-on-primary-container, var(--md-sys-color-on-primary-container, #21005d)); /* ─── Secondary palette ─── */ --secondary: var(--moni-secondary, var(--md-sys-color-secondary, #625b71)); --on-secondary: var(--moni-on-secondary, var(--md-sys-color-on-secondary, #ffffff)); --secondary-container: var(--moni-secondary-container, var(--md-sys-color-secondary-container, #e8def8)); --on-secondary-container: var(--moni-on-secondary-container, var(--md-sys-color-on-secondary-container, #1d192b)); /* ─── Tertiary palette ─── */ --tertiary: var(--moni-tertiary, var(--md-sys-color-tertiary, #7d5260)); --on-tertiary: var(--moni-on-tertiary, var(--md-sys-color-on-tertiary, #ffffff)); --tertiary-container: var(--moni-tertiary-container, var(--md-sys-color-tertiary-container, #ffd8e4)); --on-tertiary-container: var(--moni-on-tertiary-container, var(--md-sys-color-on-tertiary-container, #31111d)); /* ─── Error palette ─── */ --error: var(--moni-error, var(--md-sys-color-error, #b3261e)); --on-error: var(--moni-on-error, var(--md-sys-color-on-error, #ffffff)); --error-container: var(--moni-error-container, var(--md-sys-color-error-container, #f9dedc)); --on-error-container: var(--moni-on-error-container, var(--md-sys-color-on-error-container, #410e0b)); /* ─── Background & surface ─── */ --background: var(--moni-background, var(--md-sys-color-background, #fef7ff)); --on-background: var(--moni-on-background, var(--md-sys-color-on-background, #1d1b20)); --surface: var(--moni-surface, var(--md-sys-color-surface, #fef7ff)); --on-surface: var(--moni-on-surface, var(--md-sys-color-on-surface, #1d1b20)); --surface-variant: var(--moni-surface-variant, var(--md-sys-color-surface-variant, #e7e0ec)); --on-surface-variant: var(--moni-on-surface-variant, var(--md-sys-color-on-surface-variant, #49454f)); /* ─── Outline ─── */ --outline: var(--moni-outline, var(--md-sys-color-outline, #79747e)); --outline-variant: var(--moni-outline-variant, var(--md-sys-color-outline-variant, #cac4d0)); /* ─── Surface container hierarchy (5-level elevation tones) ─── */ --surface-container-lowest: var(--moni-surface-container-lowest, var(--md-sys-color-surface-container-lowest, #ffffff)); --surface-container-low: var(--moni-surface-container-low, var(--md-sys-color-surface-container-low, #f7f2fa)); --surface-container: var(--moni-surface-container, var(--md-sys-color-surface-container, #f3edf7)); --surface-container-high: var(--moni-surface-container-high, var(--md-sys-color-surface-container-high, #ece6f0)); --surface-container-highest: var(--moni-surface-container-highest, var(--md-sys-color-surface-container-highest, #e6e1e5)); /* ─── Inverse surfaces (snackbar, tooltip) ─── */ --inverse-surface: var(--moni-inverse-surface, var(--md-sys-color-inverse-surface, #313033)); --inverse-on-surface: var(--moni-inverse-on-surface, var(--md-sys-color-inverse-on-surface, #f4f0f4)); --inverse-primary: var(--moni-inverse-primary, var(--md-sys-color-inverse-primary, #d0bcff)); /* ─── Structural / non-color tokens ─── */ /* Scrim overlay used in modals, bottom sheets, dialogs. */ --shadow: 0 0 0 0 transparent; --scrim: rgb(0 0 0 / 0.5); /* State layer color for hover/pressed overlays. */ --active: color-mix(in srgb, currentColor 10%, transparent); --overlay: rgb(0 0 0 / 0.5); --image: linear-gradient(currentColor 0 0); /* ─── Typography tokens ─── */ --font: var(--moni-font-sans, 'Geist', system-ui, -apple-system, sans-serif); --font-title: var( --moni-font-title, 'Instrument Serif', Georgia, serif ); --font-mono: 'Geist Mono', 'Fira Code', monospace; /* Icon font: override via --font-icon-override to use a different icon set. */ --font-icon: var(--font-icon-override, 'Material Symbols Rounded'); /* ─── Elevation shadows (MD3 tonal levels 1–3) ─── */ --elevate1: 0 0.125rem 0.125rem 0 rgb(0 0 0 / 0.32); --elevate2: 0 0.25rem 0.5rem 0 rgb(0 0 0 / 0.4); --elevate3: 0 0.375rem 0.75rem 0 rgb(0 0 0 / 0.48); /* ─── Transition speed scale ─── */ /* speed1 = fastest micro-interactions; speed4 = slowest page transitions. */ --speed1: 100ms; --speed2: 200ms; --speed3: 300ms; --speed4: 400ms; } /* ─── 3. MD3 --color-* backward-compatibility aliases ─────────────── */ /* These map the older --color-<role> naming convention used in legacy */ /* Moni apps to the primary bare tokens above. Do not remove. */ :host { --color-primary: var(--primary); --color-on-primary: var(--on-primary); --color-primary-container: var(--primary-container); --color-on-primary-container: var(--on-primary-container); --color-secondary: var(--secondary); --color-on-secondary: var(--on-secondary); --color-secondary-container: var(--secondary-container); --color-on-secondary-container: var(--on-secondary-container); --color-tertiary: var(--tertiary); --color-on-tertiary: var(--on-tertiary); --color-tertiary-container: var(--tertiary-container); --color-on-tertiary-container: var(--on-tertiary-container); --color-error: var(--error); --color-on-error: var(--on-error); --color-error-container: var(--error-container); --color-on-error-container: var(--on-error-container); --color-background: var(--background); --color-on-background: var(--on-background); --color-surface: var(--surface); --color-on-surface: var(--on-surface); --color-surface-variant: var(--surface-variant); --color-on-surface-variant: var(--on-surface-variant); --color-outline: var(--outline); --color-outline-variant: var(--outline-variant); --color-surface-container-lowest: var(--surface-container-lowest); --color-surface-container-low: var(--surface-container-low); --color-surface-container: var(--surface-container); --color-surface-container-high: var(--surface-container-high); --color-surface-container-highest: var(--surface-container-highest); --color-inverse-surface: var(--inverse-surface); --color-inverse-on-surface: var(--inverse-on-surface); --color-inverse-primary: var(--inverse-primary); /* Named aliases for tokens defined in the legacy tokens.css stylesheet. */ --font-sans: var(--font); --shadow-1: var(--elevate1); --shadow-2: var(--elevate2); --shadow-3: var(--elevate3); --radius-sm: 0.5rem; --radius-md: 0.75rem; --radius-lg: 1.5rem; --ease-standard: cubic-bezier(0.2, 0, 0, 1); --ease-emphasized: cubic-bezier(0.3, 0, 0, 1); --duration-fast: 150ms; --duration-normal: 250ms; } /* ─── 4. Utility classes ─────────────────────────────────────────────── */ /** * Clamps text to a single line with an ellipsis when overflow occurs. * Apply to any element inside a shadow root where text needs to be truncated. */ .truncate { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } /** * Visually hides an element while keeping it accessible to screen readers * and maintaining its position in the DOM and tab order. * Use for labeling icon-only buttons, skip links, etc. */ .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } `",
18787
+ "default": "css ` /* ─── 1. Box-sizing reset ─────────────────────────────────────────────── */ :host { box-sizing: border-box; font-family: var(--font); font-style: normal; position: relative; color: var(--on-surface); } *, *::before, *::after { box-sizing: border-box; font-style: normal; } ::slotted(*) { font-style: normal; } /* ─── 2. CSS custom property bridge ──────────────────────────────────── */ /* Each token resolves in order: Moni namespace → MD3 spec → M3 default. */ :host { /* ─── Primary palette ─── */ --primary: var(--moni-primary, var(--md-sys-color-primary, #6750A4)); --on-primary: var(--moni-on-primary, var(--md-sys-color-on-primary, #ffffff)); --primary-container: var(--moni-primary-container, var(--md-sys-color-primary-container, #eaddff)); --on-primary-container: var(--moni-on-primary-container, var(--md-sys-color-on-primary-container, #21005d)); /* ─── Secondary palette ─── */ --secondary: var(--moni-secondary, var(--md-sys-color-secondary, #625b71)); --on-secondary: var(--moni-on-secondary, var(--md-sys-color-on-secondary, #ffffff)); --secondary-container: var(--moni-secondary-container, var(--md-sys-color-secondary-container, #e8def8)); --on-secondary-container: var(--moni-on-secondary-container, var(--md-sys-color-on-secondary-container, #1d192b)); /* ─── Tertiary palette ─── */ --tertiary: var(--moni-tertiary, var(--md-sys-color-tertiary, #7d5260)); --on-tertiary: var(--moni-on-tertiary, var(--md-sys-color-on-tertiary, #ffffff)); --tertiary-container: var(--moni-tertiary-container, var(--md-sys-color-tertiary-container, #ffd8e4)); --on-tertiary-container: var(--moni-on-tertiary-container, var(--md-sys-color-on-tertiary-container, #31111d)); /* ─── Error palette ─── */ --error: var(--moni-error, var(--md-sys-color-error, #b3261e)); --on-error: var(--moni-on-error, var(--md-sys-color-on-error, #ffffff)); --error-container: var(--moni-error-container, var(--md-sys-color-error-container, #f9dedc)); --on-error-container: var(--moni-on-error-container, var(--md-sys-color-on-error-container, #410e0b)); /* ─── Background & surface ─── */ --background: var(--moni-background, var(--md-sys-color-background, #fef7ff)); --on-background: var(--moni-on-background, var(--md-sys-color-on-background, #1d1b20)); --surface: var(--moni-surface, var(--md-sys-color-surface, #fef7ff)); --on-surface: var(--moni-on-surface, var(--md-sys-color-on-surface, #1d1b20)); --surface-variant: var(--moni-surface-variant, var(--md-sys-color-surface-variant, #e7e0ec)); --on-surface-variant: var(--moni-on-surface-variant, var(--md-sys-color-on-surface-variant, #49454f)); /* ─── Outline ─── */ --outline: var(--moni-outline, var(--md-sys-color-outline, #79747e)); --outline-variant: var(--moni-outline-variant, var(--md-sys-color-outline-variant, #cac4d0)); /* ─── Surface container hierarchy (5-level elevation tones) ─── */ --surface-container-lowest: var(--moni-surface-container-lowest, var(--md-sys-color-surface-container-lowest, #ffffff)); --surface-container-low: var(--moni-surface-container-low, var(--md-sys-color-surface-container-low, #f7f2fa)); --surface-container: var(--moni-surface-container, var(--md-sys-color-surface-container, #f3edf7)); --surface-container-high: var(--moni-surface-container-high, var(--md-sys-color-surface-container-high, #ece6f0)); --surface-container-highest: var(--moni-surface-container-highest, var(--md-sys-color-surface-container-highest, #e6e1e5)); /* ─── Inverse surfaces (snackbar, tooltip) ─── */ --inverse-surface: var(--moni-inverse-surface, var(--md-sys-color-inverse-surface, #313033)); --inverse-on-surface: var(--moni-inverse-on-surface, var(--md-sys-color-inverse-on-surface, #f4f0f4)); --inverse-primary: var(--moni-inverse-primary, var(--md-sys-color-inverse-primary, #d0bcff)); /* ─── Structural / non-color tokens ─── */ /* Scrim overlay used in modals, bottom sheets, dialogs. */ --shadow: 0 0 0 0 transparent; --scrim: rgb(0 0 0 / 0.5); /* State layer color for hover/pressed overlays. */ --active: color-mix(in srgb, currentColor 10%, transparent); --overlay: rgb(0 0 0 / 0.5); --image: linear-gradient(currentColor 0 0); /* ─── Typography tokens ─── */ --font: var(--moni-font-sans, 'Geist', system-ui, -apple-system, sans-serif); --font-title: var( --moni-font-title, 'Instrument Serif', Georgia, serif ); --font-mono: 'Geist Mono', 'Fira Code', monospace; /* Icon font: override via --font-icon-override to use a different icon set. */ --font-icon: var(--font-icon-override, 'Material Symbols Rounded'); /* ─── Elevation shadows (MD3 tonal levels 1–3) ─── */ --elevate1: 0 0.125rem 0.125rem 0 rgb(0 0 0 / 0.32); --elevate2: 0 0.25rem 0.5rem 0 rgb(0 0 0 / 0.4); --elevate3: 0 0.375rem 0.75rem 0 rgb(0 0 0 / 0.48); /* ─── Transition speed scale ─── */ /* speed1 = fastest micro-interactions; speed4 = slowest page transitions. */ --speed1: 100ms; --speed2: 200ms; --speed3: 300ms; --speed4: 400ms; } /* ─── 3. MD3 --color-* backward-compatibility aliases ─────────────── */ /* These map the older --color-<role> naming convention used in legacy */ /* Moni apps to the primary bare tokens above. Do not remove. */ :host { --color-primary: var(--primary); --color-on-primary: var(--on-primary); --color-primary-container: var(--primary-container); --color-on-primary-container: var(--on-primary-container); --color-secondary: var(--secondary); --color-on-secondary: var(--on-secondary); --color-secondary-container: var(--secondary-container); --color-on-secondary-container: var(--on-secondary-container); --color-tertiary: var(--tertiary); --color-on-tertiary: var(--on-tertiary); --color-tertiary-container: var(--tertiary-container); --color-on-tertiary-container: var(--on-tertiary-container); --color-error: var(--error); --color-on-error: var(--on-error); --color-error-container: var(--error-container); --color-on-error-container: var(--on-error-container); --color-background: var(--background); --color-on-background: var(--on-background); --color-surface: var(--surface); --color-on-surface: var(--on-surface); --color-surface-variant: var(--surface-variant); --color-on-surface-variant: var(--on-surface-variant); --color-outline: var(--outline); --color-outline-variant: var(--outline-variant); --color-surface-container-lowest: var(--surface-container-lowest); --color-surface-container-low: var(--surface-container-low); --color-surface-container: var(--surface-container); --color-surface-container-high: var(--surface-container-high); --color-surface-container-highest: var(--surface-container-highest); --color-inverse-surface: var(--inverse-surface); --color-inverse-on-surface: var(--inverse-on-surface); --color-inverse-primary: var(--inverse-primary); /* Named aliases for tokens defined in the legacy tokens.css stylesheet. */ --font-sans: var(--font); --shadow-1: var(--elevate1); --shadow-2: var(--elevate2); --shadow-3: var(--elevate3); --radius-sm: 0.5rem; --radius-md: 0.75rem; --radius-lg: 1.5rem; --ease-standard: cubic-bezier(0.2, 0, 0, 1); --ease-emphasized: cubic-bezier(0.3, 0, 0, 1); --duration-fast: 150ms; --duration-normal: 250ms; /* ─── Material 3 scrollbar ─── */ --_scrollbar-size: var(--moni-scrollbar-size, 0.625rem); --_scrollbar-track: var(--moni-scrollbar-track, transparent); --_scrollbar-thumb: var( --moni-scrollbar-thumb, color-mix(in srgb, var(--on-surface-variant) 38%, transparent) ); --_scrollbar-thumb-hover: var( --moni-scrollbar-thumb-hover, color-mix(in srgb, var(--on-surface-variant) 62%, transparent) ); --_scrollbar-thumb-active: var( --moni-scrollbar-thumb-active, color-mix(in srgb, var(--primary) 76%, transparent) ); } /* Firefox uses its native thin geometry and the same Material color roles. */ * { scrollbar-width: thin; scrollbar-color: var(--_scrollbar-thumb) var(--_scrollbar-track); } /* Chromium, Safari and Edge: 4px pill inside a 10px interaction gutter. */ *::-webkit-scrollbar { inline-size: var(--_scrollbar-size); block-size: var(--_scrollbar-size); } *::-webkit-scrollbar-track { background: var(--_scrollbar-track); border-radius: 999px; } *::-webkit-scrollbar-thumb { min-block-size: 2.5rem; background: var(--_scrollbar-thumb); background-clip: padding-box; border: 0.1875rem solid transparent; border-radius: 999px; } *::-webkit-scrollbar-thumb:hover { background: var(--_scrollbar-thumb-hover); background-clip: padding-box; border-width: 0.125rem; } *::-webkit-scrollbar-thumb:active { background: var(--_scrollbar-thumb-active); background-clip: padding-box; } *::-webkit-scrollbar-corner { background: transparent; } /* ─── 4. Utility classes ─────────────────────────────────────────────── */ /** * Clamps text to a single line with an ellipsis when overflow occurs. * Apply to any element inside a shadow root where text needs to be truncated. */ .truncate { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } /** * Visually hides an element while keeping it accessible to screen readers * and maintaining its position in the DOM and tab order. * Use for labeling icon-only buttons, skip links, etc. */ .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } `",
18743
18788
  "description": "Baseline CSS injected into the shadow root of **every** Moni Web Component.\n\nThis stylesheet is the foundational layer of the Moni design system. It serves\nfour distinct responsibilities:\n\n**1. Box-sizing reset**\nApplies `box-sizing: border-box` to `:host`, all descendants, and slotted\nelements so every component has predictable sizing without override surprises.\n\n**2. CSS custom property bridge (Token namespace)**\nThe component library uses short \"bare\" variable names internally (e.g.\n`var(--primary)`) for concise CSS. Because shadow DOM isolates custom\nproperties by default, this sheet creates a bridge that resolves these\ninternal names from the global token namespaces in the following priority:\n\n `--moni-*` (Moni namespace) → `--md-sys-color-*` (MD3 spec) → hardcoded M3 default\n\nThis lets a host document use any of the three namespaces to theme components.\n\n**3. MD3 `--color-*` aliases**\nA second `:host` block maps the older `--color-<role>` prefix to the primary\nbare tokens. These aliases ensure backward compatibility with Moni apps\n(MoniAuth-Client, MoniAuth-Yo) that still reference `--color-primary`, etc.\n\n**4. Utility classes**\nProvides `.truncate` (single-line text clipping) and `.visually-hidden`\n(screen-reader-only content) for use inside component shadow trees.\n\n**Usage:**\n```ts\nstatic override styles = [sharedStyles, css`/* component-specific styles *\\/`];\n```"
18744
18789
  }
18745
18790
  ],
@@ -18951,7 +18996,7 @@
18951
18996
  {
18952
18997
  "kind": "variable",
18953
18998
  "name": "sharedStyles",
18954
- "default": "css` /* ─── 1. Box-sizing reset ─────────────────────────────────────────────── */ :host { box-sizing: border-box; font-family: var(--font); font-style: normal; position: relative; color: var(--on-surface); } *, *::before, *::after { box-sizing: border-box; font-style: normal; } ::slotted(*) { font-style: normal; } /* ─── 2. CSS custom property bridge ──────────────────────────────────── */ /* Each token resolves in order: Moni namespace → MD3 spec → M3 default. */ :host { /* ─── Primary palette ─── */ --primary: var(--moni-primary, var(--md-sys-color-primary, #6750A4)); --on-primary: var(--moni-on-primary, var(--md-sys-color-on-primary, #ffffff)); --primary-container: var(--moni-primary-container, var(--md-sys-color-primary-container, #eaddff)); --on-primary-container: var(--moni-on-primary-container, var(--md-sys-color-on-primary-container, #21005d)); /* ─── Secondary palette ─── */ --secondary: var(--moni-secondary, var(--md-sys-color-secondary, #625b71)); --on-secondary: var(--moni-on-secondary, var(--md-sys-color-on-secondary, #ffffff)); --secondary-container: var(--moni-secondary-container, var(--md-sys-color-secondary-container, #e8def8)); --on-secondary-container: var(--moni-on-secondary-container, var(--md-sys-color-on-secondary-container, #1d192b)); /* ─── Tertiary palette ─── */ --tertiary: var(--moni-tertiary, var(--md-sys-color-tertiary, #7d5260)); --on-tertiary: var(--moni-on-tertiary, var(--md-sys-color-on-tertiary, #ffffff)); --tertiary-container: var(--moni-tertiary-container, var(--md-sys-color-tertiary-container, #ffd8e4)); --on-tertiary-container: var(--moni-on-tertiary-container, var(--md-sys-color-on-tertiary-container, #31111d)); /* ─── Error palette ─── */ --error: var(--moni-error, var(--md-sys-color-error, #b3261e)); --on-error: var(--moni-on-error, var(--md-sys-color-on-error, #ffffff)); --error-container: var(--moni-error-container, var(--md-sys-color-error-container, #f9dedc)); --on-error-container: var(--moni-on-error-container, var(--md-sys-color-on-error-container, #410e0b)); /* ─── Background & surface ─── */ --background: var(--moni-background, var(--md-sys-color-background, #fef7ff)); --on-background: var(--moni-on-background, var(--md-sys-color-on-background, #1d1b20)); --surface: var(--moni-surface, var(--md-sys-color-surface, #fef7ff)); --on-surface: var(--moni-on-surface, var(--md-sys-color-on-surface, #1d1b20)); --surface-variant: var(--moni-surface-variant, var(--md-sys-color-surface-variant, #e7e0ec)); --on-surface-variant: var(--moni-on-surface-variant, var(--md-sys-color-on-surface-variant, #49454f)); /* ─── Outline ─── */ --outline: var(--moni-outline, var(--md-sys-color-outline, #79747e)); --outline-variant: var(--moni-outline-variant, var(--md-sys-color-outline-variant, #cac4d0)); /* ─── Surface container hierarchy (5-level elevation tones) ─── */ --surface-container-lowest: var(--moni-surface-container-lowest, var(--md-sys-color-surface-container-lowest, #ffffff)); --surface-container-low: var(--moni-surface-container-low, var(--md-sys-color-surface-container-low, #f7f2fa)); --surface-container: var(--moni-surface-container, var(--md-sys-color-surface-container, #f3edf7)); --surface-container-high: var(--moni-surface-container-high, var(--md-sys-color-surface-container-high, #ece6f0)); --surface-container-highest: var(--moni-surface-container-highest, var(--md-sys-color-surface-container-highest, #e6e1e5)); /* ─── Inverse surfaces (snackbar, tooltip) ─── */ --inverse-surface: var(--moni-inverse-surface, var(--md-sys-color-inverse-surface, #313033)); --inverse-on-surface: var(--moni-inverse-on-surface, var(--md-sys-color-inverse-on-surface, #f4f0f4)); --inverse-primary: var(--moni-inverse-primary, var(--md-sys-color-inverse-primary, #d0bcff)); /* ─── Structural / non-color tokens ─── */ /* Scrim overlay used in modals, bottom sheets, dialogs. */ --shadow: 0 0 0 0 transparent; --scrim: rgb(0 0 0 / 0.5); /* State layer color for hover/pressed overlays. */ --active: color-mix(in srgb, currentColor 10%, transparent); --overlay: rgb(0 0 0 / 0.5); --image: linear-gradient(currentColor 0 0); /* ─── Typography tokens ─── */ --font: var(--moni-font-sans, 'Geist', system-ui, -apple-system, sans-serif); --font-title: var( --moni-font-title, 'Instrument Serif', Georgia, serif ); --font-mono: 'Geist Mono', 'Fira Code', monospace; /* Icon font: override via --font-icon-override to use a different icon set. */ --font-icon: var(--font-icon-override, 'Material Symbols Rounded'); /* ─── Elevation shadows (MD3 tonal levels 1–3) ─── */ --elevate1: 0 0.125rem 0.125rem 0 rgb(0 0 0 / 0.32); --elevate2: 0 0.25rem 0.5rem 0 rgb(0 0 0 / 0.4); --elevate3: 0 0.375rem 0.75rem 0 rgb(0 0 0 / 0.48); /* ─── Transition speed scale ─── */ /* speed1 = fastest micro-interactions; speed4 = slowest page transitions. */ --speed1: 100ms; --speed2: 200ms; --speed3: 300ms; --speed4: 400ms; } /* ─── 3. MD3 --color-* backward-compatibility aliases ─────────────── */ /* These map the older --color-<role> naming convention used in legacy */ /* Moni apps to the primary bare tokens above. Do not remove. */ :host { --color-primary: var(--primary); --color-on-primary: var(--on-primary); --color-primary-container: var(--primary-container); --color-on-primary-container: var(--on-primary-container); --color-secondary: var(--secondary); --color-on-secondary: var(--on-secondary); --color-secondary-container: var(--secondary-container); --color-on-secondary-container: var(--on-secondary-container); --color-tertiary: var(--tertiary); --color-on-tertiary: var(--on-tertiary); --color-tertiary-container: var(--tertiary-container); --color-on-tertiary-container: var(--on-tertiary-container); --color-error: var(--error); --color-on-error: var(--on-error); --color-error-container: var(--error-container); --color-on-error-container: var(--on-error-container); --color-background: var(--background); --color-on-background: var(--on-background); --color-surface: var(--surface); --color-on-surface: var(--on-surface); --color-surface-variant: var(--surface-variant); --color-on-surface-variant: var(--on-surface-variant); --color-outline: var(--outline); --color-outline-variant: var(--outline-variant); --color-surface-container-lowest: var(--surface-container-lowest); --color-surface-container-low: var(--surface-container-low); --color-surface-container: var(--surface-container); --color-surface-container-high: var(--surface-container-high); --color-surface-container-highest: var(--surface-container-highest); --color-inverse-surface: var(--inverse-surface); --color-inverse-on-surface: var(--inverse-on-surface); --color-inverse-primary: var(--inverse-primary); /* Named aliases for tokens defined in the legacy tokens.css stylesheet. */ --font-sans: var(--font); --shadow-1: var(--elevate1); --shadow-2: var(--elevate2); --shadow-3: var(--elevate3); --radius-sm: 0.5rem; --radius-md: 0.75rem; --radius-lg: 1.5rem; --ease-standard: cubic-bezier(0.2, 0, 0, 1); --ease-emphasized: cubic-bezier(0.3, 0, 0, 1); --duration-fast: 150ms; --duration-normal: 250ms; } /* ─── 4. Utility classes ─────────────────────────────────────────────── */ /** * Clamps text to a single line with an ellipsis when overflow occurs. * Apply to any element inside a shadow root where text needs to be truncated. */ .truncate { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } /** * Visually hides an element while keeping it accessible to screen readers * and maintaining its position in the DOM and tab order. * Use for labeling icon-only buttons, skip links, etc. */ .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } `",
18999
+ "default": "css` /* ─── 1. Box-sizing reset ─────────────────────────────────────────────── */ :host { box-sizing: border-box; font-family: var(--font); font-style: normal; position: relative; color: var(--on-surface); } *, *::before, *::after { box-sizing: border-box; font-style: normal; } ::slotted(*) { font-style: normal; } /* ─── 2. CSS custom property bridge ──────────────────────────────────── */ /* Each token resolves in order: Moni namespace → MD3 spec → M3 default. */ :host { /* ─── Primary palette ─── */ --primary: var(--moni-primary, var(--md-sys-color-primary, #6750A4)); --on-primary: var(--moni-on-primary, var(--md-sys-color-on-primary, #ffffff)); --primary-container: var(--moni-primary-container, var(--md-sys-color-primary-container, #eaddff)); --on-primary-container: var(--moni-on-primary-container, var(--md-sys-color-on-primary-container, #21005d)); /* ─── Secondary palette ─── */ --secondary: var(--moni-secondary, var(--md-sys-color-secondary, #625b71)); --on-secondary: var(--moni-on-secondary, var(--md-sys-color-on-secondary, #ffffff)); --secondary-container: var(--moni-secondary-container, var(--md-sys-color-secondary-container, #e8def8)); --on-secondary-container: var(--moni-on-secondary-container, var(--md-sys-color-on-secondary-container, #1d192b)); /* ─── Tertiary palette ─── */ --tertiary: var(--moni-tertiary, var(--md-sys-color-tertiary, #7d5260)); --on-tertiary: var(--moni-on-tertiary, var(--md-sys-color-on-tertiary, #ffffff)); --tertiary-container: var(--moni-tertiary-container, var(--md-sys-color-tertiary-container, #ffd8e4)); --on-tertiary-container: var(--moni-on-tertiary-container, var(--md-sys-color-on-tertiary-container, #31111d)); /* ─── Error palette ─── */ --error: var(--moni-error, var(--md-sys-color-error, #b3261e)); --on-error: var(--moni-on-error, var(--md-sys-color-on-error, #ffffff)); --error-container: var(--moni-error-container, var(--md-sys-color-error-container, #f9dedc)); --on-error-container: var(--moni-on-error-container, var(--md-sys-color-on-error-container, #410e0b)); /* ─── Background & surface ─── */ --background: var(--moni-background, var(--md-sys-color-background, #fef7ff)); --on-background: var(--moni-on-background, var(--md-sys-color-on-background, #1d1b20)); --surface: var(--moni-surface, var(--md-sys-color-surface, #fef7ff)); --on-surface: var(--moni-on-surface, var(--md-sys-color-on-surface, #1d1b20)); --surface-variant: var(--moni-surface-variant, var(--md-sys-color-surface-variant, #e7e0ec)); --on-surface-variant: var(--moni-on-surface-variant, var(--md-sys-color-on-surface-variant, #49454f)); /* ─── Outline ─── */ --outline: var(--moni-outline, var(--md-sys-color-outline, #79747e)); --outline-variant: var(--moni-outline-variant, var(--md-sys-color-outline-variant, #cac4d0)); /* ─── Surface container hierarchy (5-level elevation tones) ─── */ --surface-container-lowest: var(--moni-surface-container-lowest, var(--md-sys-color-surface-container-lowest, #ffffff)); --surface-container-low: var(--moni-surface-container-low, var(--md-sys-color-surface-container-low, #f7f2fa)); --surface-container: var(--moni-surface-container, var(--md-sys-color-surface-container, #f3edf7)); --surface-container-high: var(--moni-surface-container-high, var(--md-sys-color-surface-container-high, #ece6f0)); --surface-container-highest: var(--moni-surface-container-highest, var(--md-sys-color-surface-container-highest, #e6e1e5)); /* ─── Inverse surfaces (snackbar, tooltip) ─── */ --inverse-surface: var(--moni-inverse-surface, var(--md-sys-color-inverse-surface, #313033)); --inverse-on-surface: var(--moni-inverse-on-surface, var(--md-sys-color-inverse-on-surface, #f4f0f4)); --inverse-primary: var(--moni-inverse-primary, var(--md-sys-color-inverse-primary, #d0bcff)); /* ─── Structural / non-color tokens ─── */ /* Scrim overlay used in modals, bottom sheets, dialogs. */ --shadow: 0 0 0 0 transparent; --scrim: rgb(0 0 0 / 0.5); /* State layer color for hover/pressed overlays. */ --active: color-mix(in srgb, currentColor 10%, transparent); --overlay: rgb(0 0 0 / 0.5); --image: linear-gradient(currentColor 0 0); /* ─── Typography tokens ─── */ --font: var(--moni-font-sans, 'Geist', system-ui, -apple-system, sans-serif); --font-title: var( --moni-font-title, 'Instrument Serif', Georgia, serif ); --font-mono: 'Geist Mono', 'Fira Code', monospace; /* Icon font: override via --font-icon-override to use a different icon set. */ --font-icon: var(--font-icon-override, 'Material Symbols Rounded'); /* ─── Elevation shadows (MD3 tonal levels 1–3) ─── */ --elevate1: 0 0.125rem 0.125rem 0 rgb(0 0 0 / 0.32); --elevate2: 0 0.25rem 0.5rem 0 rgb(0 0 0 / 0.4); --elevate3: 0 0.375rem 0.75rem 0 rgb(0 0 0 / 0.48); /* ─── Transition speed scale ─── */ /* speed1 = fastest micro-interactions; speed4 = slowest page transitions. */ --speed1: 100ms; --speed2: 200ms; --speed3: 300ms; --speed4: 400ms; } /* ─── 3. MD3 --color-* backward-compatibility aliases ─────────────── */ /* These map the older --color-<role> naming convention used in legacy */ /* Moni apps to the primary bare tokens above. Do not remove. */ :host { --color-primary: var(--primary); --color-on-primary: var(--on-primary); --color-primary-container: var(--primary-container); --color-on-primary-container: var(--on-primary-container); --color-secondary: var(--secondary); --color-on-secondary: var(--on-secondary); --color-secondary-container: var(--secondary-container); --color-on-secondary-container: var(--on-secondary-container); --color-tertiary: var(--tertiary); --color-on-tertiary: var(--on-tertiary); --color-tertiary-container: var(--tertiary-container); --color-on-tertiary-container: var(--on-tertiary-container); --color-error: var(--error); --color-on-error: var(--on-error); --color-error-container: var(--error-container); --color-on-error-container: var(--on-error-container); --color-background: var(--background); --color-on-background: var(--on-background); --color-surface: var(--surface); --color-on-surface: var(--on-surface); --color-surface-variant: var(--surface-variant); --color-on-surface-variant: var(--on-surface-variant); --color-outline: var(--outline); --color-outline-variant: var(--outline-variant); --color-surface-container-lowest: var(--surface-container-lowest); --color-surface-container-low: var(--surface-container-low); --color-surface-container: var(--surface-container); --color-surface-container-high: var(--surface-container-high); --color-surface-container-highest: var(--surface-container-highest); --color-inverse-surface: var(--inverse-surface); --color-inverse-on-surface: var(--inverse-on-surface); --color-inverse-primary: var(--inverse-primary); /* Named aliases for tokens defined in the legacy tokens.css stylesheet. */ --font-sans: var(--font); --shadow-1: var(--elevate1); --shadow-2: var(--elevate2); --shadow-3: var(--elevate3); --radius-sm: 0.5rem; --radius-md: 0.75rem; --radius-lg: 1.5rem; --ease-standard: cubic-bezier(0.2, 0, 0, 1); --ease-emphasized: cubic-bezier(0.3, 0, 0, 1); --duration-fast: 150ms; --duration-normal: 250ms; /* ─── Material 3 scrollbar ─── */ --_scrollbar-size: var(--moni-scrollbar-size, 0.625rem); --_scrollbar-track: var(--moni-scrollbar-track, transparent); --_scrollbar-thumb: var( --moni-scrollbar-thumb, color-mix(in srgb, var(--on-surface-variant) 38%, transparent) ); --_scrollbar-thumb-hover: var( --moni-scrollbar-thumb-hover, color-mix(in srgb, var(--on-surface-variant) 62%, transparent) ); --_scrollbar-thumb-active: var( --moni-scrollbar-thumb-active, color-mix(in srgb, var(--primary) 76%, transparent) ); } /* Firefox uses its native thin geometry and the same Material color roles. */ * { scrollbar-width: thin; scrollbar-color: var(--_scrollbar-thumb) var(--_scrollbar-track); } /* Chromium, Safari and Edge: 4px pill inside a 10px interaction gutter. */ *::-webkit-scrollbar { inline-size: var(--_scrollbar-size); block-size: var(--_scrollbar-size); } *::-webkit-scrollbar-track { background: var(--_scrollbar-track); border-radius: 999px; } *::-webkit-scrollbar-thumb { min-block-size: 2.5rem; background: var(--_scrollbar-thumb); background-clip: padding-box; border: 0.1875rem solid transparent; border-radius: 999px; } *::-webkit-scrollbar-thumb:hover { background: var(--_scrollbar-thumb-hover); background-clip: padding-box; border-width: 0.125rem; } *::-webkit-scrollbar-thumb:active { background: var(--_scrollbar-thumb-active); background-clip: padding-box; } *::-webkit-scrollbar-corner { background: transparent; } /* ─── 4. Utility classes ─────────────────────────────────────────────── */ /** * Clamps text to a single line with an ellipsis when overflow occurs. * Apply to any element inside a shadow root where text needs to be truncated. */ .truncate { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } /** * Visually hides an element while keeping it accessible to screen readers * and maintaining its position in the DOM and tab order. * Use for labeling icon-only buttons, skip links, etc. */ .visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } `",
18955
19000
  "description": "Baseline CSS injected into the shadow root of **every** Moni Web Component.\n\nThis stylesheet is the foundational layer of the Moni design system. It serves\nfour distinct responsibilities:\n\n**1. Box-sizing reset**\nApplies `box-sizing: border-box` to `:host`, all descendants, and slotted\nelements so every component has predictable sizing without override surprises.\n\n**2. CSS custom property bridge (Token namespace)**\nThe component library uses short \"bare\" variable names internally (e.g.\n`var(--primary)`) for concise CSS. Because shadow DOM isolates custom\nproperties by default, this sheet creates a bridge that resolves these\ninternal names from the global token namespaces in the following priority:\n\n `--moni-*` (Moni namespace) → `--md-sys-color-*` (MD3 spec) → hardcoded M3 default\n\nThis lets a host document use any of the three namespaces to theme components.\n\n**3. MD3 `--color-*` aliases**\nA second `:host` block maps the older `--color-<role>` prefix to the primary\nbare tokens. These aliases ensure backward compatibility with Moni apps\n(MoniAuth-Client, MoniAuth-Yo) that still reference `--color-primary`, etc.\n\n**4. Utility classes**\nProvides `.truncate` (single-line text clipping) and `.visually-hidden`\n(screen-reader-only content) for use inside component shadow trees.\n\n**Usage:**\n```ts\nstatic override styles = [sharedStyles, css`/* component-specific styles *\\/`];\n```"
18956
19001
  }
18957
19002
  ],