@viax.io/uxm 4.39.0 → 4.40.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 (122) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +33 -44
  4. package/dist/hooks/index.cjs.map +1 -1
  5. package/dist/hooks/index.d.ts +1 -1
  6. package/dist/hooks/index.js.map +1 -1
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.ts +3 -3
  9. package/dist/index.js.map +1 -1
  10. package/dist/previews/index.cjs.map +1 -1
  11. package/dist/previews/index.d.ts +2 -2
  12. package/dist/previews/index.js.map +1 -1
  13. package/dist/studio/index.cjs.map +1 -1
  14. package/dist/studio/index.d.ts +2 -2
  15. package/dist/studio/index.js.map +1 -1
  16. package/dist/studio/lib/registry/buttons.cjs +2 -2
  17. package/dist/studio/lib/registry/buttons.cjs.map +1 -1
  18. package/dist/studio/lib/registry/buttons.js +2 -2
  19. package/dist/studio/lib/registry/buttons.js.map +1 -1
  20. package/dist/studio/lib/registry/composite/sidebar-nav-trigger.cjs +1 -1
  21. package/dist/studio/lib/registry/composite/sidebar-nav-trigger.cjs.map +1 -1
  22. package/dist/studio/lib/registry/composite/sidebar-nav-trigger.js +1 -1
  23. package/dist/studio/lib/registry/composite/sidebar-nav-trigger.js.map +1 -1
  24. package/dist/studio/lib/registry/diagram.cjs +1 -1
  25. package/dist/studio/lib/registry/diagram.cjs.map +1 -1
  26. package/dist/studio/lib/registry/diagram.js +1 -1
  27. package/dist/studio/lib/registry/diagram.js.map +1 -1
  28. package/dist/studio/lib/registry/display.cjs +2 -2
  29. package/dist/studio/lib/registry/display.cjs.map +1 -1
  30. package/dist/studio/lib/registry/display.js +2 -2
  31. package/dist/studio/lib/registry/display.js.map +1 -1
  32. package/dist/studio/lib/registry/feedback.cjs +3 -3
  33. package/dist/studio/lib/registry/feedback.cjs.map +1 -1
  34. package/dist/studio/lib/registry/feedback.js +3 -3
  35. package/dist/studio/lib/registry/feedback.js.map +1 -1
  36. package/dist/studio/lib/registry/inputs/color-input.cjs +1 -1
  37. package/dist/studio/lib/registry/inputs/color-input.cjs.map +1 -1
  38. package/dist/studio/lib/registry/inputs/color-input.js +1 -1
  39. package/dist/studio/lib/registry/inputs/color-input.js.map +1 -1
  40. package/dist/studio/lib/registry/inputs/currency-input.cjs +1 -1
  41. package/dist/studio/lib/registry/inputs/currency-input.cjs.map +1 -1
  42. package/dist/studio/lib/registry/inputs/currency-input.js +1 -1
  43. package/dist/studio/lib/registry/inputs/currency-input.js.map +1 -1
  44. package/dist/studio/lib/registry/inputs/editable-cell.cjs +1 -1
  45. package/dist/studio/lib/registry/inputs/editable-cell.cjs.map +1 -1
  46. package/dist/studio/lib/registry/inputs/editable-cell.js +1 -1
  47. package/dist/studio/lib/registry/inputs/editable-cell.js.map +1 -1
  48. package/dist/studio/lib/registry/inputs/file-upload.cjs +1 -1
  49. package/dist/studio/lib/registry/inputs/file-upload.cjs.map +1 -1
  50. package/dist/studio/lib/registry/inputs/file-upload.js +1 -1
  51. package/dist/studio/lib/registry/inputs/file-upload.js.map +1 -1
  52. package/dist/studio/lib/registry/inputs/input-text.cjs +1 -1
  53. package/dist/studio/lib/registry/inputs/input-text.cjs.map +1 -1
  54. package/dist/studio/lib/registry/inputs/input-text.js +1 -1
  55. package/dist/studio/lib/registry/inputs/input-text.js.map +1 -1
  56. package/dist/studio/lib/registry/inputs/listbox.cjs +1 -1
  57. package/dist/studio/lib/registry/inputs/listbox.cjs.map +1 -1
  58. package/dist/studio/lib/registry/inputs/listbox.js +1 -1
  59. package/dist/studio/lib/registry/inputs/listbox.js.map +1 -1
  60. package/dist/studio/lib/registry/inputs/menu.cjs +1 -1
  61. package/dist/studio/lib/registry/inputs/menu.cjs.map +1 -1
  62. package/dist/studio/lib/registry/inputs/menu.js +1 -1
  63. package/dist/studio/lib/registry/inputs/menu.js.map +1 -1
  64. package/dist/studio/lib/registry/inputs/number-input.cjs +1 -1
  65. package/dist/studio/lib/registry/inputs/number-input.cjs.map +1 -1
  66. package/dist/studio/lib/registry/inputs/number-input.js +1 -1
  67. package/dist/studio/lib/registry/inputs/number-input.js.map +1 -1
  68. package/dist/studio/lib/registry/inputs/password-input.cjs +1 -1
  69. package/dist/studio/lib/registry/inputs/password-input.cjs.map +1 -1
  70. package/dist/studio/lib/registry/inputs/password-input.js +1 -1
  71. package/dist/studio/lib/registry/inputs/password-input.js.map +1 -1
  72. package/dist/studio/lib/registry/inputs/phone-input.cjs +1 -1
  73. package/dist/studio/lib/registry/inputs/phone-input.cjs.map +1 -1
  74. package/dist/studio/lib/registry/inputs/phone-input.js +1 -1
  75. package/dist/studio/lib/registry/inputs/phone-input.js.map +1 -1
  76. package/dist/studio/lib/registry/inputs/slider.cjs +1 -1
  77. package/dist/studio/lib/registry/inputs/slider.cjs.map +1 -1
  78. package/dist/studio/lib/registry/inputs/slider.js +1 -1
  79. package/dist/studio/lib/registry/inputs/slider.js.map +1 -1
  80. package/dist/studio/lib/registry/inputs/time-input.cjs +1 -1
  81. package/dist/studio/lib/registry/inputs/time-input.cjs.map +1 -1
  82. package/dist/studio/lib/registry/inputs/time-input.js +1 -1
  83. package/dist/studio/lib/registry/inputs/time-input.js.map +1 -1
  84. package/dist/studio/lib/types.cjs.map +1 -1
  85. package/dist/studio/persistence/generate-css.cjs.map +1 -1
  86. package/dist/studio/persistence/generate-css.js.map +1 -1
  87. package/dist/ui/editable-cell/editable-cell-date.cjs +226 -0
  88. package/dist/ui/editable-cell/editable-cell-date.cjs.map +1 -0
  89. package/dist/ui/editable-cell/editable-cell-date.d.ts +4 -0
  90. package/dist/ui/editable-cell/editable-cell-date.d.ts.map +1 -0
  91. package/dist/ui/editable-cell/editable-cell-date.js +202 -0
  92. package/dist/ui/editable-cell/editable-cell-date.js.map +1 -0
  93. package/dist/ui/editable-cell/editable-cell-select.cjs +297 -0
  94. package/dist/ui/editable-cell/editable-cell-select.cjs.map +1 -0
  95. package/dist/ui/editable-cell/editable-cell-select.d.ts +4 -0
  96. package/dist/ui/editable-cell/editable-cell-select.d.ts.map +1 -0
  97. package/dist/ui/editable-cell/editable-cell-select.js +273 -0
  98. package/dist/ui/editable-cell/editable-cell-select.js.map +1 -0
  99. package/dist/ui/editable-cell/editable-cell-text.cjs +175 -0
  100. package/dist/ui/editable-cell/editable-cell-text.cjs.map +1 -0
  101. package/dist/ui/editable-cell/editable-cell-text.d.ts +4 -0
  102. package/dist/ui/editable-cell/editable-cell-text.d.ts.map +1 -0
  103. package/dist/ui/editable-cell/editable-cell-text.js +151 -0
  104. package/dist/ui/editable-cell/editable-cell-text.js.map +1 -0
  105. package/dist/ui/editable-cell/editable-cell.cjs +27 -705
  106. package/dist/ui/editable-cell/editable-cell.cjs.map +1 -1
  107. package/dist/ui/editable-cell/editable-cell.d.ts +1 -1
  108. package/dist/ui/editable-cell/editable-cell.d.ts.map +1 -1
  109. package/dist/ui/editable-cell/editable-cell.js +28 -706
  110. package/dist/ui/editable-cell/editable-cell.js.map +1 -1
  111. package/dist/ui/editable-cell/use-editable-cell.cjs +284 -0
  112. package/dist/ui/editable-cell/use-editable-cell.cjs.map +1 -0
  113. package/dist/ui/editable-cell/use-editable-cell.d.ts +51 -0
  114. package/dist/ui/editable-cell/use-editable-cell.d.ts.map +1 -0
  115. package/dist/ui/editable-cell/use-editable-cell.js +260 -0
  116. package/dist/ui/editable-cell/use-editable-cell.js.map +1 -0
  117. package/dist/ui/page-shell/page-shell.cjs.map +1 -1
  118. package/dist/ui/page-shell/page-shell.d.ts +1 -1
  119. package/dist/ui/page-shell/page-shell.d.ts.map +1 -1
  120. package/dist/ui/page-shell/page-shell.js.map +1 -1
  121. package/dist/ui/styles.css +1 -1
  122. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/studio/lib/registry/inputs/time-input.ts"],"sourcesContent":["import type { ComponentDef } from '../../types';\n\nexport const timeInputDef: ComponentDef = {\n id: 'time-input',\n name: 'Time Input',\n category: 'Inputs',\n description: 'Time entry field with a trailing clock icon and an HH:MM mask. The `12h` format adds an AM/PM selector at the trailing edge. State knobs cover default / hover / focus / disabled / error.',\n styleProperties: [\n // Default\n { key: 'backgroundColor', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'fieldColors', showWhen: { state: 'default' } },\n { key: 'borderColor', label: 'Border', control: 'color', defaultValue: 'var(--color-border)', section: 'fieldColors', showWhen: { state: 'default' } },\n { key: 'color', label: 'Text', control: 'color', defaultValue: 'var(--color-text)', section: 'fieldColors', showWhen: { state: 'default' } },\n { key: 'iconColor', label: 'Icon', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'fieldColors', showWhen: { state: 'default' } },\n // Hover\n { key: 'hoverBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'fieldColors', showWhen: { state: 'hover' } },\n { key: 'hoverBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-accent)', section: 'fieldColors', showWhen: { state: 'hover' } },\n // Focus\n { key: 'focusBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-accent)', section: 'fieldColors', showWhen: { state: 'focus' } },\n { key: 'focusRing', label: 'Ring', control: 'color', defaultValue: 'var(--color-accent)', section: 'focusState', showWhen: { state: 'focus' } },\n // Disabled\n { key: 'disabledBg', label: 'Background', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'fieldColors', showWhen: { state: 'disabled' } },\n { key: 'disabledBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-border)', section: 'fieldColors', showWhen: { state: 'disabled' } },\n { key: 'disabledColor', label: 'Text', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'fieldColors', showWhen: { state: 'disabled' } },\n { key: 'disabledOpacity', label: 'Opacity', control: 'slider', defaultValue: 0.6, min: 0.1, max: 1, step: 0.05, section: 'disabledState', showWhen: { state: 'disabled' } },\n // Error\n { key: 'errorBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'fieldColors', showWhen: { state: 'error' } },\n { key: 'errorBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-danger-text)', section: 'fieldColors', showWhen: { state: 'error' } },\n { key: 'errorColor', label: 'Message', control: 'color', defaultValue: 'var(--color-danger-text)', section: 'errorState', showWhen: { state: 'error' } },\n { key: 'errorMessageSize', label: 'Message Size', control: 'number', defaultValue: 12, min: 10, max: 16, step: 1, unit: 'px', section: 'errorState', showWhen: { state: 'error' } },\n // Shared\n { key: 'borderRadius', label: 'Border Radius', control: 'slider', defaultValue: 8, min: 0, max: 99, step: 1, unit: 'px' },\n { key: 'paddingX', label: 'Padding X', control: 'number', defaultValue: 12, min: 4, max: 24, step: 2, unit: 'px' },\n { key: 'paddingY', label: 'Padding Y', control: 'number', defaultValue: 10, min: 4, max: 20, step: 2, unit: 'px' },\n { key: 'fontSize', label: 'Font Size', control: 'number', defaultValue: 14, min: 10, max: 20, step: 1, unit: 'px' },\n { key: 'iconSize', label: 'Icon Size', control: 'number', defaultValue: 16, min: 12, max: 24, step: 1, unit: 'px' },\n { key: 'iconOffset', label: 'Icon Offset', control: 'number', defaultValue: 12, min: 4, max: 24, step: 1, unit: 'px' },\n { key: 'iconHoverColor', label: 'Icon Hover', control: 'color', defaultValue: 'var(--color-text)' },\n // 12h-mode-only — the AM/PM suffix is a quiet inline label (no chip\n // background), so only the text color is themable. Gated by `showWhen`\n // so the knob only surfaces in 12h mode, matching DateInput's\n // range-mode-only knobs precedent.\n { key: 'meridiemColor', label: 'AM/PM Text', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'meridiem', showWhen: { format: '12h' } },\n // Popover knobs — the column-scroll picker that opens when the\n // clock icon is clicked. Grouped into their own section so the\n // panel reads as \"the floating surface\" distinct from the field.\n { key: 'popoverBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'popover' },\n { key: 'popoverBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-border)', section: 'popover' },\n { key: 'popoverRadius', label: 'Border Radius', control: 'slider', defaultValue: 8, min: 0, max: 24, step: 1, unit: 'px', section: 'popover' },\n { key: 'popoverHeadBg', label: 'Column Heading', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'popover' },\n // Row hover + selected defaults align with the Listbox option\n // tokens (`--uxm-listbox-option-{active,selected}-{bg,color}`) so a\n // selected time visually matches a selected listbox option out of\n // the box. TimeInput keeps its own knobs (the columns aren't a\n // Listbox), but the defaults stay in lockstep — designers can tune\n // them independently if they want a different time-picker palette.\n { key: 'popoverRowHoverBg', label: 'Row Hover', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'popover' },\n { key: 'popoverRowSelectedBg', label: 'Selected Row', control: 'color', defaultValue: 'var(--color-accent-subtle)', section: 'popover' },\n { key: 'popoverRowSelectedColor', label: 'Selected Text', control: 'color', defaultValue: 'var(--color-accent-bold)', section: 'popover' },\n ],\n layoutVariants: [\n {\n key: 'format',\n label: 'Format',\n options: [\n { value: '24h', label: '24-hour (HH:MM)' },\n { value: '12h', label: '12-hour (HH:MM AM/PM)' },\n ],\n defaultValue: '24h',\n },\n {\n key: 'state',\n label: 'State',\n options: [\n { value: 'default', label: 'Default' },\n { value: 'hover', label: 'Hover' },\n { value: 'focus', label: 'Focus' },\n { value: 'disabled', label: 'Disabled' },\n { value: 'error', label: 'Error' },\n ],\n defaultValue: 'default',\n },\n ],\n events: [\n { name: 'onChange', description: 'Fires on every keystroke, meridiem flip, or popover column pick. Payload is the masked, joined value (`HH:MM` for 24h, `HH:MM AM/PM` for 12h).', payload: '{ value: string }' },\n { name: 'onOpen', description: 'Fires when the time-picker popover opens.', payload: 'void' },\n { name: 'onClose', description: 'Fires when the time-picker popover closes (selection or outside click).', payload: 'void' },\n { name: 'onFocus', description: 'Fires when the field receives focus.', payload: 'FocusEvent' },\n { name: 'onBlur', description: 'Fires when the field loses focus.', payload: 'FocusEvent' },\n ],\n api: {\n importPath: '@viax/uxm/ui',\n importNames: 'TimeInput',\n props: [\n { name: 'value', type: 'string', description: 'Current value (controlled). 24h: `\"HH:MM\"`. 12h: `\"HH:MM AM\"` or `\"HH:MM PM\"`.' },\n { name: 'onChange', type: '(value: string) => void', description: 'Called with the masked, joined value after each edit.' },\n { name: 'format', type: '\"24h\" | \"12h\"', defaultValue: '\"24h\"', description: 'Clock convention. `12h` adds an AM/PM selector and a third popover column.' },\n { name: 'clock', type: 'boolean', defaultValue: 'true', description: 'Render the trailing clock icon. Pass `false` for an icon-less field.' },\n { name: 'picker', type: 'boolean', defaultValue: 'true', description: 'Mount the click-list popover (hour / minute / AM-PM). Pass `false` for a typing-only field — the clock icon stays decorative.' },\n { name: 'minuteStep', type: 'number', defaultValue: '1', description: \"Increment shown in the minute column of the popover. Off-step values typed into the field are still included (sorted), so a typed `09:03` under `minuteStep={5}` doesn't vanish.\" },\n { name: 'clearable', type: 'boolean', defaultValue: 'true', description: 'Show a trailing clear (✕) button when the field has a value. The ✕ sits inboard of the clock icon (and the AM/PM badge in 12h) and resets the value.' },\n { name: 'disabled', type: 'boolean', defaultValue: 'false', description: 'Disables the input, the AM/PM selector, and the picker trigger.' },\n { name: '...rest', type: 'InputHTMLAttributes<HTMLInputElement>', description: 'All other native input attributes (placeholder, name, aria-label, etc.) pass through.' },\n ],\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEO,MAAM,eAA6B;AAAA,EACxC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU;AAAA,EACV,aAAa;AAAA,EACb,iBAAiB;AAAA;AAAA,IAEf,EAAE,KAAK,mBAAmB,OAAO,cAAc,SAAS,SAAS,cAAc,qBAAqB,SAAS,eAAe,UAAU,EAAE,OAAO,UAAU,EAAE;AAAA,IAC3J,EAAE,KAAK,eAAe,OAAO,UAAU,SAAS,SAAS,cAAc,uBAAuB,SAAS,eAAe,UAAU,EAAE,OAAO,UAAU,EAAE;AAAA,IACrJ,EAAE,KAAK,SAAS,OAAO,QAAQ,SAAS,SAAS,cAAc,qBAAqB,SAAS,eAAe,UAAU,EAAE,OAAO,UAAU,EAAE;AAAA,IAC3I,EAAE,KAAK,aAAa,OAAO,QAAQ,SAAS,SAAS,cAAc,2BAA2B,SAAS,eAAe,UAAU,EAAE,OAAO,UAAU,EAAE;AAAA;AAAA,IAErJ,EAAE,KAAK,WAAW,OAAO,cAAc,SAAS,SAAS,cAAc,qBAAqB,SAAS,eAAe,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA,IACjJ,EAAE,KAAK,eAAe,OAAO,UAAU,SAAS,SAAS,cAAc,uBAAuB,SAAS,eAAe,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA;AAAA,IAEnJ,EAAE,KAAK,eAAe,OAAO,UAAU,SAAS,SAAS,cAAc,uBAAuB,SAAS,eAAe,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA,IACnJ,EAAE,KAAK,aAAa,OAAO,QAAQ,SAAS,SAAS,cAAc,uBAAuB,SAAS,cAAc,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA;AAAA,IAE9I,EAAE,KAAK,cAAc,OAAO,cAAc,SAAS,SAAS,cAAc,4BAA4B,SAAS,eAAe,UAAU,EAAE,OAAO,WAAW,EAAE;AAAA,IAC9J,EAAE,KAAK,kBAAkB,OAAO,UAAU,SAAS,SAAS,cAAc,uBAAuB,SAAS,eAAe,UAAU,EAAE,OAAO,WAAW,EAAE;AAAA,IACzJ,EAAE,KAAK,iBAAiB,OAAO,QAAQ,SAAS,SAAS,cAAc,2BAA2B,SAAS,eAAe,UAAU,EAAE,OAAO,WAAW,EAAE;AAAA,IAC1J,EAAE,KAAK,mBAAmB,OAAO,WAAW,SAAS,UAAU,cAAc,KAAK,KAAK,KAAK,KAAK,GAAG,MAAM,MAAM,SAAS,iBAAiB,UAAU,EAAE,OAAO,WAAW,EAAE;AAAA;AAAA,IAE1K,EAAE,KAAK,WAAW,OAAO,cAAc,SAAS,SAAS,cAAc,qBAAqB,SAAS,eAAe,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA,IACjJ,EAAE,KAAK,eAAe,OAAO,UAAU,SAAS,SAAS,cAAc,4BAA4B,SAAS,eAAe,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA,IACxJ,EAAE,KAAK,cAAc,OAAO,WAAW,SAAS,SAAS,cAAc,4BAA4B,SAAS,cAAc,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA,IACvJ,EAAE,KAAK,oBAAoB,OAAO,gBAAgB,SAAS,UAAU,cAAc,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,MAAM,SAAS,cAAc,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA;AAAA,IAElL,EAAE,KAAK,gBAAgB,OAAO,iBAAiB,SAAS,UAAU,cAAc,GAAG,KAAK,GAAG,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK;AAAA,IACxH,EAAE,KAAK,YAAY,OAAO,aAAa,SAAS,UAAU,cAAc,IAAI,KAAK,GAAG,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK;AAAA,IACjH,EAAE,KAAK,YAAY,OAAO,aAAa,SAAS,UAAU,cAAc,IAAI,KAAK,GAAG,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK;AAAA,IACjH,EAAE,KAAK,YAAY,OAAO,aAAa,SAAS,UAAU,cAAc,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK;AAAA,IAClH,EAAE,KAAK,YAAY,OAAO,aAAa,SAAS,UAAU,cAAc,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK;AAAA,IAClH,EAAE,KAAK,cAAc,OAAO,eAAe,SAAS,UAAU,cAAc,IAAI,KAAK,GAAG,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK;AAAA,IACrH,EAAE,KAAK,kBAAkB,OAAO,cAAc,SAAS,SAAS,cAAc,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKlG,EAAE,KAAK,iBAAiB,OAAO,cAAc,SAAS,SAAS,cAAc,2BAA2B,SAAS,YAAY,UAAU,EAAE,QAAQ,MAAM,EAAE;AAAA;AAAA;AAAA;AAAA,IAIzJ,EAAE,KAAK,aAAa,OAAO,cAAc,SAAS,SAAS,cAAc,qBAAqB,SAAS,UAAU;AAAA,IACjH,EAAE,KAAK,iBAAiB,OAAO,UAAU,SAAS,SAAS,cAAc,uBAAuB,SAAS,UAAU;AAAA,IACnH,EAAE,KAAK,iBAAiB,OAAO,iBAAiB,SAAS,UAAU,cAAc,GAAG,KAAK,GAAG,KAAK,IAAI,MAAM,GAAG,MAAM,MAAM,SAAS,UAAU;AAAA,IAC7I,EAAE,KAAK,iBAAiB,OAAO,kBAAkB,SAAS,SAAS,cAAc,4BAA4B,SAAS,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOhI,EAAE,KAAK,qBAAqB,OAAO,aAAa,SAAS,SAAS,cAAc,4BAA4B,SAAS,UAAU;AAAA,IAC/H,EAAE,KAAK,wBAAwB,OAAO,gBAAgB,SAAS,SAAS,cAAc,8BAA8B,SAAS,UAAU;AAAA,IACvI,EAAE,KAAK,2BAA2B,OAAO,iBAAiB,SAAS,SAAS,cAAc,4BAA4B,SAAS,UAAU;AAAA,EAC3I;AAAA,EACA,gBAAgB;AAAA,IACd;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,SAAS;AAAA,QACP,EAAE,OAAO,OAAO,OAAO,kBAAkB;AAAA,QACzC,EAAE,OAAO,OAAO,OAAO,wBAAwB;AAAA,MACjD;AAAA,MACA,cAAc;AAAA,IAChB;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,SAAS;AAAA,QACP,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,QACrC,EAAE,OAAO,SAAS,OAAO,QAAQ;AAAA,QACjC,EAAE,OAAO,SAAS,OAAO,QAAQ;AAAA,QACjC,EAAE,OAAO,YAAY,OAAO,WAAW;AAAA,QACvC,EAAE,OAAO,SAAS,OAAO,QAAQ;AAAA,MACnC;AAAA,MACA,cAAc;AAAA,IAChB;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,MAAM,YAAY,aAAa,kJAAkJ,SAAS,oBAAoB;AAAA,IAChN,EAAE,MAAM,UAAU,aAAa,6CAA6C,SAAS,OAAO;AAAA,IAC5F,EAAE,MAAM,WAAW,aAAa,2EAA2E,SAAS,OAAO;AAAA,IAC3H,EAAE,MAAM,WAAW,aAAa,wCAAwC,SAAS,aAAa;AAAA,IAC9F,EAAE,MAAM,UAAU,aAAa,qCAAqC,SAAS,aAAa;AAAA,EAC5F;AAAA,EACA,KAAK;AAAA,IACH,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,OAAO;AAAA,MACL,EAAE,MAAM,SAAS,MAAM,UAAU,aAAa,iFAAiF;AAAA,MAC/H,EAAE,MAAM,YAAY,MAAM,2BAA2B,aAAa,wDAAwD;AAAA,MAC1H,EAAE,MAAM,UAAU,MAAM,iBAAiB,cAAc,SAAS,aAAa,6EAA6E;AAAA,MAC1J,EAAE,MAAM,SAAS,MAAM,WAAW,cAAc,QAAQ,aAAa,uEAAuE;AAAA,MAC5I,EAAE,MAAM,UAAU,MAAM,WAAW,cAAc,QAAQ,aAAa,qIAAgI;AAAA,MACtM,EAAE,MAAM,cAAc,MAAM,UAAU,cAAc,KAAK,aAAa,mLAAmL;AAAA,MACzP,EAAE,MAAM,aAAa,MAAM,WAAW,cAAc,QAAQ,aAAa,iKAAuJ;AAAA,MAChO,EAAE,MAAM,YAAY,MAAM,WAAW,cAAc,SAAS,aAAa,kEAAkE;AAAA,MAC3I,EAAE,MAAM,WAAW,MAAM,yCAAyC,aAAa,wFAAwF;AAAA,IACzK;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/studio/lib/registry/inputs/time-input.ts"],"sourcesContent":["import type { ComponentDef } from '../../types';\n\nexport const timeInputDef: ComponentDef = {\n id: 'time-input',\n name: 'Time Input',\n category: 'Inputs',\n description: 'Time entry field with a trailing clock icon and an HH:MM mask. The `12h` format adds an AM/PM selector at the trailing edge. State knobs cover default / hover / focus / disabled / error.',\n styleProperties: [\n // Default\n { key: 'backgroundColor', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'fieldColors', showWhen: { state: 'default' } },\n { key: 'borderColor', label: 'Border', control: 'color', defaultValue: 'var(--color-border)', section: 'fieldColors', showWhen: { state: 'default' } },\n { key: 'color', label: 'Text', control: 'color', defaultValue: 'var(--color-text)', section: 'fieldColors', showWhen: { state: 'default' } },\n { key: 'iconColor', label: 'Icon', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'fieldColors', showWhen: { state: 'default' } },\n // Hover\n { key: 'hoverBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'fieldColors', showWhen: { state: 'hover' } },\n { key: 'hoverBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-accent)', section: 'fieldColors', showWhen: { state: 'hover' } },\n // Focus\n { key: 'focusBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-accent)', section: 'fieldColors', showWhen: { state: 'focus' } },\n { key: 'focusRing', label: 'Ring', control: 'color', defaultValue: 'var(--color-accent)', section: 'focusState', showWhen: { state: 'focus' } },\n // Disabled\n { key: 'disabledBg', label: 'Background', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'fieldColors', showWhen: { state: 'disabled' } },\n { key: 'disabledBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-border)', section: 'fieldColors', showWhen: { state: 'disabled' } },\n { key: 'disabledColor', label: 'Text', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'fieldColors', showWhen: { state: 'disabled' } },\n { key: 'disabledOpacity', label: 'Opacity', control: 'slider', defaultValue: 0.6, min: 0.1, max: 1, step: 0.05, section: 'disabledState', showWhen: { state: 'disabled' } },\n // Error\n { key: 'errorBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'fieldColors', showWhen: { state: 'error' } },\n { key: 'errorBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-danger-text)', section: 'fieldColors', showWhen: { state: 'error' } },\n { key: 'errorColor', label: 'Message', control: 'color', defaultValue: 'var(--color-danger-text)', section: 'errorState', showWhen: { state: 'error' } },\n { key: 'errorMessageSize', label: 'Message Size', control: 'number', defaultValue: 12, min: 10, max: 16, step: 1, unit: 'px', section: 'errorState', showWhen: { state: 'error' } },\n // Shared\n { key: 'borderRadius', label: 'Border Radius', control: 'slider', defaultValue: 8, min: 0, max: 99, step: 1, unit: 'px' },\n { key: 'paddingX', label: 'Padding X', control: 'number', defaultValue: 12, min: 4, max: 24, step: 2, unit: 'px' },\n { key: 'paddingY', label: 'Padding Y', control: 'number', defaultValue: 10, min: 4, max: 20, step: 2, unit: 'px' },\n { key: 'fontSize', label: 'Font Size', control: 'number', defaultValue: 14, min: 10, max: 20, step: 1, unit: 'px' },\n { key: 'iconSize', label: 'Icon Size', control: 'number', defaultValue: 16, min: 12, max: 24, step: 1, unit: 'px' },\n { key: 'iconOffset', label: 'Icon Offset', control: 'number', defaultValue: 12, min: 4, max: 24, step: 1, unit: 'px' },\n { key: 'iconHoverColor', label: 'Icon Hover', control: 'color', defaultValue: 'var(--color-text)' },\n // 12h-mode-only — the AM/PM suffix is a quiet inline label (no chip\n // background), so only the text color is themable. Gated by `showWhen`\n // so the knob only surfaces in 12h mode, matching DateInput's\n // range-mode-only knobs precedent.\n { key: 'meridiemColor', label: 'AM/PM Text', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'meridiem', showWhen: { format: '12h' } },\n // Popover knobs — the column-scroll picker that opens when the\n // clock icon is clicked. Grouped into their own section so the\n // panel reads as \"the floating surface\" distinct from the field.\n { key: 'popoverBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'popover' },\n { key: 'popoverBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-border)', section: 'popover' },\n { key: 'popoverRadius', label: 'Border Radius', control: 'slider', defaultValue: 8, min: 0, max: 24, step: 1, unit: 'px', section: 'popover' },\n { key: 'popoverHeadBg', label: 'Column Heading', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'popover' },\n // Row hover + selected defaults align with the Listbox option\n // tokens (`--uxm-listbox-option-{active,selected}-{bg,color}`) so a\n // selected time visually matches a selected listbox option out of\n // the box. TimeInput keeps its own knobs (the columns aren't a\n // Listbox), but the defaults stay in lockstep — designers can tune\n // them independently if they want a different time-picker palette.\n { key: 'popoverRowHoverBg', label: 'Row Hover', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'popover' },\n { key: 'popoverRowSelectedBg', label: 'Selected Row', control: 'color', defaultValue: 'var(--color-accent-subtle)', section: 'popover' },\n { key: 'popoverRowSelectedColor', label: 'Selected Text', control: 'color', defaultValue: 'var(--color-accent-bold)', section: 'popover' },\n ],\n layoutVariants: [\n {\n key: 'format',\n label: 'Format',\n options: [\n { value: '24h', label: '24-hour (HH:MM)' },\n { value: '12h', label: '12-hour (HH:MM AM/PM)' },\n ],\n defaultValue: '24h',\n },\n {\n key: 'state',\n label: 'State',\n options: [\n { value: 'default', label: 'Default' },\n { value: 'hover', label: 'Hover' },\n { value: 'focus', label: 'Focus' },\n { value: 'disabled', label: 'Disabled' },\n { value: 'error', label: 'Error' },\n ],\n defaultValue: 'default',\n },\n ],\n events: [\n { name: 'onChange', description: 'Fires on every keystroke, meridiem flip, or popover column pick. Payload is the masked, joined value (`HH:MM` for 24h, `HH:MM AM/PM` for 12h).', payload: '{ value: string }' },\n { name: 'onOpen', description: 'Fires when the time-picker popover opens.', payload: 'void' },\n { name: 'onClose', description: 'Fires when the time-picker popover closes (selection or outside click).', payload: 'void' },\n { name: 'onFocus', description: 'Fires when the field receives focus.', payload: 'FocusEvent' },\n { name: 'onBlur', description: 'Fires when the field loses focus.', payload: 'FocusEvent' },\n ],\n api: {\n importPath: '@viax.io/uxm/ui',\n importNames: 'TimeInput',\n props: [\n { name: 'value', type: 'string', description: 'Current value (controlled). 24h: `\"HH:MM\"`. 12h: `\"HH:MM AM\"` or `\"HH:MM PM\"`.' },\n { name: 'onChange', type: '(value: string) => void', description: 'Called with the masked, joined value after each edit.' },\n { name: 'format', type: '\"24h\" | \"12h\"', defaultValue: '\"24h\"', description: 'Clock convention. `12h` adds an AM/PM selector and a third popover column.' },\n { name: 'clock', type: 'boolean', defaultValue: 'true', description: 'Render the trailing clock icon. Pass `false` for an icon-less field.' },\n { name: 'picker', type: 'boolean', defaultValue: 'true', description: 'Mount the click-list popover (hour / minute / AM-PM). Pass `false` for a typing-only field — the clock icon stays decorative.' },\n { name: 'minuteStep', type: 'number', defaultValue: '1', description: \"Increment shown in the minute column of the popover. Off-step values typed into the field are still included (sorted), so a typed `09:03` under `minuteStep={5}` doesn't vanish.\" },\n { name: 'clearable', type: 'boolean', defaultValue: 'true', description: 'Show a trailing clear (✕) button when the field has a value. The ✕ sits inboard of the clock icon (and the AM/PM badge in 12h) and resets the value.' },\n { name: 'disabled', type: 'boolean', defaultValue: 'false', description: 'Disables the input, the AM/PM selector, and the picker trigger.' },\n { name: '...rest', type: 'InputHTMLAttributes<HTMLInputElement>', description: 'All other native input attributes (placeholder, name, aria-label, etc.) pass through.' },\n ],\n },\n};\n"],"mappings":"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","names":[]}
@@ -86,7 +86,7 @@ const timeInputDef = {
86
86
  { name: "onBlur", description: "Fires when the field loses focus.", payload: "FocusEvent" }
87
87
  ],
88
88
  api: {
89
- importPath: "@viax/uxm/ui",
89
+ importPath: "@viax.io/uxm/ui",
90
90
  importNames: "TimeInput",
91
91
  props: [
92
92
  { name: "value", type: "string", description: 'Current value (controlled). 24h: `"HH:MM"`. 12h: `"HH:MM AM"` or `"HH:MM PM"`.' },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/studio/lib/registry/inputs/time-input.ts"],"sourcesContent":["import type { ComponentDef } from '../../types';\n\nexport const timeInputDef: ComponentDef = {\n id: 'time-input',\n name: 'Time Input',\n category: 'Inputs',\n description: 'Time entry field with a trailing clock icon and an HH:MM mask. The `12h` format adds an AM/PM selector at the trailing edge. State knobs cover default / hover / focus / disabled / error.',\n styleProperties: [\n // Default\n { key: 'backgroundColor', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'fieldColors', showWhen: { state: 'default' } },\n { key: 'borderColor', label: 'Border', control: 'color', defaultValue: 'var(--color-border)', section: 'fieldColors', showWhen: { state: 'default' } },\n { key: 'color', label: 'Text', control: 'color', defaultValue: 'var(--color-text)', section: 'fieldColors', showWhen: { state: 'default' } },\n { key: 'iconColor', label: 'Icon', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'fieldColors', showWhen: { state: 'default' } },\n // Hover\n { key: 'hoverBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'fieldColors', showWhen: { state: 'hover' } },\n { key: 'hoverBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-accent)', section: 'fieldColors', showWhen: { state: 'hover' } },\n // Focus\n { key: 'focusBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-accent)', section: 'fieldColors', showWhen: { state: 'focus' } },\n { key: 'focusRing', label: 'Ring', control: 'color', defaultValue: 'var(--color-accent)', section: 'focusState', showWhen: { state: 'focus' } },\n // Disabled\n { key: 'disabledBg', label: 'Background', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'fieldColors', showWhen: { state: 'disabled' } },\n { key: 'disabledBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-border)', section: 'fieldColors', showWhen: { state: 'disabled' } },\n { key: 'disabledColor', label: 'Text', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'fieldColors', showWhen: { state: 'disabled' } },\n { key: 'disabledOpacity', label: 'Opacity', control: 'slider', defaultValue: 0.6, min: 0.1, max: 1, step: 0.05, section: 'disabledState', showWhen: { state: 'disabled' } },\n // Error\n { key: 'errorBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'fieldColors', showWhen: { state: 'error' } },\n { key: 'errorBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-danger-text)', section: 'fieldColors', showWhen: { state: 'error' } },\n { key: 'errorColor', label: 'Message', control: 'color', defaultValue: 'var(--color-danger-text)', section: 'errorState', showWhen: { state: 'error' } },\n { key: 'errorMessageSize', label: 'Message Size', control: 'number', defaultValue: 12, min: 10, max: 16, step: 1, unit: 'px', section: 'errorState', showWhen: { state: 'error' } },\n // Shared\n { key: 'borderRadius', label: 'Border Radius', control: 'slider', defaultValue: 8, min: 0, max: 99, step: 1, unit: 'px' },\n { key: 'paddingX', label: 'Padding X', control: 'number', defaultValue: 12, min: 4, max: 24, step: 2, unit: 'px' },\n { key: 'paddingY', label: 'Padding Y', control: 'number', defaultValue: 10, min: 4, max: 20, step: 2, unit: 'px' },\n { key: 'fontSize', label: 'Font Size', control: 'number', defaultValue: 14, min: 10, max: 20, step: 1, unit: 'px' },\n { key: 'iconSize', label: 'Icon Size', control: 'number', defaultValue: 16, min: 12, max: 24, step: 1, unit: 'px' },\n { key: 'iconOffset', label: 'Icon Offset', control: 'number', defaultValue: 12, min: 4, max: 24, step: 1, unit: 'px' },\n { key: 'iconHoverColor', label: 'Icon Hover', control: 'color', defaultValue: 'var(--color-text)' },\n // 12h-mode-only — the AM/PM suffix is a quiet inline label (no chip\n // background), so only the text color is themable. Gated by `showWhen`\n // so the knob only surfaces in 12h mode, matching DateInput's\n // range-mode-only knobs precedent.\n { key: 'meridiemColor', label: 'AM/PM Text', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'meridiem', showWhen: { format: '12h' } },\n // Popover knobs — the column-scroll picker that opens when the\n // clock icon is clicked. Grouped into their own section so the\n // panel reads as \"the floating surface\" distinct from the field.\n { key: 'popoverBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'popover' },\n { key: 'popoverBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-border)', section: 'popover' },\n { key: 'popoverRadius', label: 'Border Radius', control: 'slider', defaultValue: 8, min: 0, max: 24, step: 1, unit: 'px', section: 'popover' },\n { key: 'popoverHeadBg', label: 'Column Heading', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'popover' },\n // Row hover + selected defaults align with the Listbox option\n // tokens (`--uxm-listbox-option-{active,selected}-{bg,color}`) so a\n // selected time visually matches a selected listbox option out of\n // the box. TimeInput keeps its own knobs (the columns aren't a\n // Listbox), but the defaults stay in lockstep — designers can tune\n // them independently if they want a different time-picker palette.\n { key: 'popoverRowHoverBg', label: 'Row Hover', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'popover' },\n { key: 'popoverRowSelectedBg', label: 'Selected Row', control: 'color', defaultValue: 'var(--color-accent-subtle)', section: 'popover' },\n { key: 'popoverRowSelectedColor', label: 'Selected Text', control: 'color', defaultValue: 'var(--color-accent-bold)', section: 'popover' },\n ],\n layoutVariants: [\n {\n key: 'format',\n label: 'Format',\n options: [\n { value: '24h', label: '24-hour (HH:MM)' },\n { value: '12h', label: '12-hour (HH:MM AM/PM)' },\n ],\n defaultValue: '24h',\n },\n {\n key: 'state',\n label: 'State',\n options: [\n { value: 'default', label: 'Default' },\n { value: 'hover', label: 'Hover' },\n { value: 'focus', label: 'Focus' },\n { value: 'disabled', label: 'Disabled' },\n { value: 'error', label: 'Error' },\n ],\n defaultValue: 'default',\n },\n ],\n events: [\n { name: 'onChange', description: 'Fires on every keystroke, meridiem flip, or popover column pick. Payload is the masked, joined value (`HH:MM` for 24h, `HH:MM AM/PM` for 12h).', payload: '{ value: string }' },\n { name: 'onOpen', description: 'Fires when the time-picker popover opens.', payload: 'void' },\n { name: 'onClose', description: 'Fires when the time-picker popover closes (selection or outside click).', payload: 'void' },\n { name: 'onFocus', description: 'Fires when the field receives focus.', payload: 'FocusEvent' },\n { name: 'onBlur', description: 'Fires when the field loses focus.', payload: 'FocusEvent' },\n ],\n api: {\n importPath: '@viax/uxm/ui',\n importNames: 'TimeInput',\n props: [\n { name: 'value', type: 'string', description: 'Current value (controlled). 24h: `\"HH:MM\"`. 12h: `\"HH:MM AM\"` or `\"HH:MM PM\"`.' },\n { name: 'onChange', type: '(value: string) => void', description: 'Called with the masked, joined value after each edit.' },\n { name: 'format', type: '\"24h\" | \"12h\"', defaultValue: '\"24h\"', description: 'Clock convention. `12h` adds an AM/PM selector and a third popover column.' },\n { name: 'clock', type: 'boolean', defaultValue: 'true', description: 'Render the trailing clock icon. Pass `false` for an icon-less field.' },\n { name: 'picker', type: 'boolean', defaultValue: 'true', description: 'Mount the click-list popover (hour / minute / AM-PM). Pass `false` for a typing-only field — the clock icon stays decorative.' },\n { name: 'minuteStep', type: 'number', defaultValue: '1', description: \"Increment shown in the minute column of the popover. Off-step values typed into the field are still included (sorted), so a typed `09:03` under `minuteStep={5}` doesn't vanish.\" },\n { name: 'clearable', type: 'boolean', defaultValue: 'true', description: 'Show a trailing clear (✕) button when the field has a value. The ✕ sits inboard of the clock icon (and the AM/PM badge in 12h) and resets the value.' },\n { name: 'disabled', type: 'boolean', defaultValue: 'false', description: 'Disables the input, the AM/PM selector, and the picker trigger.' },\n { name: '...rest', type: 'InputHTMLAttributes<HTMLInputElement>', description: 'All other native input attributes (placeholder, name, aria-label, etc.) pass through.' },\n ],\n },\n};\n"],"mappings":"AAEO,MAAM,eAA6B;AAAA,EACxC,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,UAAU;AAAA,EACV,aAAa;AAAA,EACb,iBAAiB;AAAA;AAAA,IAEf,EAAE,KAAK,mBAAmB,OAAO,cAAc,SAAS,SAAS,cAAc,qBAAqB,SAAS,eAAe,UAAU,EAAE,OAAO,UAAU,EAAE;AAAA,IAC3J,EAAE,KAAK,eAAe,OAAO,UAAU,SAAS,SAAS,cAAc,uBAAuB,SAAS,eAAe,UAAU,EAAE,OAAO,UAAU,EAAE;AAAA,IACrJ,EAAE,KAAK,SAAS,OAAO,QAAQ,SAAS,SAAS,cAAc,qBAAqB,SAAS,eAAe,UAAU,EAAE,OAAO,UAAU,EAAE;AAAA,IAC3I,EAAE,KAAK,aAAa,OAAO,QAAQ,SAAS,SAAS,cAAc,2BAA2B,SAAS,eAAe,UAAU,EAAE,OAAO,UAAU,EAAE;AAAA;AAAA,IAErJ,EAAE,KAAK,WAAW,OAAO,cAAc,SAAS,SAAS,cAAc,qBAAqB,SAAS,eAAe,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA,IACjJ,EAAE,KAAK,eAAe,OAAO,UAAU,SAAS,SAAS,cAAc,uBAAuB,SAAS,eAAe,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA;AAAA,IAEnJ,EAAE,KAAK,eAAe,OAAO,UAAU,SAAS,SAAS,cAAc,uBAAuB,SAAS,eAAe,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA,IACnJ,EAAE,KAAK,aAAa,OAAO,QAAQ,SAAS,SAAS,cAAc,uBAAuB,SAAS,cAAc,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA;AAAA,IAE9I,EAAE,KAAK,cAAc,OAAO,cAAc,SAAS,SAAS,cAAc,4BAA4B,SAAS,eAAe,UAAU,EAAE,OAAO,WAAW,EAAE;AAAA,IAC9J,EAAE,KAAK,kBAAkB,OAAO,UAAU,SAAS,SAAS,cAAc,uBAAuB,SAAS,eAAe,UAAU,EAAE,OAAO,WAAW,EAAE;AAAA,IACzJ,EAAE,KAAK,iBAAiB,OAAO,QAAQ,SAAS,SAAS,cAAc,2BAA2B,SAAS,eAAe,UAAU,EAAE,OAAO,WAAW,EAAE;AAAA,IAC1J,EAAE,KAAK,mBAAmB,OAAO,WAAW,SAAS,UAAU,cAAc,KAAK,KAAK,KAAK,KAAK,GAAG,MAAM,MAAM,SAAS,iBAAiB,UAAU,EAAE,OAAO,WAAW,EAAE;AAAA;AAAA,IAE1K,EAAE,KAAK,WAAW,OAAO,cAAc,SAAS,SAAS,cAAc,qBAAqB,SAAS,eAAe,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA,IACjJ,EAAE,KAAK,eAAe,OAAO,UAAU,SAAS,SAAS,cAAc,4BAA4B,SAAS,eAAe,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA,IACxJ,EAAE,KAAK,cAAc,OAAO,WAAW,SAAS,SAAS,cAAc,4BAA4B,SAAS,cAAc,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA,IACvJ,EAAE,KAAK,oBAAoB,OAAO,gBAAgB,SAAS,UAAU,cAAc,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,MAAM,SAAS,cAAc,UAAU,EAAE,OAAO,QAAQ,EAAE;AAAA;AAAA,IAElL,EAAE,KAAK,gBAAgB,OAAO,iBAAiB,SAAS,UAAU,cAAc,GAAG,KAAK,GAAG,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK;AAAA,IACxH,EAAE,KAAK,YAAY,OAAO,aAAa,SAAS,UAAU,cAAc,IAAI,KAAK,GAAG,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK;AAAA,IACjH,EAAE,KAAK,YAAY,OAAO,aAAa,SAAS,UAAU,cAAc,IAAI,KAAK,GAAG,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK;AAAA,IACjH,EAAE,KAAK,YAAY,OAAO,aAAa,SAAS,UAAU,cAAc,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK;AAAA,IAClH,EAAE,KAAK,YAAY,OAAO,aAAa,SAAS,UAAU,cAAc,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK;AAAA,IAClH,EAAE,KAAK,cAAc,OAAO,eAAe,SAAS,UAAU,cAAc,IAAI,KAAK,GAAG,KAAK,IAAI,MAAM,GAAG,MAAM,KAAK;AAAA,IACrH,EAAE,KAAK,kBAAkB,OAAO,cAAc,SAAS,SAAS,cAAc,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKlG,EAAE,KAAK,iBAAiB,OAAO,cAAc,SAAS,SAAS,cAAc,2BAA2B,SAAS,YAAY,UAAU,EAAE,QAAQ,MAAM,EAAE;AAAA;AAAA;AAAA;AAAA,IAIzJ,EAAE,KAAK,aAAa,OAAO,cAAc,SAAS,SAAS,cAAc,qBAAqB,SAAS,UAAU;AAAA,IACjH,EAAE,KAAK,iBAAiB,OAAO,UAAU,SAAS,SAAS,cAAc,uBAAuB,SAAS,UAAU;AAAA,IACnH,EAAE,KAAK,iBAAiB,OAAO,iBAAiB,SAAS,UAAU,cAAc,GAAG,KAAK,GAAG,KAAK,IAAI,MAAM,GAAG,MAAM,MAAM,SAAS,UAAU;AAAA,IAC7I,EAAE,KAAK,iBAAiB,OAAO,kBAAkB,SAAS,SAAS,cAAc,4BAA4B,SAAS,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOhI,EAAE,KAAK,qBAAqB,OAAO,aAAa,SAAS,SAAS,cAAc,4BAA4B,SAAS,UAAU;AAAA,IAC/H,EAAE,KAAK,wBAAwB,OAAO,gBAAgB,SAAS,SAAS,cAAc,8BAA8B,SAAS,UAAU;AAAA,IACvI,EAAE,KAAK,2BAA2B,OAAO,iBAAiB,SAAS,SAAS,cAAc,4BAA4B,SAAS,UAAU;AAAA,EAC3I;AAAA,EACA,gBAAgB;AAAA,IACd;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,SAAS;AAAA,QACP,EAAE,OAAO,OAAO,OAAO,kBAAkB;AAAA,QACzC,EAAE,OAAO,OAAO,OAAO,wBAAwB;AAAA,MACjD;AAAA,MACA,cAAc;AAAA,IAChB;AAAA,IACA;AAAA,MACE,KAAK;AAAA,MACL,OAAO;AAAA,MACP,SAAS;AAAA,QACP,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,QACrC,EAAE,OAAO,SAAS,OAAO,QAAQ;AAAA,QACjC,EAAE,OAAO,SAAS,OAAO,QAAQ;AAAA,QACjC,EAAE,OAAO,YAAY,OAAO,WAAW;AAAA,QACvC,EAAE,OAAO,SAAS,OAAO,QAAQ;AAAA,MACnC;AAAA,MACA,cAAc;AAAA,IAChB;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,MAAM,YAAY,aAAa,kJAAkJ,SAAS,oBAAoB;AAAA,IAChN,EAAE,MAAM,UAAU,aAAa,6CAA6C,SAAS,OAAO;AAAA,IAC5F,EAAE,MAAM,WAAW,aAAa,2EAA2E,SAAS,OAAO;AAAA,IAC3H,EAAE,MAAM,WAAW,aAAa,wCAAwC,SAAS,aAAa;AAAA,IAC9F,EAAE,MAAM,UAAU,aAAa,qCAAqC,SAAS,aAAa;AAAA,EAC5F;AAAA,EACA,KAAK;AAAA,IACH,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,OAAO;AAAA,MACL,EAAE,MAAM,SAAS,MAAM,UAAU,aAAa,iFAAiF;AAAA,MAC/H,EAAE,MAAM,YAAY,MAAM,2BAA2B,aAAa,wDAAwD;AAAA,MAC1H,EAAE,MAAM,UAAU,MAAM,iBAAiB,cAAc,SAAS,aAAa,6EAA6E;AAAA,MAC1J,EAAE,MAAM,SAAS,MAAM,WAAW,cAAc,QAAQ,aAAa,uEAAuE;AAAA,MAC5I,EAAE,MAAM,UAAU,MAAM,WAAW,cAAc,QAAQ,aAAa,qIAAgI;AAAA,MACtM,EAAE,MAAM,cAAc,MAAM,UAAU,cAAc,KAAK,aAAa,mLAAmL;AAAA,MACzP,EAAE,MAAM,aAAa,MAAM,WAAW,cAAc,QAAQ,aAAa,iKAAuJ;AAAA,MAChO,EAAE,MAAM,YAAY,MAAM,WAAW,cAAc,SAAS,aAAa,kEAAkE;AAAA,MAC3I,EAAE,MAAM,WAAW,MAAM,yCAAyC,aAAa,wFAAwF;AAAA,IACzK;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/studio/lib/registry/inputs/time-input.ts"],"sourcesContent":["import type { ComponentDef } from '../../types';\n\nexport const timeInputDef: ComponentDef = {\n id: 'time-input',\n name: 'Time Input',\n category: 'Inputs',\n description: 'Time entry field with a trailing clock icon and an HH:MM mask. The `12h` format adds an AM/PM selector at the trailing edge. State knobs cover default / hover / focus / disabled / error.',\n styleProperties: [\n // Default\n { key: 'backgroundColor', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'fieldColors', showWhen: { state: 'default' } },\n { key: 'borderColor', label: 'Border', control: 'color', defaultValue: 'var(--color-border)', section: 'fieldColors', showWhen: { state: 'default' } },\n { key: 'color', label: 'Text', control: 'color', defaultValue: 'var(--color-text)', section: 'fieldColors', showWhen: { state: 'default' } },\n { key: 'iconColor', label: 'Icon', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'fieldColors', showWhen: { state: 'default' } },\n // Hover\n { key: 'hoverBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'fieldColors', showWhen: { state: 'hover' } },\n { key: 'hoverBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-accent)', section: 'fieldColors', showWhen: { state: 'hover' } },\n // Focus\n { key: 'focusBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-accent)', section: 'fieldColors', showWhen: { state: 'focus' } },\n { key: 'focusRing', label: 'Ring', control: 'color', defaultValue: 'var(--color-accent)', section: 'focusState', showWhen: { state: 'focus' } },\n // Disabled\n { key: 'disabledBg', label: 'Background', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'fieldColors', showWhen: { state: 'disabled' } },\n { key: 'disabledBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-border)', section: 'fieldColors', showWhen: { state: 'disabled' } },\n { key: 'disabledColor', label: 'Text', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'fieldColors', showWhen: { state: 'disabled' } },\n { key: 'disabledOpacity', label: 'Opacity', control: 'slider', defaultValue: 0.6, min: 0.1, max: 1, step: 0.05, section: 'disabledState', showWhen: { state: 'disabled' } },\n // Error\n { key: 'errorBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'fieldColors', showWhen: { state: 'error' } },\n { key: 'errorBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-danger-text)', section: 'fieldColors', showWhen: { state: 'error' } },\n { key: 'errorColor', label: 'Message', control: 'color', defaultValue: 'var(--color-danger-text)', section: 'errorState', showWhen: { state: 'error' } },\n { key: 'errorMessageSize', label: 'Message Size', control: 'number', defaultValue: 12, min: 10, max: 16, step: 1, unit: 'px', section: 'errorState', showWhen: { state: 'error' } },\n // Shared\n { key: 'borderRadius', label: 'Border Radius', control: 'slider', defaultValue: 8, min: 0, max: 99, step: 1, unit: 'px' },\n { key: 'paddingX', label: 'Padding X', control: 'number', defaultValue: 12, min: 4, max: 24, step: 2, unit: 'px' },\n { key: 'paddingY', label: 'Padding Y', control: 'number', defaultValue: 10, min: 4, max: 20, step: 2, unit: 'px' },\n { key: 'fontSize', label: 'Font Size', control: 'number', defaultValue: 14, min: 10, max: 20, step: 1, unit: 'px' },\n { key: 'iconSize', label: 'Icon Size', control: 'number', defaultValue: 16, min: 12, max: 24, step: 1, unit: 'px' },\n { key: 'iconOffset', label: 'Icon Offset', control: 'number', defaultValue: 12, min: 4, max: 24, step: 1, unit: 'px' },\n { key: 'iconHoverColor', label: 'Icon Hover', control: 'color', defaultValue: 'var(--color-text)' },\n // 12h-mode-only — the AM/PM suffix is a quiet inline label (no chip\n // background), so only the text color is themable. Gated by `showWhen`\n // so the knob only surfaces in 12h mode, matching DateInput's\n // range-mode-only knobs precedent.\n { key: 'meridiemColor', label: 'AM/PM Text', control: 'color', defaultValue: 'var(--color-text-muted)', section: 'meridiem', showWhen: { format: '12h' } },\n // Popover knobs — the column-scroll picker that opens when the\n // clock icon is clicked. Grouped into their own section so the\n // panel reads as \"the floating surface\" distinct from the field.\n { key: 'popoverBg', label: 'Background', control: 'color', defaultValue: 'var(--color-card)', section: 'popover' },\n { key: 'popoverBorder', label: 'Border', control: 'color', defaultValue: 'var(--color-border)', section: 'popover' },\n { key: 'popoverRadius', label: 'Border Radius', control: 'slider', defaultValue: 8, min: 0, max: 24, step: 1, unit: 'px', section: 'popover' },\n { key: 'popoverHeadBg', label: 'Column Heading', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'popover' },\n // Row hover + selected defaults align with the Listbox option\n // tokens (`--uxm-listbox-option-{active,selected}-{bg,color}`) so a\n // selected time visually matches a selected listbox option out of\n // the box. TimeInput keeps its own knobs (the columns aren't a\n // Listbox), but the defaults stay in lockstep — designers can tune\n // them independently if they want a different time-picker palette.\n { key: 'popoverRowHoverBg', label: 'Row Hover', control: 'color', defaultValue: 'var(--color-surface-alt)', section: 'popover' },\n { key: 'popoverRowSelectedBg', label: 'Selected Row', control: 'color', defaultValue: 'var(--color-accent-subtle)', section: 'popover' },\n { key: 'popoverRowSelectedColor', label: 'Selected Text', control: 'color', defaultValue: 'var(--color-accent-bold)', section: 'popover' },\n ],\n layoutVariants: [\n {\n key: 'format',\n label: 'Format',\n options: [\n { value: '24h', label: '24-hour (HH:MM)' },\n { value: '12h', label: '12-hour (HH:MM AM/PM)' },\n ],\n defaultValue: '24h',\n },\n {\n key: 'state',\n label: 'State',\n options: [\n { value: 'default', label: 'Default' },\n { value: 'hover', label: 'Hover' },\n { value: 'focus', label: 'Focus' },\n { value: 'disabled', label: 'Disabled' },\n { value: 'error', label: 'Error' },\n ],\n defaultValue: 'default',\n },\n ],\n events: [\n { name: 'onChange', description: 'Fires on every keystroke, meridiem flip, or popover column pick. Payload is the masked, joined value (`HH:MM` for 24h, `HH:MM AM/PM` for 12h).', payload: '{ value: string }' },\n { name: 'onOpen', description: 'Fires when the time-picker popover opens.', payload: 'void' },\n { name: 'onClose', description: 'Fires when the time-picker popover closes (selection or outside click).', payload: 'void' },\n { name: 'onFocus', description: 'Fires when the field receives focus.', payload: 'FocusEvent' },\n { name: 'onBlur', description: 'Fires when the field loses focus.', payload: 'FocusEvent' },\n ],\n api: {\n importPath: '@viax.io/uxm/ui',\n importNames: 'TimeInput',\n props: [\n { name: 'value', type: 'string', description: 'Current value (controlled). 24h: `\"HH:MM\"`. 12h: `\"HH:MM AM\"` or `\"HH:MM PM\"`.' },\n { name: 'onChange', type: '(value: string) => void', description: 'Called with the masked, joined value after each edit.' },\n { name: 'format', type: '\"24h\" | \"12h\"', defaultValue: '\"24h\"', description: 'Clock convention. `12h` adds an AM/PM selector and a third popover column.' },\n { name: 'clock', type: 'boolean', defaultValue: 'true', description: 'Render the trailing clock icon. Pass `false` for an icon-less field.' },\n { name: 'picker', type: 'boolean', defaultValue: 'true', description: 'Mount the click-list popover (hour / minute / AM-PM). Pass `false` for a typing-only field — the clock icon stays decorative.' },\n { name: 'minuteStep', type: 'number', defaultValue: '1', description: \"Increment shown in the minute column of the popover. Off-step values typed into the field are still included (sorted), so a typed `09:03` under `minuteStep={5}` doesn't vanish.\" },\n { name: 'clearable', type: 'boolean', defaultValue: 'true', description: 'Show a trailing clear (✕) button when the field has a value. The ✕ sits inboard of the clock icon (and the AM/PM badge in 12h) and resets the value.' },\n { name: 'disabled', type: 'boolean', defaultValue: 'false', description: 'Disables the input, the AM/PM selector, and the picker trigger.' },\n { name: '...rest', type: 'InputHTMLAttributes<HTMLInputElement>', description: 'All other native input attributes (placeholder, name, aria-label, etc.) pass through.' },\n ],\n },\n};\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/studio/lib/types.ts"],"sourcesContent":["export type Category = 'App' | 'Buttons' | 'Inputs' | 'Display' | 'Feedback' | 'Forms' | 'Composite' | 'Configuration' | 'Diagram' | 'Icons' | 'Layout';\n\nexport interface BrandTokens {\n light?: Record<string, string>;\n dark?: Record<string, string>;\n}\n\nexport interface BrandConfig {\n logoUrl?: string;\n iconUrl?: string;\n faviconUrl?: string;\n logoUrlDark?: string;\n iconUrlDark?: string;\n faviconUrlDark?: string;\n fontFamily?: string;\n /** Typeface for heading surfaces. Unset = headings inherit the body face. */\n headingFontFamily?: string;\n /** Weight for heading surfaces ('500' | '600' | '700'). Unset = each\n * component keeps its own default (600, or 700 for the display sizes). */\n headingFontWeight?: string;\n /** Per-role heading refinements, layered UNDER the umbrella above: unset =\n * the role follows `headingFontFamily` / `headingFontWeight`. Roles are\n * display (StatCard value, ErrorPage code), h1 (PageHeader / ErrorPage\n * title) and h2 (DetailSection / SegmentRow title). */\n displayFontFamily?: string;\n displayFontWeight?: string;\n displayScale?: string;\n pageTitleFontFamily?: string;\n pageTitleFontWeight?: string;\n pageTitleScale?: string;\n sectionTitleFontFamily?: string;\n sectionTitleFontWeight?: string;\n sectionTitleScale?: string;\n /** Base text size multiplier applied to every font-size in the library. */\n typeScale?: string;\n /** Line height for multi-line body copy. Unset = each surface's own value. */\n bodyLineHeight?: string;\n tokens?: BrandTokens;\n}\n\nexport type EditorControlType = 'color' | 'number' | 'select' | 'toggle' | 'slider' | 'text';\n\nexport interface StyleProperty {\n key: string;\n label: string;\n control: EditorControlType;\n defaultValue: string | number | boolean;\n options?: string[];\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n /** Variant scoping. A string matches one variant value; a string[] matches\n * any of several (same shape as a LayoutVariant option's `showWhen`) — use\n * the array form for \"this knob applies to several types / states\" instead\n * of a `'a|b'` string, which is compared verbatim and can never match. */\n showWhen?: Record<string, string | string[]>;\n section?: string;\n}\n\nexport interface LayoutVariant {\n key: string;\n label: string;\n options: Array<{\n value: string;\n label: string;\n /** Per-option scoping. A string matches one variant value; a string[]\n * matches any of several (e.g. show `editing` only for the input-like\n * types `['text','number','date']`, hiding it for the pickers). */\n showWhen?: Record<string, string | string[]>;\n }>;\n defaultValue: string;\n /** Variant-level scoping — same shape as styleProperties.showWhen. The\n * entire variant picker is hidden when its conditions aren't met by\n * the current variant selections. Use to hide a picker that's\n * irrelevant in a given context (e.g. the State picker on list-item\n * when `trailing` is `meta` / `none`). */\n showWhen?: Record<string, string>;\n}\n\n/**\n * One emitted event a component fires during interaction. Surfaces in\n * the Events tab of the Properties pane: a static spec on top (what\n * fires when, with what payload) and a live log below (chronological\n * capture of real events from the canvas).\n *\n * Names use the canonical React handler name (`onClick`, `onChange`)\n * rather than the DOM event name (`click`, `change`) so the spec reads\n * the way a consumer would type it in JSX. The DOM-event → handler\n * mapping lives in canvas.tsx's capture wrapper.\n */\nexport interface EventSpec {\n /** Handler name, e.g. \"onClick\". */\n name: string;\n /** Short prose describing what triggers the event. */\n description: string;\n /** TypeScript-ish payload signature, e.g. \"MouseEvent\" or \"{ value: string }\". */\n payload?: string;\n /** Variant scoping — same shape as styleProperties.showWhen. */\n showWhen?: Record<string, string>;\n}\n\n/**\n * One prop in the developer-facing API documentation. Surfaces in the\n * Dev tab's API section as a row in the props table.\n *\n * Authored alongside the atom (in the registry) rather than auto-\n * extracted from TypeScript today. The shape mirrors what an\n * extraction tool (react-docgen-typescript, ts-morph) would emit, so a\n * future build-time generator can populate this slot without changing\n * the consumer shape.\n */\nexport interface PropSpec {\n /** Prop name as it appears in JSX, e.g. \"variant\". */\n name: string;\n /** TypeScript-ish type signature, e.g. `\"primary\" | \"ghost\"`, `ReactNode`, `(e: MouseEvent) => void`. */\n type: string;\n /** Whether the prop is required (no `?` in the source interface). */\n required?: boolean;\n /** Default value as it appears in the destructured fn signature, stringified. */\n defaultValue?: string;\n /** Short prose — JSDoc summary, or hand-authored description. */\n description?: string;\n}\n\n/**\n * Developer-facing API documentation for a component. Drives the Dev\n * tab's API section (import path + props table + usage snippet).\n * Optional — atoms without `api` show only Events + Variant Matrix.\n */\nexport interface ComponentApi {\n /** Import path consumers use, e.g. \"@modo/uxm/ui\". */\n importPath: string;\n /** Named export(s) to import. Single string or array for atoms that ship multiple exports (Button family). */\n importNames: string | string[];\n /** Props in declaration order — exclude inherited HTML attributes for clarity. */\n props: PropSpec[];\n}\n\nexport interface ComponentDef {\n id: string;\n name: string;\n category: Category;\n description: string;\n styleProperties: StyleProperty[];\n layoutVariants: LayoutVariant[];\n canvasBackground?: boolean;\n /**\n * Fill the canvas width and top-align instead of the default center-both-\n * axes layout. For previews that are galleries / long lists (e.g. the Icon\n * set grid) where centering wastes horizontal space and — worse — re-centres\n * vertically as content height changes (a filter shrinking results makes the\n * whole block jump); top-aligning keeps it anchored so filtering stays in\n * place.\n */\n canvasFill?: boolean;\n /** Optional event-API documentation, surfaced in the Events tab. */\n events?: EventSpec[];\n /** Optional dev-facing API documentation, surfaced in the Dev tab. */\n api?: ComponentApi;\n}\n\nexport type StyleOverrides = Record<string, string | number | boolean>;\n\n// Authoritative PreviewProps now lives in @viax/uxm/previews. Re-exported\n// here so the rest of the shell (registry, panels) keeps working without\n// import-site changes.\nexport type { PreviewProps } from '@/previews';\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../../../src/studio/lib/types.ts"],"sourcesContent":["export type Category = 'App' | 'Buttons' | 'Inputs' | 'Display' | 'Feedback' | 'Forms' | 'Composite' | 'Configuration' | 'Diagram' | 'Icons' | 'Layout';\n\nexport interface BrandTokens {\n light?: Record<string, string>;\n dark?: Record<string, string>;\n}\n\nexport interface BrandConfig {\n logoUrl?: string;\n iconUrl?: string;\n faviconUrl?: string;\n logoUrlDark?: string;\n iconUrlDark?: string;\n faviconUrlDark?: string;\n fontFamily?: string;\n /** Typeface for heading surfaces. Unset = headings inherit the body face. */\n headingFontFamily?: string;\n /** Weight for heading surfaces ('500' | '600' | '700'). Unset = each\n * component keeps its own default (600, or 700 for the display sizes). */\n headingFontWeight?: string;\n /** Per-role heading refinements, layered UNDER the umbrella above: unset =\n * the role follows `headingFontFamily` / `headingFontWeight`. Roles are\n * display (StatCard value, ErrorPage code), h1 (PageHeader / ErrorPage\n * title) and h2 (DetailSection / SegmentRow title). */\n displayFontFamily?: string;\n displayFontWeight?: string;\n displayScale?: string;\n pageTitleFontFamily?: string;\n pageTitleFontWeight?: string;\n pageTitleScale?: string;\n sectionTitleFontFamily?: string;\n sectionTitleFontWeight?: string;\n sectionTitleScale?: string;\n /** Base text size multiplier applied to every font-size in the library. */\n typeScale?: string;\n /** Line height for multi-line body copy. Unset = each surface's own value. */\n bodyLineHeight?: string;\n tokens?: BrandTokens;\n}\n\nexport type EditorControlType = 'color' | 'number' | 'select' | 'toggle' | 'slider' | 'text';\n\nexport interface StyleProperty {\n key: string;\n label: string;\n control: EditorControlType;\n defaultValue: string | number | boolean;\n options?: string[];\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n /** Variant scoping. A string matches one variant value; a string[] matches\n * any of several (same shape as a LayoutVariant option's `showWhen`) — use\n * the array form for \"this knob applies to several types / states\" instead\n * of a `'a|b'` string, which is compared verbatim and can never match. */\n showWhen?: Record<string, string | string[]>;\n section?: string;\n}\n\nexport interface LayoutVariant {\n key: string;\n label: string;\n options: Array<{\n value: string;\n label: string;\n /** Per-option scoping. A string matches one variant value; a string[]\n * matches any of several (e.g. show `editing` only for the input-like\n * types `['text','number','date']`, hiding it for the pickers). */\n showWhen?: Record<string, string | string[]>;\n }>;\n defaultValue: string;\n /** Variant-level scoping — same shape as styleProperties.showWhen. The\n * entire variant picker is hidden when its conditions aren't met by\n * the current variant selections. Use to hide a picker that's\n * irrelevant in a given context (e.g. the State picker on list-item\n * when `trailing` is `meta` / `none`). */\n showWhen?: Record<string, string>;\n}\n\n/**\n * One emitted event a component fires during interaction. Surfaces in\n * the Events tab of the Properties pane: a static spec on top (what\n * fires when, with what payload) and a live log below (chronological\n * capture of real events from the canvas).\n *\n * Names use the canonical React handler name (`onClick`, `onChange`)\n * rather than the DOM event name (`click`, `change`) so the spec reads\n * the way a consumer would type it in JSX. The DOM-event → handler\n * mapping lives in canvas.tsx's capture wrapper.\n */\nexport interface EventSpec {\n /** Handler name, e.g. \"onClick\". */\n name: string;\n /** Short prose describing what triggers the event. */\n description: string;\n /** TypeScript-ish payload signature, e.g. \"MouseEvent\" or \"{ value: string }\". */\n payload?: string;\n /** Variant scoping — same shape as styleProperties.showWhen. */\n showWhen?: Record<string, string>;\n}\n\n/**\n * One prop in the developer-facing API documentation. Surfaces in the\n * Dev tab's API section as a row in the props table.\n *\n * Authored alongside the atom (in the registry) rather than auto-\n * extracted from TypeScript today. The shape mirrors what an\n * extraction tool (react-docgen-typescript, ts-morph) would emit, so a\n * future build-time generator can populate this slot without changing\n * the consumer shape.\n */\nexport interface PropSpec {\n /** Prop name as it appears in JSX, e.g. \"variant\". */\n name: string;\n /** TypeScript-ish type signature, e.g. `\"primary\" | \"ghost\"`, `ReactNode`, `(e: MouseEvent) => void`. */\n type: string;\n /** Whether the prop is required (no `?` in the source interface). */\n required?: boolean;\n /** Default value as it appears in the destructured fn signature, stringified. */\n defaultValue?: string;\n /** Short prose — JSDoc summary, or hand-authored description. */\n description?: string;\n}\n\n/**\n * Developer-facing API documentation for a component. Drives the Dev\n * tab's API section (import path + props table + usage snippet).\n * Optional — atoms without `api` show only Events + Variant Matrix.\n */\nexport interface ComponentApi {\n /** Import path consumers use, e.g. \"@modo/uxm/ui\". */\n importPath: string;\n /** Named export(s) to import. Single string or array for atoms that ship multiple exports (Button family). */\n importNames: string | string[];\n /** Props in declaration order — exclude inherited HTML attributes for clarity. */\n props: PropSpec[];\n}\n\nexport interface ComponentDef {\n id: string;\n name: string;\n category: Category;\n description: string;\n styleProperties: StyleProperty[];\n layoutVariants: LayoutVariant[];\n canvasBackground?: boolean;\n /**\n * Fill the canvas width and top-align instead of the default center-both-\n * axes layout. For previews that are galleries / long lists (e.g. the Icon\n * set grid) where centering wastes horizontal space and — worse — re-centres\n * vertically as content height changes (a filter shrinking results makes the\n * whole block jump); top-aligning keeps it anchored so filtering stays in\n * place.\n */\n canvasFill?: boolean;\n /** Optional event-API documentation, surfaced in the Events tab. */\n events?: EventSpec[];\n /** Optional dev-facing API documentation, surfaced in the Dev tab. */\n api?: ComponentApi;\n}\n\nexport type StyleOverrides = Record<string, string | number | boolean>;\n\n// Authoritative PreviewProps now lives in @viax.io/uxm/previews. Re-exported\n// here so the rest of the shell (registry, panels) keeps working without\n// import-site changes.\nexport type { PreviewProps } from '@/previews';\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/studio/persistence/generate-css.ts"],"sourcesContent":["import { safeFontFamily, safeFontWeight, safeLineHeight, safeTokenKey, safeTokenValue, safeTypeScale, safeUrl } from './css-sanitizers';\nimport { PER_COMPONENT_MAPPING, PER_COMPONENT_SELECTOR } from './generate-css-mapping';\n\nimport type { AllOverrides } from './types';\nimport type { BrandConfig } from '../lib/types';\n\n/**\n * Pure CSS generator for UXM Studio overrides.\n *\n * Single source of truth (R5): the modo Hono `/api/uxm/save` route imports\n * `generateOverridesCss` from here instead of keeping its own copy. No Node\n * APIs — runs in browser (client export) and server alike.\n *\n * Emits a stylesheet from per-component style overrides plus brand config\n * (font, logo/icon URLs, brand token blocks). All attacker-controlled brand\n * values are sanitised before interpolation because the output is served to\n * every browser session.\n */\n\nconst BRAND_FONT_WEIGHTS = 'wght@400;500;600;700';\n\nexport function fontFileUrl(fontFamily: string): string {\n const encoded = fontFamily.trim().replace(/\\s+/g, '+');\n return `https://fonts.googleapis.com/css2?family=${encoded}:${BRAND_FONT_WEIGHTS}&display=swap`;\n}\n\nconst REAL_CSS_PROPS = new Set([\n 'background', 'backgroundColor',\n 'color',\n 'border', 'borderColor', 'borderRadius', 'borderWidth', 'borderStyle',\n 'padding', 'paddingX', 'paddingY', 'margin',\n 'gap', 'rowGap', 'columnGap',\n 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight',\n 'fontSize', 'fontWeight', 'fontFamily', 'lineHeight',\n 'opacity',\n 'cursor', 'display', 'position', 'overflow', 'direction',\n]);\n\nfunction toCSS(key: string, componentId: string): string {\n const componentMapping = PER_COMPONENT_MAPPING[componentId];\n if (componentMapping?.[key]) return componentMapping[key];\n\n const mapping: Record<string, string> = {\n paddingX: 'padding-inline',\n paddingY: 'padding-block',\n };\n if (mapping[key]) return mapping[key];\n\n const kebab = key.replace(/([A-Z])/g, '-$1').toLowerCase();\n if (REAL_CSS_PROPS.has(key)) return kebab;\n return `--uxm-${componentId}-${kebab}`;\n}\n\nfunction formatValue(value: string | number | boolean, key: string): string {\n if (typeof value === 'boolean') return value ? '1' : '0';\n if (typeof value === 'number') {\n // `lineHeight` is a ratio, not a length — without it here a knob saved as\n // 1.5 emits `line-height: 1.5px`, which collapses every line box.\n const unitless = ['fontWeight', 'titleWeight', 'nameWeight', 'typeLabelWeight', 'lineHeight', 'shadow', 'disabledOpacity'];\n // `opacity` is always unitless — match every *Opacity knob (e.g. the\n // per-element `itemDisabledOpacity` / `optionDisabledOpacity` variants)\n // so a saved value never gets a spurious `px` suffix.\n if (unitless.includes(key) || key.endsWith('Opacity')) return String(value);\n return `${value}px`;\n }\n // String style-override values are user/attacker-controlled and persisted\n // to a stylesheet served to every browser session — sanitise the same way\n // as brand token values so one can't break out of the declaration (e.g.\n // `red; } body{display:none}/*`). An unsafe value is dropped to '' rather\n // than throwing, so a single bad override can't blank the whole rule.\n return safeTokenValue(value) ?? '';\n}\n\n// The sanitizers moved to ./css-sanitizers.ts; they stay part of this module's\n// public surface (`@viax/uxm/studio/generate-css`, brand-*-styles.tsx).\nexport { safeFontFamily, safeFontWeight, safeLineHeight, safeTokenKey, safeTokenValue, safeTypeScale } from './css-sanitizers';\n\n// The three heading roles, in one place. Both the publish path below and the\n// live mirror (`brand-font-styles.tsx`) derive their emitted var names from\n// `role` by interpolation, so a rename here reaches both — keeping these in two\n// hand-maintained lists is how `--type-${role}-font` silently drifts away from\n// the names the stylesheets actually read.\nexport const TYPE_ROLES = [\n { role: 'display', family: 'displayFontFamily', weight: 'displayFontWeight', scale: 'displayScale' },\n { role: 'page-title', family: 'pageTitleFontFamily', weight: 'pageTitleFontWeight', scale: 'pageTitleScale' },\n { role: 'section-title', family: 'sectionTitleFontFamily', weight: 'sectionTitleFontWeight', scale: 'sectionTitleScale' },\n] as const satisfies ReadonlyArray<{\n role: string;\n family: keyof BrandConfig;\n weight: keyof BrandConfig;\n scale: keyof BrandConfig;\n}>;\n\n/** Sanitised per-role typography, shared by the generator and the live mirror. */\nexport function resolveTypeRoles(brand: BrandConfig) {\n return TYPE_ROLES.map((r) => ({\n role: r.role,\n family: safeFontFamily(brand[r.family]),\n weight: safeFontWeight(brand[r.weight]),\n scale: safeTypeScale(brand[r.scale]),\n }));\n}\n\n/** Generate the overrides stylesheet from style overrides + brand config. */\nexport function generateOverridesCss(allOverrides: AllOverrides, brand: BrandConfig): string {\n const lines: string[] = ['/* Auto-generated by UXM — do not edit manually */', ''];\n\n const fontFamily = safeFontFamily(brand.fontFamily);\n const headingFontFamily = safeFontFamily(brand.headingFontFamily);\n const headingFontWeight = safeFontWeight(brand.headingFontWeight);\n // Per-role refinements, layered under the umbrella heading vars above.\n const roles = resolveTypeRoles(brand);\n const typeScale = safeTypeScale(brand.typeScale);\n const bodyLineHeight = safeLineHeight(brand.bodyLineHeight);\n const logoUrl = safeUrl(brand.logoUrl);\n const iconUrl = safeUrl(brand.iconUrl);\n const logoUrlDark = safeUrl(brand.logoUrlDark);\n const iconUrlDark = safeUrl(brand.iconUrlDark);\n\n if (fontFamily) {\n lines.push(`@import url(\"${fontFileUrl(fontFamily)}\");`);\n lines.push('');\n }\n // Second font file only when the heading face is actually a different family\n // — picking the same one for both is a no-op, not a reason to fetch twice.\n if (headingFontFamily && headingFontFamily !== fontFamily) {\n lines.push(`@import url(\"${fontFileUrl(headingFontFamily)}\");`);\n lines.push('');\n }\n // A role may pick a face neither the body nor the umbrella uses — fetch each\n // distinct one exactly once (@import must stay above the rules below).\n const imported = new Set([fontFamily, headingFontFamily].filter(Boolean));\n for (const { family } of roles) {\n if (family && !imported.has(family)) {\n imported.add(family);\n lines.push(`@import url(\"${fontFileUrl(family)}\");`);\n lines.push('');\n }\n }\n\n const brandRules: string[] = [];\n if (fontFamily) {\n brandRules.push(` --brand-font: \"${fontFamily}\", var(--font-inter), system-ui, sans-serif;`);\n }\n // Heading vars are consumed by the components' own two-layer fallbacks (see\n // e.g. page-header.scss), so there is no `body` rule and no `!important`:\n // an unset var simply lets each component's own default paint.\n if (headingFontFamily) {\n brandRules.push(` --brand-heading-font: \"${headingFontFamily}\", var(--font-inter), system-ui, sans-serif;`);\n }\n if (headingFontWeight) {\n brandRules.push(` --brand-heading-weight: ${headingFontWeight};`);\n }\n // Role vars sit between the component var and the umbrella in each atom's\n // chain, so an unset role simply falls through to the umbrella.\n for (const { role, family, weight, scale } of roles) {\n if (family) {\n brandRules.push(` --type-${role}-font: \"${family}\", var(--font-inter), system-ui, sans-serif;`);\n }\n if (weight) brandRules.push(` --type-${role}-weight: ${weight};`);\n if (scale) brandRules.push(` --type-${role}-scale: ${scale};`);\n }\n if (typeScale) brandRules.push(` --type-scale: ${typeScale};`);\n if (bodyLineHeight) brandRules.push(` --type-body-line-height: ${bodyLineHeight};`);\n if (logoUrl) brandRules.push(` --brand-logo-url: url(\"${logoUrl}\");`);\n if (iconUrl) brandRules.push(` --brand-icon-url: url(\"${iconUrl}\");`);\n\n if (brandRules.length > 0) {\n lines.push(':root {');\n lines.push(...brandRules);\n lines.push('}');\n lines.push('');\n if (fontFamily) {\n lines.push('body { font-family: var(--brand-font) !important; }');\n lines.push('');\n }\n // Emitted ONLY when set. The library ships no element-level rules at all,\n // so an unconditional `body { line-height }` would reflow the host's own\n // content — same reasoning as the font-family rule above.\n if (bodyLineHeight) {\n // Explicit fallback: if the var is later cancelled (the studio mirror\n // re-declares it as `initial` on revert) a bare `var()` would be invalid\n // at computed-value time and `line-height` — being inherited — would\n // resolve to the parent's value, silently replacing whatever the host\n // had set on `body`. `normal` makes the wind-down state deliberate.\n lines.push(`body { line-height: var(--type-body-line-height, normal); }`);\n lines.push('');\n }\n }\n\n const darkBrandRules: string[] = [];\n if (logoUrlDark) darkBrandRules.push(` --brand-logo-url: url(\"${logoUrlDark}\");`);\n if (iconUrlDark) darkBrandRules.push(` --brand-icon-url: url(\"${iconUrlDark}\");`);\n if (darkBrandRules.length > 0) {\n lines.push('[data-theme=\"dark\"] {');\n lines.push(...darkBrandRules);\n lines.push('}');\n lines.push('');\n }\n\n const emitTokens = (selector: string, overrides: Record<string, string> | undefined) => {\n if (!overrides) return;\n const safeEntries = Object.entries(overrides).flatMap(([k, v]) => {\n if (!safeTokenKey(k)) return [];\n const safeValue = safeTokenValue(v);\n return safeValue === undefined ? [] : [[k, safeValue] as const];\n });\n if (safeEntries.length === 0) return;\n lines.push(`${selector} {`);\n for (const [cssVar, value] of safeEntries) {\n lines.push(` ${cssVar}: ${value};`);\n }\n lines.push('}');\n lines.push('');\n };\n // Scope customized light-mode color tokens to non-dark. `:root` and\n // `[data-theme=\"dark\"]` carry equal specificity, so an unconditional\n // `:root` block would win over this stylesheet's own dark block for any\n // token NOT also customized for dark (equal specificity, later source\n // order) — pinning that token to its light value in dark mode too. Scoping\n // lets an unset dark token fall through the cascade to tokens/index.css's\n // own `[data-theme=\"dark\"]` default instead.\n emitTokens(':root:not([data-theme=\"dark\"])', brand.tokens?.light);\n emitTokens('[data-theme=\"dark\"]', brand.tokens?.dark);\n\n const componentIds = Object.keys(allOverrides).sort();\n\n for (const id of componentIds) {\n const overrides = allOverrides[id];\n const entries = Object.entries(overrides);\n if (entries.length === 0) continue;\n\n const selector = PER_COMPONENT_SELECTOR[id] ?? `.uxm-${id}`;\n lines.push(`${selector} {`);\n for (const [key, value] of entries) {\n const cssProp = toCSS(key, id);\n const cssValue = formatValue(value, key);\n lines.push(` ${cssProp}: ${cssValue};`);\n }\n lines.push('}');\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["import_css_sanitizers"]}
1
+ {"version":3,"sources":["../../../src/studio/persistence/generate-css.ts"],"sourcesContent":["import { safeFontFamily, safeFontWeight, safeLineHeight, safeTokenKey, safeTokenValue, safeTypeScale, safeUrl } from './css-sanitizers';\nimport { PER_COMPONENT_MAPPING, PER_COMPONENT_SELECTOR } from './generate-css-mapping';\n\nimport type { AllOverrides } from './types';\nimport type { BrandConfig } from '../lib/types';\n\n/**\n * Pure CSS generator for UXM Studio overrides.\n *\n * Single source of truth (R5): the modo Hono `/api/uxm/save` route imports\n * `generateOverridesCss` from here instead of keeping its own copy. No Node\n * APIs — runs in browser (client export) and server alike.\n *\n * Emits a stylesheet from per-component style overrides plus brand config\n * (font, logo/icon URLs, brand token blocks). All attacker-controlled brand\n * values are sanitised before interpolation because the output is served to\n * every browser session.\n */\n\nconst BRAND_FONT_WEIGHTS = 'wght@400;500;600;700';\n\nexport function fontFileUrl(fontFamily: string): string {\n const encoded = fontFamily.trim().replace(/\\s+/g, '+');\n return `https://fonts.googleapis.com/css2?family=${encoded}:${BRAND_FONT_WEIGHTS}&display=swap`;\n}\n\nconst REAL_CSS_PROPS = new Set([\n 'background', 'backgroundColor',\n 'color',\n 'border', 'borderColor', 'borderRadius', 'borderWidth', 'borderStyle',\n 'padding', 'paddingX', 'paddingY', 'margin',\n 'gap', 'rowGap', 'columnGap',\n 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight',\n 'fontSize', 'fontWeight', 'fontFamily', 'lineHeight',\n 'opacity',\n 'cursor', 'display', 'position', 'overflow', 'direction',\n]);\n\nfunction toCSS(key: string, componentId: string): string {\n const componentMapping = PER_COMPONENT_MAPPING[componentId];\n if (componentMapping?.[key]) return componentMapping[key];\n\n const mapping: Record<string, string> = {\n paddingX: 'padding-inline',\n paddingY: 'padding-block',\n };\n if (mapping[key]) return mapping[key];\n\n const kebab = key.replace(/([A-Z])/g, '-$1').toLowerCase();\n if (REAL_CSS_PROPS.has(key)) return kebab;\n return `--uxm-${componentId}-${kebab}`;\n}\n\nfunction formatValue(value: string | number | boolean, key: string): string {\n if (typeof value === 'boolean') return value ? '1' : '0';\n if (typeof value === 'number') {\n // `lineHeight` is a ratio, not a length — without it here a knob saved as\n // 1.5 emits `line-height: 1.5px`, which collapses every line box.\n const unitless = ['fontWeight', 'titleWeight', 'nameWeight', 'typeLabelWeight', 'lineHeight', 'shadow', 'disabledOpacity'];\n // `opacity` is always unitless — match every *Opacity knob (e.g. the\n // per-element `itemDisabledOpacity` / `optionDisabledOpacity` variants)\n // so a saved value never gets a spurious `px` suffix.\n if (unitless.includes(key) || key.endsWith('Opacity')) return String(value);\n return `${value}px`;\n }\n // String style-override values are user/attacker-controlled and persisted\n // to a stylesheet served to every browser session — sanitise the same way\n // as brand token values so one can't break out of the declaration (e.g.\n // `red; } body{display:none}/*`). An unsafe value is dropped to '' rather\n // than throwing, so a single bad override can't blank the whole rule.\n return safeTokenValue(value) ?? '';\n}\n\n// The sanitizers moved to ./css-sanitizers.ts; they stay part of this module's\n// public surface (`@viax.io/uxm/studio/generate-css`, brand-*-styles.tsx).\nexport { safeFontFamily, safeFontWeight, safeLineHeight, safeTokenKey, safeTokenValue, safeTypeScale } from './css-sanitizers';\n\n// The three heading roles, in one place. Both the publish path below and the\n// live mirror (`brand-font-styles.tsx`) derive their emitted var names from\n// `role` by interpolation, so a rename here reaches both — keeping these in two\n// hand-maintained lists is how `--type-${role}-font` silently drifts away from\n// the names the stylesheets actually read.\nexport const TYPE_ROLES = [\n { role: 'display', family: 'displayFontFamily', weight: 'displayFontWeight', scale: 'displayScale' },\n { role: 'page-title', family: 'pageTitleFontFamily', weight: 'pageTitleFontWeight', scale: 'pageTitleScale' },\n { role: 'section-title', family: 'sectionTitleFontFamily', weight: 'sectionTitleFontWeight', scale: 'sectionTitleScale' },\n] as const satisfies ReadonlyArray<{\n role: string;\n family: keyof BrandConfig;\n weight: keyof BrandConfig;\n scale: keyof BrandConfig;\n}>;\n\n/** Sanitised per-role typography, shared by the generator and the live mirror. */\nexport function resolveTypeRoles(brand: BrandConfig) {\n return TYPE_ROLES.map((r) => ({\n role: r.role,\n family: safeFontFamily(brand[r.family]),\n weight: safeFontWeight(brand[r.weight]),\n scale: safeTypeScale(brand[r.scale]),\n }));\n}\n\n/** Generate the overrides stylesheet from style overrides + brand config. */\nexport function generateOverridesCss(allOverrides: AllOverrides, brand: BrandConfig): string {\n const lines: string[] = ['/* Auto-generated by UXM — do not edit manually */', ''];\n\n const fontFamily = safeFontFamily(brand.fontFamily);\n const headingFontFamily = safeFontFamily(brand.headingFontFamily);\n const headingFontWeight = safeFontWeight(brand.headingFontWeight);\n // Per-role refinements, layered under the umbrella heading vars above.\n const roles = resolveTypeRoles(brand);\n const typeScale = safeTypeScale(brand.typeScale);\n const bodyLineHeight = safeLineHeight(brand.bodyLineHeight);\n const logoUrl = safeUrl(brand.logoUrl);\n const iconUrl = safeUrl(brand.iconUrl);\n const logoUrlDark = safeUrl(brand.logoUrlDark);\n const iconUrlDark = safeUrl(brand.iconUrlDark);\n\n if (fontFamily) {\n lines.push(`@import url(\"${fontFileUrl(fontFamily)}\");`);\n lines.push('');\n }\n // Second font file only when the heading face is actually a different family\n // — picking the same one for both is a no-op, not a reason to fetch twice.\n if (headingFontFamily && headingFontFamily !== fontFamily) {\n lines.push(`@import url(\"${fontFileUrl(headingFontFamily)}\");`);\n lines.push('');\n }\n // A role may pick a face neither the body nor the umbrella uses — fetch each\n // distinct one exactly once (@import must stay above the rules below).\n const imported = new Set([fontFamily, headingFontFamily].filter(Boolean));\n for (const { family } of roles) {\n if (family && !imported.has(family)) {\n imported.add(family);\n lines.push(`@import url(\"${fontFileUrl(family)}\");`);\n lines.push('');\n }\n }\n\n const brandRules: string[] = [];\n if (fontFamily) {\n brandRules.push(` --brand-font: \"${fontFamily}\", var(--font-inter), system-ui, sans-serif;`);\n }\n // Heading vars are consumed by the components' own two-layer fallbacks (see\n // e.g. page-header.scss), so there is no `body` rule and no `!important`:\n // an unset var simply lets each component's own default paint.\n if (headingFontFamily) {\n brandRules.push(` --brand-heading-font: \"${headingFontFamily}\", var(--font-inter), system-ui, sans-serif;`);\n }\n if (headingFontWeight) {\n brandRules.push(` --brand-heading-weight: ${headingFontWeight};`);\n }\n // Role vars sit between the component var and the umbrella in each atom's\n // chain, so an unset role simply falls through to the umbrella.\n for (const { role, family, weight, scale } of roles) {\n if (family) {\n brandRules.push(` --type-${role}-font: \"${family}\", var(--font-inter), system-ui, sans-serif;`);\n }\n if (weight) brandRules.push(` --type-${role}-weight: ${weight};`);\n if (scale) brandRules.push(` --type-${role}-scale: ${scale};`);\n }\n if (typeScale) brandRules.push(` --type-scale: ${typeScale};`);\n if (bodyLineHeight) brandRules.push(` --type-body-line-height: ${bodyLineHeight};`);\n if (logoUrl) brandRules.push(` --brand-logo-url: url(\"${logoUrl}\");`);\n if (iconUrl) brandRules.push(` --brand-icon-url: url(\"${iconUrl}\");`);\n\n if (brandRules.length > 0) {\n lines.push(':root {');\n lines.push(...brandRules);\n lines.push('}');\n lines.push('');\n if (fontFamily) {\n lines.push('body { font-family: var(--brand-font) !important; }');\n lines.push('');\n }\n // Emitted ONLY when set. The library ships no element-level rules at all,\n // so an unconditional `body { line-height }` would reflow the host's own\n // content — same reasoning as the font-family rule above.\n if (bodyLineHeight) {\n // Explicit fallback: if the var is later cancelled (the studio mirror\n // re-declares it as `initial` on revert) a bare `var()` would be invalid\n // at computed-value time and `line-height` — being inherited — would\n // resolve to the parent's value, silently replacing whatever the host\n // had set on `body`. `normal` makes the wind-down state deliberate.\n lines.push(`body { line-height: var(--type-body-line-height, normal); }`);\n lines.push('');\n }\n }\n\n const darkBrandRules: string[] = [];\n if (logoUrlDark) darkBrandRules.push(` --brand-logo-url: url(\"${logoUrlDark}\");`);\n if (iconUrlDark) darkBrandRules.push(` --brand-icon-url: url(\"${iconUrlDark}\");`);\n if (darkBrandRules.length > 0) {\n lines.push('[data-theme=\"dark\"] {');\n lines.push(...darkBrandRules);\n lines.push('}');\n lines.push('');\n }\n\n const emitTokens = (selector: string, overrides: Record<string, string> | undefined) => {\n if (!overrides) return;\n const safeEntries = Object.entries(overrides).flatMap(([k, v]) => {\n if (!safeTokenKey(k)) return [];\n const safeValue = safeTokenValue(v);\n return safeValue === undefined ? [] : [[k, safeValue] as const];\n });\n if (safeEntries.length === 0) return;\n lines.push(`${selector} {`);\n for (const [cssVar, value] of safeEntries) {\n lines.push(` ${cssVar}: ${value};`);\n }\n lines.push('}');\n lines.push('');\n };\n // Scope customized light-mode color tokens to non-dark. `:root` and\n // `[data-theme=\"dark\"]` carry equal specificity, so an unconditional\n // `:root` block would win over this stylesheet's own dark block for any\n // token NOT also customized for dark (equal specificity, later source\n // order) — pinning that token to its light value in dark mode too. Scoping\n // lets an unset dark token fall through the cascade to tokens/index.css's\n // own `[data-theme=\"dark\"]` default instead.\n emitTokens(':root:not([data-theme=\"dark\"])', brand.tokens?.light);\n emitTokens('[data-theme=\"dark\"]', brand.tokens?.dark);\n\n const componentIds = Object.keys(allOverrides).sort();\n\n for (const id of componentIds) {\n const overrides = allOverrides[id];\n const entries = Object.entries(overrides);\n if (entries.length === 0) continue;\n\n const selector = PER_COMPONENT_SELECTOR[id] ?? `.uxm-${id}`;\n lines.push(`${selector} {`);\n for (const [key, value] of entries) {\n const cssProp = toCSS(key, id);\n const cssValue = formatValue(value, key);\n lines.push(` ${cssProp}: ${cssValue};`);\n }\n lines.push('}');\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["import_css_sanitizers"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/studio/persistence/generate-css.ts"],"sourcesContent":["import { safeFontFamily, safeFontWeight, safeLineHeight, safeTokenKey, safeTokenValue, safeTypeScale, safeUrl } from './css-sanitizers';\nimport { PER_COMPONENT_MAPPING, PER_COMPONENT_SELECTOR } from './generate-css-mapping';\n\nimport type { AllOverrides } from './types';\nimport type { BrandConfig } from '../lib/types';\n\n/**\n * Pure CSS generator for UXM Studio overrides.\n *\n * Single source of truth (R5): the modo Hono `/api/uxm/save` route imports\n * `generateOverridesCss` from here instead of keeping its own copy. No Node\n * APIs — runs in browser (client export) and server alike.\n *\n * Emits a stylesheet from per-component style overrides plus brand config\n * (font, logo/icon URLs, brand token blocks). All attacker-controlled brand\n * values are sanitised before interpolation because the output is served to\n * every browser session.\n */\n\nconst BRAND_FONT_WEIGHTS = 'wght@400;500;600;700';\n\nexport function fontFileUrl(fontFamily: string): string {\n const encoded = fontFamily.trim().replace(/\\s+/g, '+');\n return `https://fonts.googleapis.com/css2?family=${encoded}:${BRAND_FONT_WEIGHTS}&display=swap`;\n}\n\nconst REAL_CSS_PROPS = new Set([\n 'background', 'backgroundColor',\n 'color',\n 'border', 'borderColor', 'borderRadius', 'borderWidth', 'borderStyle',\n 'padding', 'paddingX', 'paddingY', 'margin',\n 'gap', 'rowGap', 'columnGap',\n 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight',\n 'fontSize', 'fontWeight', 'fontFamily', 'lineHeight',\n 'opacity',\n 'cursor', 'display', 'position', 'overflow', 'direction',\n]);\n\nfunction toCSS(key: string, componentId: string): string {\n const componentMapping = PER_COMPONENT_MAPPING[componentId];\n if (componentMapping?.[key]) return componentMapping[key];\n\n const mapping: Record<string, string> = {\n paddingX: 'padding-inline',\n paddingY: 'padding-block',\n };\n if (mapping[key]) return mapping[key];\n\n const kebab = key.replace(/([A-Z])/g, '-$1').toLowerCase();\n if (REAL_CSS_PROPS.has(key)) return kebab;\n return `--uxm-${componentId}-${kebab}`;\n}\n\nfunction formatValue(value: string | number | boolean, key: string): string {\n if (typeof value === 'boolean') return value ? '1' : '0';\n if (typeof value === 'number') {\n // `lineHeight` is a ratio, not a length — without it here a knob saved as\n // 1.5 emits `line-height: 1.5px`, which collapses every line box.\n const unitless = ['fontWeight', 'titleWeight', 'nameWeight', 'typeLabelWeight', 'lineHeight', 'shadow', 'disabledOpacity'];\n // `opacity` is always unitless — match every *Opacity knob (e.g. the\n // per-element `itemDisabledOpacity` / `optionDisabledOpacity` variants)\n // so a saved value never gets a spurious `px` suffix.\n if (unitless.includes(key) || key.endsWith('Opacity')) return String(value);\n return `${value}px`;\n }\n // String style-override values are user/attacker-controlled and persisted\n // to a stylesheet served to every browser session — sanitise the same way\n // as brand token values so one can't break out of the declaration (e.g.\n // `red; } body{display:none}/*`). An unsafe value is dropped to '' rather\n // than throwing, so a single bad override can't blank the whole rule.\n return safeTokenValue(value) ?? '';\n}\n\n// The sanitizers moved to ./css-sanitizers.ts; they stay part of this module's\n// public surface (`@viax/uxm/studio/generate-css`, brand-*-styles.tsx).\nexport { safeFontFamily, safeFontWeight, safeLineHeight, safeTokenKey, safeTokenValue, safeTypeScale } from './css-sanitizers';\n\n// The three heading roles, in one place. Both the publish path below and the\n// live mirror (`brand-font-styles.tsx`) derive their emitted var names from\n// `role` by interpolation, so a rename here reaches both — keeping these in two\n// hand-maintained lists is how `--type-${role}-font` silently drifts away from\n// the names the stylesheets actually read.\nexport const TYPE_ROLES = [\n { role: 'display', family: 'displayFontFamily', weight: 'displayFontWeight', scale: 'displayScale' },\n { role: 'page-title', family: 'pageTitleFontFamily', weight: 'pageTitleFontWeight', scale: 'pageTitleScale' },\n { role: 'section-title', family: 'sectionTitleFontFamily', weight: 'sectionTitleFontWeight', scale: 'sectionTitleScale' },\n] as const satisfies ReadonlyArray<{\n role: string;\n family: keyof BrandConfig;\n weight: keyof BrandConfig;\n scale: keyof BrandConfig;\n}>;\n\n/** Sanitised per-role typography, shared by the generator and the live mirror. */\nexport function resolveTypeRoles(brand: BrandConfig) {\n return TYPE_ROLES.map((r) => ({\n role: r.role,\n family: safeFontFamily(brand[r.family]),\n weight: safeFontWeight(brand[r.weight]),\n scale: safeTypeScale(brand[r.scale]),\n }));\n}\n\n/** Generate the overrides stylesheet from style overrides + brand config. */\nexport function generateOverridesCss(allOverrides: AllOverrides, brand: BrandConfig): string {\n const lines: string[] = ['/* Auto-generated by UXM — do not edit manually */', ''];\n\n const fontFamily = safeFontFamily(brand.fontFamily);\n const headingFontFamily = safeFontFamily(brand.headingFontFamily);\n const headingFontWeight = safeFontWeight(brand.headingFontWeight);\n // Per-role refinements, layered under the umbrella heading vars above.\n const roles = resolveTypeRoles(brand);\n const typeScale = safeTypeScale(brand.typeScale);\n const bodyLineHeight = safeLineHeight(brand.bodyLineHeight);\n const logoUrl = safeUrl(brand.logoUrl);\n const iconUrl = safeUrl(brand.iconUrl);\n const logoUrlDark = safeUrl(brand.logoUrlDark);\n const iconUrlDark = safeUrl(brand.iconUrlDark);\n\n if (fontFamily) {\n lines.push(`@import url(\"${fontFileUrl(fontFamily)}\");`);\n lines.push('');\n }\n // Second font file only when the heading face is actually a different family\n // — picking the same one for both is a no-op, not a reason to fetch twice.\n if (headingFontFamily && headingFontFamily !== fontFamily) {\n lines.push(`@import url(\"${fontFileUrl(headingFontFamily)}\");`);\n lines.push('');\n }\n // A role may pick a face neither the body nor the umbrella uses — fetch each\n // distinct one exactly once (@import must stay above the rules below).\n const imported = new Set([fontFamily, headingFontFamily].filter(Boolean));\n for (const { family } of roles) {\n if (family && !imported.has(family)) {\n imported.add(family);\n lines.push(`@import url(\"${fontFileUrl(family)}\");`);\n lines.push('');\n }\n }\n\n const brandRules: string[] = [];\n if (fontFamily) {\n brandRules.push(` --brand-font: \"${fontFamily}\", var(--font-inter), system-ui, sans-serif;`);\n }\n // Heading vars are consumed by the components' own two-layer fallbacks (see\n // e.g. page-header.scss), so there is no `body` rule and no `!important`:\n // an unset var simply lets each component's own default paint.\n if (headingFontFamily) {\n brandRules.push(` --brand-heading-font: \"${headingFontFamily}\", var(--font-inter), system-ui, sans-serif;`);\n }\n if (headingFontWeight) {\n brandRules.push(` --brand-heading-weight: ${headingFontWeight};`);\n }\n // Role vars sit between the component var and the umbrella in each atom's\n // chain, so an unset role simply falls through to the umbrella.\n for (const { role, family, weight, scale } of roles) {\n if (family) {\n brandRules.push(` --type-${role}-font: \"${family}\", var(--font-inter), system-ui, sans-serif;`);\n }\n if (weight) brandRules.push(` --type-${role}-weight: ${weight};`);\n if (scale) brandRules.push(` --type-${role}-scale: ${scale};`);\n }\n if (typeScale) brandRules.push(` --type-scale: ${typeScale};`);\n if (bodyLineHeight) brandRules.push(` --type-body-line-height: ${bodyLineHeight};`);\n if (logoUrl) brandRules.push(` --brand-logo-url: url(\"${logoUrl}\");`);\n if (iconUrl) brandRules.push(` --brand-icon-url: url(\"${iconUrl}\");`);\n\n if (brandRules.length > 0) {\n lines.push(':root {');\n lines.push(...brandRules);\n lines.push('}');\n lines.push('');\n if (fontFamily) {\n lines.push('body { font-family: var(--brand-font) !important; }');\n lines.push('');\n }\n // Emitted ONLY when set. The library ships no element-level rules at all,\n // so an unconditional `body { line-height }` would reflow the host's own\n // content — same reasoning as the font-family rule above.\n if (bodyLineHeight) {\n // Explicit fallback: if the var is later cancelled (the studio mirror\n // re-declares it as `initial` on revert) a bare `var()` would be invalid\n // at computed-value time and `line-height` — being inherited — would\n // resolve to the parent's value, silently replacing whatever the host\n // had set on `body`. `normal` makes the wind-down state deliberate.\n lines.push(`body { line-height: var(--type-body-line-height, normal); }`);\n lines.push('');\n }\n }\n\n const darkBrandRules: string[] = [];\n if (logoUrlDark) darkBrandRules.push(` --brand-logo-url: url(\"${logoUrlDark}\");`);\n if (iconUrlDark) darkBrandRules.push(` --brand-icon-url: url(\"${iconUrlDark}\");`);\n if (darkBrandRules.length > 0) {\n lines.push('[data-theme=\"dark\"] {');\n lines.push(...darkBrandRules);\n lines.push('}');\n lines.push('');\n }\n\n const emitTokens = (selector: string, overrides: Record<string, string> | undefined) => {\n if (!overrides) return;\n const safeEntries = Object.entries(overrides).flatMap(([k, v]) => {\n if (!safeTokenKey(k)) return [];\n const safeValue = safeTokenValue(v);\n return safeValue === undefined ? [] : [[k, safeValue] as const];\n });\n if (safeEntries.length === 0) return;\n lines.push(`${selector} {`);\n for (const [cssVar, value] of safeEntries) {\n lines.push(` ${cssVar}: ${value};`);\n }\n lines.push('}');\n lines.push('');\n };\n // Scope customized light-mode color tokens to non-dark. `:root` and\n // `[data-theme=\"dark\"]` carry equal specificity, so an unconditional\n // `:root` block would win over this stylesheet's own dark block for any\n // token NOT also customized for dark (equal specificity, later source\n // order) — pinning that token to its light value in dark mode too. Scoping\n // lets an unset dark token fall through the cascade to tokens/index.css's\n // own `[data-theme=\"dark\"]` default instead.\n emitTokens(':root:not([data-theme=\"dark\"])', brand.tokens?.light);\n emitTokens('[data-theme=\"dark\"]', brand.tokens?.dark);\n\n const componentIds = Object.keys(allOverrides).sort();\n\n for (const id of componentIds) {\n const overrides = allOverrides[id];\n const entries = Object.entries(overrides);\n if (entries.length === 0) continue;\n\n const selector = PER_COMPONENT_SELECTOR[id] ?? `.uxm-${id}`;\n lines.push(`${selector} {`);\n for (const [key, value] of entries) {\n const cssProp = toCSS(key, id);\n const cssValue = formatValue(value, key);\n lines.push(` ${cssProp}: ${cssValue};`);\n }\n lines.push('}');\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["safeFontFamily","safeFontWeight","safeLineHeight","safeTokenKey","safeTokenValue","safeTypeScale"]}
1
+ {"version":3,"sources":["../../../src/studio/persistence/generate-css.ts"],"sourcesContent":["import { safeFontFamily, safeFontWeight, safeLineHeight, safeTokenKey, safeTokenValue, safeTypeScale, safeUrl } from './css-sanitizers';\nimport { PER_COMPONENT_MAPPING, PER_COMPONENT_SELECTOR } from './generate-css-mapping';\n\nimport type { AllOverrides } from './types';\nimport type { BrandConfig } from '../lib/types';\n\n/**\n * Pure CSS generator for UXM Studio overrides.\n *\n * Single source of truth (R5): the modo Hono `/api/uxm/save` route imports\n * `generateOverridesCss` from here instead of keeping its own copy. No Node\n * APIs — runs in browser (client export) and server alike.\n *\n * Emits a stylesheet from per-component style overrides plus brand config\n * (font, logo/icon URLs, brand token blocks). All attacker-controlled brand\n * values are sanitised before interpolation because the output is served to\n * every browser session.\n */\n\nconst BRAND_FONT_WEIGHTS = 'wght@400;500;600;700';\n\nexport function fontFileUrl(fontFamily: string): string {\n const encoded = fontFamily.trim().replace(/\\s+/g, '+');\n return `https://fonts.googleapis.com/css2?family=${encoded}:${BRAND_FONT_WEIGHTS}&display=swap`;\n}\n\nconst REAL_CSS_PROPS = new Set([\n 'background', 'backgroundColor',\n 'color',\n 'border', 'borderColor', 'borderRadius', 'borderWidth', 'borderStyle',\n 'padding', 'paddingX', 'paddingY', 'margin',\n 'gap', 'rowGap', 'columnGap',\n 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight',\n 'fontSize', 'fontWeight', 'fontFamily', 'lineHeight',\n 'opacity',\n 'cursor', 'display', 'position', 'overflow', 'direction',\n]);\n\nfunction toCSS(key: string, componentId: string): string {\n const componentMapping = PER_COMPONENT_MAPPING[componentId];\n if (componentMapping?.[key]) return componentMapping[key];\n\n const mapping: Record<string, string> = {\n paddingX: 'padding-inline',\n paddingY: 'padding-block',\n };\n if (mapping[key]) return mapping[key];\n\n const kebab = key.replace(/([A-Z])/g, '-$1').toLowerCase();\n if (REAL_CSS_PROPS.has(key)) return kebab;\n return `--uxm-${componentId}-${kebab}`;\n}\n\nfunction formatValue(value: string | number | boolean, key: string): string {\n if (typeof value === 'boolean') return value ? '1' : '0';\n if (typeof value === 'number') {\n // `lineHeight` is a ratio, not a length — without it here a knob saved as\n // 1.5 emits `line-height: 1.5px`, which collapses every line box.\n const unitless = ['fontWeight', 'titleWeight', 'nameWeight', 'typeLabelWeight', 'lineHeight', 'shadow', 'disabledOpacity'];\n // `opacity` is always unitless — match every *Opacity knob (e.g. the\n // per-element `itemDisabledOpacity` / `optionDisabledOpacity` variants)\n // so a saved value never gets a spurious `px` suffix.\n if (unitless.includes(key) || key.endsWith('Opacity')) return String(value);\n return `${value}px`;\n }\n // String style-override values are user/attacker-controlled and persisted\n // to a stylesheet served to every browser session — sanitise the same way\n // as brand token values so one can't break out of the declaration (e.g.\n // `red; } body{display:none}/*`). An unsafe value is dropped to '' rather\n // than throwing, so a single bad override can't blank the whole rule.\n return safeTokenValue(value) ?? '';\n}\n\n// The sanitizers moved to ./css-sanitizers.ts; they stay part of this module's\n// public surface (`@viax.io/uxm/studio/generate-css`, brand-*-styles.tsx).\nexport { safeFontFamily, safeFontWeight, safeLineHeight, safeTokenKey, safeTokenValue, safeTypeScale } from './css-sanitizers';\n\n// The three heading roles, in one place. Both the publish path below and the\n// live mirror (`brand-font-styles.tsx`) derive their emitted var names from\n// `role` by interpolation, so a rename here reaches both — keeping these in two\n// hand-maintained lists is how `--type-${role}-font` silently drifts away from\n// the names the stylesheets actually read.\nexport const TYPE_ROLES = [\n { role: 'display', family: 'displayFontFamily', weight: 'displayFontWeight', scale: 'displayScale' },\n { role: 'page-title', family: 'pageTitleFontFamily', weight: 'pageTitleFontWeight', scale: 'pageTitleScale' },\n { role: 'section-title', family: 'sectionTitleFontFamily', weight: 'sectionTitleFontWeight', scale: 'sectionTitleScale' },\n] as const satisfies ReadonlyArray<{\n role: string;\n family: keyof BrandConfig;\n weight: keyof BrandConfig;\n scale: keyof BrandConfig;\n}>;\n\n/** Sanitised per-role typography, shared by the generator and the live mirror. */\nexport function resolveTypeRoles(brand: BrandConfig) {\n return TYPE_ROLES.map((r) => ({\n role: r.role,\n family: safeFontFamily(brand[r.family]),\n weight: safeFontWeight(brand[r.weight]),\n scale: safeTypeScale(brand[r.scale]),\n }));\n}\n\n/** Generate the overrides stylesheet from style overrides + brand config. */\nexport function generateOverridesCss(allOverrides: AllOverrides, brand: BrandConfig): string {\n const lines: string[] = ['/* Auto-generated by UXM — do not edit manually */', ''];\n\n const fontFamily = safeFontFamily(brand.fontFamily);\n const headingFontFamily = safeFontFamily(brand.headingFontFamily);\n const headingFontWeight = safeFontWeight(brand.headingFontWeight);\n // Per-role refinements, layered under the umbrella heading vars above.\n const roles = resolveTypeRoles(brand);\n const typeScale = safeTypeScale(brand.typeScale);\n const bodyLineHeight = safeLineHeight(brand.bodyLineHeight);\n const logoUrl = safeUrl(brand.logoUrl);\n const iconUrl = safeUrl(brand.iconUrl);\n const logoUrlDark = safeUrl(brand.logoUrlDark);\n const iconUrlDark = safeUrl(brand.iconUrlDark);\n\n if (fontFamily) {\n lines.push(`@import url(\"${fontFileUrl(fontFamily)}\");`);\n lines.push('');\n }\n // Second font file only when the heading face is actually a different family\n // — picking the same one for both is a no-op, not a reason to fetch twice.\n if (headingFontFamily && headingFontFamily !== fontFamily) {\n lines.push(`@import url(\"${fontFileUrl(headingFontFamily)}\");`);\n lines.push('');\n }\n // A role may pick a face neither the body nor the umbrella uses — fetch each\n // distinct one exactly once (@import must stay above the rules below).\n const imported = new Set([fontFamily, headingFontFamily].filter(Boolean));\n for (const { family } of roles) {\n if (family && !imported.has(family)) {\n imported.add(family);\n lines.push(`@import url(\"${fontFileUrl(family)}\");`);\n lines.push('');\n }\n }\n\n const brandRules: string[] = [];\n if (fontFamily) {\n brandRules.push(` --brand-font: \"${fontFamily}\", var(--font-inter), system-ui, sans-serif;`);\n }\n // Heading vars are consumed by the components' own two-layer fallbacks (see\n // e.g. page-header.scss), so there is no `body` rule and no `!important`:\n // an unset var simply lets each component's own default paint.\n if (headingFontFamily) {\n brandRules.push(` --brand-heading-font: \"${headingFontFamily}\", var(--font-inter), system-ui, sans-serif;`);\n }\n if (headingFontWeight) {\n brandRules.push(` --brand-heading-weight: ${headingFontWeight};`);\n }\n // Role vars sit between the component var and the umbrella in each atom's\n // chain, so an unset role simply falls through to the umbrella.\n for (const { role, family, weight, scale } of roles) {\n if (family) {\n brandRules.push(` --type-${role}-font: \"${family}\", var(--font-inter), system-ui, sans-serif;`);\n }\n if (weight) brandRules.push(` --type-${role}-weight: ${weight};`);\n if (scale) brandRules.push(` --type-${role}-scale: ${scale};`);\n }\n if (typeScale) brandRules.push(` --type-scale: ${typeScale};`);\n if (bodyLineHeight) brandRules.push(` --type-body-line-height: ${bodyLineHeight};`);\n if (logoUrl) brandRules.push(` --brand-logo-url: url(\"${logoUrl}\");`);\n if (iconUrl) brandRules.push(` --brand-icon-url: url(\"${iconUrl}\");`);\n\n if (brandRules.length > 0) {\n lines.push(':root {');\n lines.push(...brandRules);\n lines.push('}');\n lines.push('');\n if (fontFamily) {\n lines.push('body { font-family: var(--brand-font) !important; }');\n lines.push('');\n }\n // Emitted ONLY when set. The library ships no element-level rules at all,\n // so an unconditional `body { line-height }` would reflow the host's own\n // content — same reasoning as the font-family rule above.\n if (bodyLineHeight) {\n // Explicit fallback: if the var is later cancelled (the studio mirror\n // re-declares it as `initial` on revert) a bare `var()` would be invalid\n // at computed-value time and `line-height` — being inherited — would\n // resolve to the parent's value, silently replacing whatever the host\n // had set on `body`. `normal` makes the wind-down state deliberate.\n lines.push(`body { line-height: var(--type-body-line-height, normal); }`);\n lines.push('');\n }\n }\n\n const darkBrandRules: string[] = [];\n if (logoUrlDark) darkBrandRules.push(` --brand-logo-url: url(\"${logoUrlDark}\");`);\n if (iconUrlDark) darkBrandRules.push(` --brand-icon-url: url(\"${iconUrlDark}\");`);\n if (darkBrandRules.length > 0) {\n lines.push('[data-theme=\"dark\"] {');\n lines.push(...darkBrandRules);\n lines.push('}');\n lines.push('');\n }\n\n const emitTokens = (selector: string, overrides: Record<string, string> | undefined) => {\n if (!overrides) return;\n const safeEntries = Object.entries(overrides).flatMap(([k, v]) => {\n if (!safeTokenKey(k)) return [];\n const safeValue = safeTokenValue(v);\n return safeValue === undefined ? [] : [[k, safeValue] as const];\n });\n if (safeEntries.length === 0) return;\n lines.push(`${selector} {`);\n for (const [cssVar, value] of safeEntries) {\n lines.push(` ${cssVar}: ${value};`);\n }\n lines.push('}');\n lines.push('');\n };\n // Scope customized light-mode color tokens to non-dark. `:root` and\n // `[data-theme=\"dark\"]` carry equal specificity, so an unconditional\n // `:root` block would win over this stylesheet's own dark block for any\n // token NOT also customized for dark (equal specificity, later source\n // order) — pinning that token to its light value in dark mode too. Scoping\n // lets an unset dark token fall through the cascade to tokens/index.css's\n // own `[data-theme=\"dark\"]` default instead.\n emitTokens(':root:not([data-theme=\"dark\"])', brand.tokens?.light);\n emitTokens('[data-theme=\"dark\"]', brand.tokens?.dark);\n\n const componentIds = Object.keys(allOverrides).sort();\n\n for (const id of componentIds) {\n const overrides = allOverrides[id];\n const entries = Object.entries(overrides);\n if (entries.length === 0) continue;\n\n const selector = PER_COMPONENT_SELECTOR[id] ?? `.uxm-${id}`;\n lines.push(`${selector} {`);\n for (const [key, value] of entries) {\n const cssProp = toCSS(key, id);\n const cssValue = formatValue(value, key);\n lines.push(` ${cssProp}: ${cssValue};`);\n }\n lines.push('}');\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["safeFontFamily","safeFontWeight","safeLineHeight","safeTokenKey","safeTokenValue","safeTypeScale"]}
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var editable_cell_date_exports = {};
20
+ __export(editable_cell_date_exports, {
21
+ EditableCellDateView: () => EditableCellDateView
22
+ });
23
+ module.exports = __toCommonJS(editable_cell_date_exports);
24
+ var import_jsx_runtime = require("react/jsx-runtime");
25
+ var import_helpers = require("../../helpers/index.cjs");
26
+ var import_banner = require("../banner/index.cjs");
27
+ var import_calendar = require("../calendar/index.cjs");
28
+ var import_date_input = require("../date-input/index.cjs");
29
+ var import_hover_tooltip = require("../hover-tooltip/index.cjs");
30
+ var import_icon = require("../icon/index.cjs");
31
+ var import_popover = require("../popover/index.cjs");
32
+ function EditableCellDateView({ props, cell }) {
33
+ const {
34
+ value,
35
+ format,
36
+ disabled,
37
+ placeholder,
38
+ className,
39
+ style,
40
+ "aria-label": ariaLabel,
41
+ type,
42
+ dateFormat,
43
+ clearable,
44
+ align,
45
+ size,
46
+ clearDateLabel,
47
+ openCalendarLabel,
48
+ calendarDialogLabel,
49
+ addDateLabel
50
+ } = props;
51
+ const {
52
+ setIsEditing,
53
+ draft,
54
+ setDraft,
55
+ submitting,
56
+ error,
57
+ setError,
58
+ open,
59
+ setOpen,
60
+ inputRef,
61
+ wrapRef,
62
+ parsedValue,
63
+ displayFormatted,
64
+ handleCommit,
65
+ handlePick,
66
+ handleClearDraft,
67
+ handleKeyDown,
68
+ editing,
69
+ dateHint,
70
+ isEmpty,
71
+ display,
72
+ titleText,
73
+ shownError
74
+ } = cell;
75
+ if (type === "date") {
76
+ const calendarValue = { start: parsedValue, end: parsedValue };
77
+ const spec = import_date_input.FORMAT_SPEC[dateFormat];
78
+ if (!editing) {
79
+ const displayNode = isEmpty ? placeholder ?? dateHint : format ? format(value) : displayFormatted || value;
80
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
81
+ "button",
82
+ {
83
+ type: "button",
84
+ className: (0, import_helpers.cn)(
85
+ "uxm-editable-cell",
86
+ `uxm-editable-cell--${size}`,
87
+ `uxm-editable-cell--align-${align}`,
88
+ `uxm-editable-cell--type-date`,
89
+ disabled && "uxm-editable-cell--disabled",
90
+ isEmpty && "uxm-editable-cell--empty",
91
+ className
92
+ ),
93
+ style,
94
+ onClick: () => {
95
+ if (disabled) return;
96
+ setIsEditing(true);
97
+ setOpen(true);
98
+ },
99
+ disabled,
100
+ "aria-label": ariaLabel ?? (isEmpty ? addDateLabel : `Edit date ${displayFormatted || String(value)}`),
101
+ children: [
102
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_hover_tooltip.HoverTooltip, { content: titleText, disabled, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uxm-editable-cell__value", children: displayNode }) }),
103
+ !disabled && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uxm-editable-cell__pencil", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icon.Icon, { glyph: "calendar", size: 14 }) })
104
+ ]
105
+ }
106
+ );
107
+ }
108
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
109
+ "span",
110
+ {
111
+ ref: wrapRef,
112
+ className: (0, import_helpers.cn)(
113
+ "uxm-editable-cell uxm-editable-cell--editing",
114
+ `uxm-editable-cell--${size}`,
115
+ `uxm-editable-cell--align-${align}`,
116
+ `uxm-editable-cell--type-date`,
117
+ clearable && "uxm-editable-cell--clearable",
118
+ shownError?.severity === "error" && "uxm-editable-cell--error",
119
+ shownError?.severity === "warning" && "uxm-editable-cell--warning",
120
+ className
121
+ ),
122
+ style,
123
+ children: [
124
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uxm-editable-cell__ghost", "aria-hidden": "true", children: display }),
125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "uxm-editable-cell__ghost", "aria-hidden": "true", children: draft || spec.placeholder }),
126
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
127
+ "input",
128
+ {
129
+ ref: inputRef,
130
+ type: "text",
131
+ inputMode: "numeric",
132
+ className: "uxm-editable-cell__input",
133
+ value: draft,
134
+ placeholder: spec.placeholder,
135
+ onChange: (e) => {
136
+ setDraft((0, import_date_input.maskDate)(e.target.value, dateFormat));
137
+ if (error) setError(null);
138
+ },
139
+ onBlur: () => {
140
+ if (!submitting) handleCommit();
141
+ },
142
+ onKeyDown: handleKeyDown,
143
+ disabled: submitting,
144
+ "aria-invalid": Boolean(shownError),
145
+ "aria-describedby": shownError ? "uxm-editable-cell__error" : void 0
146
+ }
147
+ ),
148
+ clearable && !submitting && draft !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
149
+ "button",
150
+ {
151
+ type: "button",
152
+ className: "uxm-editable-cell__clear",
153
+ tabIndex: -1,
154
+ onMouseDown: (e) => e.preventDefault(),
155
+ onClick: handleClearDraft,
156
+ "aria-label": clearDateLabel,
157
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icon.Icon, { glyph: "close", size: 12 })
158
+ }
159
+ ),
160
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
161
+ "button",
162
+ {
163
+ type: "button",
164
+ className: "uxm-editable-cell__date-icon",
165
+ tabIndex: -1,
166
+ onMouseDown: (e) => e.preventDefault(),
167
+ onClick: () => {
168
+ if (submitting) return;
169
+ setError(null);
170
+ setOpen((o) => !o);
171
+ },
172
+ disabled: submitting,
173
+ "aria-label": openCalendarLabel,
174
+ "aria-expanded": open && !shownError,
175
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icon.Icon, { glyph: "calendar", size: 14 })
176
+ }
177
+ ),
178
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
179
+ import_popover.Popover,
180
+ {
181
+ open: open && !shownError,
182
+ onOpenChange: setOpen,
183
+ anchor: wrapRef,
184
+ placement: "bottom-start",
185
+ closeOnEscape: true,
186
+ closeOnOutsideClick: true,
187
+ restoreFocus: false,
188
+ role: "dialog",
189
+ "aria-label": calendarDialogLabel,
190
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { onMouseDown: (e) => e.preventDefault(), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_calendar.Calendar, { value: calendarValue, onChange: handlePick }) })
191
+ }
192
+ ),
193
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
194
+ import_popover.Popover,
195
+ {
196
+ open: Boolean(shownError),
197
+ onOpenChange: (next) => {
198
+ if (!next) setError(null);
199
+ },
200
+ anchor: wrapRef,
201
+ placement: "bottom-start",
202
+ closeOnEscape: false,
203
+ closeOnOutsideClick: false,
204
+ restoreFocus: false,
205
+ role: "presentation",
206
+ children: shownError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
207
+ import_banner.Banner,
208
+ {
209
+ variant: shownError.severity,
210
+ id: "uxm-editable-cell__error",
211
+ className: "uxm-editable-cell__error-banner",
212
+ children: shownError.message
213
+ }
214
+ )
215
+ }
216
+ )
217
+ ]
218
+ }
219
+ );
220
+ }
221
+ }
222
+ // Annotate the CommonJS export names for ESM import in node:
223
+ 0 && (module.exports = {
224
+ EditableCellDateView
225
+ });
226
+ //# sourceMappingURL=editable-cell-date.cjs.map