@exsto/ds 0.0.1-rc.23 → 0.0.1-rc.24

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 (51) hide show
  1. package/README.md +44 -0
  2. package/dist/color-field.js +3 -0
  3. package/dist/date-time.js +2 -0
  4. package/dist/ds/src/color-field.d.ts +9 -0
  5. package/dist/ds/src/date-time.d.ts +8 -0
  6. package/dist/source-hash.txt +1 -1
  7. package/dist/styles.css +216 -7
  8. package/dist/ui/src/components/edyn/animated-number/animated-number.js +5 -4
  9. package/dist/ui/src/components/edyn/animated-number/animated-number.js.map +1 -1
  10. package/dist/ui/src/components/edyn/color-field/color-field.d.ts +47 -0
  11. package/dist/ui/src/components/edyn/color-field/color-field.js +200 -0
  12. package/dist/ui/src/components/edyn/color-field/color-field.js.map +1 -0
  13. package/dist/ui/src/components/edyn/color-field/color-field.state.test.d.ts +6 -0
  14. package/dist/ui/src/components/edyn/color-field/color-value.d.ts +14 -0
  15. package/dist/ui/src/components/edyn/color-field/color-value.js +103 -0
  16. package/dist/ui/src/components/edyn/color-field/color-value.js.map +1 -0
  17. package/dist/ui/src/components/edyn/color-field/color-value.test.d.ts +1 -0
  18. package/dist/ui/src/components/edyn/color-field/index.d.ts +2 -0
  19. package/dist/ui/src/components/edyn/date-picker/date-picker.js +1 -0
  20. package/dist/ui/src/components/edyn/date-picker/date-picker.js.map +1 -1
  21. package/dist/ui/src/components/edyn/date-range-picker/date-range-picker.js +1 -0
  22. package/dist/ui/src/components/edyn/date-range-picker/date-range-picker.js.map +1 -1
  23. package/dist/ui/src/components/edyn/date-time/date-time-value.d.ts +23 -0
  24. package/dist/ui/src/components/edyn/date-time/date-time-value.js +26 -0
  25. package/dist/ui/src/components/edyn/date-time/date-time-value.js.map +1 -0
  26. package/dist/ui/src/components/edyn/date-time/date-time-value.test.d.ts +1 -0
  27. package/dist/ui/src/components/edyn/date-time/date-time.d.ts +73 -0
  28. package/dist/ui/src/components/edyn/date-time/date-time.js +119 -0
  29. package/dist/ui/src/components/edyn/date-time/date-time.js.map +1 -0
  30. package/dist/ui/src/components/edyn/date-time/date-time.state.test.d.ts +6 -0
  31. package/dist/ui/src/components/edyn/date-time/index.d.ts +1 -0
  32. package/dist/ui/src/components/edyn/multi-select/multi-select.js +2 -0
  33. package/dist/ui/src/components/edyn/multi-select/multi-select.js.map +1 -1
  34. package/dist/ui/src/components/edyn/rich-text-editor/toolbar.js +1 -0
  35. package/dist/ui/src/components/edyn/rich-text-editor/toolbar.js.map +1 -1
  36. package/dist/ui/src/components/edyn/time-field/time-field.js +7 -3
  37. package/dist/ui/src/components/edyn/time-field/time-field.js.map +1 -1
  38. package/dist/ui/src/components/ui/checkbox/checkbox.d.ts +1 -0
  39. package/dist/ui/src/components/ui/checkbox/checkbox.js +3 -2
  40. package/dist/ui/src/components/ui/checkbox/checkbox.js.map +1 -1
  41. package/dist/ui/src/components/ui/command/command.d.ts +1 -16
  42. package/dist/ui/src/components/ui/command/command.js +5 -23
  43. package/dist/ui/src/components/ui/command/command.js.map +1 -1
  44. package/dist/ui/src/components/ui/indicator/indicator.d.ts +1 -1
  45. package/dist/ui/src/components/ui/input-group/input-group.d.ts +1 -1
  46. package/dist/ui/src/components/ui/scroll-area/popup-list-scroll.d.ts +3 -1
  47. package/dist/ui/src/components/ui/scroll-area/popup-list-scroll.js +2 -1
  48. package/dist/ui/src/components/ui/scroll-area/popup-list-scroll.js.map +1 -1
  49. package/dist/ui/src/components/ui/sidebar/sidebar.js +1 -1
  50. package/dist/ui/src/components/ui/sidebar/sidebar.js.map +1 -1
  51. package/package.json +9 -1
package/README.md CHANGED
@@ -164,6 +164,36 @@ import { TimeField } from '@exsto/ds/time-field';
164
164
  <TimeField aria-label="Start time" value={time} onValueChange={setTime} />;
