@flanksource/clicky-ui 0.2.16 → 0.2.17

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 (143) hide show
  1. package/dist/chat.cjs +16 -0
  2. package/dist/chat.cjs.map +1 -1
  3. package/dist/chat.js +17 -1
  4. package/dist/chat.js.map +1 -1
  5. package/dist/components/Combobox.cjs +19 -4
  6. package/dist/components/Combobox.cjs.map +1 -1
  7. package/dist/components/Combobox.d.ts.map +1 -1
  8. package/dist/components/Combobox.js +19 -4
  9. package/dist/components/Combobox.js.map +1 -1
  10. package/dist/components/FilterBar.cjs +40 -6
  11. package/dist/components/FilterBar.cjs.map +1 -1
  12. package/dist/components/FilterBar.d.ts +28 -0
  13. package/dist/components/FilterBar.d.ts.map +1 -1
  14. package/dist/components/FilterBar.js +40 -6
  15. package/dist/components/FilterBar.js.map +1 -1
  16. package/dist/components/JsonSchemaForm.cjs +26 -3
  17. package/dist/components/JsonSchemaForm.cjs.map +1 -1
  18. package/dist/components/JsonSchemaForm.d.ts.map +1 -1
  19. package/dist/components/JsonSchemaForm.js +26 -3
  20. package/dist/components/JsonSchemaForm.js.map +1 -1
  21. package/dist/components/json-schema-form-preferences.cjs +4 -0
  22. package/dist/components/json-schema-form-preferences.cjs.map +1 -1
  23. package/dist/components/json-schema-form-preferences.d.ts +2 -0
  24. package/dist/components/json-schema-form-preferences.d.ts.map +1 -1
  25. package/dist/components/json-schema-form-preferences.js +4 -0
  26. package/dist/components/json-schema-form-preferences.js.map +1 -1
  27. package/dist/components/json-schema-form-render.cjs +1 -1
  28. package/dist/components/json-schema-form-render.cjs.map +1 -1
  29. package/dist/components/json-schema-form-render.d.ts.map +1 -1
  30. package/dist/components/json-schema-form-render.js +2 -2
  31. package/dist/components/json-schema-form-render.js.map +1 -1
  32. package/dist/components/json-schema-form-types.d.ts +3 -2
  33. package/dist/components/json-schema-form-types.d.ts.map +1 -1
  34. package/dist/components/json-schema-form-utils.cjs +13 -0
  35. package/dist/components/json-schema-form-utils.cjs.map +1 -1
  36. package/dist/components/json-schema-form-utils.d.ts +2 -0
  37. package/dist/components/json-schema-form-utils.d.ts.map +1 -1
  38. package/dist/components/json-schema-form-utils.js +13 -0
  39. package/dist/components/json-schema-form-utils.js.map +1 -1
  40. package/dist/components/loading.cjs +19 -6
  41. package/dist/components/loading.cjs.map +1 -1
  42. package/dist/components/loading.d.ts.map +1 -1
  43. package/dist/components/loading.js +19 -6
  44. package/dist/components/loading.js.map +1 -1
  45. package/dist/data/Markdown.cjs +14 -40
  46. package/dist/data/Markdown.cjs.map +1 -1
  47. package/dist/data/Markdown.d.ts +3 -0
  48. package/dist/data/Markdown.d.ts.map +1 -1
  49. package/dist/data/Markdown.js +14 -40
  50. package/dist/data/Markdown.js.map +1 -1
  51. package/dist/data/TaskProgress.cjs +22 -0
  52. package/dist/data/TaskProgress.cjs.map +1 -1
  53. package/dist/data/TaskProgress.js +22 -0
  54. package/dist/data/TaskProgress.js.map +1 -1
  55. package/dist/data/TaskSnapshot.cjs.map +1 -1
  56. package/dist/data/TaskSnapshot.d.ts +5 -0
  57. package/dist/data/TaskSnapshot.d.ts.map +1 -1
  58. package/dist/data/TaskSnapshot.js.map +1 -1
  59. package/dist/data/Tree.cjs +32 -1
  60. package/dist/data/Tree.cjs.map +1 -1
  61. package/dist/data/Tree.d.ts +9 -1
  62. package/dist/data/Tree.d.ts.map +1 -1
  63. package/dist/data/Tree.js +32 -1
  64. package/dist/data/Tree.js.map +1 -1
  65. package/dist/data/chat/Attachment.cjs +79 -0
  66. package/dist/data/chat/Attachment.cjs.map +1 -0
  67. package/dist/data/chat/Attachment.d.ts +18 -0
  68. package/dist/data/chat/Attachment.d.ts.map +1 -0
  69. package/dist/data/chat/Attachment.js +79 -0
  70. package/dist/data/chat/Attachment.js.map +1 -0
  71. package/dist/data/chat/Chat.cjs +71 -14
  72. package/dist/data/chat/Chat.cjs.map +1 -1
  73. package/dist/data/chat/Chat.d.ts +30 -12
  74. package/dist/data/chat/Chat.d.ts.map +1 -1
  75. package/dist/data/chat/Chat.fixtures.d.ts +9 -0
  76. package/dist/data/chat/Chat.fixtures.d.ts.map +1 -1
  77. package/dist/data/chat/Chat.js +73 -16
  78. package/dist/data/chat/Chat.js.map +1 -1
  79. package/dist/data/chat/Conversation.cjs +32 -14
  80. package/dist/data/chat/Conversation.cjs.map +1 -1
  81. package/dist/data/chat/Conversation.d.ts +5 -3
  82. package/dist/data/chat/Conversation.d.ts.map +1 -1
  83. package/dist/data/chat/Conversation.js +34 -16
  84. package/dist/data/chat/Conversation.js.map +1 -1
  85. package/dist/data/chat/Message.cjs +73 -14
  86. package/dist/data/chat/Message.cjs.map +1 -1
  87. package/dist/data/chat/Message.d.ts +11 -4
  88. package/dist/data/chat/Message.d.ts.map +1 -1
  89. package/dist/data/chat/Message.js +75 -16
  90. package/dist/data/chat/Message.js.map +1 -1
  91. package/dist/data/chat/MessageActions.cjs +54 -0
  92. package/dist/data/chat/MessageActions.cjs.map +1 -0
  93. package/dist/data/chat/MessageActions.d.ts +11 -0
  94. package/dist/data/chat/MessageActions.d.ts.map +1 -0
  95. package/dist/data/chat/MessageActions.js +54 -0
  96. package/dist/data/chat/MessageActions.js.map +1 -0
  97. package/dist/data/chat/ModelSelector.cjs +43 -0
  98. package/dist/data/chat/ModelSelector.cjs.map +1 -0
  99. package/dist/data/chat/ModelSelector.d.ts +22 -0
  100. package/dist/data/chat/ModelSelector.d.ts.map +1 -0
  101. package/dist/data/chat/ModelSelector.js +43 -0
  102. package/dist/data/chat/ModelSelector.js.map +1 -0
  103. package/dist/data/chat/PromptInput.cjs +28 -15
  104. package/dist/data/chat/PromptInput.cjs.map +1 -1
  105. package/dist/data/chat/PromptInput.d.ts +14 -8
  106. package/dist/data/chat/PromptInput.d.ts.map +1 -1
  107. package/dist/data/chat/PromptInput.js +28 -15
  108. package/dist/data/chat/PromptInput.js.map +1 -1
  109. package/dist/data/chat/Reasoning.cjs +37 -0
  110. package/dist/data/chat/Reasoning.cjs.map +1 -0
  111. package/dist/data/chat/Reasoning.d.ts +11 -0
  112. package/dist/data/chat/Reasoning.d.ts.map +1 -0
  113. package/dist/data/chat/Reasoning.js +37 -0
  114. package/dist/data/chat/Reasoning.js.map +1 -0
  115. package/dist/data/chat/Suggestion.cjs +20 -0
  116. package/dist/data/chat/Suggestion.cjs.map +1 -0
  117. package/dist/data/chat/Suggestion.d.ts +10 -0
  118. package/dist/data/chat/Suggestion.d.ts.map +1 -0
  119. package/dist/data/chat/Suggestion.js +20 -0
  120. package/dist/data/chat/Suggestion.js.map +1 -0
  121. package/dist/data/chat/ToolCall.cjs +26 -3
  122. package/dist/data/chat/ToolCall.cjs.map +1 -1
  123. package/dist/data/chat/ToolCall.d.ts +5 -1
  124. package/dist/data/chat/ToolCall.d.ts.map +1 -1
  125. package/dist/data/chat/ToolCall.js +26 -3
  126. package/dist/data/chat/ToolCall.js.map +1 -1
  127. package/dist/data/chat/index.d.ts +7 -2
  128. package/dist/data/chat/index.d.ts.map +1 -1
  129. package/dist/data/chat/types.cjs +16 -0
  130. package/dist/data/chat/types.cjs.map +1 -1
  131. package/dist/data/chat/types.d.ts +29 -2
  132. package/dist/data/chat/types.d.ts.map +1 -1
  133. package/dist/data/chat/types.js +16 -0
  134. package/dist/data/chat/types.js.map +1 -1
  135. package/dist/data/version-info.cjs +3 -3
  136. package/dist/data/version-info.js +3 -3
  137. package/dist/hooks/use-task-run.cjs +33 -20
  138. package/dist/hooks/use-task-run.cjs.map +1 -1
  139. package/dist/hooks/use-task-run.d.ts.map +1 -1
  140. package/dist/hooks/use-task-run.js +33 -20
  141. package/dist/hooks/use-task-run.js.map +1 -1
  142. package/dist/styles.css +1 -1
  143. package/package.json +6 -1
@@ -45,6 +45,17 @@ function orderRequiredFirst(entries, required) {
45
45
  const tail = entries.filter(([key]) => !isRequired.has(key));
46
46
  return [...head, ...tail];
47
47
  }
