@threadlabs/looma 0.36.0 → 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/components/shared/icons.js +3 -0
  2. package/components/ui-avatar/ui-avatar.html +1 -1
  3. package/components/ui-avatar-group/ui-avatar-group.html +1 -1
  4. package/components/ui-button/ui-button.html +3 -12
  5. package/components/ui-card/ui-card.html +5 -5
  6. package/components/ui-checkbox/ui-checkbox.html +0 -6
  7. package/components/ui-color-picker/ui-color-picker.html +0 -9
  8. package/components/ui-combobox/ui-combobox.html +1 -13
  9. package/components/ui-cycle-button/ui-cycle-button.html +41 -0
  10. package/components/ui-cycle-button/ui-cycle-button.js +19 -0
  11. package/components/ui-dialog/ui-dialog.html +1 -1
  12. package/components/ui-icon/ui-icon.html +3 -0
  13. package/components/ui-icon-button/ui-icon-button.html +10 -14
  14. package/components/ui-input/ui-input.html +1 -16
  15. package/components/ui-input-group/ui-input-group.html +0 -10
  16. package/components/ui-menu/ui-menu.html +1 -1
  17. package/components/ui-popover/ui-popover.html +1 -1
  18. package/components/ui-radio/ui-radio.html +0 -6
  19. package/components/ui-select/ui-select.html +2 -8
  20. package/components/ui-switch/ui-switch.html +0 -6
  21. package/components/ui-tooltip/ui-tooltip.html +1 -1
  22. package/dist/index.js +47 -46
  23. package/docs/README.md +47 -8
  24. package/docs/card.html +2 -1
  25. package/docs/catalog.json +214 -39
  26. package/docs/customizer-render.mjs +8 -7
  27. package/docs/customizer.js +39 -21
  28. package/docs/theme.mjs +6 -5
  29. package/docs/value-editors.mjs +4 -3
  30. package/editor/icons.d.ts +3 -0
  31. package/editor/index.js +4 -1
  32. package/package.json +2 -2
  33. package/styles/ui-avatar-group.css +1 -1
  34. package/styles/ui-avatar.css +1 -1
  35. package/styles/ui-button.css +2 -14
  36. package/styles/ui-card.css +1 -1
  37. package/styles/ui-checkbox.css +0 -9
  38. package/styles/ui-color-picker.css +0 -13
  39. package/styles/ui-combobox.css +2 -28
  40. package/styles/ui-cycle-button.css +36 -0
  41. package/styles/ui-dialog.css +2 -2
  42. package/styles/ui-icon-button.css +6 -13
  43. package/styles/ui-input-group.css +0 -13
  44. package/styles/ui-input.css +2 -18
  45. package/styles/ui-menu.css +1 -1
  46. package/styles/ui-popover.css +1 -1
  47. package/styles/ui-radio.css +0 -9
  48. package/styles/ui-select.css +3 -10
  49. package/styles/ui-switch.css +0 -9
  50. package/styles/ui-tooltip.css +1 -1
  51. package/theme-system.css +19 -18
  52. package/tokens.css +20 -25
  53. package/vanilla/UiCycleButton.d.ts +29 -0
  54. package/vanilla/UiCycleButton.js +85 -0
  55. package/vanilla/UiIcon.js +1 -1
  56. package/vanilla/index.js +1 -0
  57. package/vue/UiAvatar.vue +1 -1
  58. package/vue/UiAvatarGroup.vue +1 -1
  59. package/vue/UiButton.vue +2 -11
  60. package/vue/UiCard.vue +5 -3
  61. package/vue/UiCheckbox.vue +0 -6
  62. package/vue/UiColorPicker.vue +0 -9
  63. package/vue/UiCombobox.vue +2 -26
  64. package/vue/UiCycleButton.d.ts +69 -0
  65. package/vue/UiCycleButton.js +2 -0
  66. package/vue/UiCycleButton.vue +403 -0
  67. package/vue/UiDialog.vue +6 -2
  68. package/vue/UiIcon.vue +22 -0
  69. package/vue/UiIconButton.vue +2 -16
  70. package/vue/UiInput.vue +1 -22
  71. package/vue/UiInputGroup.vue +0 -10
  72. package/vue/UiMenu.vue +3 -1
  73. package/vue/UiPopover.vue +3 -1
  74. package/vue/UiRadio.vue +0 -6
  75. package/vue/UiSelect.vue +2 -8
  76. package/vue/UiSwitch.vue +0 -6
  77. package/vue/UiTooltip.vue +3 -1
  78. package/vue/chunks/UiAffordanceScope.js +1 -1
  79. package/vue/chunks/UiAvatar.js +2 -2
  80. package/vue/chunks/UiAvatarGroup.js +2 -2
  81. package/vue/chunks/UiBadge.js +1 -1
  82. package/vue/chunks/UiBreadcrumbItem.js +1 -1
  83. package/vue/chunks/UiBreadcrumbs.js +1 -1
  84. package/vue/chunks/UiButton.js +3 -3
  85. package/vue/chunks/UiCallout.js +1 -1
  86. package/vue/chunks/UiCard.js +4 -4
  87. package/vue/chunks/UiCheckbox.js +2 -2
  88. package/vue/chunks/UiCluster.js +1 -1
  89. package/vue/chunks/UiColorPicker.js +2 -2
  90. package/vue/chunks/UiCombobox.js +2 -2
  91. package/vue/chunks/UiContainer.js +1 -1
  92. package/vue/chunks/UiContextMenu.js +1 -1
  93. package/vue/chunks/UiCover.js +1 -1
  94. package/vue/chunks/UiCurveEditor.js +1 -1
  95. package/vue/chunks/UiCycleButton.js +352 -0
  96. package/vue/chunks/UiDescriptionItem.js +1 -1
  97. package/vue/chunks/UiDescriptionList.js +1 -1
  98. package/vue/chunks/UiDialog.js +2 -2
  99. package/vue/chunks/UiDisclosure.js +1 -1
  100. package/vue/chunks/UiEditable.js +1 -1
  101. package/vue/chunks/UiEditorBlockActions.js +1 -1
  102. package/vue/chunks/UiEditorChipEditor.js +1 -1
  103. package/vue/chunks/UiEditorCodeLanguage.js +1 -1
  104. package/vue/chunks/UiEditorFootnoteEditor.js +1 -1
  105. package/vue/chunks/UiEditorImageToolbar.js +1 -1
  106. package/vue/chunks/UiEditorInsertTableGrid.js +1 -1
  107. package/vue/chunks/UiEditorLinkCard.js +1 -1
  108. package/vue/chunks/UiEditorLinkForm.js +1 -1
  109. package/vue/chunks/UiEditorMentionMenu.js +1 -1
  110. package/vue/chunks/UiEditorMentionMenuItem.js +1 -1
  111. package/vue/chunks/UiEditorSlashMenu.js +1 -1
  112. package/vue/chunks/UiEditorSlashMenuGroup.js +1 -1
  113. package/vue/chunks/UiEditorSlashMenuItem.js +1 -1
  114. package/vue/chunks/UiEditorSurface.js +1 -1
  115. package/vue/chunks/UiEditorTableContextMenu.js +1 -1
  116. package/vue/chunks/UiEditorTableOverlay.js +1 -1
  117. package/vue/chunks/UiEditorTableToolbar.js +1 -1
  118. package/vue/chunks/UiEditorToolbar.js +1 -1
  119. package/vue/chunks/UiFloatingActionButton.js +1 -1
  120. package/vue/chunks/UiFormField.js +1 -1
  121. package/vue/chunks/UiGrid.js +1 -1
  122. package/vue/chunks/UiIcon.js +70 -2
  123. package/vue/chunks/UiIconButton.js +4 -4
  124. package/vue/chunks/UiImage.js +1 -1
  125. package/vue/chunks/UiInput.js +2 -2
  126. package/vue/chunks/UiInputGroup.js +2 -2
  127. package/vue/chunks/UiLightEditor.js +1 -1
  128. package/vue/chunks/UiList.js +1 -1
  129. package/vue/chunks/UiListItem.js +1 -1
  130. package/vue/chunks/UiListbox.js +1 -1
  131. package/vue/chunks/UiMenu.js +2 -2
  132. package/vue/chunks/UiMenuGroup.js +1 -1
  133. package/vue/chunks/UiMenuItem.js +1 -1
  134. package/vue/chunks/UiMeter.js +1 -1
  135. package/vue/chunks/UiNavItem.js +1 -1
  136. package/vue/chunks/UiNumberInput.js +1 -1
  137. package/vue/chunks/UiPageHeader.js +1 -1
  138. package/vue/chunks/UiPopover.js +2 -2
  139. package/vue/chunks/UiRadio.js +2 -2
  140. package/vue/chunks/UiRadioGroup.js +1 -1
  141. package/vue/chunks/UiReel.js +1 -1
  142. package/vue/chunks/UiScrollArea.js +1 -1
  143. package/vue/chunks/UiSearchResultRow.js +1 -1
  144. package/vue/chunks/UiSearchShell.js +1 -1
  145. package/vue/chunks/UiSection.js +1 -1
  146. package/vue/chunks/UiSelect.js +2 -2
  147. package/vue/chunks/UiSeparator.js +1 -1
  148. package/vue/chunks/UiSidebar.js +1 -1
  149. package/vue/chunks/UiSlider.js +1 -1
  150. package/vue/chunks/UiSpinner.js +1 -1
  151. package/vue/chunks/UiStack.js +1 -1
  152. package/vue/chunks/UiStatusMessage.js +1 -1
  153. package/vue/chunks/UiSwatch.js +1 -1
  154. package/vue/chunks/UiSwitch.js +2 -2
  155. package/vue/chunks/UiSwitcher.js +1 -1
  156. package/vue/chunks/UiTable.js +1 -1
  157. package/vue/chunks/UiTabs.js +1 -1
  158. package/vue/chunks/UiText.js +1 -1
  159. package/vue/chunks/UiTextarea.js +1 -1
  160. package/vue/chunks/UiToast.js +1 -1
  161. package/vue/chunks/UiToastRegion.js +1 -1
  162. package/vue/chunks/UiTooltip.js +2 -2
  163. package/vue/chunks/UiTree.js +1 -1
  164. package/vue/chunks/UiTreeItem.js +1 -1
  165. package/vue/chunks/props.js +12 -1
  166. package/vue/components.css +76 -181
  167. package/vue/editor/index.js +68 -0
  168. package/vue/index.d.ts +1 -0
  169. package/vue/index.js +2 -1