165
165
  ```
166
166
 
167
+ ## Date and time
168
+
169
+ `DateTime` is a date and a time of day side by side, with the value of one `<input type="datetime-local">`: `yyyy-mm-ddTHH:mm` (`:ss` with `granularity="second"`), local time and no zone, a string on purpose for the reason the DatePicker gives. It is the DatePicker and the TimeField together, on its own entry.
170
+
171
+ ```tsx
172
+ import { DateTime } from '@exsto/ds/date-time';
173
+
174
+ <DateTime
175
+ aria-label="Go live"
176
+ value={moment}
177
+ onValueChange={(next, state) => setMoment(next)}
178
+ min="2026-03-10T09:00"
179
+ max="2026-03-12T17:30"
180
+ />;
181
+ ```
182
+
183
+ `onValueChange(value, state)` gets `undefined` as the value until both parts are in, and the parts keep what was entered meanwhile. `state.kind` says which case it was: `datetime` (both usable), `empty`, `incomplete` (`missing` is `'date'` or `'time'`), `refused` (the date text is not a day; `text` and `problem` are the DatePicker's) or `out-of-range` (both parts are in and `min`, `max` or `disabledDates` refuse the day or the time; the value is reported and the field is marked invalid). A time in `min` or `max` holds on that limit's own day only. `name` submits the moment with a form, and nothing while a part is missing; `id` goes to the date part for a Label's `htmlFor`.
184
+
185
+ ## Colour
186
+
187
+ `ColorField` is a colour typed as hex or picked from a palette, so an app does not fall back to `<input type="color">`, whose chooser is the browser's own. A trigger with a swatch and the colour opens a popup with a palette and a hex field. The value is `#rrggbb` (lower case, as the native input writes it), or `undefined` for none. It has an entry of its own.
188
+
189
+ ```tsx
190
+ import { ColorField } from '@exsto/ds/color-field';
191
+
192
+ <ColorField aria-label="Label colour" value={colour} onValueChange={setColour} />;
193
+ ```
194
+
195
+ In the palette the arrow keys move between swatches (up and down by a row), Home and End go to the ends, and Enter or Space chooses one, closes the popup and gives the focus back to the trigger. In the hex field `#rgb` and `rgb` are read as `#rrggbb`, Enter chooses the colour, leaving the field chooses it too, and text that is not a colour marks the field invalid with a message under it and is not reported. `palette` takes hex strings or `{ value, label }` (Tailwind's hues by default), `name` submits the colour with a form, `id` goes to the trigger for a Label's `htmlFor`, and `labels` translates the words.
196
+
167
197
  ## Files
168
198
 
169
199
  `FileInput` chooses files by picking or dropping them. A real `<input type="file">` lies under the drop area, so Tab, Enter and Space, the system's file dialog, a drop and a screen reader's name come from the browser; on top of that it lists the chosen files with their sizes and a remove button named after each, checks them against `accept`, `maxSize` and `maxFiles` (a drop skips the dialog's own filter), and says in one live region what was added, removed or refused. It is on its own entry, like the DatePicker.
@@ -190,6 +220,20 @@ import { cn, POPUP_FRAME_CLASS, POPUP_LIST_HEIGHT_CLASS, POPUP_LIST_WIDTH_CLASS,
190
220
  - `POPUP_GROUP_LABEL_CLASS` is a group's heading. `ComboboxGroup`, `ComboboxLabel`, `ComboboxCollection` and `ComboboxSeparator` give a Combobox its groups, drawn the same way Select draws its own.
191
221
  - `POPUP_FRAME_CLASS` is the frame (a 10px radius and a 1px ring, no border). `POPUP_LIST_WIDTH_CLASS` is its width: at least the control and 8rem, as wide as the longest row, 28rem at most. `POPUP_LIST_HEIGHT_CLASS` caps the scrolling part: eight rows, or as many whole rows as the room allows, so a list never ends on half a row. Put it on a `ScrollArea` inside a popup that is a flex column with `max-h-(--available-height)`; the class carries the 4px of air above and below the rows as a margin, so the list inside takes `px-1` and no vertical padding (padding there scrolls with the rows and leaves a strip of the next row at the foot). `POPUP_LAYER_CLASS` (`z-60`) goes on the popup's positioner, so it is drawn above the Sheet, Dialog or Drawer (`z-50`) it is opened from.
192
222
 
223
+ ## Changes in 0.0.1-rc.24
224
+
225
+ Two new fields, a Checkbox that is a 24px target, a TimeField that no longer clips its placeholder, and a size guard that counts what a consumer ships.
226
+
227
+ - **Breaking, if you style a Checkbox through `className`: its border, fill and ring now belong to the box drawn inside it.** The Checkbox is a 24px target (WCAG 2.2 AA 2.5.8; axe's target-size rule flagged every row of checkboxes) with the same 16px box drawn inside it by a `::before`, 4px in from each edge, and a `-4px` margin gives the room back, so nothing around a checkbox moves (31 of them in the harness and the demo, with their rows and table cells, measured before and after). The look is unchanged, the focus ring included (on the border of the 16px box, outer edge 18px; 16px in a Table's scrolling box). A `className` with `border-*`, `bg-*` or `ring-*` on a Checkbox now styles the 24px root, not the box you see: write `before:border-*`, `before:bg-*`, `data-checked:before:bg-*` and so on. We found no such use in Atlas, react-boilerplate, the OCR admin, api-explorer or Menu Pricing. A test that measures `[role="checkbox"]` now reads a 24px box that starts 4px earlier on every side; the drawn box is its `::before` (4px in, 16px).
228
+ - **`DateTime` (`@exsto/ds/date-time`): a date and a time of day side by side, with the value of one `<input type="datetime-local">`.** A DatePicker and a TimeField in one fieldset; the value is `yyyy-mm-ddTHH:mm` (`:ss` with `granularity="second"`), `undefined` until both parts are in, and the parts keep what was entered meanwhile. `onValueChange(value, state)` says which case it was (`datetime`, `empty`, `incomplete` with `missing`, `refused`, `out-of-range`); a time in `min` or `max` holds on that limit's own day; `name` submits it with a form. See "Date and time".
229
+ - **`ColorField` (`@exsto/ds/color-field`): a colour typed as hex or picked from a palette, so an app does not fall back to `<input type="color">`.** A trigger with the Input's height, radius and focus, a swatch and the hex, and a popup with a named palette (Tailwind's hues, or yours), the arrow keys, Home, End, Enter and Escape, and a hex field that reads `#rgb` and `#rrggbb`. The check on a swatch reaches 3:1 against it on every colour. The value is `#rrggbb` in lower case, or `undefined`. See "Colour".
230
+ - **A TimeField keeps its clock at the end, like a DatePicker's calendar, and its segments fit their placeholders.** The segments were 20px and "MM" is about 23px, so an empty field clipped it. The minute, seconds and period boxes are 28px, the hour's is 24px with its text at the start, so the first glyph begins 11px in, as a date's does; the clock is 8px from the field's end and 13px after the text. A time field is 108px wide with minutes (it was 82px): a field that gave it `w-*` or sat in a tight row may need room.
231
+ - **A SidebarMenuAction is a 24px target** (it was 20px, and every page with a collapsible menu group failed axe's target-size rule). Only the hit area grew: the hover fill, its radius, the icon and the focus ring are the 20px ones they were, measured at 0px difference in a default, small and large row.
232
+ - **The popups of the DatePicker, the DateRangePicker, the MultiSelect and the rich text editor's link button are dialogs with a name** (axe's aria-dialog-name): the label of the control that opened them ("Choose date", the picker's `aria-label` or placeholder, the MultiSelect's `aria-label` or `aria-labelledby`, or its placeholder).
233
+ - **A Command list shows eight whole rows at most**, like every other popup list (it was capped at `max-h-72`, so a ninth row was cut at the foot). A `max-h-*` you pass still replaces the cap.
234
+ - **An AnimatedNumber with reduced motion paints its value from the first frame, never 0.** It began at 0 and landed a frame or two later, once it had been seen (counts showed 0 for about 34ms); now nothing waits and nothing moves, and a changed value is followed at once. Without reduced motion it counts up from 0 as before.
235
+ - **The size guard counts what a consumer ships.** `verify:ds` used to add up the modules of the main entry as written, comments included (299,839 bytes against a budget of 300,000); it now minifies each module, as a consumer's bundler does, and counts that: 203,327 bytes against a budget of 223,700, and the lean page (Button and Table) at 58,435 against 64,300. Each budget is the measured value plus 10%. Nothing changes for a consumer; the method and the order of answers when a budget fails are in `docs/architecture.md`, "How the size is held".
236
+
193
237
  ## Changes in 0.0.1-rc.23
194
238
 
195
239
  - **A TabsList keeps the chosen tab clear of its edge fade when the row changes size after load** (a font swapping in, data arriving). The scroll that reveals the chosen tab now repeats on a change of size until the reader scrolls, presses a key or touches; before, a chosen last tab could end 2px inside the edge with the fade covering most of it.
@@ -0,0 +1,3 @@
1
+ import { DEFAULT_COLOR_PALETTE } from "./ui/src/components/edyn/color-field/color-value.js";
2
+ import { ColorField, DEFAULT_COLOR_FIELD_LABELS } from "./ui/src/components/edyn/color-field/color-field.js";
3
+ export { ColorField, DEFAULT_COLOR_FIELD_LABELS, DEFAULT_COLOR_PALETTE };
@@ -0,0 +1,2 @@
1
+ import { DEFAULT_DATE_TIME_LABELS, DateTime } from "./ui/src/components/edyn/date-time/date-time.js";
2
+ export { DEFAULT_DATE_TIME_LABELS, DateTime };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `@exsto/ds/color-field` - the ColorField, on its own entry on purpose.
3
+ *
4
+ * A colour typed as hex or picked from a palette: a trigger with a swatch, and a popup with the palette (a listbox with the arrow
5
+ * keys) and a hex field with its parsing and messages. It exists so that an app does not fall back to `<input type="color">`, whose
6
+ * chooser is the browser's own. Re-exported from `@exsto/ds` it would be one more thing in the entry every consumer loads; here an app
7
+ * pays for it only on the pages that import this path.
8
+ */
9
+ export * from '../../ui/src/components/edyn/color-field';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `@exsto/ds/date-time` - the DateTime, on its own entry on purpose.
3
+ *
4
+ * A date and a time of day typed and picked side by side, with the value of one `<input type="datetime-local">`. It is the
5
+ * DatePicker and the TimeField together (about 24 KB unminified between them), so like them it is not one more thing in the entry
6
+ * every consumer loads. Here an app pays for it only on the pages that import this path.
7
+ */
8
+ export * from '../../ui/src/components/edyn/date-time';
@@ -1 +1 @@
1
- 237f9a6de1d109e6a11bc94cd65028316f93bb0be9dac50c29fd7b50604c39e5
1
+ 5d0829a6bc53b1c43d673da833ada3b9b9e437dff1ec88f7ffb5abe6740a13a2
package/dist/styles.css CHANGED
@@ -121,6 +121,8 @@
121
121
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
122
122
  "Courier New", monospace;
123
123
  --color-red-300: oklch(80.8% .114 19.571);
124
+ --color-black: #000;
125
+ --color-white: #fff;
124
126
  --spacing: .25rem;
125
127
  --container-xs: 20rem;
126
128
  --container-sm: 24rem;
@@ -545,8 +547,8 @@
545
547
  top: calc(var(--spacing) * .5);
546
548
  }
547
549
 
548
- .top-1\.5 {
549
- top: calc(var(--spacing) * 1.5);
550
+ .top-1 {
551
+ top: var(--spacing);
550
552
  }
551
553
 
552
554
  .top-1\/2 {
@@ -743,6 +745,10 @@
743
745
  }
744
746
  }
745
747
 
748
+ .-m-1 {
749
+ margin: calc(var(--spacing) * -1);
750
+ }
751
+
746
752
  .-mx-1 {
747
753
  margin-inline: calc(var(--spacing) * -1);
748
754
  }
@@ -767,6 +773,10 @@
767
773
  margin-inline: auto;
768
774
  }
