@misoto22/design 0.8.0 → 0.9.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 (157) hide show
  1. package/dist/agent/Accordion.md +1 -1
  2. package/dist/agent/Alert.md +1 -1
  3. package/dist/agent/AppShell.md +1 -1
  4. package/dist/agent/ArchitectureFigure.md +1 -1
  5. package/dist/agent/AreaChart.md +1 -1
  6. package/dist/agent/Article.md +1 -1
  7. package/dist/agent/AspectRatio.md +1 -1
  8. package/dist/agent/Avatar.md +1 -1
  9. package/dist/agent/Badge.md +1 -1
  10. package/dist/agent/BarChart.md +1 -1
  11. package/dist/agent/BarList.md +1 -1
  12. package/dist/agent/BigNumber.md +1 -1
  13. package/dist/agent/BoxPlot.md +1 -1
  14. package/dist/agent/Breadcrumb.md +1 -1
  15. package/dist/agent/BulletChart.md +1 -1
  16. package/dist/agent/Button.md +1 -1
  17. package/dist/agent/Calendar.md +1 -1
  18. package/dist/agent/Card.md +1 -1
  19. package/dist/agent/Checkbox.md +1 -1
  20. package/dist/agent/Code.md +1 -1
  21. package/dist/agent/CodeBlock.md +1 -1
  22. package/dist/agent/Collapsible.md +1 -1
  23. package/dist/agent/ColorPicker.md +75 -0
  24. package/dist/agent/Combobox.md +1 -1
  25. package/dist/agent/Command.md +1 -1
  26. package/dist/agent/ComposedChart.md +1 -1
  27. package/dist/agent/ContextMenu.md +1 -1
  28. package/dist/agent/DataflowFigure.md +1 -1
  29. package/dist/agent/DatePicker.md +1 -1
  30. package/dist/agent/DescriptionList.md +1 -1
  31. package/dist/agent/Diagram.md +1 -1
  32. package/dist/agent/DiagramCanvas.md +1 -1
  33. package/dist/agent/DiagramExportMenu.md +1 -1
  34. package/dist/agent/DiagramInspector.md +1 -1
  35. package/dist/agent/DiagramLegend.md +1 -1
  36. package/dist/agent/DiagramMinimap.md +1 -1
  37. package/dist/agent/DiagramToolbar.md +1 -1
  38. package/dist/agent/Dialog.md +1 -1
  39. package/dist/agent/DropdownMenu.md +1 -1
  40. package/dist/agent/EmptyState.md +1 -1
  41. package/dist/agent/ErrorState.md +1 -1
  42. package/dist/agent/Facet.md +1 -1
  43. package/dist/agent/Field.md +1 -1
  44. package/dist/agent/FigureBand.md +1 -1
  45. package/dist/agent/FloatingIconButton.md +1 -1
  46. package/dist/agent/FunnelChart.md +1 -1
  47. package/dist/agent/Heading.md +1 -1
  48. package/dist/agent/Heatmap.md +1 -1
  49. package/dist/agent/Histogram.md +1 -1
  50. package/dist/agent/Input.md +1 -1
  51. package/dist/agent/Kbd.md +1 -1
  52. package/dist/agent/LifecycleFigure.md +1 -1
  53. package/dist/agent/LineChart.md +1 -1
  54. package/dist/agent/LinkArrow.md +1 -1
  55. package/dist/agent/Markdown.md +1 -1
  56. package/dist/agent/NativeSelect.md +1 -1
  57. package/dist/agent/NavItem.md +1 -1
  58. package/dist/agent/NumberField.md +72 -0
  59. package/dist/agent/Pagination.md +1 -1
  60. package/dist/agent/PieChart.md +1 -1
  61. package/dist/agent/Popover.md +1 -1
  62. package/dist/agent/Progress.md +1 -1
  63. package/dist/agent/RadarChart.md +1 -1
  64. package/dist/agent/RadialChart.md +1 -1
  65. package/dist/agent/RadioGroup.md +1 -1
  66. package/dist/agent/SankeyChart.md +1 -1
  67. package/dist/agent/ScatterChart.md +1 -1
  68. package/dist/agent/ScrollArea.md +1 -1
  69. package/dist/agent/SearchableMenu.md +1 -1
  70. package/dist/agent/Select.md +1 -1
  71. package/dist/agent/Separator.md +1 -1
  72. package/dist/agent/SequenceFigure.md +1 -1
  73. package/dist/agent/Sheet.md +1 -1
  74. package/dist/agent/Sidebar.md +193 -0
  75. package/dist/agent/Skeleton.md +1 -1
  76. package/dist/agent/Slider.md +7 -3
  77. package/dist/agent/Sparkline.md +1 -1
  78. package/dist/agent/Spinner.md +1 -1
  79. package/dist/agent/StatusDot.md +1 -1
  80. package/dist/agent/StatusPill.md +1 -1
  81. package/dist/agent/Steps.md +1 -1
  82. package/dist/agent/Switch.md +1 -1
  83. package/dist/agent/Table.md +3 -1
  84. package/dist/agent/Tabs.md +1 -1
  85. package/dist/agent/Tag.md +1 -1
  86. package/dist/agent/Text.md +1 -1
  87. package/dist/agent/Textarea.md +1 -1
  88. package/dist/agent/Timestamp.md +1 -1
  89. package/dist/agent/Toast.md +1 -1
  90. package/dist/agent/ToggleGroup.md +1 -1
  91. package/dist/agent/Toolbar.md +1 -1
  92. package/dist/agent/Tooltip.md +1 -1
  93. package/dist/agent/TreemapChart.md +1 -1
  94. package/dist/agent/WaterfallChart.md +1 -1
  95. package/dist/agent/WorkflowFigure.md +1 -1
  96. package/dist/agent/catalog.json +361 -2
  97. package/dist/agent/index.md +8 -5
  98. package/dist/charts/BulletChart/BulletChart.js +16 -2
  99. package/dist/charts/BulletChart/BulletChart.js.map +1 -1
  100. package/dist/charts/RadarChart/RadarChart.js +4 -2
  101. package/dist/charts/RadarChart/RadarChart.js.map +1 -1
  102. package/dist/components/ColorPicker/ColorPicker.d.ts +67 -0
  103. package/dist/components/ColorPicker/ColorPicker.js +334 -0
  104. package/dist/components/ColorPicker/ColorPicker.js.map +1 -0
  105. package/dist/components/Command/Command.js +1 -1
  106. package/dist/components/Command/Command.js.map +1 -1
  107. package/dist/components/ContextMenu/ContextMenu.js +1 -1
  108. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  109. package/dist/components/Dialog/Dialog.js +1 -1
  110. package/dist/components/Dialog/Dialog.js.map +1 -1
  111. package/dist/components/DropdownMenu/DropdownMenu.js +1 -1
  112. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  113. package/dist/components/ErrorState/ErrorState.js +1 -1
  114. package/dist/components/ErrorState/ErrorState.js.map +1 -1
  115. package/dist/components/NavItem/NavItem.js +1 -1
  116. package/dist/components/NavItem/NavItem.js.map +1 -1
  117. package/dist/components/NumberField/NumberField.d.ts +69 -0
  118. package/dist/components/NumberField/NumberField.js +162 -0
  119. package/dist/components/NumberField/NumberField.js.map +1 -0
  120. package/dist/components/Popover/Popover.js +1 -1
  121. package/dist/components/Popover/Popover.js.map +1 -1
  122. package/dist/components/Select/Select.js +1 -1
  123. package/dist/components/Select/Select.js.map +1 -1
  124. package/dist/components/Sheet/Sheet.js +1 -1
  125. package/dist/components/Sheet/Sheet.js.map +1 -1
  126. package/dist/components/Sidebar/Sidebar.d.ts +349 -0
  127. package/dist/components/Sidebar/Sidebar.js +455 -0
  128. package/dist/components/Sidebar/Sidebar.js.map +1 -0
  129. package/dist/components/Slider/Slider.d.ts +22 -1
  130. package/dist/components/Slider/Slider.js +72 -8
  131. package/dist/components/Slider/Slider.js.map +1 -1
  132. package/dist/components/Table/Table.d.ts +14 -0
  133. package/dist/components/Table/Table.js.map +1 -1
  134. package/dist/components/Tabs/Tabs.js +7 -1
  135. package/dist/components/Tabs/Tabs.js.map +1 -1
  136. package/dist/diagrams/lib/route.d.ts +22 -1
  137. package/dist/diagrams/lib/route.js +24 -1
  138. package/dist/diagrams/lib/route.js.map +1 -1
  139. package/dist/diagrams/lib/wires.js +9 -5
  140. package/dist/diagrams/lib/wires.js.map +1 -1
  141. package/dist/index.d.ts +3 -0
  142. package/dist/index.js +3 -0
  143. package/dist/index.js.map +1 -1
  144. package/dist/lib/color.d.ts +80 -0
  145. package/dist/lib/color.js +189 -0
  146. package/dist/lib/color.js.map +1 -0
  147. package/dist/lib/numeric.d.ts +35 -0
  148. package/dist/lib/numeric.js +29 -0
  149. package/dist/lib/numeric.js.map +1 -0
  150. package/dist/styles.css +1 -1
  151. package/dist/themes.css +8 -0
  152. package/dist/tokens.css +66 -3
  153. package/dist/tokens.d.ts +3 -0
  154. package/dist/tokens.js +17 -0
  155. package/dist/tokens.json +17 -0
  156. package/package.json +1 -1
  157. package/skills/misoto22-design/rules/forms.md +3 -1
