@threadlabs/looma 0.33.0 → 0.34.0

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 (207) hide show
  1. package/README.md +15 -0
  2. package/bin/looma-docs.mjs +3 -0
  3. package/components/shared/overlay.js +8 -2
  4. package/components/shared/surface-material.js +21 -0
  5. package/components/ui-button/ui-button.html +63 -47
  6. package/components/ui-card/ui-card.html +44 -2
  7. package/components/ui-curve-editor/curve.js +10 -0
  8. package/components/ui-curve-editor/ui-curve-editor.html +181 -0
  9. package/components/ui-curve-editor/ui-curve-editor.js +51 -0
  10. package/components/ui-dialog/ui-dialog.html +62 -1
  11. package/components/ui-dialog/ui-dialog.js +9 -1
  12. package/components/ui-icon-button/ui-icon-button.html +75 -23
  13. package/components/ui-menu/ui-menu.html +60 -0
  14. package/components/ui-menu/ui-menu.js +1 -1
  15. package/components/ui-number-input/arithmetic.js +47 -0
  16. package/components/ui-number-input/ui-number-input.html +62 -0
  17. package/components/ui-number-input/ui-number-input.js +44 -0
  18. package/components/ui-popover/ui-popover.html +64 -1
  19. package/components/ui-popover/ui-popover.js +1 -1
  20. package/components/ui-slider/ui-slider.html +84 -0
  21. package/components/ui-slider/ui-slider.js +29 -0
  22. package/components/ui-tabs/ui-tabs.html +6 -1
  23. package/components/ui-text/ui-text.html +31 -6
  24. package/components/ui-tooltip/ui-tooltip.html +60 -0
  25. package/components/ui-tooltip/ui-tooltip.js +1 -1
  26. package/dist/index.js +46 -43
  27. package/docs/README.md +146 -0
  28. package/docs/card.html +77 -0
  29. package/docs/card.js +47 -0
  30. package/docs/catalog-view.js +18 -0
  31. package/docs/catalog-view.mjs +11 -0
  32. package/docs/catalog.json +23984 -0
  33. package/docs/catalog.mjs +131 -0
  34. package/docs/cli.mjs +26 -0
  35. package/docs/commands.mjs +94 -0
  36. package/docs/customizer-render.mjs +66 -0
  37. package/docs/customizer.js +181 -0
  38. package/docs/documentation.css +16 -0
  39. package/docs/examples/ui-action-bar/examples/01-form-footer.behavior.js +7 -0
  40. package/docs/examples/ui-button/examples/08-loading.behavior.js +21 -0
  41. package/docs/examples/ui-checkbox/examples/02-checked-indeterminate-and-disabled.behavior.js +30 -0
  42. package/docs/examples/ui-combobox/examples/06-selected-values.behavior.js +11 -0
  43. package/docs/examples/ui-search-shell/examples/02-label-dismissible-status-and-footer.behavior.js +34 -0
  44. package/docs/examples/ui-toast/examples/01-authored.behavior.js +15 -0
  45. package/docs/examples/ui-toast/examples/02-danger.behavior.js +2 -0
  46. package/docs/examples/ui-tree/examples/03-max-depth-2-and-hover-expand-delay-300.behavior.js +30 -0
  47. package/docs/examples/ui-tree/examples/06-move-without-dragging.behavior.js +2 -0
  48. package/docs/examples/ui-tree/examples/07-multiple-selection.behavior.js +16 -0
  49. package/docs/examples/ui-tree-item/examples/02-container.behavior.js +1 -0
  50. package/docs/index.d.ts +56 -0
  51. package/docs/index.mjs +4 -0
  52. package/docs/local-server.mjs +68 -0
  53. package/docs/preview.js +22 -0
  54. package/docs/reference.html +1 -0
  55. package/docs/reference.js +36 -0
  56. package/docs/render.mjs +93 -0
  57. package/docs/site.mjs +239 -0
  58. package/docs/store.mjs +92 -0
  59. package/docs/theme-session.mjs +51 -0
  60. package/docs/theme.mjs +219 -0
  61. package/docs/value-editors.mjs +33 -0
  62. package/docs/workspace.mjs +32 -0
  63. package/package.json +28 -4
  64. package/styles/ui-button.css +45 -45
  65. package/styles/ui-card.css +47 -0
  66. package/styles/ui-curve-editor.css +109 -0
  67. package/styles/ui-dialog.css +132 -0
  68. package/styles/ui-icon-button.css +63 -20
  69. package/styles/ui-menu.css +72 -0
  70. package/styles/ui-number-input.css +34 -0
  71. package/styles/ui-popover.css +74 -0
  72. package/styles/ui-slider.css +53 -0
  73. package/styles/ui-tabs.css +6 -1
  74. package/styles/ui-text.css +23 -1
  75. package/styles/ui-tooltip.css +72 -0
  76. package/theme-system.css +26 -10
  77. package/tokens.css +8 -2
  78. package/vanilla/UiButton.d.ts +1 -0
  79. package/vanilla/UiButton.js +195 -130
  80. package/vanilla/UiCard.d.ts +1 -1
  81. package/vanilla/UiCard.js +1 -1
  82. package/vanilla/UiCurveEditor.d.ts +28 -0
  83. package/vanilla/UiCurveEditor.js +134 -0
  84. package/vanilla/UiIconButton.d.ts +2 -1
  85. package/vanilla/UiIconButton.js +58 -31
  86. package/vanilla/UiNumberInput.d.ts +33 -0
  87. package/vanilla/UiNumberInput.js +79 -0
  88. package/vanilla/UiSlider.d.ts +33 -0
  89. package/vanilla/UiSlider.js +125 -0
  90. package/vanilla/UiText.d.ts +2 -2
  91. package/vanilla/UiText.js +45 -11
  92. package/vanilla/index.js +3 -0
  93. package/vue/UiButton.d.ts +6 -0
  94. package/vue/UiButton.vue +155 -56
  95. package/vue/UiCard.d.ts +2 -2
  96. package/vue/UiCard.vue +55 -5
  97. package/vue/UiCurveEditor.d.ts +53 -0
  98. package/vue/UiCurveEditor.js +2 -0
  99. package/vue/UiCurveEditor.vue +427 -0
  100. package/vue/UiDialog.vue +177 -1
  101. package/vue/UiIconButton.d.ts +8 -2
  102. package/vue/UiIconButton.vue +164 -34
  103. package/vue/UiMenu.vue +89 -0
  104. package/vue/UiNumberInput.d.ts +83 -0
  105. package/vue/UiNumberInput.js +2 -0
  106. package/vue/UiNumberInput.vue +370 -0
  107. package/vue/UiPopover.vue +92 -1
  108. package/vue/UiSlider.d.ts +83 -0
  109. package/vue/UiSlider.js +2 -0
  110. package/vue/UiSlider.vue +412 -0
  111. package/vue/UiTabs.vue +6 -1
  112. package/vue/UiText.d.ts +7 -5
  113. package/vue/UiText.vue +104 -22
  114. package/vue/UiTooltip.vue +89 -0
  115. package/vue/chunks/UiActionBar.js +1 -1
  116. package/vue/chunks/UiAffordanceScope.js +2 -2
  117. package/vue/chunks/UiAvatar.js +2 -2
  118. package/vue/chunks/UiAvatarGroup.js +2 -2
  119. package/vue/chunks/UiBadge.js +2 -2
  120. package/vue/chunks/UiBreadcrumbItem.js +2 -2
  121. package/vue/chunks/UiBreadcrumbs.js +2 -2
  122. package/vue/chunks/UiButton.js +94 -11
  123. package/vue/chunks/UiCallout.js +2 -2
  124. package/vue/chunks/UiCard.js +6 -5
  125. package/vue/chunks/UiCheckbox.js +2 -2
  126. package/vue/chunks/UiCluster.js +2 -2
  127. package/vue/chunks/UiColorPicker.js +2 -2
  128. package/vue/chunks/UiCombobox.js +2 -2
  129. package/vue/chunks/UiContainer.js +2 -2
  130. package/vue/chunks/UiContextMenu.js +2 -2
  131. package/vue/chunks/UiCover.js +2 -2
  132. package/vue/chunks/UiCurveEditor.js +413 -0
  133. package/vue/chunks/UiDescriptionItem.js +2 -2
  134. package/vue/chunks/UiDescriptionList.js +2 -2
  135. package/vue/chunks/UiDialog.js +4 -4
  136. package/vue/chunks/UiDisclosure.js +2 -2
  137. package/vue/chunks/UiEditable.js +2 -2
  138. package/vue/chunks/UiEditorBlockActions.js +2 -2
  139. package/vue/chunks/UiEditorChipEditor.js +2 -2
  140. package/vue/chunks/UiEditorCodeLanguage.js +2 -2
  141. package/vue/chunks/UiEditorFootnoteEditor.js +2 -2
  142. package/vue/chunks/UiEditorImageToolbar.js +3 -3
  143. package/vue/chunks/UiEditorInsertTableGrid.js +2 -2
  144. package/vue/chunks/UiEditorLinkCard.js +3 -3
  145. package/vue/chunks/UiEditorLinkForm.js +3 -3
  146. package/vue/chunks/UiEditorMentionMenu.js +2 -2
  147. package/vue/chunks/UiEditorMentionMenuItem.js +2 -2
  148. package/vue/chunks/UiEditorSlashMenu.js +2 -2
  149. package/vue/chunks/UiEditorSlashMenuGroup.js +2 -2
  150. package/vue/chunks/UiEditorSlashMenuItem.js +2 -2
  151. package/vue/chunks/UiEditorSurface.js +2 -2
  152. package/vue/chunks/UiEditorTableContextMenu.js +2 -2
  153. package/vue/chunks/UiEditorTableOverlay.js +2 -2
  154. package/vue/chunks/UiEditorTableToolbar.js +2 -2
  155. package/vue/chunks/UiEditorToolbar.js +2 -2
  156. package/vue/chunks/UiFloatingActionButton.js +2 -2
  157. package/vue/chunks/UiFormField.js +2 -2
  158. package/vue/chunks/UiGrid.js +2 -2
  159. package/vue/chunks/UiIcon.js +2 -2
  160. package/vue/chunks/UiIconButton.js +77 -11
  161. package/vue/chunks/UiImage.js +2 -2
  162. package/vue/chunks/UiInput.js +2 -2
  163. package/vue/chunks/UiInputGroup.js +2 -2
  164. package/vue/chunks/UiList.js +2 -2
  165. package/vue/chunks/UiListItem.js +2 -2
  166. package/vue/chunks/UiListbox.js +2 -2
  167. package/vue/chunks/UiMenu.js +3 -3
  168. package/vue/chunks/UiMenuGroup.js +2 -2
  169. package/vue/chunks/UiMenuItem.js +2 -2
  170. package/vue/chunks/UiMeter.js +2 -2
  171. package/vue/chunks/UiNavItem.js +2 -2
  172. package/vue/chunks/UiNumberInput.js +390 -0
  173. package/vue/chunks/UiPageHeader.js +2 -2
  174. package/vue/chunks/UiPopover.js +4 -4
  175. package/vue/chunks/UiRadio.js +2 -2
  176. package/vue/chunks/UiRadioGroup.js +2 -2
  177. package/vue/chunks/UiReel.js +2 -2
  178. package/vue/chunks/UiScrollArea.js +2 -2
  179. package/vue/chunks/UiSearchResultRow.js +2 -2
  180. package/vue/chunks/UiSearchShell.js +2 -2
  181. package/vue/chunks/UiSection.js +2 -2
  182. package/vue/chunks/UiSelect.js +2 -2
  183. package/vue/chunks/UiSeparator.js +2 -2
  184. package/vue/chunks/UiSidebar.js +2 -2
  185. package/vue/chunks/UiSlider.js +396 -0
  186. package/vue/chunks/UiSpinner.js +2 -2
  187. package/vue/chunks/UiStack.js +2 -2
  188. package/vue/chunks/UiStatusMessage.js +2 -2
  189. package/vue/chunks/UiSwatch.js +2 -2
  190. package/vue/chunks/UiSwitch.js +2 -2
  191. package/vue/chunks/UiSwitcher.js +2 -2
  192. package/vue/chunks/UiTable.js +2 -2
  193. package/vue/chunks/UiTabs.js +3 -3
  194. package/vue/chunks/UiText.js +53 -12
  195. package/vue/chunks/UiTextarea.js +2 -2
  196. package/vue/chunks/UiToast.js +2 -2
  197. package/vue/chunks/UiToastRegion.js +2 -2
  198. package/vue/chunks/UiTooltip.js +3 -3
  199. package/vue/chunks/UiTopBar.js +1 -1
  200. package/vue/chunks/UiTree.js +2 -2
  201. package/vue/chunks/UiTreeItem.js +2 -2
  202. package/vue/chunks/_plugin-vue_export-helper.js +68 -2
  203. package/vue/chunks/props.js +24 -1
  204. package/vue/components.css +2215 -1203
  205. package/vue/editor/index.js +623 -211
  206. package/vue/index.d.ts +3 -0
  207. package/vue/index.js +5 -2