769
775
 
776
+ .my-0 {
777
+ margin-block: 0;
778
+ }
779
+
770
780
  .my-0\.5 {
771
781
  margin-block: calc(var(--spacing) * .5);
772
782
  }
@@ -1228,6 +1238,10 @@
1228
1238
  width: calc(var(--spacing) * 5);
1229
1239
  }
1230
1240
 
1241
+ .w-6 {
1242
+ width: calc(var(--spacing) * 6);
1243
+ }
1244
+
1231
1245
  .w-7 {
1232
1246
  width: calc(var(--spacing) * 7);
1233
1247
  }
@@ -1240,6 +1254,14 @@
1240
1254
  width: calc(var(--spacing) * 24);
1241
1255
  }
1242
1256
 
1257
+ .w-40 {
1258
+ width: calc(var(--spacing) * 40);
1259
+ }
1260
+
1261
+ .w-44 {
1262
+ width: calc(var(--spacing) * 44);
1263
+ }
1264
+
1243
1265
  .w-56 {
1244
1266
  width: calc(var(--spacing) * 56);
1245
1267
  }
@@ -1631,6 +1653,10 @@
1631
1653
  grid-template-columns: minmax(0, 1fr) auto auto;
1632
1654
  }
1633
1655
 
1656
+ .grid-cols-\[repeat\(6\,1\.5rem\)\] {
1657
+ grid-template-columns: repeat(6, 1.5rem);
1658
+ }
1659
+
1634
1660
  .grid-rows-1 {
1635
1661
  grid-template-rows: repeat(1, minmax(0, 1fr));
1636
1662
  }