package/vue/UiCard.vue CHANGED
@@ -51,7 +51,7 @@ const propValidityContract = {
51
51
  required: false,
52
52
  target: { attribute: 'tone' },
53
53
  description:
54
- 'accent, info, success, warning and danger add a quiet semantic surface without changing card geometry. Row highlights follow the\n surface hue; action controls retain their own intent.',
54
+ 'accent, info, success, warning and danger add a quiet semantic surface without changing card geometry. Row highlights follow the surface hue; action\n controls retain their own intent.',
55
55
  values: ['neutral', 'accent', 'info', 'success', 'warning', 'danger'],
56
56
  default: 'neutral',
57
57
  },
@@ -60,7 +60,7 @@ const propValidityContract = {
60
60
  required: false,
61
61
  target: { attribute: 'treatment' },
62
62
  description:
63
- 'How much of the theme\'s decorative material the surface expresses. standard preserves its restrained treatment; expressive uses the full material treatment.\n This does not add elevation or require glass.',
63
+ 'How much of the theme\'s decorative material the surface expresses. standard preserves its restrained treatment; expressive uses the full material\n treatment. This does not add elevation or require glass.',
64
64
  values: ['standard', 'expressive'],
65
65
  default: 'standard',
66
66
  },
@@ -329,7 +329,9 @@ const hostState = computed(() =>
329
329
  }