@@ -0,0 +1,33 @@
1
+ /** Translate canonical recipe values for the reusable value editors; never duplicate design bounds here. */
2
+ const lengthUnits = { px: 1, rem: 16, em: 16, cm: 96 / 2.54, mm: 96 / 25.4, Q: 96 / 101.6, in: 96, pt: 96 / 72, pc: 16 };
3
+ const timeUnits = { ms: 1, s: 1000 };
4
+ const rounded = value => String(Number(Number(value).toFixed(6)));
5
+
6
+ export function valueEditor(field, value, { remPixels = 16 } = {}) {
7
+ if (field.type === 'easing') return { kind: /^step-/.test(value) ? 'text' : 'curve', value };
8
+ if (['colour', 'font'].includes(field.type)) return { kind: 'text', value };
9
+ if (field.type.includes('length') || field.type === 'time') {
10
+ const unit = value.match(/[a-z]+$/i)?.[0] ?? 'px';
11
+ const units = field.type === 'time' ? timeUnits : { ...lengthUnits, rem: remPixels, em: remPixels };
12
+ const factor = units[unit];
13
+ return { kind: 'number', value: value === '0' ? '0px' : value, unit,
14
+ min: factor ? field.min / factor : .001, max: factor ? field.max / factor : 1000000,
15
+ step: (field.type === 'time' ? 10 : field.id === 'line-weight' ? .25 : 1) / (factor ?? 1),
16
+ units: field.type === 'time' ? ['ms', 's'] : ['px', 'rem'], convertible: Boolean(factor) };
17
+ }
18
+ const scale = field.type === 'fraction' || field.id === 'density' ? 100 : 1;
19
+ return { kind: field.type === 'weight' ? 'number' : 'slider', value: field.type === 'weight' ? value : Number(value) * scale,
20
+ min: field.min * scale, max: field.max * scale, step: field.type === 'weight' ? 50 : scale === 100 ? 1 : .01,
21
+ unit: scale === 100 ? '%' : '', scale };
22
+ }
23
+
24
+ export function editorValue(field, value) {
25
+ return field.type === 'fraction' || field.id === 'density' ? rounded(Number(value) / 100) : String(value);
26
+ }
27
+
28
+ export function convertValueUnit(value, nextUnit, { remPixels = 16 } = {}) {
29
+ const unit = value.match(/[a-z]+$/i)?.[0] ?? 'px';
30
+ const units = Object.hasOwn(timeUnits, unit) ? timeUnits : { ...lengthUnits, rem: remPixels, em: remPixels };
31
+ if (!Object.hasOwn(units, unit) || !Object.hasOwn(units, nextUnit)) throw new Error('Choose compatible units.');
32
+ return rounded(parseFloat(value) * units[unit] / units[nextUnit]) + nextUnit;
33
+ }
@@ -0,0 +1,32 @@
1
+ import { lstat, mkdir, open, realpath, unlink } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ export async function rejectSymlink(file) {
5
+ try { if ((await lstat(file)).isSymbolicLink()) throw new Error(`Looma's generated path cannot be a symlink: ${file}.`); }
6
+ catch (error) { if (error.code !== 'ENOENT') throw error; }
7
+ }
8
+
9
+ /** Generated sources and tooling wrappers stay in owned cache directories. */
10
+ export async function cacheDirectory(root, name = '') {
11
+ root = await realpath(path.resolve(root));
12
+ if (name && !/^[a-z][a-z-]*$/.test(name)) throw new Error('Invalid Looma cache directory.');
13
+ const cache = path.join(root, '.looma');
14
+ await rejectSymlink(cache); await mkdir(cache, { recursive: true });
15
+ const directory = path.join(cache, name);
16
+ await rejectSymlink(directory); await mkdir(directory, { recursive: true });
17
+ return directory;
18
+ }
19
+
20
+ /** One complete preparation/build owns the generated tree, including across CLI processes. */
21
+ export async function withBuildLock(root, action) {
22
+ const lockFile = path.join(await cacheDirectory(root), 'docs-build.lock');
23
+ await rejectSymlink(lockFile);
24
+ let lock;
25
+ try { lock = await open(lockFile, 'wx', 0o600); }
26
+ catch (error) {
27
+ if (error.code === 'EEXIST') throw new Error('Another documentation build holds .looma/docs-build.lock. Retry after it finishes. If the old process stopped, close it before removing that lock.');
28
+ throw error;
29
+ }
30
+ try { await lock.writeFile(JSON.stringify({ pid: process.pid })); return await action(); }
31
+ finally { try { await lock.close(); } finally { await unlink(lockFile); } }
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threadlabs/looma",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "type": "module",
5
5
  "description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
6
6
  "license": "MIT",
@@ -83,7 +83,14 @@
83
83
  "import": "./editor/expressmark.js",
84
84
  "default": "./editor/expressmark.js"
85
85
  },