@@ -1741,6 +1767,10 @@
1741
1767
  margin-inline-end: calc(calc(var(--spacing) * -2) * calc(1 - var(--tw-space-x-reverse)));
1742
1768
  }
1743
1769
 
1770
+ .gap-y-1\.5 {
1771
+ row-gap: calc(var(--spacing) * 1.5);
1772
+ }
1773
+
1744
1774
  .self-center {
1745
1775
  align-self: center;
1746
1776
  }
@@ -1847,6 +1877,14 @@
1847
1877
  border-radius: calc(var(--radius) - 3px);
1848
1878
  }
1849
1879
 
1880
+ .rounded-\[calc\(var\(--radius-md\)\+2px\)\] {
1881
+ border-radius: calc(var(--radius-md) + 2px);
1882
+ }
1883
+
1884
+ .rounded-\[calc\(var\(--radius-sm\)\+4px\)\] {
1885
+ border-radius: calc(var(--radius-sm) + 4px);
1886
+ }
1887
+
1850
1888
  .rounded-\[inherit\] {
1851
1889
  border-radius: inherit;
1852
1890
  }
@@ -2218,6 +2256,10 @@
2218
2256
  --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
2219
2257
  }
2220
2258
 
2259
+ .bg-clip-content {
2260
+ background-clip: content-box;
2261
+ }
2262
+
2221
2263
  .bg-clip-padding {
2222
2264
  background-clip: padding-box;
2223
2265
  }
@@ -2238,6 +2280,10 @@
2238
2280
  padding: 0;
2239
2281
  }
2240
2282
 
2283
+ .p-0\.5 {
2284
+ padding: calc(var(--spacing) * .5);
2285
+ }
2286
+
2241
2287
  .p-1 {
2242
2288
  padding: var(--spacing);
2243
2289
  }
@@ -2358,10 +2404,18 @@
2358
2404
  padding-right: var(--spacing);
2359
2405
  }
2360
2406
 
2407
+ .pr-1\.75 {
2408
+ padding-right: calc(var(--spacing) * 1.75);
2409
+ }
2410
+
2361
2411
  .pr-2 {
2362
2412
  padding-right: calc(var(--spacing) * 2);
2363
2413
  }
2364
2414
 
2415
+ .pr-3\.25 {
2416
+ padding-right: calc(var(--spacing) * 3.25);
2417
+ }
2418
+
2365
2419
  .pr-8 {
2366
2420
  padding-right: calc(var(--spacing) * 8);
2367
2421
  }
@@ -2543,6 +2597,10 @@
2543
2597
  color: var(--background);
2544
2598
  }
2545
2599
 
2600
+ .text-black {
2601
+ color: var(--color-black);
2602
+ }
2603
+
2546
2604
  .text-card-foreground {
2547
2605
  color: var(--card-foreground);
2548
2606
  }
@@ -2621,6 +2679,10 @@
2621
2679
  color: var(--warning);
2622
2680
  }
2623
2681
 
2682
+ .text-white {
2683
+ color: var(--color-white);
2684
+ }
2685
+
2624
2686
  .italic {
2625
2687
  font-style: italic;
2626
2688
  }
@@ -3574,14 +3636,26 @@
3574
3636
  opacity: .5;
3575
3637
  }
3576
3638
 
3639
+ .peer-data-\[size\=default\]\/menu-button\:top-1:is(:where(.peer\/menu-button)[data-size="default"] ~ *) {
3640
+ top: var(--spacing);
3641
+ }
3642
+
3577
3643
  .peer-data-\[size\=default\]\/menu-button\:top-1\.5:is(:where(.peer\/menu-button)[data-size="default"] ~ *) {
3578
3644
  top: calc(var(--spacing) * 1.5);
3579
3645
  }
3580
3646
 
3647
+ .peer-data-\[size\=lg\]\/menu-button\:top-2:is(:where(.peer\/menu-button)[data-size="lg"] ~ *) {
3648
+ top: calc(var(--spacing) * 2);
3649
+ }
3650
+
3581
3651
  .peer-data-\[size\=lg\]\/menu-button\:top-2\.5:is(:where(.peer\/menu-button)[data-size="lg"] ~ *) {
3582
3652
  top: calc(var(--spacing) * 2.5);
3583
3653
  }
3584
3654
 
3655
+ .peer-data-\[size\=sm\]\/menu-button\:top-0\.5:is(:where(.peer\/menu-button)[data-size="sm"] ~ *) {
3656
+ top: calc(var(--spacing) * .5);
3657
+ }
3658
+
3585
3659
  .peer-data-\[size\=sm\]\/menu-button\:top-1:is(:where(.peer\/menu-button)[data-size="sm"] ~ *) {
3586
3660
  top: var(--spacing);
3587
3661
  }
@@ -3649,6 +3723,16 @@
3649
3723
  color: var(--muted-foreground);
3650
3724
  }
3651
3725
 
3726
+ .before\:absolute:before {
3727
+ content: var(--tw-content);
3728
+ position: absolute;
3729
+ }
3730
+
3731
+ .before\:inset-1:before {
3732
+ content: var(--tw-content);
3733
+ inset: var(--spacing);
3734
+ }
3735
+
3652
3736
  .before\:mr-1:before {
3653
3737
  content: var(--tw-content);
3654
3738
  margin-right: var(--spacing);
@@ -3669,16 +3753,44 @@
3669
3753
  flex: 1;
3670
3754
  }
3671
3755
 