330
330
 
331
331
  :global(:scope[data-ui-card-state~="treatment=expressive"]:where([data-hn-ui-card])) {
332
- --_ui-panel-glass: clamp(0, var(--ui-glassiness, 0), 1);
332
+ --_ui-panel-glass: calc(
333
+ clamp(0, var(--ui-glassiness, 0), 1) + 2 * pow(clamp(0, var(--ui-glassiness, 0), 1), 4)
334
+ );
333
335
  --_ui-panel-separation: clamp(0, var(--ui-surface-separation, 0), 1);
334
336
  --_ui-panel-rim: calc(
335
337
  (var(--_ui-panel-separation) + var(--_ui-panel-glass) * 0.25) * 0.65 * 8px
@@ -574,12 +574,6 @@ useComponentHost(() => import('../components/ui-checkbox/ui-checkbox.js'), {
574
574
  calc((var(--_row) - max(var(--_line), var(--_ui-checkbox-size, 1.125rem))) / 2)
575
575
  ) !important;
576
576
  }
577
-
578
- @media (pointer: coarse) {
579
- :global(:scope[data-ui-checkbox-state~="size=sm"]:where([data-hn-ui-checkbox])) {
580
- --_row: var(--ui-control-min-block-size);
581
- }
582
- }
583
577
  :global(:scope[hidden]:where([data-hn-ui-checkbox])) {
584
578
  display: none !important;
585
579
  }
@@ -409,14 +409,5 @@ useComponentHost(() => import('../components/ui-color-picker/ui-color-picker.js'
409
409
  padding: 0 !important;
410
410
  accent-color: var(--ui-accent-solid);
411
411
  }
412
- @media (pointer: coarse) {
413
- :global(:scope) {
414
- --_size: max(var(--ui-control-size-md), var(--ui-control-min-block-size));
415
- }
416
- :global(:scope[data-ui-color-picker-state~="size=sm"]) {
417
- --_size: max(var(--ui-control-size-sm), var(--ui-control-min-block-size));
418
- --_font: var(--ui-font-size-md);
419
- }
420
- }
421
412
  }
422
413
  </style>
@@ -2378,20 +2378,8 @@ useComponentHost(() => import('../components/ui-combobox/ui-combobox.js'), {
2378
2378
  white-space: nowrap;
2379
2379
  }
2380
2380
  @media (pointer: coarse) {
2381
- :global(:scope:where([data-hn-ui-combobox])) {
2382
- --_row: max(var(--ui-control-size-md), var(--ui-control-min-block-size));
2383
- }
2384
- :global(button:where([data-hn-ui-combobox])) {
2385
- min-inline-size: 2.75rem;
2386
- min-block-size: calc(var(--_row) - 2 * var(--ui-border-width));
2387
- }
2388
- /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
2389
- body-size text: below 16px, iOS zooms the page into the focused field. */
2390
- :global(:scope[data-ui-combobox-state~="size=sm"]:where([data-hn-ui-combobox])) {
2391
- --_row: max(var(--ui-control-size-sm), var(--ui-control-min-block-size));
2392
- }
2393
2381
  :global(:scope[data-ui-combobox-state~="size=sm"] input:where([data-hn-ui-combobox])) {
2394
- font-size: inherit;
2382
+ font-size: var(--ui-font-size-md);
2395
2383
  }
2396
2384
  }
2397
2385
  @media (forced-colors: active) {
@@ -2729,21 +2717,9 @@ useComponentHost(() => import('../components/ui-combobox/ui-combobox.js'), {
2729
2717
  white-space: nowrap;
2730
2718
  }
2731
2719
  @media (pointer: coarse) {
2732
- :global(:where(.ui-combobox):where(:scope, :not([data-hn], [data-slotted]:not([data-slotted~="ui-combobox"]))):where([data-hn-ui-combobox])) {
2733
- --_row: max(var(--ui-control-size-md), var(--ui-control-min-block-size));
2734
- }
2735
- :global(button:where(:scope, :scope *):where([data-hn-ui-combobox])) {
2736
- min-inline-size: 2.75rem;
2737
- min-block-size: calc(var(--_row) - 2 * var(--ui-border-width));
2738
- }
2739
- /* Under touch a small field is still a touch-sized target, as a small button is, and keeps
2740
- body-size text: below 16px, iOS zooms the page into the focused field. */
2741
- :global(:where(.ui-combobox)[data-ui-combobox-state~="size=sm"]:where(:scope, :not([data-hn], [data-slotted]:not([data-slotted~="ui-combobox"]))):where([data-hn-ui-combobox])) {
2742
- --_row: max(var(--ui-control-size-sm), var(--ui-control-min-block-size));
2743
- }
2744
2720
  :global(:where(.ui-combobox)[data-ui-combobox-state~="size=sm"]
2745
2721
  input:where(:scope, :not([data-hn], [data-slotted]:not([data-slotted~="ui-combobox"]))):where([data-hn-ui-combobox])) {
2746
- font-size: inherit;
2722
+ font-size: var(--ui-font-size-md);
2747
2723
  }
2748
2724
  }
2749
2725
  @media (forced-colors: active) {
@@ -0,0 +1,69 @@
1
+ import type { PropType } from 'vue';
2
+ import { type InputBindings } from './props.js';
3
+ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
4
+ disabled: {
5
+ type: PropType<boolean | null>;
6
+ };
7
+ index: {
8
+ type: PropType<number | null>;
9
+ };
10
+ items: {
11
+ type: PropType<readonly ({
12
+ readonly value: string;
13
+ readonly label: string;
14
+ readonly icon: string;
15
+ })[]>;
16
+ };
17
+ label: {
18
+ type: PropType<string | null>;
19
+ };
20
+ size: {
21
+ type: PropType<"sm" | "md" | "lg" | null>;
22
+ };
23
+ variant: {
24
+ type: PropType<"ghost" | "outline" | "solid" | null>;
25
+ };
26
+ "\0htmlNextInputStatus": {
27
+ type: PropType<InputBindings>;
28
+ };
29
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
30
+ change: (event: CustomEvent<{
31
+ readonly value: string;
32
+ readonly index: number;
33
+ }>) => any;
34
+ "update:index": (value: number) => any;
35
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
36
+ disabled: {
37
+ type: PropType<boolean | null>;
38
+ };
39
+ index: {
40
+ type: PropType<number | null>;
41
+ };
42
+ items: {
43
+ type: PropType<readonly ({
44
+ readonly value: string;
45
+ readonly label: string;
46
+ readonly icon: string;
47
+ })[]>;
48
+ };
49
+ label: {
50
+ type: PropType<string | null>;
51
+ };
52
+ size: {
53
+ type: PropType<"sm" | "md" | "lg" | null>;
54
+ };
55
+ variant: {
56
+ type: PropType<"ghost" | "outline" | "solid" | null>;
57
+ };
58
+ "\0htmlNextInputStatus": {
59
+ type: PropType<InputBindings>;
60
+ };
61
+ }>> & Readonly<{
62
+ onChange?: ((event: CustomEvent<{
63
+ readonly value: string;
64
+ readonly index: number;
65
+ }>) => any) | undefined;
66
+ "onUpdate:index"?: ((value: number) => any) | undefined;
67
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
68
+ declare const _default: typeof __VLS_export;
69
+ export default _default;
@@ -0,0 +1,2 @@
1
+ import { t as UiCycleButton_default } from "./chunks/UiCycleButton.js";
2
+ export { UiCycleButton_default as default };
@@ -0,0 +1,403 @@
1
+ <!-- Generated by HTML Next 1.0.0-alpha.69 for Vue 3.5. Do not edit. -->
2
+ <script setup lang="ts">
3
+ import { computed, getCurrentInstance, provide, ref, watchSyncEffect } from 'vue'
4
+ import type { PropType } from 'vue'
5
+ import {
6
+ checkHydratedRoot,
7
+ concat,
8
+ createDispatch,
9
+ cycleCheckedComputed,
10
+ initializedState,
11
+ nativeAttrs,
12
+ useComponentHost,
13
+ useReflectedProp,
14
+ warnUnless,
15
+ } from './host'
16
+ import { acceptsControllerWrite } from './props'
17
+ import {
18
+ BINDING_INPUTS_PROP,
19
+ checkedProp,
20
+ checkPropInput,
21
+ copyStateSeed,
22
+ evaluateInput,
23
+ type InputBindings,
24
+ inputComputed,
25
+ inputDefault,
26
+ inputRead,
27
+ propValidityState,
28
+ READY_INPUT,
29
+ requireInputStatus,
30
+ UNRESOLVED_INPUT,
31
+ vPropValidity,
32
+ vuePropInputs,
33
+ } from './props'
34
+ import UiIcon from './UiIcon.vue'
35
+ import UiIconButton from './UiIconButton.vue'
36
+
37
+ defineOptions({ inheritAttrs: false })
38
+
39
+ const propValidityContract = {
40
+ props: {
41
+ disabled: {
42
+ type: { kind: 'terminal', name: 'boolean' },
43
+ required: false,
44
+ target: { attribute: 'disabled' },
45
+ description: 'Prevents cycling while keeping the selected state visible.',
46
+ default: false,
47
+ },
48
+ index: {
49
+ type: { kind: 'terminal', name: 'number' },
50
+ required: false,
51
+ target: { attribute: 'index' },
52
+ description:
53
+ 'Initial or externally selected zero-based state. The first state is the default.',
54
+ default: 0,
55
+ },
56
+ items: {
57
+ type: {
58
+ kind: 'list',
59
+ item: {
60
+ kind: 'object',
61
+ fields: [{ name: 'value', type: { kind: 'terminal', name: 'string' }, optional: false }, {
62
+ name: 'label',
63
+ type: { kind: 'terminal', name: 'string' },
64
+ optional: false,
65
+ }, { name: 'icon', type: { kind: 'terminal', name: 'string' }, optional: false }],
66
+ open: false,
67
+ },
68
+ },
69
+ required: true,
70
+ target: { attribute: 'items' },
71
+ description:
72
+ 'Ordered states with a value, readable label and Looma icon name. Activation advances to the next state and wraps.',
73
+ },
74
+ label: {
75
+ type: { kind: 'terminal', name: 'string' },
76
+ required: false,
77
+ target: { attribute: 'label' },
78
+ description:
79
+ 'What the button changes, such as Appearance. The selected state is included in its accessible name.',
80
+ default: 'State',
81
+ },
82
+ size: {
83
+ type: { kind: 'terminal', name: 'keyword' },
84
+ required: false,
85
+ target: { attribute: 'size' },
86
+ description: 'Uses IconButton\'s compact visible sizes and adjustable invisible touch area.',
87
+ values: ['sm', 'md', 'lg'],
88
+ default: 'md',
89
+ },
90
+ variant: {
91
+ type: { kind: 'terminal', name: 'keyword' },
92
+ required: false,
93
+ target: { attribute: 'variant' },
94
+ description: 'Uses IconButton\'s standard emphasis.',
95
+ values: ['ghost', 'outline', 'solid'],
96
+ default: 'outline',
97
+ },
98
+ },
99
+ } as const
100
+ const props = defineProps({
101
+ disabled: { type: null as unknown as PropType<boolean | null> },
102
+ index: { type: null as unknown as PropType<number | null> },
103
+ items: {
104
+ type: null as unknown as PropType<
105
+ readonly ({ readonly value: string; readonly label: string; readonly icon: string })[]
106
+ >,
107
+ },
108
+ label: { type: null as unknown as PropType<string | null> },
109
+ size: { type: null as unknown as PropType<'sm' | 'md' | 'lg' | null> },
110
+ variant: { type: null as unknown as PropType<'ghost' | 'outline' | 'solid' | null> },
111
+ [BINDING_INPUTS_PROP]: { type: null as unknown as PropType<InputBindings> },
112
+ })
113
+ const acceptedProps: Record<string, unknown> = {
114
+ disabled: false,
115
+ index: 0,
116
+ items: null,
117
+ label: 'State',
118
+ size: 'md',
119
+ variant: 'outline',
120
+ }
121
+ const inputTracking = vuePropInputs(acceptedProps, {
122
+ disabled: false,
123
+ index: 0,
124
+ label: 'State',
125
+ size: 'md',
126
+ variant: 'outline',
127
+ }, (name, value, source, values) => {
128
+ switch (name) {
129
+ case 'disabled':
130
+ return checkPropInput(value, { kind: 'terminal', name: 'boolean' }, true, true)
131
+ case 'index':
132
+ return checkPropInput(value, { kind: 'terminal', name: 'number' }, true, true)
133
+ case 'items':
134
+ return checkPropInput(
135
+ value,
136
+ {
137
+ kind: 'list',
138
+ item: {
139
+ kind: 'object',
140
+ fields: [
141
+ { name: 'value', type: { kind: 'terminal', name: 'string' }, optional: false },
142
+ { name: 'label', type: { kind: 'terminal', name: 'string' }, optional: false },
143
+ { name: 'icon', type: { kind: 'terminal', name: 'string' }, optional: false },
144
+ ],
145
+ open: false,
146
+ },
147
+ },
148
+ true,
149
+ false,
150
+ )
151
+ case 'label':
152
+ return checkPropInput(value, { kind: 'terminal', name: 'string' }, true, true)
153
+ case 'size':
154
+ return checkPropInput(value, { kind: 'terminal', name: 'keyword' }, true, true)
155
+ case 'variant':
156
+ return checkPropInput(value, { kind: 'terminal', name: 'keyword' }, true, true)
157
+ default:
158
+ return { ok: false }
159
+ }
160
+ }, () => {
161
+ void checkedProps.value
162
+ })
163
+ const propInputs = inputTracking.inputs
164
+ const checkedProps = computed(() => {
165
+ void inputTracking.version.value
166
+ return ({
167
+ disabled: propInputs.value<boolean | null>(
168
+ 'disabled',
169
+ props['disabled'],
170
+ props[BINDING_INPUTS_PROP],
171
+ ) as boolean,
172
+ index: propInputs.value<number | null>(
173
+ 'index',
174
+ props['index'],
175
+ props[BINDING_INPUTS_PROP],
176
+ ) as number,
177
+ items: propInputs.value<
178
+ readonly ({ readonly value: string; readonly label: string; readonly icon: string })[]
179
+ >('items', props['items'], props[BINDING_INPUTS_PROP]),
180
+ label: propInputs.value<string | null>(
181
+ 'label',
182
+ props['label'],
183
+ props[BINDING_INPUTS_PROP],
184
+ ) as string,
185
+ size: propInputs.value<'sm' | 'md' | 'lg' | null>(
186
+ 'size',
187
+ props['size'],
188
+ props[BINDING_INPUTS_PROP],
189
+ ) as 'sm' | 'md' | 'lg',
190
+ variant: propInputs.value<'ghost' | 'outline' | 'solid' | null>(
191
+ 'variant',
192
+ props['variant'],
193
+ props[BINDING_INPUTS_PROP],
194
+ ) as 'ghost' | 'outline' | 'solid',
195
+ })
196
+ })
197
+ const inputStatus = (name: string, handlesError: boolean) => {
198
+ void checkedProps.value
199
+ void inputTracking.version.value
200
+ return requireInputStatus(propInputs.status(name), handlesError)
201
+ }
202
+ void checkedProps.value
203
+ watchSyncEffect(() => {
204
+ void checkedProps.value
205
+ })
206
+ const reflectedProp = useReflectedProp()
207
+ const hydrationInstance = getCurrentInstance()
208
+ const emit = defineEmits<{
209
+ 'update:index': [value: number]
210
+ change: [event: CustomEvent<{ readonly value: string; readonly index: number }>]
211
+ }>()
212
+
213
+ const root = ref<HTMLElement | null>(null)
214
+ const current = initializedState<number>(
215
+ () =>
216
+ evaluateInput(() => {
217
+ return inputRead(checkedProps.value.index, propInputs.status('index'))
218
+ }),
219
+ (next: unknown) => (typeof next === 'number' && Number.isFinite(next)),
220
+ copyStateSeed,
221
+ () =>
222
+ warnUnless(false, 'initializer:current', 'State `current` does not satisfy its declared type.'),
223
+ )
224
+ let selectedPrevious: { value: string; label: string; icon: string } | null = null
225
+ const selected: ReturnType<
226
+ typeof inputComputed<{ value: string; label: string; icon: string } | null>
227
+ > = inputComputed<{ value: string; label: string; icon: string } | null>(
228
+ () => {
229
+ return inputDefault(() =>
230
+ ((objectValue: any, indexValue: any) =>
231
+ objectValue === Symbol.for('html-next.invalid-result')
232
+ || indexValue === Symbol.for('html-next.invalid-result')
233
+ ? Symbol.for('html-next.invalid-result')
234
+ : objectValue[indexValue])(
235
+ inputRead(checkedProps.value.items, propInputs.status('items')),
236
+ inputDefault(() => inputRead(current.value, declarationStatus('current')), () => 0),
237
+ ), () =>
238
+ inputRead(checkedProps.value.items, propInputs.status('items'))[0]) as any
239
+ },
240
+ (read) => {
241
+ const value = cycleCheckedComputed(read)
242
+ return { get: () => value.value }
243
+ },
244
+ next =>
245
+ (next as unknown) === Symbol.for('html-next.invalid-result')
246
+ ? selectedPrevious
247
+ : (selectedPrevious = next),
248
+ )
249
+ let guardedPrevious: any = null
250
+ const guarded = computed(() => {
251
+ const candidate: any = concat(
252
+ checkedProps.value.label,
253
+ ': ',
254
+ selected.get()?.label ?? 'No choices',
255
+ )
256
+ if (candidate === Symbol.for('html-next.invalid-result')) return guardedPrevious
257
+ return guardedPrevious = candidate
258
+ })
259
+ provide('html-next:ui-cycle-button:current', current)
260
+ const declarationStatuses = { current: () => current.status() } as Record<
261
+ string,
262
+ () => typeof READY_INPUT
263
+ >
264
+ const declarationStatus = (name: string) => declarationStatuses[name]!()
265
+ current.start()
266
+ checkHydratedRoot('span', 'ui-cycle-button')
267
+
268
+ const isChangeDetail = (
269
+ detail: unknown,
270
+ ): boolean => (detail !== null && typeof detail === 'object' && !Array.isArray(detail)
271
+ && typeof (detail as Record<string, unknown>)['value'] === 'string'
272
+ && (typeof (detail as Record<string, unknown>)['index'] === 'number'
273
+ && Number.isFinite((detail as Record<string, unknown>)['index']))
274
+ && Object.keys(detail as object).every((key) => ['value', 'index'].includes(key)))
275
+ const dispatch = createDispatch(root, emit as (name: string, detail: unknown) => void, {
276
+ checks: {
277
+ change: isChangeDetail,
278
+ },
279
+ modeled: ['index'],
280
+ })
281
+
282
+ useComponentHost(() => import('../components/ui-cycle-button/ui-cycle-button.js'), {
283
+ root,
284
+ module: true,
285
+ dispatch,
286
+ acceptsState: (name: string, keys: readonly string[], value: unknown) =>
287
+ acceptsControllerWrite(
288
+ value,
289
+ ({ current: { kind: 'terminal', name: 'number' } } as Record<string, any>)[name],
290
+ keys,
291
+ ),
292
+ controllerSource: {
293
+ specifier: '../components/ui-cycle-button/ui-cycle-button.js',
294
+ definition: './components/ui-cycle-button/ui-cycle-button.html',
295
+ },
296
+ props: checkedProps,
297
+ inputs: propInputs,
298
+ inputTracking,
299
+ propInputs: (name: string) => {
300
+ const incoming = hydrationInstance?.vnode.props
301
+ const kebab = name.replace(/[A-Z]/g, (letter) => '-' + letter.toLowerCase())
302
+ return incoming !== null && incoming !== undefined
303
+ ? (incoming[name] ?? incoming[kebab] ?? null)
304
+ : null
305
+ },
306
+ propNames: ['disabled', 'index', 'items', 'label', 'size', 'variant'],
307
+ propValidity: (name: string) =>
308
+ propValidityState({ contract: propValidityContract, values: { ...props } }, name),
309
+ state: { current },
310
+ computed: { selected },
311
+ availability: { current: () => declarationStatus('current'), selected: () => selected.status() },
312
+ })
313
+ </script>
314
+
315
+ <template>
316
+ <span
317
+ class="ui-cycle-button"
318
+ v-bind="nativeAttrs($attrs)"
319
+ data-hn
320
+ v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
321
+ :data-ui-cycle-button-prop-disabled="reflectedProp('disabled', 'disabled', checkedProps.disabled, undefined, false)"
322
+ :data-ui-cycle-button-prop-index="reflectedProp('index', 'index', checkedProps.index, undefined, false)"
323
+ :data-ui-cycle-button-prop-items="reflectedProp('items', 'items', checkedProps.items, undefined, false)"
324
+ :data-ui-cycle-button-prop-label="reflectedProp('label', 'label', checkedProps.label, undefined, false)"
325
+ :data-ui-cycle-button-prop-size="reflectedProp('size', 'size', checkedProps.size, undefined, false)"
326
+ :data-ui-cycle-button-prop-variant="reflectedProp('variant', 'variant', checkedProps.variant, undefined, false)"
327
+ ref="root"
328
+ ><template
329
+ v-for="boundInput in [evaluateInput(
330
+ () =>
331
+ (concat(
332
+ inputRead(checkedProps.label, inputStatus('label', true)),
333
+ ': ',
334
+ inputDefault(() => inputRead(selected.get(), selected.status())?.label, () => 'No choices'),
335
+ )) as any,
336
+ READY_INPUT,
337
+ )]"
338
+ ><template
339
+ v-for="boundInputSnapshot in [evaluateInput(
340
+ () =>
341
+ (inputRead(checkedProps.disabled, inputStatus('disabled', true))
342
+ || inputRead(checkedProps.items, inputStatus('items', true)).length < 2) as any,
343
+ READY_INPUT,
344
+ )]"
345
+ ><UiIconButton
346
+ class="ui-icon-button"
347
+ tone="neutral"
348
+ aria-description="Activate to choose the next state."
349
+ :label="boundInput.value"
350
+ :title="guarded"
351
+ :size="checkedProps.size"
352
+ :variant="checkedProps.variant"
353
+ :disabled="boundInputSnapshot.value"
354
+ v-bind="{
355
+ [BINDING_INPUTS_PROP]: {
356
+ label: () => boundInput,
357
+ size: () => ({ value: checkedProps.size, status: inputStatus('size', true) }),
358
+ variant: () => ({ value: checkedProps.variant, status: inputStatus('variant', true) }),
359
+ disabled: () => boundInputSnapshot,
360
+ },
361
+ }"
362
+ ><template
363
+ v-for="boundInputSnapshot2 in [evaluateInput(
364
+ () => (inputDefault(() => inputRead(selected.get(), selected.status())?.icon, () => '')) as any,
365
+ READY_INPUT,
366
+ )]"
367
+ ><UiIcon
368
+ class="ui-icon"
369
+ :name="boundInputSnapshot2.value"
370
+ v-bind="{ [BINDING_INPUTS_PROP]: { name: () => boundInputSnapshot2 } }"
371
+ /></template></UiIconButton></template></template></span>
372
+ </template>
373
+
374
+ <style scoped>
375
+ @scope (.ui-cycle-button) to ([data-hn]) {
376
+ :global(:scope) {
377
+ display: inline-flex !important;
378
+ align-items: center !important;
379
+ justify-content: center !important;
380
+ gap: 0 !important;
381
+ margin: 0 !important;
382
+ padding: 0 !important;
383
+ vertical-align: middle;
384
+ }
385
+ :global(:scope[hidden]) {
386
+ display: none !important;
387
+ }
388
+ }
389
+ @scope ([data-slotted~="ui-cycle-button"]:not([data-hn])) to ([data-hn], [data-slotted]) {
390
+ :global(:where(.ui-cycle-button):where(:scope, :not([data-hn], [data-slotted]:not([data-slotted~="ui-cycle-button"])))) {
391
+ display: inline-flex !important;
392
+ align-items: center !important;
393
+ justify-content: center !important;
394
+ gap: 0 !important;
395
+ margin: 0 !important;
396
+ padding: 0 !important;
397
+ vertical-align: middle;
398
+ }
399
+ :global(:where(.ui-cycle-button)[hidden]:where(:scope, :not([data-hn], [data-slotted]:not([data-slotted~="ui-cycle-button"])))) {
400
+ display: none !important;
401
+ }
402
+ }
403
+ </style>
package/vue/UiDialog.vue CHANGED
@@ -428,7 +428,9 @@ useComponentHost(() => import('../components/ui-dialog/ui-dialog.js'), {
428
428
  overlay var(--ui-dialog-duration, var(--ui-motion-base)) var(--ui-motion-ease) allow-discrete;
429
429
  }
430
430
  :global(:scope:where([data-hn-ui-dialog])) {
431
- --_ui-material-glassiness: clamp(0, var(--ui-glassiness, 0), 1);
431
+ --_ui-material-glassiness: calc(
432
+ clamp(0, var(--ui-glassiness, 0), 1) + 2 * pow(clamp(0, var(--ui-glassiness, 0), 1), 4)
433
+ );
432
434
  --_ui-material-separation: clamp(0, var(--ui-surface-separation, 0), 1);
433
435
  --_ui-material-active: clamp(
434
436
  0,
@@ -707,7 +709,9 @@ useComponentHost(() => import('../components/ui-dialog/ui-dialog.js'), {
707
709
  overlay var(--ui-dialog-duration, var(--ui-motion-base)) var(--ui-motion-ease) allow-discrete;
708
710
  }
709
711
  :global(:where(.ui-dialog):where(:scope, :not([data-hn], [data-slotted]:not([data-slotted~="ui-dialog"]))):where([data-hn-ui-dialog])) {
710
- --_ui-material-glassiness: clamp(0, var(--ui-glassiness, 0), 1);
712
+ --_ui-material-glassiness: calc(
713
+ clamp(0, var(--ui-glassiness, 0), 1) + 2 * pow(clamp(0, var(--ui-glassiness, 0), 1), 4)
714
+ );
711
715
  --_ui-material-separation: clamp(0, var(--ui-surface-separation, 0), 1);
712
716
  --_ui-material-active: clamp(
713
717
  0,
package/vue/UiIcon.vue CHANGED
@@ -326,6 +326,17 @@ const shapes: ReturnType<typeof inputComputed<any>> = inputComputed<any>(
326
326
  { tag: 'path', d: 'M12 7v4' },
327
327
  ],
328
328
  minus: [{ tag: 'path', d: 'M5 12h14' }],
329
+ monitor: [{ tag: 'rect', width: '20', height: '14', x: '2', y: '3', rx: '2' }, {
330
+ tag: 'line',
331
+ x1: '8',
332
+ x2: '16',
333
+ y1: '21',
334
+ y2: '21',
335
+ }, { tag: 'line', x1: '12', x2: '12', y1: '17', y2: '21' }],
336
+ moon: [{
337
+ tag: 'path',
338
+ d: 'M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401',
339
+ }],
329
340
  'notebook-pen': [
330
341
  { tag: 'path', d: 'M13.4 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.4' },
331
342
  { tag: 'path', d: 'M2 6h4' },
@@ -413,6 +424,17 @@ const shapes: ReturnType<typeof inputComputed<any>> = inputComputed<any>(
413
424
  tag: 'path',
414
425
  d: 'M14 12a4 4 0 0 1 0 8H6',
415
426
  }, { tag: 'line', x1: '4', x2: '20', y1: '12', y2: '12' }],
427
+ sun: [
428
+ { tag: 'circle', cx: '12', cy: '12', r: '4' },
429
+ { tag: 'path', d: 'M12 2v2' },
430
+ { tag: 'path', d: 'M12 20v2' },
431
+ { tag: 'path', d: 'm4.93 4.93 1.41 1.41' },
432
+ { tag: 'path', d: 'm17.66 17.66 1.41 1.41' },
433
+ { tag: 'path', d: 'M2 12h2' },
434
+ { tag: 'path', d: 'M20 12h2' },
435
+ { tag: 'path', d: 'm6.34 17.66-1.41 1.41' },
436
+ { tag: 'path', d: 'm19.07 4.93-1.41 1.41' },
437
+ ],
416
438
  superscript: [{ tag: 'path', d: 'm4 19 8-8' }, { tag: 'path', d: 'm12 19-8-8' }, {
417
439
  tag: 'path',
418
440
  d: 'M20 12h-4c0-1.5.442-2 1.5-2.5S20 8.334 20 7.002c0-.472-.17-.93-.484-1.29a2.105 2.105 0 0 0-2.617-.436c-.42.239-.738.614-.899 1.06',
@@ -77,7 +77,7 @@ const propValidityContract = {
77
77
  required: false,
78
78
  target: { attribute: 'data-match-button' },
79
79
  description:
80
- 'Matches a boxed Button of the same size when the two share a flex row. Uses the control-size theme tokens and stretches to the row\'s height, including\n touch-sized controls. Leave it off for compact toolbar and inline icon buttons.',
80
+ 'Matches a boxed Button of the same size when the two share a flex row. Uses the control-size theme tokens and stretches to the row\'s height, on mouse and\n touch alike. Leave it off for compact toolbar and inline icon buttons.',
81
81
  default: false,
82
82
  },
83
83
  pending: {
@@ -116,7 +116,7 @@ const propValidityContract = {
116
116
  required: false,
117
117
  target: { attribute: 'size' },
118
118
  description:
119
- 'Button and icon size together: sm is 1.75rem with a small icon, md is 2rem, and lg is 2.5rem with a large icon. On touch, every size gets an invisible hit\n area of at least 44px.',
119
+ 'Button and icon size together: sm is 1.75rem with a small icon, md is 2rem, and lg is 2.5rem with a large icon. Touch can use an invisible hit area sized\n by the adjustable control target preference.',
120
120
  values: ['sm', 'md', 'lg'],
121
121
  default: 'md',
122
122
  },
@@ -841,20 +841,6 @@ useComponentHost(() => import('../components/ui-icon-button/ui-icon-button.js'),
841
841
  --_size: var(--ui-control-size-lg);
842
842
  }
843
843
 
844
- @media (pointer: coarse) {
845
- :global(:scope[data-ui-icon-button-state~="matchButton"]:where([data-hn-ui-icon-button])) {
846
- inline-size: max(var(--ui-icon-button-size, var(--_size)), var(--ui-control-min-block-size));
847
- min-inline-size: max(
848
- var(--ui-icon-button-size, var(--_size)),
849
- var(--ui-control-min-block-size)
850
- );
851
- min-block-size: max(
852
- var(--ui-icon-button-size, var(--_size)),
853
- var(--ui-control-min-block-size)
854
- );
855
- }
856
- }
857
-
858
844
  :global(:scope[data-ui-icon-button-state~="round"]:where([data-hn-ui-icon-button])) {
859
845
  border-radius: var(--ui-radius-round);
860
846
  }