86
- "./theme-system.css": "./theme-system.css"
86
+ "./theme-system.css": "./theme-system.css",
87
+ "./docs/catalog.json": "./docs/catalog.json",
88
+ "./docs/*": "./docs/*",
89
+ "./docs": {
90
+ "types": "./docs/index.d.ts",
91
+ "import": "./docs/index.mjs",
92
+ "default": "./docs/index.mjs"
93
+ }
87
94
  },
88
95
  "files": [
89
96
  "dist/",
@@ -96,7 +103,9 @@
96
103
  "*.css",
97
104
  "README.md",
98
105
  "LICENSE",
99
- "EXPRESSIVE-MARKDOWN.md"
106
+ "EXPRESSIVE-MARKDOWN.md",
107
+ "docs/",
108
+ "bin/"
100
109
  ],
101
110
  "sideEffects": [
102
111
  "./dist/index.js",
@@ -117,7 +126,10 @@
117
126
  "@tiptap/core": ">=2 <3",
118
127
  "@tiptap/pm": ">=2 <3",
119
128
  "@tiptap/vue-3": ">=2 <3",
120
- "vue": "^3.5.43"
129
+ "vue": "^3.5.43",
130
+ "markupress": "^0.8.3",
131
+ "@nextwebwg/htmlkit": "1.0.0-alpha.69",
132
+ "vite": "^8.3.4"
121
133
  },