package/dist/index.js CHANGED
@@ -28,8 +28,10 @@ export * from "./components/EmptyState/EmptyState.js";
28
28
  export * from "./components/ErrorState/ErrorState.js";
29
29
  export * from "./components/Toast/Toast.js";
30
30
  export * from "./components/Field/Field.js";
31
+ export * from "./components/ColorPicker/ColorPicker.js";
31
32
  export * from "./components/Combobox/Combobox.js";
32
33
  export * from "./components/DatePicker/DatePicker.js";
34
+ export * from "./components/NumberField/NumberField.js";
33
35
  export * from "./components/Slider/Slider.js";
34
36
  export * from "./components/ToggleGroup/ToggleGroup.js";
35
37
  export * from "./components/Input/Input.js";
@@ -53,6 +55,7 @@ export * from "./components/Collapsible/Collapsible.js";
53
55
  export * from "./components/Breadcrumb/Breadcrumb.js";
54
56
  export * from "./components/Pagination/Pagination.js";
55
57
  export * from "./components/NavItem/NavItem.js";
58
+ export * from "./components/Sidebar/Sidebar.js";
56
59
  export * from "./components/Card/Card.js";
57
60
  export * from "./components/Table/Table.js";
58
61
  export * from "./components/DescriptionList/DescriptionList.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @misoto22/design — public entry.\n *\n * Components export from here; the look ships separately as CSS:\n *\n * import '@misoto22/design/styles.css' // compiled Tailwind + tokens + fonts\n *\n * or, for an app that already compiles Tailwind itself, the portable layers on\n * their own:\n *\n * import '@misoto22/design/tokens.css' // primitives, plus data-mode and data-density\n * import '@misoto22/design/semantic.css' // roles\n * import '@misoto22/design/themes.css' // the other six theming axes\n * import '@misoto22/design/keyframes.css' // motion\n *\n * All four, or the theming attributes are attributes that do nothing: every\n * axis but `data-mode` and `data-density` is declared only in `themes.css`, and\n * an app that skips it writes `data-radius=\"sharp\"` and gets no error, no\n * warning and no corner.\n *\n * Everything below is a consumer contract. Adding an export is cheap; changing\n * or removing one is a breaking change (DESIGN-API-001).\n */\n\n// ─── Utilities ───\nexport * from './lib/overlay-container'\n\n// ─── Tokens ───\nexport { BRAND } from './tokens/brand'\nexport type { BrandColor } from './tokens/brand'\n\n// ─── Utilities ───\nexport { cn } from './lib/cn'\nexport { CONTROL_BASE, CONTROL_BORDER, isInvalid } from './lib/control'\nexport { useSelectionIndicator } from './lib/useSelectionIndicator'\nexport type { IndicatorStyle } from './lib/useSelectionIndicator'\n\n// ─── Actions ───\nexport * from './components/Button/Button'\nexport * from './components/FloatingIconButton/FloatingIconButton'\n\n// ─── Display ───\nexport * from './components/Text/Text'\nexport * from './components/Heading/Heading'\nexport * from './components/Code/Code'\nexport * from './components/CodeBlock/CodeBlock'\nexport * from './components/Markdown/Markdown'\nexport * from './components/Badge/Badge'\nexport * from './components/Tag/Tag'\nexport * from './components/Kbd/Kbd'\nexport * from './components/Avatar/Avatar'\nexport * from './components/StatusDot/StatusDot'\nexport * from './components/StatusPill/StatusPill'\nexport * from './components/LinkArrow/LinkArrow'\nexport * from './components/Separator/Separator'\nexport * from './components/Timestamp/Timestamp'\nexport * from './components/FigureBand/FigureBand'\n\n// ─── Feedback ───\nexport * from './components/Spinner/Spinner'\nexport * from './components/Skeleton/Skeleton'\nexport * from './components/Progress/Progress'\nexport * from './components/Alert/Alert'\nexport * from './components/EmptyState/EmptyState'\nexport * from './components/ErrorState/ErrorState'\nexport * from './components/Toast/Toast'\n\n// ─── Forms ───\nexport * from './components/Field/Field'\nexport * from './components/Combobox/Combobox'\nexport * from './components/DatePicker/DatePicker'\nexport * from './components/Slider/Slider'\nexport * from './components/ToggleGroup/ToggleGroup'\nexport * from './components/Input/Input'\nexport * from './components/Textarea/Textarea'\nexport * from './components/Select/Select'\nexport * from './components/NativeSelect/NativeSelect'\nexport * from './components/Checkbox/Checkbox'\nexport * from './components/RadioGroup/RadioGroup'\nexport * from './components/Switch/Switch'\n\n// ─── Overlays ───\nexport * from './components/Dialog/Dialog'\nexport * from './components/DropdownMenu/DropdownMenu'\nexport * from './components/Tooltip/Tooltip'\nexport * from './components/Popover/Popover'\nexport * from './components/Sheet/Sheet'\nexport * from './components/ContextMenu/ContextMenu'\nexport * from './components/Command/Command'\nexport * from './components/SearchableMenu/SearchableMenu'\n\n// ─── Navigation ───\nexport * from './components/Tabs/Tabs'\nexport * from './components/Accordion/Accordion'\nexport * from './components/Collapsible/Collapsible'\nexport * from './components/Breadcrumb/Breadcrumb'\nexport * from './components/Pagination/Pagination'\nexport * from './components/NavItem/NavItem'\n\n// ─── Surfaces / layout ───\nexport * from './components/Card/Card'\nexport * from './components/Table/Table'\nexport * from './components/DescriptionList/DescriptionList'\nexport * from './components/Toolbar/Toolbar'\nexport * from './components/AspectRatio/AspectRatio'\nexport * from './components/Calendar/Calendar'\nexport * from './components/ScrollArea/ScrollArea'\nexport * from './components/AppShell/AppShell'\nexport * from './components/Article/Article'\nexport * from './components/Diagram/Diagram'\nexport * from './components/Steps/Steps'\n"],"mappings":"AAyBA,cAAc;AAGd,SAAS,aAAa;AAItB,SAAS,UAAU;AACnB,SAAS,cAAc,gBAAgB,iBAAiB;AACxD,SAAS,6BAA6B;AAItC,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @misoto22/design — public entry.\n *\n * Components export from here; the look ships separately as CSS:\n *\n * import '@misoto22/design/styles.css' // compiled Tailwind + tokens + fonts\n *\n * or, for an app that already compiles Tailwind itself, the portable layers on\n * their own:\n *\n * import '@misoto22/design/tokens.css' // primitives, plus data-mode and data-density\n * import '@misoto22/design/semantic.css' // roles\n * import '@misoto22/design/themes.css' // the other six theming axes\n * import '@misoto22/design/keyframes.css' // motion\n *\n * All four, or the theming attributes are attributes that do nothing: every\n * axis but `data-mode` and `data-density` is declared only in `themes.css`, and\n * an app that skips it writes `data-radius=\"sharp\"` and gets no error, no\n * warning and no corner.\n *\n * Everything below is a consumer contract. Adding an export is cheap; changing\n * or removing one is a breaking change (DESIGN-API-001).\n */\n\n// ─── Utilities ───\nexport * from './lib/overlay-container'\n\n// ─── Tokens ───\nexport { BRAND } from './tokens/brand'\nexport type { BrandColor } from './tokens/brand'\n\n// ─── Utilities ───\nexport { cn } from './lib/cn'\nexport { CONTROL_BASE, CONTROL_BORDER, isInvalid } from './lib/control'\nexport { useSelectionIndicator } from './lib/useSelectionIndicator'\nexport type { IndicatorStyle } from './lib/useSelectionIndicator'\n\n// ─── Actions ───\nexport * from './components/Button/Button'\nexport * from './components/FloatingIconButton/FloatingIconButton'\n\n// ─── Display ───\nexport * from './components/Text/Text'\nexport * from './components/Heading/Heading'\nexport * from './components/Code/Code'\nexport * from './components/CodeBlock/CodeBlock'\nexport * from './components/Markdown/Markdown'\nexport * from './components/Badge/Badge'\nexport * from './components/Tag/Tag'\nexport * from './components/Kbd/Kbd'\nexport * from './components/Avatar/Avatar'\nexport * from './components/StatusDot/StatusDot'\nexport * from './components/StatusPill/StatusPill'\nexport * from './components/LinkArrow/LinkArrow'\nexport * from './components/Separator/Separator'\nexport * from './components/Timestamp/Timestamp'\nexport * from './components/FigureBand/FigureBand'\n\n// ─── Feedback ───\nexport * from './components/Spinner/Spinner'\nexport * from './components/Skeleton/Skeleton'\nexport * from './components/Progress/Progress'\nexport * from './components/Alert/Alert'\nexport * from './components/EmptyState/EmptyState'\nexport * from './components/ErrorState/ErrorState'\nexport * from './components/Toast/Toast'\n\n// ─── Forms ───\nexport * from './components/Field/Field'\nexport * from './components/ColorPicker/ColorPicker'\nexport * from './components/Combobox/Combobox'\nexport * from './components/DatePicker/DatePicker'\nexport * from './components/NumberField/NumberField'\nexport * from './components/Slider/Slider'\nexport * from './components/ToggleGroup/ToggleGroup'\nexport * from './components/Input/Input'\nexport * from './components/Textarea/Textarea'\nexport * from './components/Select/Select'\nexport * from './components/NativeSelect/NativeSelect'\nexport * from './components/Checkbox/Checkbox'\nexport * from './components/RadioGroup/RadioGroup'\nexport * from './components/Switch/Switch'\n\n// ─── Overlays ───\nexport * from './components/Dialog/Dialog'\nexport * from './components/DropdownMenu/DropdownMenu'\nexport * from './components/Tooltip/Tooltip'\nexport * from './components/Popover/Popover'\nexport * from './components/Sheet/Sheet'\nexport * from './components/ContextMenu/ContextMenu'\nexport * from './components/Command/Command'\nexport * from './components/SearchableMenu/SearchableMenu'\n\n// ─── Navigation ───\nexport * from './components/Tabs/Tabs'\nexport * from './components/Accordion/Accordion'\nexport * from './components/Collapsible/Collapsible'\nexport * from './components/Breadcrumb/Breadcrumb'\nexport * from './components/Pagination/Pagination'\nexport * from './components/NavItem/NavItem'\nexport * from './components/Sidebar/Sidebar'\n\n// ─── Surfaces / layout ───\nexport * from './components/Card/Card'\nexport * from './components/Table/Table'\nexport * from './components/DescriptionList/DescriptionList'\nexport * from './components/Toolbar/Toolbar'\nexport * from './components/AspectRatio/AspectRatio'\nexport * from './components/Calendar/Calendar'\nexport * from './components/ScrollArea/ScrollArea'\nexport * from './components/AppShell/AppShell'\nexport * from './components/Article/Article'\nexport * from './components/Diagram/Diagram'\nexport * from './components/Steps/Steps'\n"],"mappings":"AAyBA,cAAc;AAGd,SAAS,aAAa;AAItB,SAAS,UAAU;AACnB,SAAS,cAAc,gBAAgB,iBAAiB;AACxD,SAAS,6BAA6B;AAItC,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * CSS colour maths, with no dependency and no DOM.