3756
+ .before\:rounded-sm:before {
3757
+ content: var(--tw-content);
3758
+ border-radius: calc(var(--radius) * .6);
3759
+ }
3760
+
3761
+ .before\:border:before {
3762
+ content: var(--tw-content);
3763
+ border-style: var(--tw-border-style);
3764
+ border-width: 1px;
3765
+ }
3766
+
3767
+ .before\:border-control:before {
3768
+ content: var(--tw-content);
3769
+ border-color: var(--control-border);
3770
+ }
3771
+
3672
3772
  .before\:bg-border:before {
3673
3773
  content: var(--tw-content);
3674
3774
  background-color: var(--border);
3675
3775
  }
3676
3776
 
3777
+ .before\:transition-colors:before {
3778
+ content: var(--tw-content);
3779
+ transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
3780
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
3781
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
3782
+ }
3783
+
3677
3784
  .after\:absolute:after {
3678
3785
  content: var(--tw-content);
3679
3786
  position: absolute;
3680
3787
  }
3681
3788
 
3789
+ .after\:-inset-1\.5:after {
3790
+ content: var(--tw-content);
3791
+ inset: calc(var(--spacing) * -1.5);
3792
+ }
3793
+
3682
3794
  .after\:-inset-2:after {
3683
3795
  content: var(--tw-content);
3684
3796
  inset: calc(var(--spacing) * -2);
@@ -3689,6 +3801,11 @@
3689
3801
  inset: 0;
3690
3802
  }
3691
3803
 
3804
+ .after\:-inset-x-2:after {
3805
+ content: var(--tw-content);
3806
+ inset-inline: calc(var(--spacing) * -2);
3807
+ }
3808
+
3692
3809
  .after\:-inset-x-3:after {
3693
3810
  content: var(--tw-content);
3694
3811
  inset-inline: calc(var(--spacing) * -3);
@@ -3699,6 +3816,11 @@
3699
3816
  inset-inline: calc(var(--spacing) * .5);
3700
3817
  }
3701
3818
 
3819
+ .after\:-inset-y-1:after {
3820
+ content: var(--tw-content);
3821
+ inset-block: calc(var(--spacing) * -1);
3822
+ }
3823
+
3702
3824
  .after\:-inset-y-2:after {
3703
3825
  content: var(--tw-content);
3704
3826
  inset-block: calc(var(--spacing) * -2);
@@ -4640,7 +4762,30 @@
4640
4762
  }
4641
4763
  }
4642
4764
 
4643
- .aria-invalid\:aria-checked\:border-primary[aria-invalid="true"][aria-checked="true"] {
4765
+ .aria-invalid\:before\:border-destructive[aria-invalid="true"]:before {
4766
+ content: var(--tw-content);
4767
+ border-color: var(--destructive);
4768
+ }
4769
+
4770
+ .aria-invalid\:before\:ring-3[aria-invalid="true"]:before {
4771
+ content: var(--tw-content);
4772
+ --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
4773
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
4774
+ }
4775
+
4776
+ .aria-invalid\:before\:ring-destructive\/20[aria-invalid="true"]:before {
4777
+ content: var(--tw-content);
4778
+ --tw-ring-color: var(--destructive);
4779
+ }
4780
+
4781
+ @supports (color: color-mix(in lab, red, red)) {
4782
+ .aria-invalid\:before\:ring-destructive\/20[aria-invalid="true"]:before {
4783
+ --tw-ring-color: color-mix(in oklab, var(--destructive) 20%, transparent);
4784
+ }
4785
+ }
4786
+
4787
+ .aria-invalid\:aria-checked\:before\:border-primary[aria-invalid="true"][aria-checked="true"]:before {
4788
+ content: var(--tw-content);
4644
4789
  border-color: var(--primary);
4645
4790
  }
4646
4791
 
@@ -5981,6 +6126,17 @@
5981
6126
  background-color: var(--primary);
5982
6127
  }
5983
6128
 
6129
+ .dark\:before\:bg-input\/30:is(.dark *):before {
6130
+ content: var(--tw-content);
6131
+ background-color: var(--input);
6132
+ }
6133
+
6134
+ @supports (color: color-mix(in lab, red, red)) {
6135
+ .dark\:before\:bg-input\/30:is(.dark *):before {
6136
+ background-color: color-mix(in oklab, var(--input) 30%, transparent);
6137
+ }
6138
+ }
6139
+
5984
6140
  .dark\:after\:mix-blend-lighten:is(.dark *):after {
5985
6141
  content: var(--tw-content);
5986
6142
  mix-blend-mode: lighten;
@@ -6118,6 +6274,28 @@
6118
6274
  }
6119
6275
  }
6120
6276
 
6277
+ .dark\:aria-invalid\:before\:border-destructive\/50:is(.dark *)[aria-invalid="true"]:before {
6278
+ content: var(--tw-content);
6279
+ border-color: var(--destructive);
6280
+ }
6281
+
6282
+ @supports (color: color-mix(in lab, red, red)) {
6283
+ .dark\:aria-invalid\:before\:border-destructive\/50:is(.dark *)[aria-invalid="true"]:before {
6284
+ border-color: color-mix(in oklab, var(--destructive) 50%, transparent);
6285
+ }
6286
+ }
6287
+
6288
+ .dark\:aria-invalid\:before\:ring-destructive\/40:is(.dark *)[aria-invalid="true"]:before {
6289
+ content: var(--tw-content);
6290
+ --tw-ring-color: var(--destructive);
6291
+ }
6292
+
6293
+ @supports (color: color-mix(in lab, red, red)) {
6294
+ .dark\:aria-invalid\:before\:ring-destructive\/40:is(.dark *)[aria-invalid="true"]:before {
6295
+ --tw-ring-color: color-mix(in oklab, var(--destructive) 40%, transparent);
6296
+ }
6297
+ }
6298
+
6121
6299
  .dark\:data-invalid\:ring-destructive\/40:is(.dark *)[data-invalid] {
6122
6300
  --tw-ring-color: var(--destructive);
6123
6301
  }
@@ -6304,7 +6482,7 @@
6304
6482
  --tw-exit-scale: .95;
6305
6483
  }
6306
6484
 