122
134
  "peerDependenciesMeta": {
123
135
  "@tiptap/core": {
@@ -131,6 +143,15 @@
131
143
  },
132
144
  "vue": {
133
145
  "optional": true
146
+ },
147
+ "markupress": {
148
+ "optional": true
149
+ },
150
+ "@nextwebwg/htmlkit": {
151
+ "optional": true
152
+ },
153
+ "vite": {
154
+ "optional": true
134
155
  }
135
156
  },
136
157
  "devDependencies": {
@@ -185,6 +206,9 @@
185
206
  "engines": {
186
207
  "node": ">=22.22.2 <23 || >=24.15 <25"
187
208
  },
209
+ "bin": {
210
+ "looma-docs": "./bin/looma-docs.mjs"
211
+ },
188
212
  "scripts": {
189
213
  "build": "node build.mjs",
190
214
  "lint": "vue-tsc -p tsconfig.json --noEmit",
@@ -167,7 +167,7 @@
167
167
 
168
168
  /* A count is an adornment, not another line of label text. Keep the standard control height
169
169
  with enough inset for a 24px mark; large content can still grow the button naturally. */
170
- :scope:where([data-component~="ui-button"]):not([variant="link"]):has(.badge:not(:empty)) {
170
+ :scope:where([data-component~="ui-button"]):not([data-appearance="link"]):has(.badge:not(:empty)) {
171
171
  padding-block: var(--ui-space-0-5) !important;
172
172
  }
173
173
 
@@ -212,7 +212,7 @@
212
212
 
213
213
  /* Card actions inherit Button's palette and behavior; only their shared layout changes.
214
214
  The icon may contain any decorative composition. Empty icons remove their column. */
215
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=card"] {
215
+ :scope:where([data-component~="ui-button"])[data-appearance="card"] {
216
216
  display: grid !important;
217
217
  grid-template-columns: auto minmax(0, 1fr) auto;
218
218
  align-content: center !important;
@@ -225,10 +225,10 @@
225
225
  box-shadow: none;
226
226
  background-image: none;
227
227
  }
228
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=card"][data-ui-button-state~="size=lg"] {
228
+ :scope:where([data-component~="ui-button"])[data-appearance="card"][size="lg"] {
229
229
  min-block-size: max(var(--ui-control-min-block-size), var(--ui-control-size-lg));
230
230
  }
231
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=card"]:has(.card-icon:empty) {
231
+ :scope:where([data-component~="ui-button"])[data-appearance="card"]:has(.card-icon:empty) {
232
232
  grid-template-columns: minmax(0, 1fr) auto;
233
233
  }
234
234
  .card-icon {
@@ -255,11 +255,11 @@
255
255
  }
256
256
 
257
257
  /* A neutral card encloses content quietly; ordinary outlines keep their action-tone edge. */
258
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=card"][data-ui-button-state~="tone=neutral"] {
258
+ :scope:where([data-component~="ui-button"])[data-appearance="card"][data-tone="neutral"] {
259
259
  border-color: var(--ui-button-border, var(--ui-border));
260
260
  }
261
261
 
262
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=card"][data-ui-button-state~="tone=neutral"]:hover:not(:disabled, [aria-disabled="true"]) {
262
+ :scope:where([data-component~="ui-button"])[data-appearance="card"][data-tone="neutral"]:hover:not(:disabled, [aria-disabled="true"]) {
263
263
  border-color: var(--ui-button-hover-border, var(--ui-border-strong));
264
264
  }
265
265
 
@@ -271,19 +271,19 @@
271
271
  box-shadow: var(--ui-pressed);
272
272
  }
273
273
 
274
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=solid"], :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=danger"] {
274
+ :scope:where([data-component~="ui-button"])[data-appearance="solid"], :scope:where([data-component~="ui-button"])[data-appearance="danger"] {
275
275
  border-color: var(--_tone);
276
276
  background-color: var(--_tone);
277
277
  color: var(--_on-tone);
278
278
  box-shadow: var(--ui-button-solid-shadow, var(--ui-action-shadow, var(--_ui-action-shadow)));
279
279
  }
280
280
 
281
- :is(:scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=solid"], :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=danger"]):hover:not(:disabled, [aria-disabled="true"]) {
281
+ :is(:scope:where([data-component~="ui-button"])[data-appearance="solid"], :scope:where([data-component~="ui-button"])[data-appearance="danger"]):hover:not(:disabled, [aria-disabled="true"]) {
282
282
  background-color: var(--_tone-hover);
283
283
  border-color: var(--_tone-hover);
284
284
  }
285
285
 
286
- :is(:scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=solid"], :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=danger"]):active:not(:disabled, [aria-disabled="true"]) {
286
+ :is(:scope:where([data-component~="ui-button"])[data-appearance="solid"], :scope:where([data-component~="ui-button"])[data-appearance="danger"]):active:not(:disabled, [aria-disabled="true"]) {
287
287
  background-color: var(--_tone-active);
288
288
  border-color: var(--_tone-active);
289
289
  background-image: none;
@@ -292,7 +292,7 @@
292
292
 
293
293
  /* Tone is the colour, variant is the volume. Every pairing works: a danger outline is a
294
294
  destructive secondary action, not a special case. */
295
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="tone=neutral"] {
295
+ :scope:where([data-component~="ui-button"])[data-tone="neutral"] {
296
296
  --_hue: var(--ui-text);
297
297
  --_hue-hover: var(--ui-text);
298
298
  --_hue-active: var(--ui-text);
@@ -300,40 +300,40 @@
300
300
  --_on-hue: var(--ui-surface);
301
301
  }
302
302
 
303
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="tone=danger"], :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=danger"] {
303
+ :scope:where([data-component~="ui-button"])[data-tone="danger"], :scope:where([data-component~="ui-button"])[data-appearance="danger"] {
304
304
  --_hue: var(--ui-danger);
305
305
  --_hue-hover: var(--ui-danger-hover);
306
- --_hue-active: var(--ui-danger-hover);
307
- --_hue-text: var(--ui-danger-hover);
306
+ --_hue-active: var(--ui-danger-active, var(--ui-danger-hover));
307
+ --_hue-text: var(--ui-danger-subtle-text);
308
308
  --_on-hue: var(--ui-on-danger);
309
309
  }
310
310
 
311
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="tone=success"] {
311
+ :scope:where([data-component~="ui-button"])[data-tone="success"] {
312
312
  --_hue: var(--ui-success);
313
- --_hue-hover: color-mix(in srgb, var(--ui-success) 85%, #000);
314
- --_hue-active: color-mix(in srgb, var(--ui-success) 75%, #000);
315
- --_hue-text: color-mix(in srgb, var(--ui-success) 85%, #000);
316
- --_on-hue: var(--ui-surface);
313
+ --_hue-hover: var(--ui-success-hover);
314
+ --_hue-active: var(--ui-success-active);
315
+ --_hue-text: var(--ui-success-subtle-text);
316
+ --_on-hue: var(--ui-success-on-strong);
317
317
  }
318
318
 
319
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="tone=warning"] {
319
+ :scope:where([data-component~="ui-button"])[data-tone="warning"] {
320
320
  --_hue: var(--ui-warning);
321
- --_hue-hover: color-mix(in srgb, var(--ui-warning) 85%, #000);
322
- --_hue-active: color-mix(in srgb, var(--ui-warning) 75%, #000);
323
- --_hue-text: color-mix(in srgb, var(--ui-warning) 85%, #000);
324
- --_on-hue: var(--ui-surface);
321
+ --_hue-hover: var(--ui-warning-hover);
322
+ --_hue-active: var(--ui-warning-active);
323
+ --_hue-text: var(--ui-warning-subtle-text);
324
+ --_on-hue: var(--ui-warning-on-strong);
325
325
  }
326
326
 
327
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="tone=info"] {
327
+ :scope:where([data-component~="ui-button"])[data-tone="info"] {
328
328
  --_hue: var(--ui-info);
329
- --_hue-hover: color-mix(in srgb, var(--ui-info) 85%, #000);
330
- --_hue-active: color-mix(in srgb, var(--ui-info) 75%, #000);
331
- --_hue-text: color-mix(in srgb, var(--ui-info) 85%, #000);
332
- --_on-hue: var(--ui-surface);
329
+ --_hue-hover: var(--ui-info-hover);
330
+ --_hue-active: var(--ui-info-active);
331
+ --_hue-text: var(--ui-info-subtle-text);
332
+ --_on-hue: var(--ui-info-on-strong);
333
333
  }
334
334
 
335
335
  /* A value picker is a field, with Select's quiet edge and resting lift. */
336
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=field"] {
336
+ :scope:where([data-component~="ui-button"])[data-appearance="field"] {
337
337
  --_ui-field-shadow: var(--ui-shadow-sm);
338
338
  border-color: var(--ui-button-border, var(--ui-control-border, var(--ui-border-strong)));
339
339
  border-radius: var(--ui-button-radius, var(--ui-field-radius, var(--ui-radius-md)));
@@ -343,23 +343,23 @@
343
343
  box-shadow: var(--ui-button-shadow, var(--_ui-field-shadow));
344
344
  }
345
345
 
346
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=field"]:hover:not(:disabled, [aria-disabled="true"]) {
346
+ :scope:where([data-component~="ui-button"])[data-appearance="field"]:hover:not(:disabled, [aria-disabled="true"]) {
347
347
  border-color: var(--ui-button-hover-border, var(--ui-control-border-hover, var(--ui-text-secondary)));
348
348
  background-color: var(--ui-button-hover-surface, var(--ui-control-surface, var(--ui-surface-elevated)));
349
349
  }
350
350
 
351
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=field"]:is(:active, [aria-expanded="true"]):not(:disabled, [aria-disabled="true"]) {
351
+ :scope:where([data-component~="ui-button"])[data-appearance="field"]:is(:active, [aria-expanded="true"]):not(:disabled, [aria-disabled="true"]) {
352
352
  --_ui-field-shadow: var(--ui-pressed);
353
353
  }
354
354
 
355
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=field"]:focus-visible {
355
+ :scope:where([data-component~="ui-button"])[data-appearance="field"]:focus-visible {
356
356
  border-color: var(--ui-control-focus, var(--ui-accent-solid));
357
357
  box-shadow:
358
358
  var(--ui-button-shadow, var(--_ui-field-shadow)),
359
359
  var(--ui-control-focus-halo-shadow);
360
360
  }
361
361
 
362
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=ghost"] {
362
+ :scope:where([data-component~="ui-button"])[data-appearance="ghost"] {
363
363
  border-color: transparent;
364
364
  background: transparent;
365
365
  box-shadow: none;
@@ -367,14 +367,14 @@
367
367
  }
368
368
 
369
369
  /* Tints derive from the text colour, not a surface token, so hover and press show on any background. */
370
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=ghost"]:hover:not(:disabled, [aria-disabled="true"]) {
370
+ :scope:where([data-component~="ui-button"])[data-appearance="ghost"]:hover:not(:disabled, [aria-disabled="true"]) {
371
371
  background-color: var(--ui-button-ghost-hover-surface, oklch(from var(--_tone) l c h / 10%));
372
372
  border-color: transparent;
373
373
  }
374
374
 
375
375
  /* Pressed, or holding its menu open, a ghost button sinks slightly: a light surface with a faint
376
376
  edge and a soft inner shadow. Lighter than hover, so an open menu's trigger never reads as heavy. */
377
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=ghost"]:is(:active, [aria-expanded="true"]):not(:disabled, [aria-disabled="true"]) {
377
+ :scope:where([data-component~="ui-button"])[data-appearance="ghost"]:is(:active, [aria-expanded="true"]):not(:disabled, [aria-disabled="true"]) {
378
378
  background-color: var(--ui-button-ghost-active-surface, color-mix(in oklch, var(--ui-surface), var(--_tone) 3%));
379
379
  border-color: transparent;
380
380
  box-shadow: inset 0 0 0 1px oklch(from var(--_tone) l c h / 10%), inset 0 1px 2px oklch(from var(--_tone) l c h / 8%);
@@ -388,13 +388,13 @@
388
388
  var(--ui-focus-halo);
389
389
  }
390
390
 
391
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=solid"]:focus-visible {
391
+ :scope:where([data-component~="ui-button"])[data-appearance="solid"]:focus-visible {
392
392
  box-shadow:
393
393
  var(--ui-button-solid-shadow, var(--ui-action-shadow, var(--_ui-action-shadow))),
394
394
  var(--ui-focus-halo);
395
395
  }
396
396
 
397
- :is(:scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=ghost"], :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=link"], :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=card"]):focus-visible {
397
+ :is(:scope:where([data-component~="ui-button"])[data-appearance="ghost"], :scope:where([data-component~="ui-button"])[data-appearance="link"], :scope:where([data-component~="ui-button"])[data-appearance="card"]):focus-visible {
398
398
  box-shadow: var(--ui-focus-halo);
399
399
  }
400
400
 
@@ -416,7 +416,7 @@
416
416
  }
417
417
 
418
418
  /* Ghost keeps no border, but gains the same neutral surface as other disabled buttons. */
419
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=ghost"]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
419
+ :scope:where([data-component~="ui-button"])[data-appearance="ghost"]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
420
420
  background-color: var(--ui-disabled-surface);
421
421
  border-color: transparent;
422
422
  }
@@ -433,12 +433,12 @@
433
433
  a link-style one keeps its text size, so a standalone one ("See all" under a list) needs the
434
434
  area. xs sits inside a line of text the same way, so it keeps its size and gains the area too.
435
435
  Only these need it: a hit area on every button would reach over its neighbours. */
436
- html[data-ui-input-modality="touch"] :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=link"],
436
+ html[data-ui-input-modality="touch"] :scope:where([data-component~="ui-button"])[data-appearance="link"],
437
437
  html[data-ui-input-modality="touch"] :scope:where([data-component~="ui-button"])[data-ui-button-state~="size=xs"] {
438
438
  position: relative;
439
439
  }
440
440
 
441
- html[data-ui-input-modality="touch"] :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=link"]::after,
441
+ html[data-ui-input-modality="touch"] :scope:where([data-component~="ui-button"])[data-appearance="link"]::after,
442
442
  html[data-ui-input-modality="touch"] :scope:where([data-component~="ui-button"])[data-ui-button-state~="size=xs"]::after {
443
443
  position: absolute;
444
444
  inset: 50% auto auto 50%;
@@ -450,7 +450,7 @@
450
450
 
451
451
  /* Link: an inline text action. No box, padding, or minimum height (inline targets are exempt under
452
452
  WCAG 2.5.8); the inherited font; an underline that strengthens on hover; a visible focus ring. */
453
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=link"] {
453
+ :scope:where([data-component~="ui-button"])[data-appearance="link"] {
454
454
  display: inline !important;
455
455
  min-block-size: 0;
456
456
  padding: 0 !important;
@@ -467,24 +467,24 @@
467
467
  transition: color var(--ui-motion-fast) var(--ui-motion-ease), text-decoration-color var(--ui-motion-fast) var(--ui-motion-ease);
468
468
  }
469
469
 
470
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=link"]:hover:not(:disabled, [aria-disabled="true"]) {
470
+ :scope:where([data-component~="ui-button"])[data-appearance="link"]:hover:not(:disabled, [aria-disabled="true"]) {
471
471
  color: var(--ui-button-link-hover-text, var(--ui-text-primary));
472
472
  text-decoration-color: currentColor;
473
473
  background: none;
474
474
  transform: none;
475
475
  }
476
476
 
477
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=link"]:active:not(:disabled, [aria-disabled="true"]) {
477
+ :scope:where([data-component~="ui-button"])[data-appearance="link"]:active:not(:disabled, [aria-disabled="true"]) {
478
478
  background: none;
479
479
  transform: none;
480
480
  }
481
481
 
482
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=link"]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
482
+ :scope:where([data-component~="ui-button"])[data-appearance="link"]:is(:disabled, [aria-disabled="true"]:not([aria-busy="true"])) {
483
483
  background: none;
484
484
  border-color: transparent;
485
485
  }
486
486
 
487
- :scope:where([data-component~="ui-button"])[data-ui-button-state~="variant=link"]:focus-visible {
487
+ :scope:where([data-component~="ui-button"])[data-appearance="link"]:focus-visible {
488
488
  box-shadow: none;
489
489
  /* Fallback keeps the ring if the token is missing: an unresolved var() would void the outline. */
490
490
  outline: var(--ui-focus-width) solid var(--ui-focus-ring);
@@ -49,6 +49,47 @@
49
49
  box-shadow: var(--ui-shadow-lg);
50
50
  }
51
51
 
52
+ :scope:where([data-component~="ui-card"])[data-ui-card-state~="variant=glass"] {
53
+ --_ui-panel-glass: clamp(0, var(--ui-glassiness, 0), 1);
54
+ --_ui-panel-separation: clamp(0, var(--ui-surface-separation, 0), 1);
55
+ --_ui-panel-rim: calc((var(--_ui-panel-separation) + var(--_ui-panel-glass) * 0.25) * 0.65 * 8px);
56
+ border-width: calc(var(--ui-border-width) + var(--_ui-panel-rim));
57
+ border-color: transparent;
58
+ background:
59
+ linear-gradient(var(--_ui-card-surface), var(--_ui-card-surface)) padding-box,
60
+ linear-gradient(var(--ui-material-glass, var(--ui-surface-raised)), var(--ui-material-glass, var(--ui-surface-raised))) border-box,
61
+ var(--_ui-card-surface) padding-box;
62
+ box-shadow:
63
+ var(--ui-shadow-lg),
64
+ inset 0 0 0 var(--ui-border-width) var(--ui-material-edge, var(--ui-border-strong));
65
+ backdrop-filter: blur(calc(var(--_ui-panel-glass) * 0.65 * 18px)) saturate(calc(1 + var(--_ui-panel-glass) * 0.65 * 0.18));
66
+ -webkit-backdrop-filter: blur(calc(var(--_ui-panel-glass) * 0.65 * 18px)) saturate(calc(1 + var(--_ui-panel-glass) * 0.65 * 0.18));
67
+ }
68
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
69
+ :scope:where([data-component~="ui-card"])[data-ui-card-state~="variant=glass"] {
70
+ background: var(--_ui-card-surface);
71
+ }
72
+ }
73
+ @media (prefers-reduced-transparency: reduce) {
74
+ :scope:where([data-component~="ui-card"])[data-ui-card-state~="variant=glass"] {
75
+ background: var(--_ui-card-surface);
76
+ border-width: var(--ui-border-width);
77
+ border-color: var(--ui-border-strong);
78
+ backdrop-filter: none;
79
+ -webkit-backdrop-filter: none;
80
+ }
81
+ }
82
+ @media (forced-colors: active) {
83
+ :scope:where([data-component~="ui-card"])[data-ui-card-state~="variant=glass"] {
84
+ background: Canvas;
85
+ color: CanvasText;
86
+ border: var(--ui-border-width) solid CanvasText;
87
+ box-shadow: none;
88
+ backdrop-filter: none;
89
+ -webkit-backdrop-filter: none;
90
+ }
91
+ }
92
+
52
93
  :scope:where([data-component~="ui-card"])[data-ui-card-state~="tone=accent"] {
53
94
  border-color: color-mix(in srgb, var(--ui-accent) 25%, var(--ui-border-default));
54
95
  --_ui-card-surface: color-mix(in srgb, var(--ui-accent) 6%, var(--ui-surface-default));
@@ -116,6 +157,12 @@
116
157
  @scope ([data-component~="ui-card"]) to ([data-component]) {
117
158
  /* A surface that groups content. Its children stack with even space between them, so a card
118
159
  needs no layout of its own. */
160
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
161
+ }
162
+ @media (prefers-reduced-transparency: reduce) {
163
+ }
164
+ @media (forced-colors: active) {
165
+ }
119
166
 
120
167
  /* A colored panel shades rows in its own hue, without changing button intent. */
121
168
 
@@ -0,0 +1,109 @@
1
+ /* Generated by HTML Next 1.0.0-alpha.69. Do not edit. */
2
+ @supports (-moz-appearance: none) {
3
+ :where([data-component], [data-slotted]) { --html-next-scope-owner: boundary; }
4
+ :where([data-slotted]:not([data-component])) { --html-next-scope-owner: projected !important; }
5
+ }
6
+ @supports (-moz-appearance: none) {
7
+ [data-component~="ui-curve-editor"] { --html-next-scope-owner: "ui-curve-editor" !important; }
8
+ }
9
+ @scope ([data-component~="ui-curve-editor"]) to ([data-component], [data-slotted]) {
10
+ :scope:where([data-component~="ui-curve-editor"]) {
11
+ display: grid !important;
12
+ gap: var(--ui-space-3) !important;
13
+ margin: 0 !important;
14
+ padding: 0 !important;
15
+ min-inline-size: 0;
16
+ }
17
+ .plot-frame {
18
+ box-sizing: border-box !important;
19
+ display: block !important;
20
+ block-size: 12rem;
21
+ padding: max(var(--ui-space-4), 0.75rem, calc(var(--ui-control-min-block-size) / 2)) !important;
22
+ margin: 0 !important;
23
+ border: var(--ui-border-width) solid var(--ui-control-border);
24
+ border-radius: var(--ui-field-radius, var(--ui-radius-md));
25
+ background: var(--ui-control-surface, var(--ui-surface));
26
+ }
27
+ .plot {
28
+ position: relative;
29
+ display: block !important;
30
+ inline-size: 100%;
31
+ block-size: 100%;
32
+ }
33
+ svg {
34
+ inline-size: 100%;
35
+ block-size: 100%;
36
+ overflow: visible;
37
+ }
38
+ path {
39
+ fill: none;
40
+ stroke: var(--ui-accent-solid);
41
+ stroke-width: 2;
42
+ vector-effect: non-scaling-stroke;
43
+ }
44
+ .guide {
45
+ stroke: var(--ui-control-border);
46
+ stroke-width: 1;
47
+ }
48
+ .handle {
49
+ position: absolute;
50
+ left: 25%;
51
+ top: 63.333%;
52
+ translate: -50% -50%;
53
+ inline-size: max(1.5rem, var(--ui-control-min-block-size));
54
+ block-size: max(1.5rem, var(--ui-control-min-block-size));
55
+ border: 0;
56
+ border-radius: 50%;
57
+ background: transparent;
58
+ color: var(--ui-text-primary);
59
+ touch-action: none;
60
+ cursor: grab;
61
+ padding: 0 !important;
62
+ margin: 0 !important;
63
+ }
64
+ .second {
65
+ top: 33.333%;
66
+ }
67
+ .handle:where(:not([data-component], [data-slotted]))::after {
68
+ content: "";
69
+ position: absolute;
70
+ inset: 25%;
71
+ border: var(--ui-border-width) solid var(--ui-accent-solid);
72
+ background: var(--ui-surface-raised);
73
+ border-radius: 50%;
74
+ box-shadow: var(--ui-shadow-xs);
75
+ }
76
+ .handle:where(:not([data-component], [data-slotted]))::before {
77
+ content: "";
78
+ position: absolute;
79
+ inset: 40%;
80
+ background: var(--ui-accent-solid);
81
+ border-radius: 50%;
82
+ z-index: 1;
83
+ }
84
+ .handle:focus-visible {
85
+ outline: var(--ui-focus-width) solid var(--ui-control-focus, var(--ui-accent-solid));
86
+ outline-offset: var(--ui-space-1);
87
+ }
88
+ .handle:disabled {
89
+ cursor: not-allowed;
90
+ }
91
+ .coordinates {
92
+ display: grid !important;
93
+ grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
94
+ gap: var(--ui-space-2) !important;
95
+ }
96
+ .coordinate {
97
+ display: grid !important;
98
+ gap: var(--ui-space-1) !important;
99
+ font-size: var(--ui-font-size-sm);
100
+ }
101
+ output {
102
+ overflow-wrap: anywhere;
103
+ font-family: var(--ui-font-mono, monospace);
104
+ font-size: var(--ui-font-size-sm);
105
+ }
106
+ :scope:where([data-component~="ui-curve-editor"])[hidden] {
107
+ display: none !important;
108
+ }
109
+ }