3
+ *
4
+ * Adapted from DialKit (https://github.com/joshpuckett/dialkit), MIT licensed:
5
+ *
6
+ * Copyright (c) 2026 Josh Puckett
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a
9
+ * copy of this software and associated documentation files (the "Software"),
10
+ * to deal in the Software without restriction, including without limitation
11
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12
+ * and/or sell copies of the Software, and to permit persons to whom the
13
+ * Software is furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in
16
+ * all copies or substantial portions of the Software.
17
+ *
18
+ * The working space is OKLCH, and that is the substantive choice here rather
19
+ * than an implementation detail. A picker built on HSV hands a reader two
20
+ * colours at the same "lightness" where one of them is visibly darker, because
21
+ * HSV's lightness is a property of the numbers rather than of the eye. Every
22
+ * decision in `ColorPicker` — where the handle sits, what the hue strip looks
23
+ * like at this lightness, which colours are reachable at all — depends on a
24
+ * space that is perceptually uniform, and OKLCH is the one CSS ships.
25
+ *
26
+ * The matrices are the CSS Color 4 reference implementation's, D65.
27
+ * https://www.w3.org/TR/css-color-4/#color-conversion-code
28
+ */
29
+ /** A colour in OKLCH: lightness 0–1, chroma, hue in degrees, alpha 0–1. */
30
+ interface Color {
31
+ l: number;
32
+ c: number;
33
+ h: number;
34
+ a: number;
35
+ }
36
+ /** The notations this module reads and writes. */
37
+ type ColorFormat = 'hex' | 'oklch' | 'p3';
38
+ /** The two RGB gamuts a colour can be fitted to. */
39
+ type ColorSpace = 'srgb' | 'p3';
40
+ type Triple = [number, number, number];
41
+ declare const clamp: (n: number, min?: number, max?: number) => number;
42
+ declare const wrapHue: (h: number) => number;
43
+ /** Gamma-encoded RGB in 0–1 to OKLCH. */
44
+ declare function rgbToColor(rgb: Triple, a?: number, space?: ColorSpace): Color;
45
+ /** OKLCH to gamma-encoded RGB in 0–1. May fall outside it — see `fitGamut`. */
46
+ declare function colorToRgb(color: Color, space?: ColorSpace): Triple;
47
+ declare function inGamut(color: Color, space?: ColorSpace): boolean;
48
+ /**
49
+ * Brings a colour inside a gamut by giving up CHROMA, and nothing else.
50
+ *
51
+ * The naive fix is to clip each channel at 0 and 1, which changes the hue and
52
+ * the lightness together — a vivid blue clipped that way comes back purple and
53
+ * darker. Bisecting on chroma keeps both, so what a reader loses is only the
54
+ * saturation the display could never have shown them.
55
+ */
56
+ declare function fitGamut(color: Color, space?: ColorSpace): Color;
57
+ /** The most chroma a gamut holds at this lightness and hue — the plane's edge. */
58
+ declare function maxChroma(l: number, h: number, space?: ColorSpace): number;
59
+ /** Which notation a string is written in. Anything unrecognised reads as hex. */
60
+ declare function colorFormat(value: string): ColorFormat;
61
+ /**
62
+ * A colour as a CSS string.
63
+ *
64
+ * Hex and Display P3 are bounded output spaces, so the colour is fitted on the
65
+ * way out; OKLCH is not, and writing an out-of-gamut OKLCH is legitimate — the
66
+ * browser fits it to whatever the display actually has.
67
+ */
68
+ declare function formatColor(color: Color, format: ColorFormat): string;
69
+ /**
70
+ * An absolute CSS colour as OKLCH, or `null` when the string is not one.
71
+ *
72
+ * Hex, `rgb()`, `hsl()`, `oklch()` and `color(display-p3 …)`, in both the
73
+ * comma and the space syntax. Named colours are deliberately absent: resolving
74
+ * them needs a table of a hundred and forty-eight entries or a DOM, and a
75
+ * picker that accepts "rebeccapurple" but not "papayawhip" is worse than one
76
+ * that accepts neither.
77
+ */
78
+ declare function parseColor(value: string): Color | null;
79
+
80
+ export { type Color, type ColorFormat, type ColorSpace, clamp, colorFormat, colorToRgb, fitGamut, formatColor, inGamut, maxChroma, parseColor, rgbToColor, wrapHue };
@@ -0,0 +1,189 @@
1
+ const clamp = (n, min = 0, max = 1) => Math.max(min, Math.min(max, n));
2
+ const wrapHue = (h) => (h % 360 + 360) % 360;
3
+ const multiply = (m, v) => m.map((row) => row.reduce((n, x, i) => n + x * (v[i] ?? 0), 0));
4
+ const linearize = (v) => Math.abs(v) <= 0.04045 ? v / 12.92 : Math.sign(v) * ((Math.abs(v) + 0.055) / 1.055) ** 2.4;
5
+ const encode = (v) => Math.abs(v) <= 31308e-7 ? 12.92 * v : Math.sign(v) * (1.055 * Math.abs(v) ** (1 / 2.4) - 0.055);
6
+ const RGB_XYZ = [
7
+ [0.4123907993, 0.3575843394, 0.1804807884],
8
+ [0.2126390059, 0.7151686788, 0.0721923154],
9
+ [0.0193308187, 0.1191947798, 0.9505321522]
10
+ ];
11
+ const P3_XYZ = [
12
+ [0.4865709486, 0.2656676932, 0.1982172852],
13
+ [0.2289745641, 0.6917385218, 0.0792869141],
14
+ [0, 0.0451133819, 1.0439443689]
15
+ ];
16
+ const XYZ_RGB = [
17
+ [3.2409699419, -1.5373831776, -0.4986107603],
18
+ [-0.9692436363, 1.8759675015, 0.0415550574],
19
+ [0.0556300797, -0.2039769589, 1.0569715142]
20
+ ];
21
+ const XYZ_P3 = [
22
+ [2.4934969119, -0.9313836179, -0.4027107845],
23
+ [-0.8294889696, 1.7626640603, 0.0236246858],
24
+ [0.0358458302, -0.0761723893, 0.956884524]
25
+ ];
26
+ const XYZ_LMS = [
27
+ [0.819022438, 0.3619062601, -0.1288737815],
28
+ [0.0329836539, 0.9292868616, 0.0361446664],
29
+ [0.0481771894, 0.2642395318, 0.6335478285]
30
+ ];
31
+ const LMS_XYZ = [
32
+ [1.2268798734, -0.5578149966, 0.2813910502],
33
+ [-0.0405757626, 1.1122868294, -0.0717110667],
34
+ [-0.0763729497, -0.421493324, 1.5869240244]
35
+ ];
36
+ function rgbToColor(rgb, a = 1, space = "srgb") {
37
+ const xyz = multiply(space === "p3" ? P3_XYZ : RGB_XYZ, rgb.map(linearize));
38
+ const [l, m, s] = multiply(XYZ_LMS, xyz).map(Math.cbrt);
39
+ const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
40
+ const A = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
41
+ const B = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
42
+ const c = Math.hypot(A, B);
43
+ const grey = c < 1e-7;
44
+ return {
45
+ l: clamp(L),
46
+ c: grey ? 0 : c,
47
+ h: grey ? 0 : wrapHue(Math.atan2(B, A) * 180 / Math.PI),
48
+ a: clamp(a)
49
+ };
50
+ }
51
+ function colorToRgb(color, space = "srgb") {
52
+ const a = color.c * Math.cos(color.h * Math.PI / 180);
53
+ const b = color.c * Math.sin(color.h * Math.PI / 180);
54
+ const lms = [
55
+ (color.l + 0.3963377774 * a + 0.2158037573 * b) ** 3,
56
+ (color.l - 0.1055613458 * a - 0.0638541728 * b) ** 3,
57
+ (color.l - 0.0894841775 * a - 1.291485548 * b) ** 3
58
+ ];
59
+ return multiply(space === "p3" ? XYZ_P3 : XYZ_RGB, multiply(LMS_XYZ, lms)).map(encode);
60
+ }
61
+ function inGamut(color, space = "srgb") {
62
+ return colorToRgb(color, space).every((n) => n >= -1e-5 && n <= 1.00001);
63
+ }
64
+ function fitGamut(color, space = "srgb") {
65
+ if (inGamut(color, space)) return color;
66
+ let lo = 0;
67
+ let hi = color.c;
68
+ for (let i = 0; i < 20; i++) {
69
+ const mid = (lo + hi) / 2;
70
+ if (inGamut({ ...color, c: mid }, space)) lo = mid;
71
+ else hi = mid;
72
+ }
73
+ return { ...color, c: lo };
74
+ }
75
+ function maxChroma(l, h, space = "srgb") {
76
+ if (l <= 0 || l >= 1) return 0;
77
+ return fitGamut({ l, c: 0.5, h, a: 1 }, space).c;
78
+ }
79
+ function colorFormat(value) {
80
+ const text = value.trim();
81
+ if (/^oklch\(/i.test(text)) return "oklch";
82
+ if (/^color\(display-p3\s/i.test(text)) return "p3";
83
+ return "hex";
84
+ }
85
+ const round = (v, digits = 4) => Number(v.toFixed(digits));
86
+ function formatColor(color, format) {
87
+ const alpha = color.a < 1 ? ` / ${round(color.a)}` : "";
88
+ if (format === "oklch") {
89
+ return `oklch(${round(color.l)} ${round(color.c)} ${round(color.h, 2)}${alpha})`;
90
+ }
91
+ const space = format === "p3" ? "p3" : "srgb";
92
+ const rgb = colorToRgb(fitGamut(color, space), space);
93
+ if (format === "p3") {
94
+ return `color(display-p3 ${rgb.map((n) => round(clamp(n), 5)).join(" ")}${alpha})`;
95
+ }
96
+ const bytes = rgb.map((n) => Math.round(clamp(n) * 255));
97
+ if (color.a < 1) bytes.push(Math.round(color.a * 255));
98
+ return `#${bytes.map((n) => n.toString(16).padStart(2, "0")).join("")}`;
99
+ }
100
+ const NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?(%|deg|grad|rad|turn)?$/i;
101
+ function number(value, percentScale = 1, hue = false) {
102
+ const match = NUMBER.exec(value);
103
+ if (!match) return null;
104
+ const n = parseFloat(value);
105
+ if (!Number.isFinite(n)) return null;
106
+ const unit = match[1]?.toLowerCase();
107
+ if (hue) {
108
+ if (unit === "rad") return n * 180 / Math.PI;
109
+ if (unit === "turn") return n * 360;
110
+ if (unit === "grad") return n * 0.9;
111
+ return !unit || unit === "deg" ? n : null;
112
+ }
113
+ if (unit === "%") return n * percentScale / 100;
114
+ return unit ? null : n;
115
+ }
116
+ function parseColor(value) {
117
+ const text = value.trim().toLowerCase();
118
+ if (text === "transparent") return { l: 0, c: 0, h: 0, a: 0 };
119
+ if (/^#(?:[\da-f]{3,4}|[\da-f]{6}|[\da-f]{8})$/.test(text)) {
120
+ const short = text.slice(1);
121
+ const hex = short.length <= 4 ? [...short].map((c) => c + c).join("") : short;
122
+ const bytes = (hex.match(/../g) ?? []).map((c) => parseInt(c, 16) / 255);
123
+ return rgbToColor(bytes.slice(0, 3), bytes[3] ?? 1);
124
+ }
125
+ const match = /^(oklch|rgb|rgba|hsl|hsla|color)\(([^()]*)\)$/.exec(text);
126
+ if (!match) return null;
127
+ const kind = match[1] ?? "";
128
+ let body = (match[2] ?? "").trim();
129
+ const p3 = kind === "color";
130
+ if (p3) {
131
+ if (!body.startsWith("display-p3 ")) return null;
132
+ body = body.slice(11).trim();
133
+ }
134
+ const legacy = body.includes(",");
135
+ if (legacy && (p3 || kind === "oklch" || body.includes("/"))) return null;
136
+ const parts = legacy ? body.split(",").map((x) => x.trim()) : body.split(/\s*\/\s*/);
137
+ if (!legacy && parts.length > 2) return null;
138
+ const channels = legacy ? parts.slice(0, 3) : (parts[0] ?? "").split(/\s+/);
139
+ if (channels.length !== 3) return null;
140
+ if (legacy && parts.length !== 3 && parts.length !== 4) return null;
141
+ if (legacy && kind.startsWith("rgb") && channels.some((c) => c.endsWith("%")) && !channels.every((c) => c.endsWith("%"))) {
142
+ return null;
143
+ }
144
+ const alphaText = legacy ? parts[3] : parts[1];
145
+ const alpha = alphaText === void 0 ? 1 : number(alphaText);
146
+ if (alpha === null) return null;
147
+ if (kind === "oklch") {
148
+ const l = number(channels[0] ?? "");
149
+ const c = number(channels[1] ?? "", 0.4);
150
+ const h = number(channels[2] ?? "", 1, true);
151
+ if (l === null || c === null || h === null) return null;
152
+ return { l: clamp(l), c: Math.max(0, c), h: wrapHue(h), a: clamp(alpha) };
153
+ }
154
+ if (kind.startsWith("hsl")) {
155
+ const h = number(channels[0] ?? "", 1, true);
156
+ const s = number(channels[1] ?? "");
157
+ const l = number(channels[2] ?? "");
158
+ if (h === null || s === null || l === null) return null;
159
+ if (!channels[1]?.endsWith("%") || !channels[2]?.endsWith("%")) return null;
160
+ const sat = clamp(s);
161
+ const light = clamp(l);
162
+ const amount = sat * Math.min(light, 1 - light);
163
+ const channel = (n) => {
164
+ const k = (n + wrapHue(h) / 30) % 12;
165
+ return light - amount * Math.max(-1, Math.min(k - 3, 9 - k, 1));
166
+ };
167
+ return rgbToColor([channel(0), channel(8), channel(4)], alpha);
168
+ }
169
+ const values = channels.map((c) => number(c, p3 ? 1 : 255));
170
+ if (values.some((n) => n === null)) return null;
171
+ return rgbToColor(
172
+ values.map((n) => p3 ? n ?? 0 : clamp((n ?? 0) / 255)),
173
+ alpha,
174
+ p3 ? "p3" : "srgb"
175
+ );
176
+ }
177
+ export {
178
+ clamp,
179
+ colorFormat,
180
+ colorToRgb,
181
+ fitGamut,
182
+ formatColor,
183
+ inGamut,
184
+ maxChroma,
185
+ parseColor,
186
+ rgbToColor,
187
+ wrapHue
188
+ };
189
+ //# sourceMappingURL=color.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/color.ts"],"sourcesContent":["/**\n * CSS colour maths, with no dependency and no DOM.\n *\n * Adapted from DialKit (https://github.com/joshpuckett/dialkit), MIT licensed:\n *\n * Copyright (c) 2026 Josh Puckett\n *\n * Permission is hereby granted, free of charge, to any person obtaining a\n * copy of this software and associated documentation files (the \"Software\"),\n * to deal in the Software without restriction, including without limitation\n * the rights to use, copy, modify, merge, publish, distribute, sublicense,\n * and/or sell copies of the Software, and to permit persons to whom the\n * Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * The working space is OKLCH, and that is the substantive choice here rather\n * than an implementation detail. A picker built on HSV hands a reader two\n * colours at the same \"lightness\" where one of them is visibly darker, because\n * HSV's lightness is a property of the numbers rather than of the eye. Every\n * decision in `ColorPicker` — where the handle sits, what the hue strip looks\n * like at this lightness, which colours are reachable at all — depends on a\n * space that is perceptually uniform, and OKLCH is the one CSS ships.\n *\n * The matrices are the CSS Color 4 reference implementation's, D65.\n * https://www.w3.org/TR/css-color-4/#color-conversion-code\n */\n\n/** A colour in OKLCH: lightness 0–1, chroma, hue in degrees, alpha 0–1. */\nexport interface Color {\n l: number\n c: number\n h: number\n a: number\n}\n\n/** The notations this module reads and writes. */\nexport type ColorFormat = 'hex' | 'oklch' | 'p3'\n\n/** The two RGB gamuts a colour can be fitted to. */\nexport type ColorSpace = 'srgb' | 'p3'\n\ntype Triple = [number, number, number]\n\nexport const clamp = (n: number, min = 0, max = 1) => Math.max(min, Math.min(max, n))\n\nexport const wrapHue = (h: number) => ((h % 360) + 360) % 360\n\nconst multiply = (m: number[][], v: Triple): Triple =>\n m.map((row) => row.reduce((n, x, i) => n + x * (v[i] ?? 0), 0)) as Triple\n\n/** sRGB transfer function, undone. Signed, so an out-of-gamut channel survives. */\nconst linearize = (v: number) =>\n Math.abs(v) <= 0.04045 ? v / 12.92 : Math.sign(v) * ((Math.abs(v) + 0.055) / 1.055) ** 2.4\n\nconst encode = (v: number) =>\n Math.abs(v) <= 0.0031308 ? 12.92 * v : Math.sign(v) * (1.055 * Math.abs(v) ** (1 / 2.4) - 0.055)\n\nconst RGB_XYZ = [\n [0.4123907993, 0.3575843394, 0.1804807884],\n [0.2126390059, 0.7151686788, 0.0721923154],\n [0.0193308187, 0.1191947798, 0.9505321522],\n]\nconst P3_XYZ = [\n [0.4865709486, 0.2656676932, 0.1982172852],\n [0.2289745641, 0.6917385218, 0.0792869141],\n [0, 0.0451133819, 1.0439443689],\n]\nconst XYZ_RGB = [\n [3.2409699419, -1.5373831776, -0.4986107603],\n [-0.9692436363, 1.8759675015, 0.0415550574],\n [0.0556300797, -0.2039769589, 1.0569715142],\n]\nconst XYZ_P3 = [\n [2.4934969119, -0.9313836179, -0.4027107845],\n [-0.8294889696, 1.7626640603, 0.0236246858],\n [0.0358458302, -0.0761723893, 0.956884524],\n]\nconst XYZ_LMS = [\n [0.819022438, 0.3619062601, -0.1288737815],\n [0.0329836539, 0.9292868616, 0.0361446664],\n [0.0481771894, 0.2642395318, 0.6335478285],\n]\nconst LMS_XYZ = [\n [1.2268798734, -0.5578149966, 0.2813910502],\n [-0.0405757626, 1.1122868294, -0.0717110667],\n [-0.0763729497, -0.421493324, 1.5869240244],\n]\n\n/** Gamma-encoded RGB in 0–1 to OKLCH. */\nexport function rgbToColor(rgb: Triple, a = 1, space: ColorSpace = 'srgb'): Color {\n const xyz = multiply(space === 'p3' ? P3_XYZ : RGB_XYZ, rgb.map(linearize) as Triple)\n const [l, m, s] = multiply(XYZ_LMS, xyz).map(Math.cbrt) as Triple\n const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s\n const A = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s\n const B = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s\n const c = Math.hypot(A, B)\n // Below the threshold the hue is the angle of rounding error, not a hue. Left\n // in, a slider dragged to pure white would spin the strip through 360°.\n const grey = c < 1e-7\n return {\n l: clamp(L),\n c: grey ? 0 : c,\n h: grey ? 0 : wrapHue((Math.atan2(B, A) * 180) / Math.PI),\n a: clamp(a),\n }\n}\n\n/** OKLCH to gamma-encoded RGB in 0–1. May fall outside it — see `fitGamut`. */\nexport function colorToRgb(color: Color, space: ColorSpace = 'srgb'): Triple {\n const a = color.c * Math.cos((color.h * Math.PI) / 180)\n const b = color.c * Math.sin((color.h * Math.PI) / 180)\n const lms: Triple = [\n (color.l + 0.3963377774 * a + 0.2158037573 * b) ** 3,\n (color.l - 0.1055613458 * a - 0.0638541728 * b) ** 3,\n (color.l - 0.0894841775 * a - 1.291485548 * b) ** 3,\n ]\n return multiply(space === 'p3' ? XYZ_P3 : XYZ_RGB, multiply(LMS_XYZ, lms)).map(encode) as Triple\n}\n\nexport function inGamut(color: Color, space: ColorSpace = 'srgb'): boolean {\n return colorToRgb(color, space).every((n) => n >= -0.00001 && n <= 1.00001)\n}\n\n/**\n * Brings a colour inside a gamut by giving up CHROMA, and nothing else.\n *\n * The naive fix is to clip each channel at 0 and 1, which changes the hue and\n * the lightness together — a vivid blue clipped that way comes back purple and\n * darker. Bisecting on chroma keeps both, so what a reader loses is only the\n * saturation the display could never have shown them.\n */\nexport function fitGamut(color: Color, space: ColorSpace = 'srgb'): Color {\n if (inGamut(color, space)) return color\n let lo = 0\n let hi = color.c\n // Twenty halvings of a chroma under 0.5 lands well inside a 24-bit step.\n for (let i = 0; i < 20; i++) {\n const mid = (lo + hi) / 2\n if (inGamut({ ...color, c: mid }, space)) lo = mid\n else hi = mid\n }\n return { ...color, c: lo }\n}\n\n/** The most chroma a gamut holds at this lightness and hue — the plane's edge. */\nexport function maxChroma(l: number, h: number, space: ColorSpace = 'srgb'): number {\n if (l <= 0 || l >= 1) return 0\n return fitGamut({ l, c: 0.5, h, a: 1 }, space).c\n}\n\n/** Which notation a string is written in. Anything unrecognised reads as hex. */\nexport function colorFormat(value: string): ColorFormat {\n const text = value.trim()\n if (/^oklch\\(/i.test(text)) return 'oklch'\n if (/^color\\(display-p3\\s/i.test(text)) return 'p3'\n return 'hex'\n}\n\nconst round = (v: number, digits = 4) => Number(v.toFixed(digits))\n\n/**\n * A colour as a CSS string.\n *\n * Hex and Display P3 are bounded output spaces, so the colour is fitted on the\n * way out; OKLCH is not, and writing an out-of-gamut OKLCH is legitimate — the\n * browser fits it to whatever the display actually has.\n */\nexport function formatColor(color: Color, format: ColorFormat): string {\n const alpha = color.a < 1 ? ` / ${round(color.a)}` : ''\n if (format === 'oklch') {\n return `oklch(${round(color.l)} ${round(color.c)} ${round(color.h, 2)}${alpha})`\n }\n const space: ColorSpace = format === 'p3' ? 'p3' : 'srgb'\n const rgb = colorToRgb(fitGamut(color, space), space)\n if (format === 'p3') {\n return `color(display-p3 ${rgb.map((n) => round(clamp(n), 5)).join(' ')}${alpha})`\n }\n const bytes = rgb.map((n) => Math.round(clamp(n) * 255))\n if (color.a < 1) bytes.push(Math.round(color.a * 255))\n return `#${bytes.map((n) => n.toString(16).padStart(2, '0')).join('')}`\n}\n\nconst NUMBER = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:e[+-]?\\d+)?(%|deg|grad|rad|turn)?$/i\n\n/** One CSS numeric token, in the unit its channel allows. */\nfunction number(value: string, percentScale = 1, hue = false): number | null {\n const match = NUMBER.exec(value)\n if (!match) return null\n const n = parseFloat(value)\n if (!Number.isFinite(n)) return null\n const unit = match[1]?.toLowerCase()\n if (hue) {\n if (unit === 'rad') return (n * 180) / Math.PI\n if (unit === 'turn') return n * 360\n if (unit === 'grad') return n * 0.9\n return !unit || unit === 'deg' ? n : null\n }\n if (unit === '%') return (n * percentScale) / 100\n return unit ? null : n\n}\n\n/**\n * An absolute CSS colour as OKLCH, or `null` when the string is not one.\n *\n * Hex, `rgb()`, `hsl()`, `oklch()` and `color(display-p3 …)`, in both the\n * comma and the space syntax. Named colours are deliberately absent: resolving\n * them needs a table of a hundred and forty-eight entries or a DOM, and a\n * picker that accepts \"rebeccapurple\" but not \"papayawhip\" is worse than one\n * that accepts neither.\n */\nexport function parseColor(value: string): Color | null {\n const text = value.trim().toLowerCase()\n if (text === 'transparent') return { l: 0, c: 0, h: 0, a: 0 }\n\n if (/^#(?:[\\da-f]{3,4}|[\\da-f]{6}|[\\da-f]{8})$/.test(text)) {\n const short = text.slice(1)\n const hex = short.length <= 4 ? [...short].map((c) => c + c).join('') : short\n const bytes = (hex.match(/../g) ?? []).map((c) => parseInt(c, 16) / 255)\n return rgbToColor(bytes.slice(0, 3) as Triple, bytes[3] ?? 1)\n }\n\n const match = /^(oklch|rgb|rgba|hsl|hsla|color)\\(([^()]*)\\)$/.exec(text)\n if (!match) return null\n const kind = match[1] ?? ''\n let body = (match[2] ?? '').trim()\n\n const p3 = kind === 'color'\n if (p3) {\n if (!body.startsWith('display-p3 ')) return null\n body = body.slice(11).trim()\n }\n\n // The comma syntax is the legacy one, and it never applied to `oklch()` or\n // `color()`; mixing it with the modern `/` alpha is not a colour either.\n const legacy = body.includes(',')\n if (legacy && (p3 || kind === 'oklch' || body.includes('/'))) return null\n\n const parts = legacy ? body.split(',').map((x) => x.trim()) : body.split(/\\s*\\/\\s*/)\n if (!legacy && parts.length > 2) return null\n const channels = legacy ? parts.slice(0, 3) : (parts[0] ?? '').split(/\\s+/)\n if (channels.length !== 3) return null\n if (legacy && parts.length !== 3 && parts.length !== 4) return null\n // Legacy rgb() is all percentages or all numbers, never a mixture.\n if (\n legacy &&\n kind.startsWith('rgb') &&\n channels.some((c) => c.endsWith('%')) &&\n !channels.every((c) => c.endsWith('%'))\n ) {\n return null\n }\n\n const alphaText = legacy ? parts[3] : parts[1]\n const alpha = alphaText === undefined ? 1 : number(alphaText)\n if (alpha === null) return null\n\n if (kind === 'oklch') {\n const l = number(channels[0] ?? '')\n // 100% chroma is 0.4 by the specification, not 1.\n const c = number(channels[1] ?? '', 0.4)\n const h = number(channels[2] ?? '', 1, true)\n if (l === null || c === null || h === null) return null\n return { l: clamp(l), c: Math.max(0, c), h: wrapHue(h), a: clamp(alpha) }\n }\n\n if (kind.startsWith('hsl')) {\n const h = number(channels[0] ?? '', 1, true)\n const s = number(channels[1] ?? '')\n const l = number(channels[2] ?? '')\n if (h === null || s === null || l === null) return null\n if (!channels[1]?.endsWith('%') || !channels[2]?.endsWith('%')) return null\n const sat = clamp(s)\n const light = clamp(l)\n const amount = sat * Math.min(light, 1 - light)\n const channel = (n: number) => {\n const k = (n + wrapHue(h) / 30) % 12\n return light - amount * Math.max(-1, Math.min(k - 3, 9 - k, 1))\n }\n return rgbToColor([channel(0), channel(8), channel(4)], alpha)\n }\n\n const values = channels.map((c) => number(c, p3 ? 1 : 255))\n if (values.some((n) => n === null)) return null\n return rgbToColor(\n values.map((n) => (p3 ? (n ?? 0) : clamp((n ?? 0) / 255))) as Triple,\n alpha,\n p3 ? 'p3' : 'srgb',\n )\n}\n"],"mappings":"AA6CO,MAAM,QAAQ,CAAC,GAAW,MAAM,GAAG,MAAM,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,CAAC,CAAC;AAE7E,MAAM,UAAU,CAAC,OAAgB,IAAI,MAAO,OAAO;AAE1D,MAAM,WAAW,CAAC,GAAe,MAC/B,EAAE,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC;AAGhE,MAAM,YAAY,CAAC,MACjB,KAAK,IAAI,CAAC,KAAK,UAAU,IAAI,QAAQ,KAAK,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,IAAI,SAAS,UAAU;AAEzF,MAAM,SAAS,CAAC,MACd,KAAK,IAAI,CAAC,KAAK,WAAY,QAAQ,IAAI,KAAK,KAAK,CAAC,KAAK,QAAQ,KAAK,IAAI,CAAC,MAAM,IAAI,OAAO;AAE5F,MAAM,UAAU;AAAA,EACd,CAAC,cAAc,cAAc,YAAY;AAAA,EACzC,CAAC,cAAc,cAAc,YAAY;AAAA,EACzC,CAAC,cAAc,cAAc,YAAY;AAC3C;AACA,MAAM,SAAS;AAAA,EACb,CAAC,cAAc,cAAc,YAAY;AAAA,EACzC,CAAC,cAAc,cAAc,YAAY;AAAA,EACzC,CAAC,GAAG,cAAc,YAAY;AAChC;AACA,MAAM,UAAU;AAAA,EACd,CAAC,cAAc,eAAe,aAAa;AAAA,EAC3C,CAAC,eAAe,cAAc,YAAY;AAAA,EAC1C,CAAC,cAAc,eAAe,YAAY;AAC5C;AACA,MAAM,SAAS;AAAA,EACb,CAAC,cAAc,eAAe,aAAa;AAAA,EAC3C,CAAC,eAAe,cAAc,YAAY;AAAA,EAC1C,CAAC,cAAc,eAAe,WAAW;AAC3C;AACA,MAAM,UAAU;AAAA,EACd,CAAC,aAAa,cAAc,aAAa;AAAA,EACzC,CAAC,cAAc,cAAc,YAAY;AAAA,EACzC,CAAC,cAAc,cAAc,YAAY;AAC3C;AACA,MAAM,UAAU;AAAA,EACd,CAAC,cAAc,eAAe,YAAY;AAAA,EAC1C,CAAC,eAAe,cAAc,aAAa;AAAA,EAC3C,CAAC,eAAe,cAAc,YAAY;AAC5C;AAGO,SAAS,WAAW,KAAa,IAAI,GAAG,QAAoB,QAAe;AAChF,QAAM,MAAM,SAAS,UAAU,OAAO,SAAS,SAAS,IAAI,IAAI,SAAS,CAAW;AACpF,QAAM,CAAC,GAAG,GAAG,CAAC,IAAI,SAAS,SAAS,GAAG,EAAE,IAAI,KAAK,IAAI;AACtD,QAAM,IAAI,eAAe,IAAI,cAAc,IAAI,eAAe;AAC9D,QAAM,IAAI,eAAe,IAAI,cAAc,IAAI,eAAe;AAC9D,QAAM,IAAI,eAAe,IAAI,eAAe,IAAI,cAAc;AAC9D,QAAM,IAAI,KAAK,MAAM,GAAG,CAAC;AAGzB,QAAM,OAAO,IAAI;AACjB,SAAO;AAAA,IACL,GAAG,MAAM,CAAC;AAAA,IACV,GAAG,OAAO,IAAI;AAAA,IACd,GAAG,OAAO,IAAI,QAAS,KAAK,MAAM,GAAG,CAAC,IAAI,MAAO,KAAK,EAAE;AAAA,IACxD,GAAG,MAAM,CAAC;AAAA,EACZ;AACF;AAGO,SAAS,WAAW,OAAc,QAAoB,QAAgB;AAC3E,QAAM,IAAI,MAAM,IAAI,KAAK,IAAK,MAAM,IAAI,KAAK,KAAM,GAAG;AACtD,QAAM,IAAI,MAAM,IAAI,KAAK,IAAK,MAAM,IAAI,KAAK,KAAM,GAAG;AACtD,QAAM,MAAc;AAAA,KACjB,MAAM,IAAI,eAAe,IAAI,eAAe,MAAM;AAAA,KAClD,MAAM,IAAI,eAAe,IAAI,eAAe,MAAM;AAAA,KAClD,MAAM,IAAI,eAAe,IAAI,cAAc,MAAM;AAAA,EACpD;AACA,SAAO,SAAS,UAAU,OAAO,SAAS,SAAS,SAAS,SAAS,GAAG,CAAC,EAAE,IAAI,MAAM;AACvF;AAEO,SAAS,QAAQ,OAAc,QAAoB,QAAiB;AACzE,SAAO,WAAW,OAAO,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,SAAY,KAAK,OAAO;AAC5E;AAUO,SAAS,SAAS,OAAc,QAAoB,QAAe;AACxE,MAAI,QAAQ,OAAO,KAAK,EAAG,QAAO;AAClC,MAAI,KAAK;AACT,MAAI,KAAK,MAAM;AAEf,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAM,OAAO,KAAK,MAAM;AACxB,QAAI,QAAQ,EAAE,GAAG,OAAO,GAAG,IAAI,GAAG,KAAK,EAAG,MAAK;AAAA,QAC1C,MAAK;AAAA,EACZ;AACA,SAAO,EAAE,GAAG,OAAO,GAAG,GAAG;AAC3B;AAGO,SAAS,UAAU,GAAW,GAAW,QAAoB,QAAgB;AAClF,MAAI,KAAK,KAAK,KAAK,EAAG,QAAO;AAC7B,SAAO,SAAS,EAAE,GAAG,GAAG,KAAK,GAAG,GAAG,EAAE,GAAG,KAAK,EAAE;AACjD;AAGO,SAAS,YAAY,OAA4B;AACtD,QAAM,OAAO,MAAM,KAAK;AACxB,MAAI,YAAY,KAAK,IAAI,EAAG,QAAO;AACnC,MAAI,wBAAwB,KAAK,IAAI,EAAG,QAAO;AAC/C,SAAO;AACT;AAEA,MAAM,QAAQ,CAAC,GAAW,SAAS,MAAM,OAAO,EAAE,QAAQ,MAAM,CAAC;AAS1D,SAAS,YAAY,OAAc,QAA6B;AACrE,QAAM,QAAQ,MAAM,IAAI,IAAI,MAAM,MAAM,MAAM,CAAC,CAAC,KAAK;AACrD,MAAI,WAAW,SAAS;AACtB,WAAO,SAAS,MAAM,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,CAAC,CAAC,IAAI,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,KAAK;AAAA,EAC/E;AACA,QAAM,QAAoB,WAAW,OAAO,OAAO;AACnD,QAAM,MAAM,WAAW,SAAS,OAAO,KAAK,GAAG,KAAK;AACpD,MAAI,WAAW,MAAM;AACnB,WAAO,oBAAoB,IAAI,IAAI,CAAC,MAAM,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,GAAG,KAAK;AAAA,EACjF;AACA,QAAM,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,MAAM,CAAC,IAAI,GAAG,CAAC;AACvD,MAAI,MAAM,IAAI,EAAG,OAAM,KAAK,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC;AACrD,SAAO,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;AACvE;AAEA,MAAM,SAAS;AAGf,SAAS,OAAO,OAAe,eAAe,GAAG,MAAM,OAAsB;AAC3E,QAAM,QAAQ,OAAO,KAAK,KAAK;AAC/B,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,IAAI,WAAW,KAAK;AAC1B,MAAI,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AAChC,QAAM,OAAO,MAAM,CAAC,GAAG,YAAY;AACnC,MAAI,KAAK;AACP,QAAI,SAAS,MAAO,QAAQ,IAAI,MAAO,KAAK;AAC5C,QAAI,SAAS,OAAQ,QAAO,IAAI;AAChC,QAAI,SAAS,OAAQ,QAAO,IAAI;AAChC,WAAO,CAAC,QAAQ,SAAS,QAAQ,IAAI;AAAA,EACvC;AACA,MAAI,SAAS,IAAK,QAAQ,IAAI,eAAgB;AAC9C,SAAO,OAAO,OAAO;AACvB;AAWO,SAAS,WAAW,OAA6B;AACtD,QAAM,OAAO,MAAM,KAAK,EAAE,YAAY;AACtC,MAAI,SAAS,cAAe,QAAO,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AAE5D,MAAI,4CAA4C,KAAK,IAAI,GAAG;AAC1D,UAAM,QAAQ,KAAK,MAAM,CAAC;AAC1B,UAAM,MAAM,MAAM,UAAU,IAAI,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI;AACxE,UAAM,SAAS,IAAI,MAAM,KAAK,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,IAAI,GAAG;AACvE,WAAO,WAAW,MAAM,MAAM,GAAG,CAAC,GAAa,MAAM,CAAC,KAAK,CAAC;AAAA,EAC9D;AAEA,QAAM,QAAQ,gDAAgD,KAAK,IAAI;AACvE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,MAAM,CAAC,KAAK;AACzB,MAAI,QAAQ,MAAM,CAAC,KAAK,IAAI,KAAK;AAEjC,QAAM,KAAK,SAAS;AACpB,MAAI,IAAI;AACN,QAAI,CAAC,KAAK,WAAW,aAAa,EAAG,QAAO;AAC5C,WAAO,KAAK,MAAM,EAAE,EAAE,KAAK;AAAA,EAC7B;AAIA,QAAM,SAAS,KAAK,SAAS,GAAG;AAChC,MAAI,WAAW,MAAM,SAAS,WAAW,KAAK,SAAS,GAAG,GAAI,QAAO;AAErE,QAAM,QAAQ,SAAS,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,KAAK,MAAM,UAAU;AACnF,MAAI,CAAC,UAAU,MAAM,SAAS,EAAG,QAAO;AACxC,QAAM,WAAW,SAAS,MAAM,MAAM,GAAG,CAAC,KAAK,MAAM,CAAC,KAAK,IAAI,MAAM,KAAK;AAC1E,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,MAAI,UAAU,MAAM,WAAW,KAAK,MAAM,WAAW,EAAG,QAAO;AAE/D,MACE,UACA,KAAK,WAAW,KAAK,KACrB,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,GAAG,CAAC,KACpC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,CAAC,GACtC;AACA,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,SAAS,MAAM,CAAC,IAAI,MAAM,CAAC;AAC7C,QAAM,QAAQ,cAAc,SAAY,IAAI,OAAO,SAAS;AAC5D,MAAI,UAAU,KAAM,QAAO;AAE3B,MAAI,SAAS,SAAS;AACpB,UAAM,IAAI,OAAO,SAAS,CAAC,KAAK,EAAE;AAElC,UAAM,IAAI,OAAO,SAAS,CAAC,KAAK,IAAI,GAAG;AACvC,UAAM,IAAI,OAAO,SAAS,CAAC,KAAK,IAAI,GAAG,IAAI;AAC3C,QAAI,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAM,QAAO;AACnD,WAAO,EAAE,GAAG,MAAM,CAAC,GAAG,GAAG,KAAK,IAAI,GAAG,CAAC,GAAG,GAAG,QAAQ,CAAC,GAAG,GAAG,MAAM,KAAK,EAAE;AAAA,EAC1E;AAEA,MAAI,KAAK,WAAW,KAAK,GAAG;AAC1B,UAAM,IAAI,OAAO,SAAS,CAAC,KAAK,IAAI,GAAG,IAAI;AAC3C,UAAM,IAAI,OAAO,SAAS,CAAC,KAAK,EAAE;AAClC,UAAM,IAAI,OAAO,SAAS,CAAC,KAAK,EAAE;AAClC,QAAI,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAM,QAAO;AACnD,QAAI,CAAC,SAAS,CAAC,GAAG,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,SAAS,GAAG,EAAG,QAAO;AACvE,UAAM,MAAM,MAAM,CAAC;AACnB,UAAM,QAAQ,MAAM,CAAC;AACrB,UAAM,SAAS,MAAM,KAAK,IAAI,OAAO,IAAI,KAAK;AAC9C,UAAM,UAAU,CAAC,MAAc;AAC7B,YAAM,KAAK,IAAI,QAAQ,CAAC,IAAI,MAAM;AAClC,aAAO,QAAQ,SAAS,KAAK,IAAI,IAAI,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;AAAA,IAChE;AACA,WAAO,WAAW,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,KAAK;AAAA,EAC/D;AAEA,QAAM,SAAS,SAAS,IAAI,CAAC,MAAM,OAAO,GAAG,KAAK,IAAI,GAAG,CAAC;AAC1D,MAAI,OAAO,KAAK,CAAC,MAAM,MAAM,IAAI,EAAG,QAAO;AAC3C,SAAO;AAAA,IACL,OAAO,IAAI,CAAC,MAAO,KAAM,KAAK,IAAK,OAAO,KAAK,KAAK,GAAG,CAAE;AAAA,IACzD;AAAA,IACA,KAAK,OAAO;AAAA,EACd;AACF;","names":[]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The three things a numeric control has to do to a typed number, in one place.
3
+ *
4
+ * `Slider` and `NumberField` both take a number from a keyboard and both have
5
+ * to reconcile it with `min`, `max` and `step`. Written twice they drift, and
6
+ * they drift in the direction nobody tests: a slider that snaps 0.1 + 0.2 to
7
+ * `0.30000000000000004` and a field beside it that prints `0.3`.
8
+ *
9
+ * The step base is `min`, not zero, because that is what the HTML step
10
+ * algorithm uses — a range from 5 to 100 stepping by 10 lands on 15, not on 10.
11
+ */
12
+ /**
13
+ * Decimal places implied by a step.
14
+ *
15
+ * Snapping is arithmetic on binary floats, so a value that should be exactly
16
+ * one step from the last one usually is not. Rounding the result to the step's
17
+ * own precision is what stops that reaching a readout — and the step's own
18
+ * precision is the most a caller can have meant.
19
+ */
20
+ declare function decimalsForStep(step: number): number;
21
+ /** Rounds to the nearest step from `min`, at the step's own precision. */
22
+ declare function snapToStep(value: number, step: number, min?: number): number;
23
+ /** Holds a value inside a range. An inverted range yields its own lower bound. */
24
+ declare function clampToRange(value: number, min: number, max: number): number;
25
+ /**
26
+ * A typed string as a number, or `null` when it is not one yet.
27
+ *
28
+ * `null` rather than `NaN` because the caller has to tell "not a number" from
29
+ * "not finished being typed" and act the same way on both: leave the committed
30
+ * value alone. `Number('')` is `0`, which is the trap this exists for — an
31
+ * emptied box would otherwise commit zero on every keystroke that cleared it.
32
+ */
33
+ declare function parseNumber(text: string): number | null;
34
+
35
+ export { clampToRange, decimalsForStep, parseNumber, snapToStep };
@@ -0,0 +1,29 @@
1
+ function decimalsForStep(step) {
2
+ if (!Number.isFinite(step) || step <= 0) return 0;
3
+ const text = String(step);
4
+ const exponential = /e-(\d+)$/i.exec(text);
5
+ if (exponential) return Number(exponential[1]);
6
+ return text.split(".")[1]?.length ?? 0;
7
+ }
8
+ function snapToStep(value, step, min = 0) {
9
+ if (!Number.isFinite(step) || step <= 0) return value;
10
+ const base = Number.isFinite(min) ? min : 0;
11
+ const snapped = base + Math.round((value - base) / step) * step;
12
+ return Number(snapped.toFixed(decimalsForStep(step)));
13
+ }
14
+ function clampToRange(value, min, max) {
15
+ return Math.min(Math.max(value, min), max);
16
+ }
17
+ function parseNumber(text) {
18
+ const trimmed = text.trim();
19
+ if (trimmed === "") return null;
20
+ const value = Number(trimmed);
21
+ return Number.isFinite(value) ? value : null;
22
+ }
23
+ export {
24
+ clampToRange,
25
+ decimalsForStep,
26
+ parseNumber,
27
+ snapToStep
28
+ };
29
+ //# sourceMappingURL=numeric.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/numeric.ts"],"sourcesContent":["/**\n * The three things a numeric control has to do to a typed number, in one place.\n *\n * `Slider` and `NumberField` both take a number from a keyboard and both have\n * to reconcile it with `min`, `max` and `step`. Written twice they drift, and\n * they drift in the direction nobody tests: a slider that snaps 0.1 + 0.2 to\n * `0.30000000000000004` and a field beside it that prints `0.3`.\n *\n * The step base is `min`, not zero, because that is what the HTML step\n * algorithm uses — a range from 5 to 100 stepping by 10 lands on 15, not on 10.\n */\n\n/**\n * Decimal places implied by a step.\n *\n * Snapping is arithmetic on binary floats, so a value that should be exactly\n * one step from the last one usually is not. Rounding the result to the step's\n * own precision is what stops that reaching a readout — and the step's own\n * precision is the most a caller can have meant.\n */\nexport function decimalsForStep(step: number): number {\n if (!Number.isFinite(step) || step <= 0) return 0\n const text = String(step)\n // Exponential notation carries its precision in the exponent, and `1e-7` has\n // no '.' to split on — `String(0.0000001)` is exactly that.\n const exponential = /e-(\\d+)$/i.exec(text)\n if (exponential) return Number(exponential[1])\n return text.split('.')[1]?.length ?? 0\n}\n\n/** Rounds to the nearest step from `min`, at the step's own precision. */\nexport function snapToStep(value: number, step: number, min = 0): number {\n if (!Number.isFinite(step) || step <= 0) return value\n const base = Number.isFinite(min) ? min : 0\n const snapped = base + Math.round((value - base) / step) * step\n return Number(snapped.toFixed(decimalsForStep(step)))\n}\n\n/** Holds a value inside a range. An inverted range yields its own lower bound. */\nexport function clampToRange(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\n/**\n * A typed string as a number, or `null` when it is not one yet.\n *\n * `null` rather than `NaN` because the caller has to tell \"not a number\" from\n * \"not finished being typed\" and act the same way on both: leave the committed\n * value alone. `Number('')` is `0`, which is the trap this exists for — an\n * emptied box would otherwise commit zero on every keystroke that cleared it.\n */\nexport function parseNumber(text: string): number | null {\n const trimmed = text.trim()\n if (trimmed === '') return null\n const value = Number(trimmed)\n return Number.isFinite(value) ? value : null\n}\n"],"mappings":"AAoBO,SAAS,gBAAgB,MAAsB;AACpD,MAAI,CAAC,OAAO,SAAS,IAAI,KAAK,QAAQ,EAAG,QAAO;AAChD,QAAM,OAAO,OAAO,IAAI;AAGxB,QAAM,cAAc,YAAY,KAAK,IAAI;AACzC,MAAI,YAAa,QAAO,OAAO,YAAY,CAAC,CAAC;AAC7C,SAAO,KAAK,MAAM,GAAG,EAAE,CAAC,GAAG,UAAU;AACvC;AAGO,SAAS,WAAW,OAAe,MAAc,MAAM,GAAW;AACvE,MAAI,CAAC,OAAO,SAAS,IAAI,KAAK,QAAQ,EAAG,QAAO;AAChD,QAAM,OAAO,OAAO,SAAS,GAAG,IAAI,MAAM;AAC1C,QAAM,UAAU,OAAO,KAAK,OAAO,QAAQ,QAAQ,IAAI,IAAI;AAC3D,SAAO,OAAO,QAAQ,QAAQ,gBAAgB,IAAI,CAAC,CAAC;AACtD;AAGO,SAAS,aAAa,OAAe,KAAa,KAAqB;AAC5E,SAAO,KAAK,IAAI,KAAK,IAAI,OAAO,GAAG,GAAG,GAAG;AAC3C;AAUO,SAAS,YAAY,MAA6B;AACvD,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,YAAY,GAAI,QAAO;AAC3B,QAAM,QAAQ,OAAO,OAAO;AAC5B,SAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAC1C;","names":[]}