6307
- .data-checked\:border-primary:where([data-state="checked"]), .data-checked\:border-primary:where([data-checked]:not([data-checked="false"])), .data-checked\:border-primary\/40:where([data-state="checked"]), .data-checked\:border-primary\/40:where([data-checked]:not([data-checked="false"])) {
6485
+ .data-checked\:border-primary\/40:where([data-state="checked"]), .data-checked\:border-primary\/40:where([data-checked]:not([data-checked="false"])) {
6308
6486
  border-color: var(--primary);
6309
6487
  }
6310
6488
 
@@ -6336,18 +6514,29 @@
6336
6514
  translate: var(--tw-translate-x) var(--tw-translate-y);
6337
6515
  }
6338
6516
 
6339
- .dark\:data-checked\:bg-muted:is(.dark *):where([data-state="checked"]), .dark\:data-checked\:bg-muted:is(.dark *):where([data-checked]:not([data-checked="false"])) {
6340
- background-color: var(--muted);
6517
+ :is(.data-checked\:before\:border-primary:where([data-state="checked"]), .data-checked\:before\:border-primary:where([data-checked]:not([data-checked="false"]))):before {
6518
+ content: var(--tw-content);
6519
+ border-color: var(--primary);
6341
6520
  }
6342
6521
 
6343
- .dark\:data-checked\:bg-primary:is(.dark *):where([data-state="checked"]), .dark\:data-checked\:bg-primary:is(.dark *):where([data-checked]:not([data-checked="false"])) {
6522
+ :is(.data-checked\:before\:bg-primary:where([data-state="checked"]), .data-checked\:before\:bg-primary:where([data-checked]:not([data-checked="false"]))):before {
6523
+ content: var(--tw-content);
6344
6524
  background-color: var(--primary);
6345
6525
  }
6346
6526
 
6527
+ .dark\:data-checked\:bg-muted:is(.dark *):where([data-state="checked"]), .dark\:data-checked\:bg-muted:is(.dark *):where([data-checked]:not([data-checked="false"])) {
6528
+ background-color: var(--muted);
6529
+ }
6530
+
6347
6531
  .dark\:data-checked\:bg-primary-foreground:is(.dark *):where([data-state="checked"]), .dark\:data-checked\:bg-primary-foreground:is(.dark *):where([data-checked]:not([data-checked="false"])) {
6348
6532
  background-color: var(--primary-foreground);
6349
6533
  }
6350
6534
 
6535
+ :is(.dark\:data-checked\:before\:bg-primary:is(.dark *):where([data-state="checked"]), .dark\:data-checked\:before\:bg-primary:is(.dark *):where([data-checked]:not([data-checked="false"]))):before {
6536
+ content: var(--tw-content);
6537
+ background-color: var(--primary);
6538
+ }
6539
+
6351
6540
  .data-unchecked\:bg-input:where([data-state="unchecked"]), .data-unchecked\:bg-input:where([data-unchecked]:not([data-unchecked="false"])) {
6352
6541
  background-color: var(--input);
6353
6542
  }
@@ -7748,6 +7937,26 @@
7748
7937
  outline-offset: -2px;
7749
7938
  }
7750
7939
 
7940
+ [data-slot="checkbox"] {
7941
+ outline-offset: -5px;
7942
+ }
7943
+
7944
+ [data-focus="inset"] [data-slot="checkbox"] {
7945
+ outline-offset: -6px;
7946
+ }
7947
+
7948
+ [data-slot="sidebar-menu-action"] {
7949
+ outline-offset: 0;
7950
+ }
7951
+
7952
+ [data-focus="inset"] [data-slot="sidebar-menu-action"] {
7953
+ outline-offset: -4px;
7954
+ }
7955
+
7956
+ [data-slot="checkbox"]:focus-visible:before {
7957
+ --tw-ring-shadow: 0 0 #0000;
7958
+ }
7959
+
7751
7960
  :root {
7752
7961
  --motion-expand: .2s;
7753
7962
  --motion-collapse: .16s;
@@ -3,7 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  //#region ../ui/src/components/edyn/animated-number/animated-number.tsx
4
4
  var DURATION = 800;
5
5
  var easeInOutCubic = (t) => t < .5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2;
6
- var reducedMotion = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches;
6
+ var reducedMotion = () => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
7
7
  var NUMBER = new Intl.NumberFormat("en-GB");
8
8
  /** Thousands separated, rounded to a whole number: the default way a count reads. */
9
9
  var formatCount = (value) => NUMBER.format(Math.round(value) || 0);
@@ -30,15 +30,16 @@ var useSeen = (element) => {
30
30
  */
31
31
  var useAnimatedValues = (targets, element) => {
32
32
  const seen = useSeen(element);
33
- const [shown, setShown] = useState(() => targets.map(() => 0));
33
+ const [shown, setShown] = useState(() => reducedMotion() ? [...targets] : targets.map(() => 0));
34
34
  const refCurrent = useRef(shown);
35
35
  const key = targets.join(",");
36
36
  useEffect(() => {
37
- if (!seen) return;
37
+ const reduced = reducedMotion();
38
+ if (!seen && !reduced) return;
38
39
  const goal = key.split(",").map(Number);
39
40
  const from = goal.map((_, index) => refCurrent.current[index] ?? 0);
40
41
  if (from.every((value, index) => value === goal[index])) return;
41
- if (reducedMotion()) {
42
+ if (reduced) {
42
43
  refCurrent.current = goal;
43
44
  setShown(goal);
44
45
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"animated-number.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/animated-number/animated-number.tsx"],"sourcesContent":["import { type ComponentPropsWithoutRef, type RefObject, useEffect, useRef, useState } from 'react';\n\n// Numbers that move instead of jumping when a tab or a filter changes them: from the\n// value shown to the new one over 800ms, easing in and out; the first time, from 0\n// once it scrolls into view. Interrupted by another change, it carries on from where\n// it is, never back. With reduced motion, or when the value has not changed (a refetch\n// with the same answer), nothing moves.\n\nconst DURATION = 800;\n\nconst easeInOutCubic = (t: number): number => (t < 0.5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2);\n\nconst reducedMotion = (): boolean => window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\nconst NUMBER = new Intl.NumberFormat('en-GB');\n\n/** Thousands separated, rounded to a whole number: the default way a count reads. */\nexport const formatCount = (value: number): string => NUMBER.format(Math.round(value) || 0);\n\n// Whether the element has been on screen yet; once it has, it stays counted as seen.\nconst useSeen = (element: RefObject<Element | null>): boolean => {\n const [seen, setSeen] = useState(() => typeof IntersectionObserver === 'undefined');\n useEffect(() => {\n const target = element.current;\n if (seen || target === null) {\n return;\n }\n const observer = new IntersectionObserver((entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n setSeen(true);\n observer.disconnect();\n }\n });\n observer.observe(target);\n return () => observer.disconnect();\n }, [element, seen]);\n return seen;\n};\n\n/**\n * The values to show for `targets` this frame, all on one clock and one curve, so a\n * count and its bar move together. They start from 0 and move once `element` has\n * been on screen.\n */\nexport const useAnimatedValues = (targets: number[], element: RefObject<Element | null>): number[] => {\n const seen = useSeen(element);\n const [shown, setShown] = useState<number[]>(() => targets.map(() => 0));\n const refCurrent = useRef(shown);\n const key = targets.join(',');\n useEffect(() => {\n if (!seen) {\n return;\n }\n const goal = key.split(',').map(Number);\n const from = goal.map((_, index) => refCurrent.current[index] ?? 0);\n if (from.every((value, index) => value === goal[index])) {\n return;\n }\n if (reducedMotion()) {\n refCurrent.current = goal;\n setShown(goal);\n return;\n }\n const start = performance.now();\n let frame = 0;\n const tick = (now: number): void => {\n const progress = Math.min(1, (now - start) / DURATION);\n const eased = easeInOutCubic(progress);\n // Exactly the goal at the end, never a float a hair beside it.\n const next = progress === 1 ? goal : goal.map((value, index) => (from[index] ?? 0) + (value - (from[index] ?? 0)) * eased);\n refCurrent.current = next;\n setShown(next);\n if (progress < 1) {\n frame = requestAnimationFrame(tick);\n }\n };\n frame = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(frame);\n }, [key, seen]);\n return shown;\n};\n\nexport interface IAnimatedNumberProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children' | 'className'> {\n value: number;\n /** What it shows while it moves and once there (formatCount by default; a percentage formatter for shares). */\n format?: (value: number) => string;\n className?: string;\n}\n\n/**\n * A number that moves to its new value. Its width holds the widest of where it starts\n * and where it lands, in tabular figures, so nothing beside it moves while the digits\n * change. Assistive technology reads only the final value.\n */\nexport const AnimatedNumber = ({ value, format = formatCount, className, ...props }: IAnimatedNumberProps) => {\n const refElement = useRef<HTMLSpanElement>(null);\n const [shown = 0] = useAnimatedValues([value], refElement);\n return <NumberText element={refElement} shown={shown} value={value} format={format} className={className} {...props} />;\n};\n\nexport interface INumberTextProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children' | 'className'> {\n element?: RefObject<HTMLSpanElement | null>;\n shown: number;\n value: number;\n format?: (value: number) => string;\n className?: string;\n}\n\n/** The text of a moving number: what it shows now, held at its widest, with the final value for assistive technology. */\nexport const NumberText = ({ element, shown, value, format = formatCount, className, ...props }: INumberTextProps) => {\n const now = format(shown);\n const final = format(value);\n // The widest of the two, laid invisibly under the number: it holds the width of the\n // real figures (tabular digits are not exactly 1ch), so the number grows or shrinks\n // only once it has landed.\n const widest = now.length > final.length ? now : final;\n return (\n <span data-slot=\"animated-number\" {...props} ref={element} className={className}>\n <span aria-hidden=\"true\" className=\"inline-grid tabular-nums\">\n <span data-ghost={widest} className=\"invisible col-start-1 row-start-1 ghost-text\" />\n <span data-slot=\"animated-number-value\" className=\"col-start-1 row-start-1\">\n {now}\n </span>\n </span>\n <span className=\"sr-only\">{final}</span>\n </span>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"animated-number.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/animated-number/animated-number.tsx"],"sourcesContent":["import { type ComponentPropsWithoutRef, type RefObject, useEffect, useRef, useState } from 'react';\n\n// Numbers that move instead of jumping when a tab or a filter changes them: from the\n// value shown to the new one over 800ms, easing in and out; the first time, from 0\n// once it scrolls into view. Interrupted by another change, it carries on from where\n// it is, never back. With reduced motion, or when the value has not changed (a refetch\n// with the same answer), nothing moves, and the first paint is the value, never 0.\n\nconst DURATION = 800;\n\nconst easeInOutCubic = (t: number): number => (t < 0.5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2);\n\nconst reducedMotion = (): boolean => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\nconst NUMBER = new Intl.NumberFormat('en-GB');\n\n/** Thousands separated, rounded to a whole number: the default way a count reads. */\nexport const formatCount = (value: number): string => NUMBER.format(Math.round(value) || 0);\n\n// Whether the element has been on screen yet; once it has, it stays counted as seen.\nconst useSeen = (element: RefObject<Element | null>): boolean => {\n const [seen, setSeen] = useState(() => typeof IntersectionObserver === 'undefined');\n useEffect(() => {\n const target = element.current;\n if (seen || target === null) {\n return;\n }\n const observer = new IntersectionObserver((entries) => {\n if (entries.some((entry) => entry.isIntersecting)) {\n setSeen(true);\n observer.disconnect();\n }\n });\n observer.observe(target);\n return () => observer.disconnect();\n }, [element, seen]);\n return seen;\n};\n\n/**\n * The values to show for `targets` this frame, all on one clock and one curve, so a\n * count and its bar move together. They start from 0 and move once `element` has\n * been on screen.\n */\nexport const useAnimatedValues = (targets: number[], element: RefObject<Element | null>): number[] => {\n const seen = useSeen(element);\n const [shown, setShown] = useState<number[]>(() => (reducedMotion() ? [...targets] : targets.map(() => 0)));\n const refCurrent = useRef(shown);\n const key = targets.join(',');\n useEffect(() => {\n const reduced = reducedMotion();\n // Reduced motion has nothing to wait to see: the value is shown whether or not it is on screen yet.\n if (!seen && !reduced) {\n return;\n }\n const goal = key.split(',').map(Number);\n const from = goal.map((_, index) => refCurrent.current[index] ?? 0);\n if (from.every((value, index) => value === goal[index])) {\n return;\n }\n if (reduced) {\n refCurrent.current = goal;\n setShown(goal);\n return;\n }\n const start = performance.now();\n let frame = 0;\n const tick = (now: number): void => {\n const progress = Math.min(1, (now - start) / DURATION);\n const eased = easeInOutCubic(progress);\n // Exactly the goal at the end, never a float a hair beside it.\n const next = progress === 1 ? goal : goal.map((value, index) => (from[index] ?? 0) + (value - (from[index] ?? 0)) * eased);\n refCurrent.current = next;\n setShown(next);\n if (progress < 1) {\n frame = requestAnimationFrame(tick);\n }\n };\n frame = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(frame);\n }, [key, seen]);\n return shown;\n};\n\nexport interface IAnimatedNumberProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children' | 'className'> {\n value: number;\n /** What it shows while it moves and once there (formatCount by default; a percentage formatter for shares). */\n format?: (value: number) => string;\n className?: string;\n}\n\n/**\n * A number that moves to its new value. Its width holds the widest of where it starts\n * and where it lands, in tabular figures, so nothing beside it moves while the digits\n * change. Assistive technology reads only the final value.\n */\nexport const AnimatedNumber = ({ value, format = formatCount, className, ...props }: IAnimatedNumberProps) => {\n const refElement = useRef<HTMLSpanElement>(null);\n const [shown = 0] = useAnimatedValues([value], refElement);\n return <NumberText element={refElement} shown={shown} value={value} format={format} className={className} {...props} />;\n};\n\nexport interface INumberTextProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children' | 'className'> {\n element?: RefObject<HTMLSpanElement | null>;\n shown: number;\n value: number;\n format?: (value: number) => string;\n className?: string;\n}\n\n/** The text of a moving number: what it shows now, held at its widest, with the final value for assistive technology. */\nexport const NumberText = ({ element, shown, value, format = formatCount, className, ...props }: INumberTextProps) => {\n const now = format(shown);\n const final = format(value);\n // The widest of the two, laid invisibly under the number: it holds the width of the\n // real figures (tabular digits are not exactly 1ch), so the number grows or shrinks\n // only once it has landed.\n const widest = now.length > final.length ? now : final;\n return (\n <span data-slot=\"animated-number\" {...props} ref={element} className={className}>\n <span aria-hidden=\"true\" className=\"inline-grid tabular-nums\">\n <span data-ghost={widest} className=\"invisible col-start-1 row-start-1 ghost-text\" />\n <span data-slot=\"animated-number-value\" className=\"col-start-1 row-start-1\">\n {now}\n </span>\n </span>\n <span className=\"sr-only\">{final}</span>\n </span>\n );\n};\n"],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { IColorSwatch } from './color-value';
2
+ export interface IColorFieldLabels {
3
+ /** The name of the popup, when the field has no label of its own. */
4
+ choose: string;
5
+ /** The name of the hex field in the popup. */
6
+ hex: string;
7
+ /** The name of the palette. */
8
+ palette: string;
9
+ /** What the trigger says when no colour is chosen and there is no placeholder. */
10
+ none: string;
11
+ /** The message under the hex field when its text is not a colour. */
12
+ invalid: string;
13
+ }
14
+ export declare const DEFAULT_COLOR_FIELD_LABELS: IColorFieldLabels;
15
+ export interface IColorFieldProps {
16
+ /** For a Label's htmlFor: it goes to the trigger. */
17
+ id?: string;
18
+ /** The colour as `#rrggbb`, as `<input type="color">` writes it, or `undefined` for none. `#rgb` is read as `#rrggbb`. */
19
+ value: string | undefined;
20
+ /** Called with `#rrggbb` (lower case), or `undefined` when the hex field is emptied. Text that is not a colour is not reported. */
21
+ onValueChange: (value: string | undefined) => void;
22
+ /** The swatches offered: hex strings, or a hex and the name a screen reader says. Tailwind's hues by default. */
23
+ palette?: readonly (string | IColorSwatch)[];
24
+ disabled?: boolean;
25
+ /** What the trigger says when no colour is chosen. */
26
+ placeholder?: string;
27
+ /** Submits the colour with a form, under this name, as `#rrggbb`; empty for none. */
28
+ name?: string;
29
+ /** An accessible name, when there is no visible label to point at. The chosen colour is added to it. */
30
+ 'aria-label'?: string;
31
+ 'aria-invalid'?: boolean | 'true' | 'false';
32
+ 'aria-describedby'?: string;
33
+ labels?: Partial<IColorFieldLabels>;
34
+ /** Layout only (a width); the look is the kit's. */
35
+ className?: string;
36
+ }
37
+ /**
38
+ * A colour typed as hex or picked from a palette, in the kit's field: a trigger with a swatch and the colour, and a popup with the
39
+ * palette and a hex field. It exists so that an app does not fall back to `<input type="color">`, whose chooser is the browser's own.
40
+ * It has an entry of its own, `@exsto/ds/color-field`.
41
+ *
42
+ * The trigger opens the popup with Enter, Space or the pointer. In the palette the arrow keys move between swatches (up and down by
43
+ * a row), Home and End go to the ends, and Enter or Space chooses one, closes the popup and gives the focus back to the trigger.
44
+ * In the hex field Enter chooses the colour typed; leaving the field chooses it too, and text that is not a colour marks the field
45
+ * invalid, says so under it and is not reported.
46
+ */
47
+ export declare const ColorField: ({ id, value, onValueChange, palette, disabled, placeholder, name, "aria-label": ariaLabel, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, labels: labelOverrides, className, }: IColorFieldProps) => import('react').JSX.Element;