48
+ function isEmptyValue(value) {
49
+ if (value === void 0 || value === null || value === "") return true;
50
+ if (Array.isArray(value)) return value.length === 0;
51
+ if (typeof value === "object") return Object.keys(value).length === 0;
52
+ return false;
53
+ }
54
+ function orderByPriority(entries, required, values) {
55
+ const isRequired = new Set(required);
56
+ const score = (key) => (isRequired.has(key) ? 2 : 0) + (isEmptyValue(values[key]) ? 0 : 1);
57
+ return entries.map((entry, index) => ({ entry, index, score: score(entry[0]) })).sort((a, b) => b.score - a.score || a.index - b.index).map((ranked) => ranked.entry);
58
+ }
48
59
  function softError(field) {
49
60
  const v = field.value;
50
61
  const isEmpty = v === void 0 || v === null || v === "";
@@ -82,9 +93,11 @@ function seedFromSchema(schema) {
82
93
  exports.defaultPlaceholder = defaultPlaceholder;
83
94
  exports.fieldInputId = fieldInputId;
84
95
  exports.inputClass = inputClass;
96
+ exports.isEmptyValue = isEmptyValue;
85
97
  exports.isPlainObject = isPlainObject;
86
98
  exports.keyPickerOptions = keyPickerOptions;
87
99
  exports.moveItem = moveItem;
100
+ exports.orderByPriority = orderByPriority;
88
101
  exports.orderByXOrder = orderByXOrder;
89
102
  exports.orderRequiredFirst = orderRequiredFirst;
90
103
  exports.removeIndex = removeIndex;
@@ -1 +1 @@
1
- {"version":3,"file":"json-schema-form-utils.cjs","sources":["../../src/components/json-schema-form-utils.ts"],"sourcesContent":["import { cn } from \"../lib/utils\";\nimport { inputSizeClass, type FormSize } from \"./json-schema-form-size\";\nimport type { FieldControl, FieldOption, JsonSchemaProperty } from \"./json-schema-form-types\";\n\n// Non-size styling shared by every text-like input box; the size token supplies\n// height, horizontal padding, and text size (see inputSizeClass).\nconst INPUT_BASE =\n \"w-full min-w-0 rounded-md border border-input bg-background text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:bg-muted/40 focus-visible:ring-2 focus-visible:ring-ring\";\n\nexport function inputClass(size: FormSize): string {\n return cn(INPUT_BASE, inputSizeClass[size]);\n}\n\nexport function fieldInputId(key: string, prefix?: string): string {\n const safe = key.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n return prefix ? `jsf-${prefix}-${safe}` : `jsf-${safe}`;\n}\n\nexport function defaultPlaceholder(schema: JsonSchemaProperty): string {\n if (schema.default != null && typeof schema.default !== \"object\") return String(schema.default);\n return \"\";\n}\n\nexport function toText(value: unknown): string {\n if (value === undefined || value === null) return \"\";\n if (typeof value === \"object\") return \"\";\n return String(value);\n}\n\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n// withSyntheticValue prepends the current value as an option when it is not in\n// the enum, so an out-of-enum value (e.g. a template token) still displays.\nexport function withSyntheticValue(options: FieldOption[], value: string): FieldOption[] {\n if (!value || options.some((o) => o.value === value)) return options;\n return [{ value, label: value }, ...options];\n}\n\n// keyPickerOptions narrows the map-key enum to keys not already used by another\n// entry, while always keeping this row's current key so it stays selectable.\nexport function keyPickerOptions(\n options: FieldOption[],\n usedKeys: string[],\n currentKey: string,\n): FieldOption[] {\n const taken = new Set(usedKeys.filter((k) => k !== currentKey));\n return options.filter((o) => !taken.has(o.value));\n}\n\n// orderByXOrder reorders property entries by the schema's `x-order` hint: keys\n// named there render first (in that order), the rest keep document order. A\n// missing/empty hint is a no-op, so document order remains the default.\nexport function orderByXOrder<T>(entries: [string, T][], order: unknown): [string, T][] {\n if (!Array.isArray(order) || order.length === 0) return entries;\n const rank = new Map(\n order.filter((k): k is string => typeof k === \"string\").map((k, i) => [k, i]),\n );\n const listed = entries\n .filter(([key]) => rank.has(key))\n .sort((a, b) => rank.get(a[0])! - rank.get(b[0])!);\n const rest = entries.filter(([key]) => !rank.has(key));\n return [...listed, ...rest];\n}\n\n// orderRequiredFirst stably partitions property entries so the keys named in\n// `required` come first (in their original order), then the rest (in theirs).\n// A stable two-pass partition, so it never reshuffles within either group.\nexport function orderRequiredFirst<T>(\n entries: [string, T][],\n required: string[],\n): [string, T][] {\n const isRequired = new Set(required);\n const head = entries.filter(([key]) => isRequired.has(key));\n const tail = entries.filter(([key]) => !isRequired.has(key));\n return [...head, ...tail];\n}\n\n// softError computes a display-only validation hint. It never blocks onChange.\n// Consumers suppress the unknown-enum hint by setting allowCustomValue.\nexport function softError(field: FieldControl): string | undefined {\n const v = field.value;\n const isEmpty = v === undefined || v === null || v === \"\";\n if (field.required && isEmpty) return \"Required\";\n\n if (field.kind === \"number\" && typeof field.minimum === \"number\" && typeof v === \"number\") {\n if (v < field.minimum) return `Must be ≥ ${field.minimum}`;\n }\n\n if (field.kind === \"enum\" && !field.allowCustomValue && !isEmpty) {\n const known = (field.options ?? []).some((o) => o.value === String(v));\n if (!known) return \"Unknown value (allowed)\";\n }\n return undefined;\n}\n\n// Immutable array helpers used by the array control.\nexport function setIndex<T>(arr: T[], i: number, v: T): T[] {\n return arr.map((x, idx) => (idx === i ? v : x));\n}\n\nexport function removeIndex<T>(arr: T[], i: number): T[] {\n return arr.filter((_, idx) => idx !== i);\n}\n\nexport function moveItem<T>(arr: T[], from: number, to: number): T[] {\n if (to < 0 || to >= arr.length) return arr;\n const next = [...arr];\n const [moved] = next.splice(from, 1);\n next.splice(to, 0, moved as T);\n return next;\n}\n\n// seedFromSchema produces the initial value for a freshly-added array item or\n// object field, honouring an explicit default.\nexport function seedFromSchema(schema: JsonSchemaProperty): unknown {\n if (schema.default !== undefined) return schema.default;\n const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;\n if (type === \"array\") return [];\n if (type === \"object\") return {};\n if (type === \"boolean\") return false;\n // Numbers seed as \"\" so the soft Required hint can show without coercion\n // surprises; the number control coerces on first real input.\n return \"\";\n}\n"],"names":["cn","inputSizeClass"],"mappings":";;;;AAMA,MAAM,aACJ;AAEK,SAAS,WAAW,MAAwB;AACjD,SAAOA,SAAG,YAAYC,mBAAAA,eAAe,IAAI,CAAC;AAC5C;AAEO,SAAS,aAAa,KAAa,QAAyB;AACjE,QAAM,OAAO,IAAI,QAAQ,mBAAmB,GAAG;AAC/C,SAAO,SAAS,OAAO,MAAM,IAAI,IAAI,KAAK,OAAO,IAAI;AACvD;AAEO,SAAS,mBAAmB,QAAoC;AACrE,MAAI,OAAO,WAAW,QAAQ,OAAO,OAAO,YAAY,SAAU,QAAO,OAAO,OAAO,OAAO;AAC9F,SAAO;AACT;AAEO,SAAS,OAAO,OAAwB;AAC7C,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,SAAO,OAAO,KAAK;AACrB;AAEO,SAAS,cAAc,OAAkD;AAC9E,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAIO,SAAS,mBAAmB,SAAwB,OAA8B;AACvF,MAAI,CAAC,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK,EAAG,QAAO;AAC7D,SAAO,CAAC,EAAE,OAAO,OAAO,MAAA,GAAS,GAAG,OAAO;AAC7C;AAIO,SAAS,iBACd,SACA,UACA,YACe;AACf,QAAM,QAAQ,IAAI,IAAI,SAAS,OAAO,CAAC,MAAM,MAAM,UAAU,CAAC;AAC9D,SAAO,QAAQ,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,KAAK,CAAC;AAClD;AAKO,SAAS,cAAiB,SAAwB,OAA+B;AACtF,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,EAAG,QAAO;AACxD,QAAM,OAAO,IAAI;AAAA,IACf,MAAM,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,EAAE,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;AAAA,EAAA;AAE9E,QAAM,SAAS,QACZ,OAAO,CAAC,CAAC,GAAG,MAAM,KAAK,IAAI,GAAG,CAAC,EAC/B,KAAK,CAAC,GAAG,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC,IAAK,KAAK,IAAI,EAAE,CAAC,CAAC,CAAE;AACnD,QAAM,OAAO,QAAQ,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC;AACrD,SAAO,CAAC,GAAG,QAAQ,GAAG,IAAI;AAC5B;AAKO,SAAS,mBACd,SACA,UACe;AACf,QAAM,aAAa,IAAI,IAAI,QAAQ;AACnC,QAAM,OAAO,QAAQ,OAAO,CAAC,CAAC,GAAG,MAAM,WAAW,IAAI,GAAG,CAAC;AAC1D,QAAM,OAAO,QAAQ,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,WAAW,IAAI,GAAG,CAAC;AAC3D,SAAO,CAAC,GAAG,MAAM,GAAG,IAAI;AAC1B;AAIO,SAAS,UAAU,OAAyC;AACjE,QAAM,IAAI,MAAM;AAChB,QAAM,UAAU,MAAM,UAAa,MAAM,QAAQ,MAAM;AACvD,MAAI,MAAM,YAAY,QAAS,QAAO;AAEtC,MAAI,MAAM,SAAS,YAAY,OAAO,MAAM,YAAY,YAAY,OAAO,MAAM,UAAU;AACzF,QAAI,IAAI,MAAM,QAAS,QAAO,aAAa,MAAM,OAAO;AAAA,EAC1D;AAEA,MAAI,MAAM,SAAS,UAAU,CAAC,MAAM,oBAAoB,CAAC,SAAS;AAChE,UAAM,SAAS,MAAM,WAAW,CAAA,GAAI,KAAK,CAAC,MAAM,EAAE,UAAU,OAAO,CAAC,CAAC;AACrE,QAAI,CAAC,MAAO,QAAO;AAAA,EACrB;AACA,SAAO;AACT;AAGO,SAAS,SAAY,KAAU,GAAW,GAAW;AAC1D,SAAO,IAAI,IAAI,CAAC,GAAG,QAAS,QAAQ,IAAI,IAAI,CAAE;AAChD;AAEO,SAAS,YAAe,KAAU,GAAgB;AACvD,SAAO,IAAI,OAAO,CAAC,GAAG,QAAQ,QAAQ,CAAC;AACzC;AAEO,SAAS,SAAY,KAAU,MAAc,IAAiB;AACnE,MAAI,KAAK,KAAK,MAAM,IAAI,OAAQ,QAAO;AACvC,QAAM,OAAO,CAAC,GAAG,GAAG;AACpB,QAAM,CAAC,KAAK,IAAI,KAAK,OAAO,MAAM,CAAC;AACnC,OAAK,OAAO,IAAI,GAAG,KAAU;AAC7B,SAAO;AACT;AAIO,SAAS,eAAe,QAAqC;AAClE,MAAI,OAAO,YAAY,OAAW,QAAO,OAAO;AAChD,QAAM,OAAO,MAAM,QAAQ,OAAO,IAAI,IAAI,OAAO,KAAK,CAAC,IAAI,OAAO;AAClE,MAAI,SAAS,QAAS,QAAO,CAAA;AAC7B,MAAI,SAAS,SAAU,QAAO,CAAA;AAC9B,MAAI,SAAS,UAAW,QAAO;AAG/B,SAAO;AACT;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"json-schema-form-utils.cjs","sources":["../../src/components/json-schema-form-utils.ts"],"sourcesContent":["import { cn } from \"../lib/utils\";\nimport { inputSizeClass, type FormSize } from \"./json-schema-form-size\";\nimport type { FieldControl, FieldOption, JsonSchemaProperty } from \"./json-schema-form-types\";\n\n// Non-size styling shared by every text-like input box; the size token supplies\n// height, horizontal padding, and text size (see inputSizeClass).\nconst INPUT_BASE =\n \"w-full min-w-0 rounded-md border border-input bg-background text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:bg-muted/40 focus-visible:ring-2 focus-visible:ring-ring\";\n\nexport function inputClass(size: FormSize): string {\n return cn(INPUT_BASE, inputSizeClass[size]);\n}\n\nexport function fieldInputId(key: string, prefix?: string): string {\n const safe = key.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n return prefix ? `jsf-${prefix}-${safe}` : `jsf-${safe}`;\n}\n\nexport function defaultPlaceholder(schema: JsonSchemaProperty): string {\n if (schema.default != null && typeof schema.default !== \"object\") return String(schema.default);\n return \"\";\n}\n\nexport function toText(value: unknown): string {\n if (value === undefined || value === null) return \"\";\n if (typeof value === \"object\") return \"\";\n return String(value);\n}\n\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n// withSyntheticValue prepends the current value as an option when it is not in\n// the enum, so an out-of-enum value (e.g. a template token) still displays.\nexport function withSyntheticValue(options: FieldOption[], value: string): FieldOption[] {\n if (!value || options.some((o) => o.value === value)) return options;\n return [{ value, label: value }, ...options];\n}\n\n// keyPickerOptions narrows the map-key enum to keys not already used by another\n// entry, while always keeping this row's current key so it stays selectable.\nexport function keyPickerOptions(\n options: FieldOption[],\n usedKeys: string[],\n currentKey: string,\n): FieldOption[] {\n const taken = new Set(usedKeys.filter((k) => k !== currentKey));\n return options.filter((o) => !taken.has(o.value));\n}\n\n// orderByXOrder reorders property entries by the schema's `x-order` hint: keys\n// named there render first (in that order), the rest keep document order. A\n// missing/empty hint is a no-op, so document order remains the default.\nexport function orderByXOrder<T>(entries: [string, T][], order: unknown): [string, T][] {\n if (!Array.isArray(order) || order.length === 0) return entries;\n const rank = new Map(\n order.filter((k): k is string => typeof k === \"string\").map((k, i) => [k, i]),\n );\n const listed = entries\n .filter(([key]) => rank.has(key))\n .sort((a, b) => rank.get(a[0])! - rank.get(b[0])!);\n const rest = entries.filter(([key]) => !rank.has(key));\n return [...listed, ...rest];\n}\n\n// orderRequiredFirst stably partitions property entries so the keys named in\n// `required` come first (in their original order), then the rest (in theirs).\n// A stable two-pass partition, so it never reshuffles within either group.\nexport function orderRequiredFirst<T>(\n entries: [string, T][],\n required: string[],\n): [string, T][] {\n const isRequired = new Set(required);\n const head = entries.filter(([key]) => isRequired.has(key));\n const tail = entries.filter(([key]) => !isRequired.has(key));\n return [...head, ...tail];\n}\n\n// isEmptyValue reports whether a field's value carries no data, so priority\n// sorting can sink unfilled fields. Empty = undefined/null/\"\" plus empty arrays\n// and empty plain objects; `false` and `0` count as filled (they are choices).\nexport function isEmptyValue(value: unknown): boolean {\n if (value === undefined || value === null || value === \"\") return true;\n if (Array.isArray(value)) return value.length === 0;\n if (typeof value === \"object\") return Object.keys(value).length === 0;\n return false;\n}\n\n// orderByPriority sorts property entries so the fields that matter most surface\n// first, ranking each by a score: +2 if required, +1 if its value is non-empty.\n// Required-and-filled lead, then required-and-empty, then optional-and-filled,\n// then optional-and-empty. Ties keep their incoming order (explicit index\n// tie-break, so it does not depend on the engine's sort stability).\nexport function orderByPriority<T>(\n entries: [string, T][],\n required: string[],\n values: Record<string, unknown>,\n): [string, T][] {\n const isRequired = new Set(required);\n const score = (key: string): number =>\n (isRequired.has(key) ? 2 : 0) + (isEmptyValue(values[key]) ? 0 : 1);\n return entries\n .map((entry, index) => ({ entry, index, score: score(entry[0]) }))\n .sort((a, b) => b.score - a.score || a.index - b.index)\n .map((ranked) => ranked.entry);\n}\n\n// softError computes a display-only validation hint. It never blocks onChange.\n// Consumers suppress the unknown-enum hint by setting allowCustomValue.\nexport function softError(field: FieldControl): string | undefined {\n const v = field.value;\n const isEmpty = v === undefined || v === null || v === \"\";\n if (field.required && isEmpty) return \"Required\";\n\n if (field.kind === \"number\" && typeof field.minimum === \"number\" && typeof v === \"number\") {\n if (v < field.minimum) return `Must be ≥ ${field.minimum}`;\n }\n\n if (field.kind === \"enum\" && !field.allowCustomValue && !isEmpty) {\n const known = (field.options ?? []).some((o) => o.value === String(v));\n if (!known) return \"Unknown value (allowed)\";\n }\n return undefined;\n}\n\n// Immutable array helpers used by the array control.\nexport function setIndex<T>(arr: T[], i: number, v: T): T[] {\n return arr.map((x, idx) => (idx === i ? v : x));\n}\n\nexport function removeIndex<T>(arr: T[], i: number): T[] {\n return arr.filter((_, idx) => idx !== i);\n}\n\nexport function moveItem<T>(arr: T[], from: number, to: number): T[] {\n if (to < 0 || to >= arr.length) return arr;\n const next = [...arr];\n const [moved] = next.splice(from, 1);\n next.splice(to, 0, moved as T);\n return next;\n}\n\n// seedFromSchema produces the initial value for a freshly-added array item or\n// object field, honouring an explicit default.\nexport function seedFromSchema(schema: JsonSchemaProperty): unknown {\n if (schema.default !== undefined) return schema.default;\n const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;\n if (type === \"array\") return [];\n if (type === \"object\") return {};\n if (type === \"boolean\") return false;\n // Numbers seed as \"\" so the soft Required hint can show without coercion\n // surprises; the number control coerces on first real input.\n return \"\";\n}\n"],"names":["cn","inputSizeClass"],"mappings":";;;;AAMA,MAAM,aACJ;AAEK,SAAS,WAAW,MAAwB;AACjD,SAAOA,SAAG,YAAYC,mBAAAA,eAAe,IAAI,CAAC;AAC5C;AAEO,SAAS,aAAa,KAAa,QAAyB;AACjE,QAAM,OAAO,IAAI,QAAQ,mBAAmB,GAAG;AAC/C,SAAO,SAAS,OAAO,MAAM,IAAI,IAAI,KAAK,OAAO,IAAI;AACvD;AAEO,SAAS,mBAAmB,QAAoC;AACrE,MAAI,OAAO,WAAW,QAAQ,OAAO,OAAO,YAAY,SAAU,QAAO,OAAO,OAAO,OAAO;AAC9F,SAAO;AACT;AAEO,SAAS,OAAO,OAAwB;AAC7C,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,SAAO,OAAO,KAAK;AACrB;AAEO,SAAS,cAAc,OAAkD;AAC9E,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAIO,SAAS,mBAAmB,SAAwB,OAA8B;AACvF,MAAI,CAAC,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,KAAK,EAAG,QAAO;AAC7D,SAAO,CAAC,EAAE,OAAO,OAAO,MAAA,GAAS,GAAG,OAAO;AAC7C;AAIO,SAAS,iBACd,SACA,UACA,YACe;AACf,QAAM,QAAQ,IAAI,IAAI,SAAS,OAAO,CAAC,MAAM,MAAM,UAAU,CAAC;AAC9D,SAAO,QAAQ,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,KAAK,CAAC;AAClD;AAKO,SAAS,cAAiB,SAAwB,OAA+B;AACtF,MAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,EAAG,QAAO;AACxD,QAAM,OAAO,IAAI;AAAA,IACf,MAAM,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,EAAE,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;AAAA,EAAA;AAE9E,QAAM,SAAS,QACZ,OAAO,CAAC,CAAC,GAAG,MAAM,KAAK,IAAI,GAAG,CAAC,EAC/B,KAAK,CAAC,GAAG,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC,IAAK,KAAK,IAAI,EAAE,CAAC,CAAC,CAAE;AACnD,QAAM,OAAO,QAAQ,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,IAAI,GAAG,CAAC;AACrD,SAAO,CAAC,GAAG,QAAQ,GAAG,IAAI;AAC5B;AAKO,SAAS,mBACd,SACA,UACe;AACf,QAAM,aAAa,IAAI,IAAI,QAAQ;AACnC,QAAM,OAAO,QAAQ,OAAO,CAAC,CAAC,GAAG,MAAM,WAAW,IAAI,GAAG,CAAC;AAC1D,QAAM,OAAO,QAAQ,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,WAAW,IAAI,GAAG,CAAC;AAC3D,SAAO,CAAC,GAAG,MAAM,GAAG,IAAI;AAC1B;AAKO,SAAS,aAAa,OAAyB;AACpD,MAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,GAAI,QAAO;AAClE,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,WAAW;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK,KAAK,EAAE,WAAW;AACpE,SAAO;AACT;AAOO,SAAS,gBACd,SACA,UACA,QACe;AACf,QAAM,aAAa,IAAI,IAAI,QAAQ;AACnC,QAAM,QAAQ,CAAC,SACZ,WAAW,IAAI,GAAG,IAAI,IAAI,MAAM,aAAa,OAAO,GAAG,CAAC,IAAI,IAAI;AACnE,SAAO,QACJ,IAAI,CAAC,OAAO,WAAW,EAAE,OAAO,OAAO,OAAO,MAAM,MAAM,CAAC,CAAC,IAAI,EAChE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EACrD,IAAI,CAAC,WAAW,OAAO,KAAK;AACjC;AAIO,SAAS,UAAU,OAAyC;AACjE,QAAM,IAAI,MAAM;AAChB,QAAM,UAAU,MAAM,UAAa,MAAM,QAAQ,MAAM;AACvD,MAAI,MAAM,YAAY,QAAS,QAAO;AAEtC,MAAI,MAAM,SAAS,YAAY,OAAO,MAAM,YAAY,YAAY,OAAO,MAAM,UAAU;AACzF,QAAI,IAAI,MAAM,QAAS,QAAO,aAAa,MAAM,OAAO;AAAA,EAC1D;AAEA,MAAI,MAAM,SAAS,UAAU,CAAC,MAAM,oBAAoB,CAAC,SAAS;AAChE,UAAM,SAAS,MAAM,WAAW,CAAA,GAAI,KAAK,CAAC,MAAM,EAAE,UAAU,OAAO,CAAC,CAAC;AACrE,QAAI,CAAC,MAAO,QAAO;AAAA,EACrB;AACA,SAAO;AACT;AAGO,SAAS,SAAY,KAAU,GAAW,GAAW;AAC1D,SAAO,IAAI,IAAI,CAAC,GAAG,QAAS,QAAQ,IAAI,IAAI,CAAE;AAChD;AAEO,SAAS,YAAe,KAAU,GAAgB;AACvD,SAAO,IAAI,OAAO,CAAC,GAAG,QAAQ,QAAQ,CAAC;AACzC;AAEO,SAAS,SAAY,KAAU,MAAc,IAAiB;AACnE,MAAI,KAAK,KAAK,MAAM,IAAI,OAAQ,QAAO;AACvC,QAAM,OAAO,CAAC,GAAG,GAAG;AACpB,QAAM,CAAC,KAAK,IAAI,KAAK,OAAO,MAAM,CAAC;AACnC,OAAK,OAAO,IAAI,GAAG,KAAU;AAC7B,SAAO;AACT;AAIO,SAAS,eAAe,QAAqC;AAClE,MAAI,OAAO,YAAY,OAAW,QAAO,OAAO;AAChD,QAAM,OAAO,MAAM,QAAQ,OAAO,IAAI,IAAI,OAAO,KAAK,CAAC,IAAI,OAAO;AAClE,MAAI,SAAS,QAAS,QAAO,CAAA;AAC7B,MAAI,SAAS,SAAU,QAAO,CAAA;AAC9B,MAAI,SAAS,UAAW,QAAO;AAG/B,SAAO;AACT;;;;;;;;;;;;;;;;;"}
@@ -9,6 +9,8 @@ export declare function withSyntheticValue(options: FieldOption[], value: string
9
9
  export declare function keyPickerOptions(options: FieldOption[], usedKeys: string[], currentKey: string): FieldOption[];
10
10
  export declare function orderByXOrder<T>(entries: [string, T][], order: unknown): [string, T][];
11
11
  export declare function orderRequiredFirst<T>(entries: [string, T][], required: string[]): [string, T][];
12
+ export declare function isEmptyValue(value: unknown): boolean;
13
+ export declare function orderByPriority<T>(entries: [string, T][], required: string[], values: Record<string, unknown>): [string, T][];
12
14
  export declare function softError(field: FieldControl): string | undefined;
13
15
  export declare function setIndex<T>(arr: T[], i: number, v: T): T[];
14
16
  export declare function removeIndex<T>(arr: T[], i: number): T[];
@@ -1 +1 @@
1
- {"version":3,"file":"json-schema-form-utils.d.ts","sourceRoot":"","sources":["../../src/components/json-schema-form-utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAO9F,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAEjD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAGjE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAGrE;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAI7C;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE9E;AAID,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,EAAE,CAGvF;AAID,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,WAAW,EAAE,EACtB,QAAQ,EAAE,MAAM,EAAE,EAClB,UAAU,EAAE,MAAM,GACjB,WAAW,EAAE,CAGf;AAKD,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAUtF;AAKD,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EACtB,QAAQ,EAAE,MAAM,EAAE,GACjB,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAKf;AAID,wBAAgB,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAcjE;AAGD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAE1D;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAEvD;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,CAAC,EAAE,CAMnE;AAID,wBAAgB,cAAc,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CASlE"}
1
+ {"version":3,"file":"json-schema-form-utils.d.ts","sourceRoot":"","sources":["../../src/components/json-schema-form-utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAO9F,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAEjD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAGjE;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAGrE;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAI7C;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE9E;AAID,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,EAAE,CAGvF;AAID,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,WAAW,EAAE,EACtB,QAAQ,EAAE,MAAM,EAAE,EAClB,UAAU,EAAE,MAAM,GACjB,WAAW,EAAE,CAGf;AAKD,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAUtF;AAKD,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EACtB,QAAQ,EAAE,MAAM,EAAE,GACjB,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAKf;AAKD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKpD;AAOD,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EACtB,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAQf;AAID,wBAAgB,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAcjE;AAGD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAE1D;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAEvD;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,CAAC,EAAE,CAMnE;AAID,wBAAgB,cAAc,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CASlE"}
@@ -43,6 +43,17 @@ function orderRequiredFirst(entries, required) {
43
43
  const tail = entries.filter(([key]) => !isRequired.has(key));
44
44
  return [...head, ...tail];
45
45
  }
46
+ function isEmptyValue(value) {
47
+ if (value === void 0 || value === null || value === "") return true;
48
+ if (Array.isArray(value)) return value.length === 0;
49
+ if (typeof value === "object") return Object.keys(value).length === 0;
50
+ return false;
51
+ }
52
+ function orderByPriority(entries, required, values) {
53
+ const isRequired = new Set(required);
54
+ const score = (key) => (isRequired.has(key) ? 2 : 0) + (isEmptyValue(values[key]) ? 0 : 1);
55
+ return entries.map((entry, index) => ({ entry, index, score: score(entry[0]) })).sort((a, b) => b.score - a.score || a.index - b.index).map((ranked) => ranked.entry);
56
+ }
46
57
  function softError(field) {
47
58
  const v = field.value;
48
59
  const isEmpty = v === void 0 || v === null || v === "";
@@ -81,9 +92,11 @@ export {
81
92
  defaultPlaceholder,
82
93
  fieldInputId,
83
94
  inputClass,
95
+ isEmptyValue,
84
96
  isPlainObject,
85
97
  keyPickerOptions,
86
98
  moveItem,
99
+ orderByPriority,
87
100
  orderByXOrder,
88
101
  orderRequiredFirst,
89
102
  removeIndex,
@@ -1 +1 @@
1
- {"version":3,"file":"json-schema-form-utils.js","sources":["../../src/components/json-schema-form-utils.ts"],"sourcesContent":["import { cn } from \"../lib/utils\";\nimport { inputSizeClass, type FormSize } from \"./json-schema-form-size\";\nimport type { FieldControl, FieldOption, JsonSchemaProperty } from \"./json-schema-form-types\";\n\n// Non-size styling shared by every text-like input box; the size token supplies\n// height, horizontal padding, and text size (see inputSizeClass).\nconst INPUT_BASE =\n \"w-full min-w-0 rounded-md border border-input bg-background text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:bg-muted/40 focus-visible:ring-2 focus-visible:ring-ring\";\n\nexport function inputClass(size: FormSize): string {\n return cn(INPUT_BASE, inputSizeClass[size]);\n}\n\nexport function fieldInputId(key: string, prefix?: string): string {\n const safe = key.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n return prefix ? `jsf-${prefix}-${safe}` : `jsf-${safe}`;\n}\n\nexport function defaultPlaceholder(schema: JsonSchemaProperty): string {\n if (schema.default != null && typeof schema.default !== \"object\") return String(schema.default);\n return \"\";\n}\n\nexport function toText(value: unknown): string {\n if (value === undefined || value === null) return \"\";\n if (typeof value === \"object\") return \"\";\n return String(value);\n}\n\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n// withSyntheticValue prepends the current value as an option when it is not in\n// the enum, so an out-of-enum value (e.g. a template token) still displays.\nexport function withSyntheticValue(options: FieldOption[], value: string): FieldOption[] {\n if (!value || options.some((o) => o.value === value)) return options;\n return [{ value, label: value }, ...options];\n}\n\n// keyPickerOptions narrows the map-key enum to keys not already used by another\n// entry, while always keeping this row's current key so it stays selectable.\nexport function keyPickerOptions(\n options: FieldOption[],\n usedKeys: string[],\n currentKey: string,\n): FieldOption[] {\n const taken = new Set(usedKeys.filter((k) => k !== currentKey));\n return options.filter((o) => !taken.has(o.value));\n}\n\n// orderByXOrder reorders property entries by the schema's `x-order` hint: keys\n// named there render first (in that order), the rest keep document order. A\n// missing/empty hint is a no-op, so document order remains the default.\nexport function orderByXOrder<T>(entries: [string, T][], order: unknown): [string, T][] {\n if (!Array.isArray(order) || order.length === 0) return entries;\n const rank = new Map(\n order.filter((k): k is string => typeof k === \"string\").map((k, i) => [k, i]),\n );\n const listed = entries\n .filter(([key]) => rank.has(key))\n .sort((a, b) => rank.get(a[0])! - rank.get(b[0])!);\n const rest = entries.filter(([key]) => !rank.has(key));\n return [...listed, ...rest];\n}\n\n// orderRequiredFirst stably partitions property entries so the keys named in\n// `required` come first (in their original order), then the rest (in theirs).\n// A stable two-pass partition, so it never reshuffles within either group.\nexport function orderRequiredFirst<T>(\n entries: [string, T][],\n required: string[],\n): [string, T][] {\n const isRequired = new Set(required);\n const head = entries.filter(([key]) => isRequired.has(key));\n const tail = entries.filter(([key]) => !isRequired.has(key));\n return [...head, ...tail];\n}\n\n// softError computes a display-only validation hint. It never blocks onChange.\n// Consumers suppress the unknown-enum hint by setting allowCustomValue.\nexport function softError(field: FieldControl): string | undefined {\n const v = field.value;\n const isEmpty = v === undefined || v === null || v === \"\";\n if (field.required && isEmpty) return \"Required\";\n\n if (field.kind === \"number\" && typeof field.minimum === \"number\" && typeof v === \"number\") {\n if (v < field.minimum) return `Must be ≥ ${field.minimum}`;\n }\n\n if (field.kind === \"enum\" && !field.allowCustomValue && !isEmpty) {\n const known = (field.options ?? []).some((o) => o.value === String(v));\n if (!known) return \"Unknown value (allowed)\";\n }\n return undefined;\n}\n\n// Immutable array helpers used by the array control.\nexport function setIndex<T>(arr: T[], i: number, v: T): T[] {\n return arr.map((x, idx) => (idx === i ? v : x));\n}\n\nexport function removeIndex<T>(arr: T[], i: number): T[] {\n return arr.filter((_, idx) => idx !== i);\n}\n\nexport function moveItem<T>(arr: T[], from: number, to: number): T[] {\n if (to < 0 || to >= arr.length) return arr;\n const next = [...arr];\n const [moved] = next.splice(from, 1);\n next.splice(to, 0, moved as T);\n return next;\n}\n\n// seedFromSchema produces the initial value for a freshly-added array item or\n// object field, honouring an explicit default.\nexport function seedFromSchema(schema: JsonSchemaProperty): unknown {\n if (schema.default !== undefined) return schema.default;\n const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;\n if (type === \"array\") return [];\n if (type === \"object\") return {};\n if (type === \"boolean\") return false;\n // Numbers seed as \"\" so the soft Required hint can show without coercion\n // surprises; the number control coerces on first real input.\n return \"\";\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"json-schema-form-utils.js","sources":["../../src/components/json-schema-form-utils.ts"],"sourcesContent":["import { cn } from \"../lib/utils\";\nimport { inputSizeClass, type FormSize } from \"./json-schema-form-size\";\nimport type { FieldControl, FieldOption, JsonSchemaProperty } from \"./json-schema-form-types\";\n\n// Non-size styling shared by every text-like input box; the size token supplies\n// height, horizontal padding, and text size (see inputSizeClass).\nconst INPUT_BASE =\n \"w-full min-w-0 rounded-md border border-input bg-background text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:bg-muted/40 focus-visible:ring-2 focus-visible:ring-ring\";\n\nexport function inputClass(size: FormSize): string {\n return cn(INPUT_BASE, inputSizeClass[size]);\n}\n\nexport function fieldInputId(key: string, prefix?: string): string {\n const safe = key.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n return prefix ? `jsf-${prefix}-${safe}` : `jsf-${safe}`;\n}\n\nexport function defaultPlaceholder(schema: JsonSchemaProperty): string {\n if (schema.default != null && typeof schema.default !== \"object\") return String(schema.default);\n return \"\";\n}\n\nexport function toText(value: unknown): string {\n if (value === undefined || value === null) return \"\";\n if (typeof value === \"object\") return \"\";\n return String(value);\n}\n\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n// withSyntheticValue prepends the current value as an option when it is not in\n// the enum, so an out-of-enum value (e.g. a template token) still displays.\nexport function withSyntheticValue(options: FieldOption[], value: string): FieldOption[] {\n if (!value || options.some((o) => o.value === value)) return options;\n return [{ value, label: value }, ...options];\n}\n\n// keyPickerOptions narrows the map-key enum to keys not already used by another\n// entry, while always keeping this row's current key so it stays selectable.\nexport function keyPickerOptions(\n options: FieldOption[],\n usedKeys: string[],\n currentKey: string,\n): FieldOption[] {\n const taken = new Set(usedKeys.filter((k) => k !== currentKey));\n return options.filter((o) => !taken.has(o.value));\n}\n\n// orderByXOrder reorders property entries by the schema's `x-order` hint: keys\n// named there render first (in that order), the rest keep document order. A\n// missing/empty hint is a no-op, so document order remains the default.\nexport function orderByXOrder<T>(entries: [string, T][], order: unknown): [string, T][] {\n if (!Array.isArray(order) || order.length === 0) return entries;\n const rank = new Map(\n order.filter((k): k is string => typeof k === \"string\").map((k, i) => [k, i]),\n );\n const listed = entries\n .filter(([key]) => rank.has(key))\n .sort((a, b) => rank.get(a[0])! - rank.get(b[0])!);\n const rest = entries.filter(([key]) => !rank.has(key));\n return [...listed, ...rest];\n}\n\n// orderRequiredFirst stably partitions property entries so the keys named in\n// `required` come first (in their original order), then the rest (in theirs).\n// A stable two-pass partition, so it never reshuffles within either group.\nexport function orderRequiredFirst<T>(\n entries: [string, T][],\n required: string[],\n): [string, T][] {\n const isRequired = new Set(required);\n const head = entries.filter(([key]) => isRequired.has(key));\n const tail = entries.filter(([key]) => !isRequired.has(key));\n return [...head, ...tail];\n}\n\n// isEmptyValue reports whether a field's value carries no data, so priority\n// sorting can sink unfilled fields. Empty = undefined/null/\"\" plus empty arrays\n// and empty plain objects; `false` and `0` count as filled (they are choices).\nexport function isEmptyValue(value: unknown): boolean {\n if (value === undefined || value === null || value === \"\") return true;\n if (Array.isArray(value)) return value.length === 0;\n if (typeof value === \"object\") return Object.keys(value).length === 0;\n return false;\n}\n\n// orderByPriority sorts property entries so the fields that matter most surface\n// first, ranking each by a score: +2 if required, +1 if its value is non-empty.\n// Required-and-filled lead, then required-and-empty, then optional-and-filled,\n// then optional-and-empty. Ties keep their incoming order (explicit index\n// tie-break, so it does not depend on the engine's sort stability).\nexport function orderByPriority<T>(\n entries: [string, T][],\n required: string[],\n values: Record<string, unknown>,\n): [string, T][] {\n const isRequired = new Set(required);\n const score = (key: string): number =>\n (isRequired.has(key) ? 2 : 0) + (isEmptyValue(values[key]) ? 0 : 1);\n return entries\n .map((entry, index) => ({ entry, index, score: score(entry[0]) }))\n .sort((a, b) => b.score - a.score || a.index - b.index)\n .map((ranked) => ranked.entry);\n}\n\n// softError computes a display-only validation hint. It never blocks onChange.\n// Consumers suppress the unknown-enum hint by setting allowCustomValue.\nexport function softError(field: FieldControl): string | undefined {\n const v = field.value;\n const isEmpty = v === undefined || v === null || v === \"\";\n if (field.required && isEmpty) return \"Required\";\n\n if (field.kind === \"number\" && typeof field.minimum === \"number\" && typeof v === \"number\") {\n if (v < field.minimum) return `Must be ≥ ${field.minimum}`;\n }\n\n if (field.kind === \"enum\" && !field.allowCustomValue && !isEmpty) {\n const known = (field.options ?? []).some((o) => o.value === String(v));\n if (!known) return \"Unknown value (allowed)\";\n }\n return undefined;\n}\n\n// Immutable array helpers used by the array control.\nexport function setIndex<T>(arr: T[], i: number, v: T): T[] {\n return arr.map((x, idx) => (idx === i ? v : x));\n}\n\nexport function removeIndex<T>(arr: T[], i: number): T[] {\n return arr.filter((_, idx) => idx !== i);\n}\n\nexport function moveItem<T>(arr: T[], from: number, to: number): T[] {\n if (to < 0 || to >= arr.length) return arr;\n const next = [...arr];\n const [moved] = next.splice(from, 1);\n next.splice(to, 0, moved as T);\n return next;\n}\n\n// seedFromSchema produces the initial value for a freshly-added array item or\n// object field, honouring an explicit default.\nexport function seedFromSchema(schema: JsonSchemaProperty): unknown {\n if (schema.default !== undefined) return schema.default;\n const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;\n if (type === \"array\") return [];\n if (type === \"object\") return {};\n if (type === \"boolean\") return false;\n // Numbers seed as \"\" so the soft Required hint can show without coercion\n // surprises; the number control coerces on first real input.\n return \"\";\n}\n"],"names":[],"mappings":"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"}
@@ -8,8 +8,13 @@ const FIXED_DOT_SIZE = {
8
8
  md: "size-6",
9
9
  lg: "size-10"
10
10
  };
11
- const RESPONSIVE_DOT_SIZE = "size-[clamp(1.25rem,18cqmin,2.5rem)]";
12
- const RESPONSIVE_LABEL_SIZE = "text-[clamp(0.72rem,4.5cqmin,0.9rem)]";
11
+ const RESPONSIVE_DOT_STYLE = {
12
+ width: "clamp(1.25rem, 18cqmin, 2.5rem)",
13
+ height: "clamp(1.25rem, 18cqmin, 2.5rem)"
14
+ };
15
+ const RESPONSIVE_LABEL_STYLE = {
16
+ fontSize: "clamp(0.72rem, 4.5cqmin, 0.9rem)"
17
+ };
13
18
  const LoadingDots = react.forwardRef(
14
19
  ({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(
15
20
  "svg",
@@ -63,7 +68,8 @@ const Loading = react.forwardRef(
63
68
  ({ size = "responsive", variant = "inline", label, className, ...props }, ref) => {
64
69
  const effectiveSize = variant === "inline" && size === "responsive" ? "sm" : size;
65
70
  const isResponsive = effectiveSize === "responsive";
66
- const dotClass = isResponsive ? RESPONSIVE_DOT_SIZE : FIXED_DOT_SIZE[effectiveSize];
71
+ const dotClass = isResponsive ? "" : FIXED_DOT_SIZE[effectiveSize];
72
+ const dotStyle = isResponsive ? RESPONSIVE_DOT_STYLE : void 0;
67
73
  if (variant === "centered") {
68
74
  return /* @__PURE__ */ jsxRuntime.jsxs(
69
75
  "div",
@@ -78,8 +84,15 @@ const Loading = react.forwardRef(
78
84
  ),
79
85
  ...props,
80
86
  children: [
81
- /* @__PURE__ */ jsxRuntime.jsx(LoadingDots, { className: dotClass }),
82
- label != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: utils.cn(isResponsive ? RESPONSIVE_LABEL_SIZE : "text-sm"), children: label })
87
+ /* @__PURE__ */ jsxRuntime.jsx(LoadingDots, { className: dotClass, style: dotStyle }),
88
+ label != null && /* @__PURE__ */ jsxRuntime.jsx(
89
+ "span",
90
+ {
91
+ className: isResponsive ? void 0 : "text-sm",
92
+ style: isResponsive ? RESPONSIVE_LABEL_STYLE : void 0,
93
+ children: label
94
+ }
95
+ )
83
96
  ]
84
97
  }
85
98
  );
@@ -96,7 +109,7 @@ const Loading = react.forwardRef(
96
109
  ),
97
110
  ...props,
98
111
  children: [
99
- /* @__PURE__ */ jsxRuntime.jsx(LoadingDots, { className: dotClass }),
112
+ /* @__PURE__ */ jsxRuntime.jsx(LoadingDots, { className: dotClass, style: dotStyle }),
100
113
  label != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: label })
101
114
  ]
102
115
  }
@@ -1 +1 @@
1
- {"version":3,"file":"loading.cjs","sources":["../../src/components/loading.tsx"],"sourcesContent":["import {\n forwardRef,\n type HTMLAttributes,\n type ReactNode,\n type SVGProps,\n} from \"react\";\nimport { cn } from \"../lib/utils\";\n\nexport type LoadingSize = \"sm\" | \"md\" | \"lg\" | \"responsive\";\nexport type LoadingVariant = \"inline\" | \"centered\";\n\nexport interface LoadingProps extends HTMLAttributes<HTMLDivElement> {\n /** Fixed size, or `responsive` (default) to scale with the container. */\n size?: LoadingSize;\n /** `inline` for a small in-flow loader, `centered` for a section/page loader. */\n variant?: LoadingVariant;\n /** Optional caption rendered beside (inline) or beneath (centered) the dots. */\n label?: ReactNode;\n}\n\nconst FIXED_DOT_SIZE: Record<Exclude<LoadingSize, \"responsive\">, string> = {\n sm: \"size-4\",\n md: \"size-6\",\n lg: \"size-10\",\n};\n\n// Scales with the SMALLER of the container's two dimensions (`cqmin`, enabled by\n// `container-type: size` on the centered wrapper). Using `cqmin` rather than the\n// width alone means a wide-but-short panel reads as small — only a genuinely\n// large area (a full-page load) grows the dots. Floors at 1.25rem (20px) in\n// tight panels, climbs to 2.5rem (40px) on big loads. Pure CSS — no\n// ResizeObserver, SSR-safe.\nconst RESPONSIVE_DOT_SIZE = \"size-[clamp(1.25rem,18cqmin,2.5rem)]\";\nconst RESPONSIVE_LABEL_SIZE = \"text-[clamp(0.72rem,4.5cqmin,0.9rem)]\";\n\n/**\n * Three bouncing dots. `fill=\"currentColor\"` so the dots inherit the surrounding\n * theme text color (works in light and dark). Size is driven entirely by the\n * className (width + height); the viewBox keeps the 1:1 aspect.\n */\nexport const LoadingDots = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(\n ({ className, ...props }, ref) => (\n <svg\n ref={ref}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n className={cn(\"shrink-0\", className)}\n {...props}\n >\n <circle cx=\"4\" cy=\"12\" r=\"3\">\n <animate\n id=\"clickyLoadDotA\"\n fill=\"freeze\"\n attributeName=\"opacity\"\n begin=\"0;clickyLoadDotC.end-0.25s\"\n dur=\"0.75s\"\n values=\"1;0.2\"\n />\n </circle>\n <circle cx=\"12\" cy=\"12\" r=\"3\" opacity=\"0.4\">\n <animate\n fill=\"freeze\"\n attributeName=\"opacity\"\n begin=\"clickyLoadDotA.begin+0.15s\"\n dur=\"0.75s\"\n values=\"1;0.2\"\n />\n </circle>\n <circle cx=\"20\" cy=\"12\" r=\"3\" opacity=\"0.3\">\n <animate\n id=\"clickyLoadDotC\"\n fill=\"freeze\"\n attributeName=\"opacity\"\n begin=\"clickyLoadDotA.begin+0.3s\"\n dur=\"0.75s\"\n values=\"1;0.2\"\n />\n </circle>\n </svg>\n ),\n);\nLoadingDots.displayName = \"LoadingDots\";\n\n/**\n * Loading indicator built on the shared 3-dot animation.\n *\n * `centered` fills the space it is given (`h-full` within a `min-h-32` floor) and\n * establishes a size container (`container-type: size`) so the default\n * `responsive` size scales the dots to that space via `cqmin` — small inside a\n * narrow/short panel, large on a full-page route shell that stretches it tall.\n * The `inline` variant is not a container, so a `responsive` request there\n * renders at `sm`.\n */\nexport const Loading = forwardRef<HTMLDivElement, LoadingProps>(\n ({ size = \"responsive\", variant = \"inline\", label, className, ...props }, ref) => {\n const effectiveSize =\n variant === \"inline\" && size === \"responsive\" ? \"sm\" : size;\n const isResponsive = effectiveSize === \"responsive\";\n const dotClass = isResponsive\n ? RESPONSIVE_DOT_SIZE\n : FIXED_DOT_SIZE[effectiveSize];\n\n if (variant === \"centered\") {\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\n \"flex h-full min-h-32 w-full flex-col items-center justify-center gap-3 text-muted-foreground\",\n \"[container-type:size]\",\n className,\n )}\n {...props}\n >\n <LoadingDots className={dotClass} />\n {label != null && (\n <span className={cn(isResponsive ? RESPONSIVE_LABEL_SIZE : \"text-sm\")}>\n {label}\n </span>\n )}\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\n \"inline-flex items-center gap-2 text-muted-foreground\",\n className,\n )}\n {...props}\n >\n <LoadingDots className={dotClass} />\n {label != null && <span className=\"text-sm\">{label}</span>}\n </div>\n );\n },\n);\nLoading.displayName = \"Loading\";\n"],"names":["forwardRef","jsxs","cn","jsx"],"mappings":";;;;;AAoBA,MAAM,iBAAqE;AAAA,EACzE,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAQA,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB;AAOvB,MAAM,cAAcA,MAAAA;AAAAA,EACzB,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QACxBC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,OAAM;AAAA,MACN,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,eAAY;AAAA,MACZ,WAAWC,MAAAA,GAAG,YAAY,SAAS;AAAA,MAClC,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAAC,+BAAC,YAAO,IAAG,KAAI,IAAG,MAAK,GAAE,KACvB,UAAAA,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,eAAc;AAAA,YACd,OAAM;AAAA,YACN,KAAI;AAAA,YACJ,QAAO;AAAA,UAAA;AAAA,QAAA,GAEX;AAAA,QACAA,2BAAAA,IAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,KAAI,SAAQ,OACpC,UAAAA,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,eAAc;AAAA,YACd,OAAM;AAAA,YACN,KAAI;AAAA,YACJ,QAAO;AAAA,UAAA;AAAA,QAAA,GAEX;AAAA,QACAA,2BAAAA,IAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,KAAI,SAAQ,OACpC,UAAAA,2BAAAA;AAAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,eAAc;AAAA,YACd,OAAM;AAAA,YACN,KAAI;AAAA,YACJ,QAAO;AAAA,UAAA;AAAA,QAAA,EACT,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;AACA,YAAY,cAAc;AAYnB,MAAM,UAAUH,MAAAA;AAAAA,EACrB,CAAC,EAAE,OAAO,cAAc,UAAU,UAAU,OAAO,WAAW,GAAG,MAAA,GAAS,QAAQ;AAChF,UAAM,gBACJ,YAAY,YAAY,SAAS,eAAe,OAAO;AACzD,UAAM,eAAe,kBAAkB;AACvC,UAAM,WAAW,eACb,sBACA,eAAe,aAAa;AAEhC,QAAI,YAAY,YAAY;AAC1B,aACEC,2BAAAA;AAAAA,QAAC;AAAA,QAAA;AAAA,UACC;AAAA,UACA,MAAK;AAAA,UACL,cAAW;AAAA,UACX,WAAWC,MAAAA;AAAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,UAAA;AAAA,UAED,GAAG;AAAA,UAEJ,UAAA;AAAA,YAAAC,2BAAAA,IAAC,aAAA,EAAY,WAAW,SAAA,CAAU;AAAA,YACjC,SAAS,QACRA,+BAAC,QAAA,EAAK,WAAWD,SAAG,eAAe,wBAAwB,SAAS,GACjE,UAAA,MAAA,CACH;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IAIR;AAEA,WACED,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,cAAW;AAAA,QACX,WAAWC,MAAAA;AAAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAAC,2BAAAA,IAAC,aAAA,EAAY,WAAW,SAAA,CAAU;AAAA,UACjC,SAAS,QAAQA,2BAAAA,IAAC,QAAA,EAAK,WAAU,WAAW,UAAA,MAAA,CAAM;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGzD;AACF;AACA,QAAQ,cAAc;;;"}
1
+ {"version":3,"file":"loading.cjs","sources":["../../src/components/loading.tsx"],"sourcesContent":["import {\n forwardRef,\n type HTMLAttributes,\n type ReactNode,\n type SVGProps,\n} from \"react\";\nimport { cn } from \"../lib/utils\";\n\nexport type LoadingSize = \"sm\" | \"md\" | \"lg\" | \"responsive\";\nexport type LoadingVariant = \"inline\" | \"centered\";\n\nexport interface LoadingProps extends HTMLAttributes<HTMLDivElement> {\n /** Fixed size, or `responsive` (default) to scale with the container. */\n size?: LoadingSize;\n /** `inline` for a small in-flow loader, `centered` for a section/page loader. */\n variant?: LoadingVariant;\n /** Optional caption rendered beside (inline) or beneath (centered) the dots. */\n label?: ReactNode;\n}\n\nconst FIXED_DOT_SIZE: Record<Exclude<LoadingSize, \"responsive\">, string> = {\n sm: \"size-4\",\n md: \"size-6\",\n lg: \"size-10\",\n};\n\n// Scales with the SMALLER of the container's two dimensions (`cqmin`, enabled by\n// `container-type: size` on the centered wrapper). Using `cqmin` rather than the\n// width alone means a wide-but-short panel reads as small — only a genuinely\n// large area (a full-page load) grows the dots. Floors at 1.25rem (20px) in\n// tight panels, climbs to 2.5rem (40px) on big loads. Pure CSS — no\n// ResizeObserver, SSR-safe.\n//\n// These are inline styles, NOT Tailwind arbitrary classes (`size-[clamp(…)]`):\n// a dynamic arbitrary class only ships if the consumer's Tailwind happens to\n// scan a file containing that exact literal. The moment the served component\n// and the scanned `dist` diverge (e.g. a linked local checkout vs a stale\n// node_modules) the class is never generated, the SVG loses its width/height,\n// and `h-full` balloons it to fill the page. Inline styles resolve natively in\n// every consumer regardless of their content-scan config.\nconst RESPONSIVE_DOT_STYLE = {\n width: \"clamp(1.25rem, 18cqmin, 2.5rem)\",\n height: \"clamp(1.25rem, 18cqmin, 2.5rem)\",\n} as const;\nconst RESPONSIVE_LABEL_STYLE = {\n fontSize: \"clamp(0.72rem, 4.5cqmin, 0.9rem)\",\n} as const;\n\n/**\n * Three bouncing dots. `fill=\"currentColor\"` so the dots inherit the surrounding\n * theme text color (works in light and dark). Size is driven entirely by the\n * className (width + height); the viewBox keeps the 1:1 aspect.\n */\nexport const LoadingDots = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(\n ({ className, ...props }, ref) => (\n <svg\n ref={ref}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n className={cn(\"shrink-0\", className)}\n {...props}\n >\n <circle cx=\"4\" cy=\"12\" r=\"3\">\n <animate\n id=\"clickyLoadDotA\"\n fill=\"freeze\"\n attributeName=\"opacity\"\n begin=\"0;clickyLoadDotC.end-0.25s\"\n dur=\"0.75s\"\n values=\"1;0.2\"\n />\n </circle>\n <circle cx=\"12\" cy=\"12\" r=\"3\" opacity=\"0.4\">\n <animate\n fill=\"freeze\"\n attributeName=\"opacity\"\n begin=\"clickyLoadDotA.begin+0.15s\"\n dur=\"0.75s\"\n values=\"1;0.2\"\n />\n </circle>\n <circle cx=\"20\" cy=\"12\" r=\"3\" opacity=\"0.3\">\n <animate\n id=\"clickyLoadDotC\"\n fill=\"freeze\"\n attributeName=\"opacity\"\n begin=\"clickyLoadDotA.begin+0.3s\"\n dur=\"0.75s\"\n values=\"1;0.2\"\n />\n </circle>\n </svg>\n ),\n);\nLoadingDots.displayName = \"LoadingDots\";\n\n/**\n * Loading indicator built on the shared 3-dot animation.\n *\n * `centered` fills the space it is given (`h-full` within a `min-h-32` floor) and\n * establishes a size container (`container-type: size`) so the default\n * `responsive` size scales the dots to that space via `cqmin` — small inside a\n * narrow/short panel, large on a full-page route shell that stretches it tall.\n * The `inline` variant is not a container, so a `responsive` request there\n * renders at `sm`.\n */\nexport const Loading = forwardRef<HTMLDivElement, LoadingProps>(\n ({ size = \"responsive\", variant = \"inline\", label, className, ...props }, ref) => {\n const effectiveSize =\n variant === \"inline\" && size === \"responsive\" ? \"sm\" : size;\n const isResponsive = effectiveSize === \"responsive\";\n const dotClass = isResponsive ? \"\" : FIXED_DOT_SIZE[effectiveSize];\n const dotStyle = isResponsive ? RESPONSIVE_DOT_STYLE : undefined;\n\n if (variant === \"centered\") {\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\n \"flex h-full min-h-32 w-full flex-col items-center justify-center gap-3 text-muted-foreground\",\n \"[container-type:size]\",\n className,\n )}\n {...props}\n >\n <LoadingDots className={dotClass} style={dotStyle} />\n {label != null && (\n <span\n className={isResponsive ? undefined : \"text-sm\"}\n style={isResponsive ? RESPONSIVE_LABEL_STYLE : undefined}\n >\n {label}\n </span>\n )}\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\n \"inline-flex items-center gap-2 text-muted-foreground\",\n className,\n )}\n {...props}\n >\n <LoadingDots className={dotClass} style={dotStyle} />\n {label != null && <span className=\"text-sm\">{label}</span>}\n </div>\n );\n },\n);\nLoading.displayName = \"Loading\";\n"],"names":["forwardRef","jsxs","cn","jsx"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"loading.d.ts","sourceRoot":"","sources":["../../src/components/loading.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,QAAQ,EACd,MAAM,OAAO,CAAC;AAGf,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,YAAY,CAAC;AAC5D,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEnD,MAAM,WAAW,YAAa,SAAQ,cAAc,CAAC,cAAc,CAAC;IAClE,yEAAyE;IACzE,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,iFAAiF;IACjF,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,gFAAgF;IAChF,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAiBD;;;;GAIG;AACH,eAAO,MAAM,WAAW,gIA0CvB,CAAC;AAGF;;;;;;;;;GASG;AACH,eAAO,MAAM,OAAO,yGAgDnB,CAAC"}
1
+ {"version":3,"file":"loading.d.ts","sourceRoot":"","sources":["../../src/components/loading.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,QAAQ,EACd,MAAM,OAAO,CAAC;AAGf,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,YAAY,CAAC;AAC5D,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEnD,MAAM,WAAW,YAAa,SAAQ,cAAc,CAAC,cAAc,CAAC;IAClE,yEAAyE;IACzE,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,iFAAiF;IACjF,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,gFAAgF;IAChF,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AA8BD;;;;GAIG;AACH,eAAO,MAAM,WAAW,gIA0CvB,CAAC;AAGF;;;;;;;;;GASG;AACH,eAAO,MAAM,OAAO,yGAkDnB,CAAC"}
@@ -6,8 +6,13 @@ const FIXED_DOT_SIZE = {
6
6
  md: "size-6",
7
7
  lg: "size-10"
8
8
  };
9
- const RESPONSIVE_DOT_SIZE = "size-[clamp(1.25rem,18cqmin,2.5rem)]";
10
- const RESPONSIVE_LABEL_SIZE = "text-[clamp(0.72rem,4.5cqmin,0.9rem)]";
9
+ const RESPONSIVE_DOT_STYLE = {
10
+ width: "clamp(1.25rem, 18cqmin, 2.5rem)",
11
+ height: "clamp(1.25rem, 18cqmin, 2.5rem)"
12
+ };
13
+ const RESPONSIVE_LABEL_STYLE = {
14
+ fontSize: "clamp(0.72rem, 4.5cqmin, 0.9rem)"
15
+ };
11
16
  const LoadingDots = forwardRef(
12
17
  ({ className, ...props }, ref) => /* @__PURE__ */ jsxs(
13
18
  "svg",
@@ -61,7 +66,8 @@ const Loading = forwardRef(
61
66
  ({ size = "responsive", variant = "inline", label, className, ...props }, ref) => {
62
67
  const effectiveSize = variant === "inline" && size === "responsive" ? "sm" : size;
63
68
  const isResponsive = effectiveSize === "responsive";
64
- const dotClass = isResponsive ? RESPONSIVE_DOT_SIZE : FIXED_DOT_SIZE[effectiveSize];
69
+ const dotClass = isResponsive ? "" : FIXED_DOT_SIZE[effectiveSize];
70
+ const dotStyle = isResponsive ? RESPONSIVE_DOT_STYLE : void 0;
65
71
  if (variant === "centered") {
66
72
  return /* @__PURE__ */ jsxs(
67
73
  "div",
@@ -76,8 +82,15 @@ const Loading = forwardRef(
76
82
  ),
77
83
  ...props,
78
84
  children: [
79
- /* @__PURE__ */ jsx(LoadingDots, { className: dotClass }),
80
- label != null && /* @__PURE__ */ jsx("span", { className: cn(isResponsive ? RESPONSIVE_LABEL_SIZE : "text-sm"), children: label })
85
+ /* @__PURE__ */ jsx(LoadingDots, { className: dotClass, style: dotStyle }),
86
+ label != null && /* @__PURE__ */ jsx(
87
+ "span",
88
+ {
89
+ className: isResponsive ? void 0 : "text-sm",
90
+ style: isResponsive ? RESPONSIVE_LABEL_STYLE : void 0,
91
+ children: label
92
+ }
93
+ )
81
94
  ]
82
95
  }
83
96
  );
@@ -94,7 +107,7 @@ const Loading = forwardRef(
94
107
  ),
95
108
  ...props,
96
109
  children: [
97
- /* @__PURE__ */ jsx(LoadingDots, { className: dotClass }),
110
+ /* @__PURE__ */ jsx(LoadingDots, { className: dotClass, style: dotStyle }),
98
111
  label != null && /* @__PURE__ */ jsx("span", { className: "text-sm", children: label })
99
112
  ]
100
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"loading.js","sources":["../../src/components/loading.tsx"],"sourcesContent":["import {\n forwardRef,\n type HTMLAttributes,\n type ReactNode,\n type SVGProps,\n} from \"react\";\nimport { cn } from \"../lib/utils\";\n\nexport type LoadingSize = \"sm\" | \"md\" | \"lg\" | \"responsive\";\nexport type LoadingVariant = \"inline\" | \"centered\";\n\nexport interface LoadingProps extends HTMLAttributes<HTMLDivElement> {\n /** Fixed size, or `responsive` (default) to scale with the container. */\n size?: LoadingSize;\n /** `inline` for a small in-flow loader, `centered` for a section/page loader. */\n variant?: LoadingVariant;\n /** Optional caption rendered beside (inline) or beneath (centered) the dots. */\n label?: ReactNode;\n}\n\nconst FIXED_DOT_SIZE: Record<Exclude<LoadingSize, \"responsive\">, string> = {\n sm: \"size-4\",\n md: \"size-6\",\n lg: \"size-10\",\n};\n\n// Scales with the SMALLER of the container's two dimensions (`cqmin`, enabled by\n// `container-type: size` on the centered wrapper). Using `cqmin` rather than the\n// width alone means a wide-but-short panel reads as small — only a genuinely\n// large area (a full-page load) grows the dots. Floors at 1.25rem (20px) in\n// tight panels, climbs to 2.5rem (40px) on big loads. Pure CSS — no\n// ResizeObserver, SSR-safe.\nconst RESPONSIVE_DOT_SIZE = \"size-[clamp(1.25rem,18cqmin,2.5rem)]\";\nconst RESPONSIVE_LABEL_SIZE = \"text-[clamp(0.72rem,4.5cqmin,0.9rem)]\";\n\n/**\n * Three bouncing dots. `fill=\"currentColor\"` so the dots inherit the surrounding\n * theme text color (works in light and dark). Size is driven entirely by the\n * className (width + height); the viewBox keeps the 1:1 aspect.\n */\nexport const LoadingDots = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(\n ({ className, ...props }, ref) => (\n <svg\n ref={ref}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n className={cn(\"shrink-0\", className)}\n {...props}\n >\n <circle cx=\"4\" cy=\"12\" r=\"3\">\n <animate\n id=\"clickyLoadDotA\"\n fill=\"freeze\"\n attributeName=\"opacity\"\n begin=\"0;clickyLoadDotC.end-0.25s\"\n dur=\"0.75s\"\n values=\"1;0.2\"\n />\n </circle>\n <circle cx=\"12\" cy=\"12\" r=\"3\" opacity=\"0.4\">\n <animate\n fill=\"freeze\"\n attributeName=\"opacity\"\n begin=\"clickyLoadDotA.begin+0.15s\"\n dur=\"0.75s\"\n values=\"1;0.2\"\n />\n </circle>\n <circle cx=\"20\" cy=\"12\" r=\"3\" opacity=\"0.3\">\n <animate\n id=\"clickyLoadDotC\"\n fill=\"freeze\"\n attributeName=\"opacity\"\n begin=\"clickyLoadDotA.begin+0.3s\"\n dur=\"0.75s\"\n values=\"1;0.2\"\n />\n </circle>\n </svg>\n ),\n);\nLoadingDots.displayName = \"LoadingDots\";\n\n/**\n * Loading indicator built on the shared 3-dot animation.\n *\n * `centered` fills the space it is given (`h-full` within a `min-h-32` floor) and\n * establishes a size container (`container-type: size`) so the default\n * `responsive` size scales the dots to that space via `cqmin` — small inside a\n * narrow/short panel, large on a full-page route shell that stretches it tall.\n * The `inline` variant is not a container, so a `responsive` request there\n * renders at `sm`.\n */\nexport const Loading = forwardRef<HTMLDivElement, LoadingProps>(\n ({ size = \"responsive\", variant = \"inline\", label, className, ...props }, ref) => {\n const effectiveSize =\n variant === \"inline\" && size === \"responsive\" ? \"sm\" : size;\n const isResponsive = effectiveSize === \"responsive\";\n const dotClass = isResponsive\n ? RESPONSIVE_DOT_SIZE\n : FIXED_DOT_SIZE[effectiveSize];\n\n if (variant === \"centered\") {\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\n \"flex h-full min-h-32 w-full flex-col items-center justify-center gap-3 text-muted-foreground\",\n \"[container-type:size]\",\n className,\n )}\n {...props}\n >\n <LoadingDots className={dotClass} />\n {label != null && (\n <span className={cn(isResponsive ? RESPONSIVE_LABEL_SIZE : \"text-sm\")}>\n {label}\n </span>\n )}\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\n \"inline-flex items-center gap-2 text-muted-foreground\",\n className,\n )}\n {...props}\n >\n <LoadingDots className={dotClass} />\n {label != null && <span className=\"text-sm\">{label}</span>}\n </div>\n );\n },\n);\nLoading.displayName = \"Loading\";\n"],"names":[],"mappings":";;;AAoBA,MAAM,iBAAqE;AAAA,EACzE,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAQA,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB;AAOvB,MAAM,cAAc;AAAA,EACzB,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QACxB;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,OAAM;AAAA,MACN,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,eAAY;AAAA,MACZ,WAAW,GAAG,YAAY,SAAS;AAAA,MAClC,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAA,oBAAC,YAAO,IAAG,KAAI,IAAG,MAAK,GAAE,KACvB,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,eAAc;AAAA,YACd,OAAM;AAAA,YACN,KAAI;AAAA,YACJ,QAAO;AAAA,UAAA;AAAA,QAAA,GAEX;AAAA,QACA,oBAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,KAAI,SAAQ,OACpC,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,eAAc;AAAA,YACd,OAAM;AAAA,YACN,KAAI;AAAA,YACJ,QAAO;AAAA,UAAA;AAAA,QAAA,GAEX;AAAA,QACA,oBAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,KAAI,SAAQ,OACpC,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,eAAc;AAAA,YACd,OAAM;AAAA,YACN,KAAI;AAAA,YACJ,QAAO;AAAA,UAAA;AAAA,QAAA,EACT,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;AACA,YAAY,cAAc;AAYnB,MAAM,UAAU;AAAA,EACrB,CAAC,EAAE,OAAO,cAAc,UAAU,UAAU,OAAO,WAAW,GAAG,MAAA,GAAS,QAAQ;AAChF,UAAM,gBACJ,YAAY,YAAY,SAAS,eAAe,OAAO;AACzD,UAAM,eAAe,kBAAkB;AACvC,UAAM,WAAW,eACb,sBACA,eAAe,aAAa;AAEhC,QAAI,YAAY,YAAY;AAC1B,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC;AAAA,UACA,MAAK;AAAA,UACL,cAAW;AAAA,UACX,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,UAAA;AAAA,UAED,GAAG;AAAA,UAEJ,UAAA;AAAA,YAAA,oBAAC,aAAA,EAAY,WAAW,SAAA,CAAU;AAAA,YACjC,SAAS,QACR,oBAAC,QAAA,EAAK,WAAW,GAAG,eAAe,wBAAwB,SAAS,GACjE,UAAA,MAAA,CACH;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IAIR;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,cAAW;AAAA,QACX,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAA,oBAAC,aAAA,EAAY,WAAW,SAAA,CAAU;AAAA,UACjC,SAAS,QAAQ,oBAAC,QAAA,EAAK,WAAU,WAAW,UAAA,MAAA,CAAM;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGzD;AACF;AACA,QAAQ,cAAc;"}
1
+ {"version":3,"file":"loading.js","sources":["../../src/components/loading.tsx"],"sourcesContent":["import {\n forwardRef,\n type HTMLAttributes,\n type ReactNode,\n type SVGProps,\n} from \"react\";\nimport { cn } from \"../lib/utils\";\n\nexport type LoadingSize = \"sm\" | \"md\" | \"lg\" | \"responsive\";\nexport type LoadingVariant = \"inline\" | \"centered\";\n\nexport interface LoadingProps extends HTMLAttributes<HTMLDivElement> {\n /** Fixed size, or `responsive` (default) to scale with the container. */\n size?: LoadingSize;\n /** `inline` for a small in-flow loader, `centered` for a section/page loader. */\n variant?: LoadingVariant;\n /** Optional caption rendered beside (inline) or beneath (centered) the dots. */\n label?: ReactNode;\n}\n\nconst FIXED_DOT_SIZE: Record<Exclude<LoadingSize, \"responsive\">, string> = {\n sm: \"size-4\",\n md: \"size-6\",\n lg: \"size-10\",\n};\n\n// Scales with the SMALLER of the container's two dimensions (`cqmin`, enabled by\n// `container-type: size` on the centered wrapper). Using `cqmin` rather than the\n// width alone means a wide-but-short panel reads as small — only a genuinely\n// large area (a full-page load) grows the dots. Floors at 1.25rem (20px) in\n// tight panels, climbs to 2.5rem (40px) on big loads. Pure CSS — no\n// ResizeObserver, SSR-safe.\n//\n// These are inline styles, NOT Tailwind arbitrary classes (`size-[clamp(…)]`):\n// a dynamic arbitrary class only ships if the consumer's Tailwind happens to\n// scan a file containing that exact literal. The moment the served component\n// and the scanned `dist` diverge (e.g. a linked local checkout vs a stale\n// node_modules) the class is never generated, the SVG loses its width/height,\n// and `h-full` balloons it to fill the page. Inline styles resolve natively in\n// every consumer regardless of their content-scan config.\nconst RESPONSIVE_DOT_STYLE = {\n width: \"clamp(1.25rem, 18cqmin, 2.5rem)\",\n height: \"clamp(1.25rem, 18cqmin, 2.5rem)\",\n} as const;\nconst RESPONSIVE_LABEL_STYLE = {\n fontSize: \"clamp(0.72rem, 4.5cqmin, 0.9rem)\",\n} as const;\n\n/**\n * Three bouncing dots. `fill=\"currentColor\"` so the dots inherit the surrounding\n * theme text color (works in light and dark). Size is driven entirely by the\n * className (width + height); the viewBox keeps the 1:1 aspect.\n */\nexport const LoadingDots = forwardRef<SVGSVGElement, SVGProps<SVGSVGElement>>(\n ({ className, ...props }, ref) => (\n <svg\n ref={ref}\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n className={cn(\"shrink-0\", className)}\n {...props}\n >\n <circle cx=\"4\" cy=\"12\" r=\"3\">\n <animate\n id=\"clickyLoadDotA\"\n fill=\"freeze\"\n attributeName=\"opacity\"\n begin=\"0;clickyLoadDotC.end-0.25s\"\n dur=\"0.75s\"\n values=\"1;0.2\"\n />\n </circle>\n <circle cx=\"12\" cy=\"12\" r=\"3\" opacity=\"0.4\">\n <animate\n fill=\"freeze\"\n attributeName=\"opacity\"\n begin=\"clickyLoadDotA.begin+0.15s\"\n dur=\"0.75s\"\n values=\"1;0.2\"\n />\n </circle>\n <circle cx=\"20\" cy=\"12\" r=\"3\" opacity=\"0.3\">\n <animate\n id=\"clickyLoadDotC\"\n fill=\"freeze\"\n attributeName=\"opacity\"\n begin=\"clickyLoadDotA.begin+0.3s\"\n dur=\"0.75s\"\n values=\"1;0.2\"\n />\n </circle>\n </svg>\n ),\n);\nLoadingDots.displayName = \"LoadingDots\";\n\n/**\n * Loading indicator built on the shared 3-dot animation.\n *\n * `centered` fills the space it is given (`h-full` within a `min-h-32` floor) and\n * establishes a size container (`container-type: size`) so the default\n * `responsive` size scales the dots to that space via `cqmin` — small inside a\n * narrow/short panel, large on a full-page route shell that stretches it tall.\n * The `inline` variant is not a container, so a `responsive` request there\n * renders at `sm`.\n */\nexport const Loading = forwardRef<HTMLDivElement, LoadingProps>(\n ({ size = \"responsive\", variant = \"inline\", label, className, ...props }, ref) => {\n const effectiveSize =\n variant === \"inline\" && size === \"responsive\" ? \"sm\" : size;\n const isResponsive = effectiveSize === \"responsive\";\n const dotClass = isResponsive ? \"\" : FIXED_DOT_SIZE[effectiveSize];\n const dotStyle = isResponsive ? RESPONSIVE_DOT_STYLE : undefined;\n\n if (variant === \"centered\") {\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\n \"flex h-full min-h-32 w-full flex-col items-center justify-center gap-3 text-muted-foreground\",\n \"[container-type:size]\",\n className,\n )}\n {...props}\n >\n <LoadingDots className={dotClass} style={dotStyle} />\n {label != null && (\n <span\n className={isResponsive ? undefined : \"text-sm\"}\n style={isResponsive ? RESPONSIVE_LABEL_STYLE : undefined}\n >\n {label}\n </span>\n )}\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\n \"inline-flex items-center gap-2 text-muted-foreground\",\n className,\n )}\n {...props}\n >\n <LoadingDots className={dotClass} style={dotStyle} />\n {label != null && <span className=\"text-sm\">{label}</span>}\n </div>\n );\n },\n);\nLoading.displayName = \"Loading\";\n"],"names":[],"mappings":";;;AAoBA,MAAM,iBAAqE;AAAA,EACzE,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAgBA,MAAM,uBAAuB;AAAA,EAC3B,OAAO;AAAA,EACP,QAAQ;AACV;AACA,MAAM,yBAAyB;AAAA,EAC7B,UAAU;AACZ;AAOO,MAAM,cAAc;AAAA,EACzB,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QACxB;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,OAAM;AAAA,MACN,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,eAAY;AAAA,MACZ,WAAW,GAAG,YAAY,SAAS;AAAA,MAClC,GAAG;AAAA,MAEJ,UAAA;AAAA,QAAA,oBAAC,YAAO,IAAG,KAAI,IAAG,MAAK,GAAE,KACvB,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,eAAc;AAAA,YACd,OAAM;AAAA,YACN,KAAI;AAAA,YACJ,QAAO;AAAA,UAAA;AAAA,QAAA,GAEX;AAAA,QACA,oBAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,KAAI,SAAQ,OACpC,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,eAAc;AAAA,YACd,OAAM;AAAA,YACN,KAAI;AAAA,YACJ,QAAO;AAAA,UAAA;AAAA,QAAA,GAEX;AAAA,QACA,oBAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,KAAI,SAAQ,OACpC,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,eAAc;AAAA,YACd,OAAM;AAAA,YACN,KAAI;AAAA,YACJ,QAAO;AAAA,UAAA;AAAA,QAAA,EACT,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;AACA,YAAY,cAAc;AAYnB,MAAM,UAAU;AAAA,EACrB,CAAC,EAAE,OAAO,cAAc,UAAU,UAAU,OAAO,WAAW,GAAG,MAAA,GAAS,QAAQ;AAChF,UAAM,gBACJ,YAAY,YAAY,SAAS,eAAe,OAAO;AACzD,UAAM,eAAe,kBAAkB;AACvC,UAAM,WAAW,eAAe,KAAK,eAAe,aAAa;AACjE,UAAM,WAAW,eAAe,uBAAuB;AAEvD,QAAI,YAAY,YAAY;AAC1B,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC;AAAA,UACA,MAAK;AAAA,UACL,cAAW;AAAA,UACX,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,UAAA;AAAA,UAED,GAAG;AAAA,UAEJ,UAAA;AAAA,YAAA,oBAAC,aAAA,EAAY,WAAW,UAAU,OAAO,UAAU;AAAA,YAClD,SAAS,QACR;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,WAAW,eAAe,SAAY;AAAA,gBACtC,OAAO,eAAe,yBAAyB;AAAA,gBAE9C,UAAA;AAAA,cAAA;AAAA,YAAA;AAAA,UACH;AAAA,QAAA;AAAA,MAAA;AAAA,IAIR;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,cAAW;AAAA,QACX,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEJ,UAAA;AAAA,UAAA,oBAAC,aAAA,EAAY,WAAW,UAAU,OAAO,UAAU;AAAA,UAClD,SAAS,QAAQ,oBAAC,QAAA,EAAK,WAAU,WAAW,UAAA,MAAA,CAAM;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGzD;AACF;AACA,QAAQ,cAAc;"}
@@ -26,56 +26,30 @@ const jsxRuntime = require("react/jsx-runtime");
26
26
  const react = require("react");
27
27
  const utils = require("../lib/utils.cjs");
28
28
  let loadPromise = null;
29
- function loadMarked() {
30
- if (loadPromise) return loadPromise;
31
- loadPromise = import("marked").then(({ Marked, Renderer }) => {
32
- const renderer = new Renderer();
33
- renderer.code = ({ text, lang }) => `<pre class="bg-muted rounded p-2 text-xs overflow-x-auto border border-border my-1.5"><code${lang ? ` class="language-${lang}"` : ""}>${text}</code></pre>`;
34
- renderer.codespan = ({ text }) => `<code class="bg-muted rounded px-1 text-xs">${text}</code>`;
35
- renderer.heading = ({ text, depth }) => {
36
- const cls = depth <= 1 ? "font-bold text-base mt-2 mb-1" : depth === 2 ? "font-semibold text-sm mt-2 mb-1" : "font-semibold text-sm mt-2 mb-1";
37
- return `<h${depth} class="${cls}">${text}</h${depth}>`;
38
- };
39
- renderer.link = ({ href, text }) => `<a href="${href}" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">${text}</a>`;
40
- renderer.blockquote = ({ text }) => `<blockquote class="border-l-2 border-border pl-2 text-muted-foreground italic my-1">${text}</blockquote>`;
41
- renderer.tablerow = ({ text }) => `<tr class="border-b border-border">${text}</tr>`;
42
- renderer.list = function(token) {
43
- const tag = token.ordered ? "ol" : "ul";
44
- const cls = token.ordered ? "list-decimal ml-4 my-1" : "list-disc ml-4 my-1";
45
- const body = token.items.map((item) => this.listitem(item)).join("");
46
- return `<${tag} class="${cls}">${body}</${tag}>`;
47
- };
48
- renderer.listitem = ({ text }) => `<li class="my-0.5">${text}</li>`;
49
- renderer.image = ({ href, text }) => `<img src="${href}" alt="${text}" class="max-w-full rounded my-1" />`;
50
- renderer.hr = () => '<hr class="border-border my-2" />';
51
- renderer.paragraph = ({ text }) => `<p class="mt-1.5">${text}</p>`;
52
- return new Marked({ renderer, gfm: true, breaks: true });
53
- });
29
+ function loadStreamdown() {
30
+ if (!loadPromise) {
31
+ loadPromise = import("streamdown").then((m) => m.Streamdown).catch((err) => {
32
+ console.warn("clicky-ui: streamdown not available; rendering markdown as plain text", err);
33
+ return null;
34
+ });
35
+ }
54
36
  return loadPromise;
55
37
  }
56
38
  function Markdown({ text, className }) {
57
- const [html, setHtml] = react.useState(null);
39
+ const [Streamdown, setStreamdown] = react.useState(null);
58
40
  react.useEffect(() => {
59
41
  let cancelled = false;
60
- loadMarked().then((m) => Promise.resolve(m.parse(text))).then((out) => {
61
- if (!cancelled) setHtml(out);
62
- }).catch(() => {
63
- if (!cancelled) setHtml(text);
42
+ loadStreamdown().then((comp) => {
43
+ if (!cancelled) setStreamdown(() => comp);
64
44
  });
65
45
  return () => {
66
46
  cancelled = true;
67
47
  };
68
- }, [text]);
69
- if (html === null) {
70
- return /* @__PURE__ */ jsxRuntime.jsx("pre", { className: utils.cn("text-sm whitespace-pre-wrap", className), children: text });
48
+ }, []);
49
+ if (!Streamdown) {
50
+ return /* @__PURE__ */ jsxRuntime.jsx("pre", { className: utils.cn("whitespace-pre-wrap text-sm", className), children: text });
71
51
  }
72
- return /* @__PURE__ */ jsxRuntime.jsx(
73
- "div",
74
- {
75
- className: utils.cn("prose prose-sm max-w-none dark:prose-invert", className),
76
- dangerouslySetInnerHTML: { __html: html }
77
- }
78
- );
52
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: utils.cn("prose prose-sm max-w-none dark:prose-invert", className), children: /* @__PURE__ */ jsxRuntime.jsx(Streamdown, { parseIncompleteMarkdown: true, children: text }) });
79
53
  }
80
54
  exports.Markdown = Markdown;
81
55
  //# sourceMappingURL=Markdown.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.cjs","sources":["../../src/data/Markdown.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\n\nexport type MarkdownProps = {\n /** Markdown source text. */\n text: string;\n /** Classes applied to the rendered markdown wrapper. */\n className?: string;\n};\n\ntype MarkedLike = {\n parse: (md: string) => string | Promise<string>;\n};\n\nlet loadPromise: Promise<MarkedLike> | null = null;\n\nfunction loadMarked(): Promise<MarkedLike> {\n if (loadPromise) return loadPromise;\n loadPromise = import(\"marked\").then(({ Marked, Renderer }) => {\n const renderer = new Renderer();\n\n renderer.code = ({ text, lang }) =>\n `<pre class=\"bg-muted rounded p-2 text-xs overflow-x-auto border border-border my-1.5\"><code${lang ? ` class=\"language-${lang}\"` : \"\"}>${text}</code></pre>`;\n\n renderer.codespan = ({ text }) => `<code class=\"bg-muted rounded px-1 text-xs\">${text}</code>`;\n\n renderer.heading = ({ text, depth }) => {\n const cls =\n depth <= 1\n ? \"font-bold text-base mt-2 mb-1\"\n : depth === 2\n ? \"font-semibold text-sm mt-2 mb-1\"\n : \"font-semibold text-sm mt-2 mb-1\";\n return `<h${depth} class=\"${cls}\">${text}</h${depth}>`;\n };\n\n renderer.link = ({ href, text }) =>\n `<a href=\"${href}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-primary hover:underline\">${text}</a>`;\n\n renderer.blockquote = ({ text }) =>\n `<blockquote class=\"border-l-2 border-border pl-2 text-muted-foreground italic my-1\">${text}</blockquote>`;\n\n renderer.tablerow = ({ text }) => `<tr class=\"border-b border-border\">${text}</tr>`;\n\n renderer.list = function (token) {\n const tag = token.ordered ? \"ol\" : \"ul\";\n const cls = token.ordered ? \"list-decimal ml-4 my-1\" : \"list-disc ml-4 my-1\";\n const body = token.items.map((item) => this.listitem(item)).join(\"\");\n return `<${tag} class=\"${cls}\">${body}</${tag}>`;\n };\n\n renderer.listitem = ({ text }) => `<li class=\"my-0.5\">${text}</li>`;\n\n renderer.image = ({ href, text }) =>\n `<img src=\"${href}\" alt=\"${text}\" class=\"max-w-full rounded my-1\" />`;\n\n renderer.hr = () => '<hr class=\"border-border my-2\" />';\n\n renderer.paragraph = ({ text }) => `<p class=\"mt-1.5\">${text}</p>`;\n\n return new Marked({ renderer, gfm: true, breaks: true }) as unknown as MarkedLike;\n });\n return loadPromise;\n}\n\nexport function Markdown({ text, className }: MarkdownProps) {\n const [html, setHtml] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n loadMarked()\n .then((m) => Promise.resolve(m.parse(text)))\n .then((out) => {\n if (!cancelled) setHtml(out);\n })\n .catch(() => {\n if (!cancelled) setHtml(text);\n });\n return () => {\n cancelled = true;\n };\n }, [text]);\n\n if (html === null) {\n return <pre className={cn(\"text-sm whitespace-pre-wrap\", className)}>{text}</pre>;\n }\n\n return (\n <div\n className={cn(\"prose prose-sm max-w-none dark:prose-invert\", className)}\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n}\n"],"names":["useState","useEffect","cn","jsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAcA,IAAI,cAA0C;AAE9C,SAAS,aAAkC;AACzC,MAAI,YAAa,QAAO;AACxB,gBAAc,OAAO,QAAQ,EAAE,KAAK,CAAC,EAAE,QAAQ,eAAe;AAC5D,UAAM,WAAW,IAAI,SAAA;AAErB,aAAS,OAAO,CAAC,EAAE,MAAM,KAAA,MACvB,8FAA8F,OAAO,oBAAoB,IAAI,MAAM,EAAE,IAAI,IAAI;AAE/I,aAAS,WAAW,CAAC,EAAE,KAAA,MAAW,+CAA+C,IAAI;AAErF,aAAS,UAAU,CAAC,EAAE,MAAM,YAAY;AACtC,YAAM,MACJ,SAAS,IACL,kCACA,UAAU,IACR,oCACA;AACR,aAAO,KAAK,KAAK,WAAW,GAAG,KAAK,IAAI,MAAM,KAAK;AAAA,IACrD;AAEA,aAAS,OAAO,CAAC,EAAE,MAAM,WACvB,YAAY,IAAI,oFAAoF,IAAI;AAE1G,aAAS,aAAa,CAAC,EAAE,KAAA,MACvB,uFAAuF,IAAI;AAE7F,aAAS,WAAW,CAAC,EAAE,KAAA,MAAW,sCAAsC,IAAI;AAE5E,aAAS,OAAO,SAAU,OAAO;AAC/B,YAAM,MAAM,MAAM,UAAU,OAAO;AACnC,YAAM,MAAM,MAAM,UAAU,2BAA2B;AACvD,YAAM,OAAO,MAAM,MAAM,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,EAAE,KAAK,EAAE;AACnE,aAAO,IAAI,GAAG,WAAW,GAAG,KAAK,IAAI,KAAK,GAAG;AAAA,IAC/C;AAEA,aAAS,WAAW,CAAC,EAAE,KAAA,MAAW,sBAAsB,IAAI;AAE5D,aAAS,QAAQ,CAAC,EAAE,MAAM,WACxB,aAAa,IAAI,UAAU,IAAI;AAEjC,aAAS,KAAK,MAAM;AAEpB,aAAS,YAAY,CAAC,EAAE,KAAA,MAAW,qBAAqB,IAAI;AAE5D,WAAO,IAAI,OAAO,EAAE,UAAU,KAAK,MAAM,QAAQ,MAAM;AAAA,EACzD,CAAC;AACD,SAAO;AACT;AAEO,SAAS,SAAS,EAAE,MAAM,aAA4B;AAC3D,QAAM,CAAC,MAAM,OAAO,IAAIA,MAAAA,SAAwB,IAAI;AAEpDC,QAAAA,UAAU,MAAM;AACd,QAAI,YAAY;AAChB,eAAA,EACG,KAAK,CAAC,MAAM,QAAQ,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAC,EAC1C,KAAK,CAAC,QAAQ;AACb,UAAI,CAAC,UAAW,SAAQ,GAAG;AAAA,IAC7B,CAAC,EACA,MAAM,MAAM;AACX,UAAI,CAAC,UAAW,SAAQ,IAAI;AAAA,IAC9B,CAAC;AACH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,MAAI,SAAS,MAAM;AACjB,0CAAQ,OAAA,EAAI,WAAWC,MAAAA,GAAG,+BAA+B,SAAS,GAAI,UAAA,MAAK;AAAA,EAC7E;AAEA,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWD,MAAAA,GAAG,+CAA+C,SAAS;AAAA,MACtE,yBAAyB,EAAE,QAAQ,KAAA;AAAA,IAAK;AAAA,EAAA;AAG9C;;"}
1
+ {"version":3,"file":"Markdown.cjs","sources":["../../src/data/Markdown.tsx"],"sourcesContent":["import { useEffect, useState, type ComponentType } from \"react\";\nimport { cn } from \"../lib/utils\";\n\nexport type MarkdownProps = {\n /** Markdown source text. */\n text: string;\n /** Classes applied to the rendered markdown wrapper. */\n className?: string;\n};\n\ntype StreamdownComponent = ComponentType<{\n children: string;\n className?: string;\n parseIncompleteMarkdown?: boolean;\n}>;\n\nlet loadPromise: Promise<StreamdownComponent | null> | null = null;\n\n// Streamdown is an optional peer dependency (like marked/shiki). It is loaded on\n// demand so the core library does not force it on consumers; while it loads (or\n// if it is absent) we fall back to preformatted text.\nfunction loadStreamdown(): Promise<StreamdownComponent | null> {\n if (!loadPromise) {\n loadPromise = import(\"streamdown\")\n .then((m) => m.Streamdown as unknown as StreamdownComponent)\n .catch((err) => {\n console.warn(\"clicky-ui: streamdown not available; rendering markdown as plain text\", err);\n return null;\n });\n }\n return loadPromise;\n}\n\n/** Renders markdown with Streamdown, which is purpose-built for streaming LLM\n * output (it gracefully handles incomplete markdown mid-stream). Falls back to\n * preformatted text until Streamdown loads or when it is not installed. */\nexport function Markdown({ text, className }: MarkdownProps) {\n const [Streamdown, setStreamdown] = useState<StreamdownComponent | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n loadStreamdown().then((comp) => {\n if (!cancelled) setStreamdown(() => comp);\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n if (!Streamdown) {\n return <pre className={cn(\"whitespace-pre-wrap text-sm\", className)}>{text}</pre>;\n }\n\n return (\n <div className={cn(\"prose prose-sm max-w-none dark:prose-invert\", className)}>\n <Streamdown parseIncompleteMarkdown>{text}</Streamdown>\n </div>\n );\n}\n"],"names":["useState","useEffect","cn","jsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBA,IAAI,cAA0D;AAK9D,SAAS,iBAAsD;AAC7D,MAAI,CAAC,aAAa;AAChB,kBAAc,OAAO,YAAY,EAC9B,KAAK,CAAC,MAAM,EAAE,UAA4C,EAC1D,MAAM,CAAC,QAAQ;AACd,cAAQ,KAAK,yEAAyE,GAAG;AACzF,aAAO;AAAA,IACT,CAAC;AAAA,EACL;AACA,SAAO;AACT;AAKO,SAAS,SAAS,EAAE,MAAM,aAA4B;AAC3D,QAAM,CAAC,YAAY,aAAa,IAAIA,MAAAA,SAAqC,IAAI;AAE7EC,QAAAA,UAAU,MAAM;AACd,QAAI,YAAY;AAChB,mBAAA,EAAiB,KAAK,CAAC,SAAS;AAC9B,UAAI,CAAC,UAAW,eAAc,MAAM,IAAI;AAAA,IAC1C,CAAC;AACD,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAA,CAAE;AAEL,MAAI,CAAC,YAAY;AACf,0CAAQ,OAAA,EAAI,WAAWC,MAAAA,GAAG,+BAA+B,SAAS,GAAI,UAAA,MAAK;AAAA,EAC7E;AAEA,SACEC,2BAAAA,IAAC,OAAA,EAAI,WAAWD,MAAAA,GAAG,+CAA+C,SAAS,GACzE,UAAAC,2BAAAA,IAAC,YAAA,EAAW,yBAAuB,MAAE,UAAA,KAAA,CAAK,GAC5C;AAEJ;;"}
@@ -4,5 +4,8 @@ export type MarkdownProps = {
4
4
  /** Classes applied to the rendered markdown wrapper. */
5
5
  className?: string;
6
6
  };
7
+ /** Renders markdown with Streamdown, which is purpose-built for streaming LLM
8
+ * output (it gracefully handles incomplete markdown mid-stream). Falls back to
9
+ * preformatted text until Streamdown loads or when it is not installed. */
7
10
  export declare function Markdown({ text, className }: MarkdownProps): import("react/jsx-runtime").JSX.Element;
8
11
  //# sourceMappingURL=Markdown.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.d.ts","sourceRoot":"","sources":["../../src/data/Markdown.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG;IAC1B,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAyDF,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,aAAa,2CA4B1D"}
1
+ {"version":3,"file":"Markdown.d.ts","sourceRoot":"","sources":["../../src/data/Markdown.tsx"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG;IAC1B,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAyBF;;4EAE4E;AAC5E,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,aAAa,2CAsB1D"}
@@ -2,56 +2,30 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { useState, useEffect } from "react";
3
3
  import { cn } from "../lib/utils.js";
4
4
  let loadPromise = null;
5
- function loadMarked() {
6
- if (loadPromise) return loadPromise;
7
- loadPromise = import("marked").then(({ Marked, Renderer }) => {
8
- const renderer = new Renderer();
9
- renderer.code = ({ text, lang }) => `<pre class="bg-muted rounded p-2 text-xs overflow-x-auto border border-border my-1.5"><code${lang ? ` class="language-${lang}"` : ""}>${text}</code></pre>`;
10
- renderer.codespan = ({ text }) => `<code class="bg-muted rounded px-1 text-xs">${text}</code>`;
11
- renderer.heading = ({ text, depth }) => {
12
- const cls = depth <= 1 ? "font-bold text-base mt-2 mb-1" : depth === 2 ? "font-semibold text-sm mt-2 mb-1" : "font-semibold text-sm mt-2 mb-1";
13
- return `<h${depth} class="${cls}">${text}</h${depth}>`;
14
- };
15
- renderer.link = ({ href, text }) => `<a href="${href}" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">${text}</a>`;
16
- renderer.blockquote = ({ text }) => `<blockquote class="border-l-2 border-border pl-2 text-muted-foreground italic my-1">${text}</blockquote>`;
17
- renderer.tablerow = ({ text }) => `<tr class="border-b border-border">${text}</tr>`;
18
- renderer.list = function(token) {
19
- const tag = token.ordered ? "ol" : "ul";
20
- const cls = token.ordered ? "list-decimal ml-4 my-1" : "list-disc ml-4 my-1";
21
- const body = token.items.map((item) => this.listitem(item)).join("");
22
- return `<${tag} class="${cls}">${body}</${tag}>`;
23
- };
24
- renderer.listitem = ({ text }) => `<li class="my-0.5">${text}</li>`;
25
- renderer.image = ({ href, text }) => `<img src="${href}" alt="${text}" class="max-w-full rounded my-1" />`;
26
- renderer.hr = () => '<hr class="border-border my-2" />';
27
- renderer.paragraph = ({ text }) => `<p class="mt-1.5">${text}</p>`;
28
- return new Marked({ renderer, gfm: true, breaks: true });
29
- });
5
+ function loadStreamdown() {
6
+ if (!loadPromise) {
7
+ loadPromise = import("streamdown").then((m) => m.Streamdown).catch((err) => {
8
+ console.warn("clicky-ui: streamdown not available; rendering markdown as plain text", err);
9
+ return null;
10
+ });
11
+ }
30
12
  return loadPromise;
31
13
  }
32
14
  function Markdown({ text, className }) {
33
- const [html, setHtml] = useState(null);
15
+ const [Streamdown, setStreamdown] = useState(null);
34
16
  useEffect(() => {
35
17
  let cancelled = false;
36
- loadMarked().then((m) => Promise.resolve(m.parse(text))).then((out) => {
37
- if (!cancelled) setHtml(out);
38
- }).catch(() => {
39
- if (!cancelled) setHtml(text);
18
+ loadStreamdown().then((comp) => {
19
+ if (!cancelled) setStreamdown(() => comp);
40
20
  });
41
21
  return () => {
42
22
  cancelled = true;
43
23
  };
44
- }, [text]);
45
- if (html === null) {
46
- return /* @__PURE__ */ jsx("pre", { className: cn("text-sm whitespace-pre-wrap", className), children: text });
24
+ }, []);
25
+ if (!Streamdown) {
26
+ return /* @__PURE__ */ jsx("pre", { className: cn("whitespace-pre-wrap text-sm", className), children: text });
47
27
  }
48
- return /* @__PURE__ */ jsx(
49
- "div",
50
- {
51
- className: cn("prose prose-sm max-w-none dark:prose-invert", className),
52
- dangerouslySetInnerHTML: { __html: html }
53
- }
54
- );
28
+ return /* @__PURE__ */ jsx("div", { className: cn("prose prose-sm max-w-none dark:prose-invert", className), children: /* @__PURE__ */ jsx(Streamdown, { parseIncompleteMarkdown: true, children: text }) });
55
29
  }
56
30
  export {
57
31
  Markdown
@@ -1 +1 @@
1
- {"version":3,"file":"Markdown.js","sources":["../../src/data/Markdown.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { cn } from \"../lib/utils\";\n\nexport type MarkdownProps = {\n /** Markdown source text. */\n text: string;\n /** Classes applied to the rendered markdown wrapper. */\n className?: string;\n};\n\ntype MarkedLike = {\n parse: (md: string) => string | Promise<string>;\n};\n\nlet loadPromise: Promise<MarkedLike> | null = null;\n\nfunction loadMarked(): Promise<MarkedLike> {\n if (loadPromise) return loadPromise;\n loadPromise = import(\"marked\").then(({ Marked, Renderer }) => {\n const renderer = new Renderer();\n\n renderer.code = ({ text, lang }) =>\n `<pre class=\"bg-muted rounded p-2 text-xs overflow-x-auto border border-border my-1.5\"><code${lang ? ` class=\"language-${lang}\"` : \"\"}>${text}</code></pre>`;\n\n renderer.codespan = ({ text }) => `<code class=\"bg-muted rounded px-1 text-xs\">${text}</code>`;\n\n renderer.heading = ({ text, depth }) => {\n const cls =\n depth <= 1\n ? \"font-bold text-base mt-2 mb-1\"\n : depth === 2\n ? \"font-semibold text-sm mt-2 mb-1\"\n : \"font-semibold text-sm mt-2 mb-1\";\n return `<h${depth} class=\"${cls}\">${text}</h${depth}>`;\n };\n\n renderer.link = ({ href, text }) =>\n `<a href=\"${href}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-primary hover:underline\">${text}</a>`;\n\n renderer.blockquote = ({ text }) =>\n `<blockquote class=\"border-l-2 border-border pl-2 text-muted-foreground italic my-1\">${text}</blockquote>`;\n\n renderer.tablerow = ({ text }) => `<tr class=\"border-b border-border\">${text}</tr>`;\n\n renderer.list = function (token) {\n const tag = token.ordered ? \"ol\" : \"ul\";\n const cls = token.ordered ? \"list-decimal ml-4 my-1\" : \"list-disc ml-4 my-1\";\n const body = token.items.map((item) => this.listitem(item)).join(\"\");\n return `<${tag} class=\"${cls}\">${body}</${tag}>`;\n };\n\n renderer.listitem = ({ text }) => `<li class=\"my-0.5\">${text}</li>`;\n\n renderer.image = ({ href, text }) =>\n `<img src=\"${href}\" alt=\"${text}\" class=\"max-w-full rounded my-1\" />`;\n\n renderer.hr = () => '<hr class=\"border-border my-2\" />';\n\n renderer.paragraph = ({ text }) => `<p class=\"mt-1.5\">${text}</p>`;\n\n return new Marked({ renderer, gfm: true, breaks: true }) as unknown as MarkedLike;\n });\n return loadPromise;\n}\n\nexport function Markdown({ text, className }: MarkdownProps) {\n const [html, setHtml] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n loadMarked()\n .then((m) => Promise.resolve(m.parse(text)))\n .then((out) => {\n if (!cancelled) setHtml(out);\n })\n .catch(() => {\n if (!cancelled) setHtml(text);\n });\n return () => {\n cancelled = true;\n };\n }, [text]);\n\n if (html === null) {\n return <pre className={cn(\"text-sm whitespace-pre-wrap\", className)}>{text}</pre>;\n }\n\n return (\n <div\n className={cn(\"prose prose-sm max-w-none dark:prose-invert\", className)}\n dangerouslySetInnerHTML={{ __html: html }}\n />\n );\n}\n"],"names":[],"mappings":";;;AAcA,IAAI,cAA0C;AAE9C,SAAS,aAAkC;AACzC,MAAI,YAAa,QAAO;AACxB,gBAAc,OAAO,QAAQ,EAAE,KAAK,CAAC,EAAE,QAAQ,eAAe;AAC5D,UAAM,WAAW,IAAI,SAAA;AAErB,aAAS,OAAO,CAAC,EAAE,MAAM,KAAA,MACvB,8FAA8F,OAAO,oBAAoB,IAAI,MAAM,EAAE,IAAI,IAAI;AAE/I,aAAS,WAAW,CAAC,EAAE,KAAA,MAAW,+CAA+C,IAAI;AAErF,aAAS,UAAU,CAAC,EAAE,MAAM,YAAY;AACtC,YAAM,MACJ,SAAS,IACL,kCACA,UAAU,IACR,oCACA;AACR,aAAO,KAAK,KAAK,WAAW,GAAG,KAAK,IAAI,MAAM,KAAK;AAAA,IACrD;AAEA,aAAS,OAAO,CAAC,EAAE,MAAM,WACvB,YAAY,IAAI,oFAAoF,IAAI;AAE1G,aAAS,aAAa,CAAC,EAAE,KAAA,MACvB,uFAAuF,IAAI;AAE7F,aAAS,WAAW,CAAC,EAAE,KAAA,MAAW,sCAAsC,IAAI;AAE5E,aAAS,OAAO,SAAU,OAAO;AAC/B,YAAM,MAAM,MAAM,UAAU,OAAO;AACnC,YAAM,MAAM,MAAM,UAAU,2BAA2B;AACvD,YAAM,OAAO,MAAM,MAAM,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,EAAE,KAAK,EAAE;AACnE,aAAO,IAAI,GAAG,WAAW,GAAG,KAAK,IAAI,KAAK,GAAG;AAAA,IAC/C;AAEA,aAAS,WAAW,CAAC,EAAE,KAAA,MAAW,sBAAsB,IAAI;AAE5D,aAAS,QAAQ,CAAC,EAAE,MAAM,WACxB,aAAa,IAAI,UAAU,IAAI;AAEjC,aAAS,KAAK,MAAM;AAEpB,aAAS,YAAY,CAAC,EAAE,KAAA,MAAW,qBAAqB,IAAI;AAE5D,WAAO,IAAI,OAAO,EAAE,UAAU,KAAK,MAAM,QAAQ,MAAM;AAAA,EACzD,CAAC;AACD,SAAO;AACT;AAEO,SAAS,SAAS,EAAE,MAAM,aAA4B;AAC3D,QAAM,CAAC,MAAM,OAAO,IAAI,SAAwB,IAAI;AAEpD,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,eAAA,EACG,KAAK,CAAC,MAAM,QAAQ,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAC,EAC1C,KAAK,CAAC,QAAQ;AACb,UAAI,CAAC,UAAW,SAAQ,GAAG;AAAA,IAC7B,CAAC,EACA,MAAM,MAAM;AACX,UAAI,CAAC,UAAW,SAAQ,IAAI;AAAA,IAC9B,CAAC;AACH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,MAAI,SAAS,MAAM;AACjB,+BAAQ,OAAA,EAAI,WAAW,GAAG,+BAA+B,SAAS,GAAI,UAAA,MAAK;AAAA,EAC7E;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,GAAG,+CAA+C,SAAS;AAAA,MACtE,yBAAyB,EAAE,QAAQ,KAAA;AAAA,IAAK;AAAA,EAAA;AAG9C;"}
1
+ {"version":3,"file":"Markdown.js","sources":["../../src/data/Markdown.tsx"],"sourcesContent":["import { useEffect, useState, type ComponentType } from \"react\";\nimport { cn } from \"../lib/utils\";\n\nexport type MarkdownProps = {\n /** Markdown source text. */\n text: string;\n /** Classes applied to the rendered markdown wrapper. */\n className?: string;\n};\n\ntype StreamdownComponent = ComponentType<{\n children: string;\n className?: string;\n parseIncompleteMarkdown?: boolean;\n}>;\n\nlet loadPromise: Promise<StreamdownComponent | null> | null = null;\n\n// Streamdown is an optional peer dependency (like marked/shiki). It is loaded on\n// demand so the core library does not force it on consumers; while it loads (or\n// if it is absent) we fall back to preformatted text.\nfunction loadStreamdown(): Promise<StreamdownComponent | null> {\n if (!loadPromise) {\n loadPromise = import(\"streamdown\")\n .then((m) => m.Streamdown as unknown as StreamdownComponent)\n .catch((err) => {\n console.warn(\"clicky-ui: streamdown not available; rendering markdown as plain text\", err);\n return null;\n });\n }\n return loadPromise;\n}\n\n/** Renders markdown with Streamdown, which is purpose-built for streaming LLM\n * output (it gracefully handles incomplete markdown mid-stream). Falls back to\n * preformatted text until Streamdown loads or when it is not installed. */\nexport function Markdown({ text, className }: MarkdownProps) {\n const [Streamdown, setStreamdown] = useState<StreamdownComponent | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n loadStreamdown().then((comp) => {\n if (!cancelled) setStreamdown(() => comp);\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n if (!Streamdown) {\n return <pre className={cn(\"whitespace-pre-wrap text-sm\", className)}>{text}</pre>;\n }\n\n return (\n <div className={cn(\"prose prose-sm max-w-none dark:prose-invert\", className)}>\n <Streamdown parseIncompleteMarkdown>{text}</Streamdown>\n </div>\n );\n}\n"],"names":[],"mappings":";;;AAgBA,IAAI,cAA0D;AAK9D,SAAS,iBAAsD;AAC7D,MAAI,CAAC,aAAa;AAChB,kBAAc,OAAO,YAAY,EAC9B,KAAK,CAAC,MAAM,EAAE,UAA4C,EAC1D,MAAM,CAAC,QAAQ;AACd,cAAQ,KAAK,yEAAyE,GAAG;AACzF,aAAO;AAAA,IACT,CAAC;AAAA,EACL;AACA,SAAO;AACT;AAKO,SAAS,SAAS,EAAE,MAAM,aAA4B;AAC3D,QAAM,CAAC,YAAY,aAAa,IAAI,SAAqC,IAAI;AAE7E,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,mBAAA,EAAiB,KAAK,CAAC,SAAS;AAC9B,UAAI,CAAC,UAAW,eAAc,MAAM,IAAI;AAAA,IAC1C,CAAC;AACD,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAA,CAAE;AAEL,MAAI,CAAC,YAAY;AACf,+BAAQ,OAAA,EAAI,WAAW,GAAG,+BAA+B,SAAS,GAAI,UAAA,MAAK;AAAA,EAC7E;AAEA,SACE,oBAAC,OAAA,EAAI,WAAW,GAAG,+CAA+C,SAAS,GACzE,UAAA,oBAAC,YAAA,EAAW,yBAAuB,MAAE,UAAA,KAAA,CAAK,GAC5C;AAEJ;"}