@heyramzi/ui 2.11.0 → 2.11.2

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 (267) hide show
  1. package/dist/agents/helpers.js +38 -38
  2. package/dist/agents/page-state.js +49 -50
  3. package/dist/agents/vendor/color.js +146 -127
  4. package/dist/agents/vendor/draw.js +617 -508
  5. package/dist/agents/vendor/engine.js +698 -599
  6. package/dist/agents/vendor/mesh.js +653 -683
  7. package/dist/agents/vendor/plastic.js +3913 -3112
  8. package/dist/agents/vendor/presets.js +163 -164
  9. package/dist/agents/vendor/shapes.js +21 -20
  10. package/dist/agents/vendor/ticker.js +41 -42
  11. package/dist/agents/vendor/types.js +1 -0
  12. package/dist/agents/vendor/wear.js +1421 -1448
  13. package/dist/ai/confirmation/confirmation-context.svelte.js +6 -6
  14. package/dist/ai/confirmation/index.js +3 -1
  15. package/dist/ai/index.js +33 -0
  16. package/dist/ai/prompt-input/index.js +3 -1
  17. package/dist/ai/reasoning/index.js +3 -1
  18. package/dist/ai/reasoning/reasoning-context.svelte.js +6 -6
  19. package/dist/ai/suggestion/index.js +3 -1
  20. package/dist/ai/typing-dots/index.js +3 -1
  21. package/dist/animations/text-swap.js +24 -24
  22. package/dist/brand/index.js +54 -48
  23. package/dist/canvas/card/resize.js +5 -3
  24. package/dist/canvas/edges/edge-path.js +255 -242
  25. package/dist/canvas/editor/context-menu.js +39 -36
  26. package/dist/canvas/editor/dispatch.svelte.js +154 -151
  27. package/dist/canvas/editor/editor-context.js +7 -2
  28. package/dist/canvas/editor/editor-mode.js +1 -0
  29. package/dist/canvas/editor/frame.js +73 -76
  30. package/dist/canvas/editor/hand-context.js +5 -2
  31. package/dist/canvas/editor/history-core.js +163 -191
  32. package/dist/canvas/editor/keymap.js +95 -169
  33. package/dist/canvas/editor/layout-actions.js +143 -144
  34. package/dist/canvas/editor/lock.js +52 -51
  35. package/dist/canvas/editor/rebase.js +217 -240
  36. package/dist/canvas/editor/style.js +132 -137
  37. package/dist/canvas/editor/text-field-context.js +7 -2
  38. package/dist/canvas/editor/z-order.js +65 -70
  39. package/dist/canvas/headless/alignment.js +22 -16
  40. package/dist/canvas/headless/break-apart-find.js +286 -279
  41. package/dist/canvas/headless/break-apart-pieces.js +62 -58
  42. package/dist/canvas/headless/break-apart-plan.js +299 -325
  43. package/dist/canvas/headless/break-apart-style.js +75 -70
  44. package/dist/canvas/headless/break-apart.js +188 -181
  45. package/dist/canvas/headless/canvas-cleanup.js +78 -58
  46. package/dist/canvas/headless/edge-anchors.js +115 -116
  47. package/dist/canvas/headless/edge-memo.js +24 -22
  48. package/dist/canvas/headless/edge-serialization.js +17 -27
  49. package/dist/canvas/headless/figure-geometry.js +232 -264
  50. package/dist/canvas/headless/fit-viewport.js +84 -67
  51. package/dist/canvas/headless/grid.js +88 -86
  52. package/dist/canvas/headless/icon-node.js +44 -36
  53. package/dist/canvas/headless/index.js +1 -0
  54. package/dist/canvas/headless/linkify.js +17 -19
  55. package/dist/canvas/headless/pen-marks.js +101 -95
  56. package/dist/canvas/headless/sketch.js +231 -209
  57. package/dist/canvas/headless/snap-guides.js +99 -83
  58. package/dist/canvas/headless/svg-figure.js +133 -117
  59. package/dist/canvas/headless/table-layout.js +146 -107
  60. package/dist/canvas/index.js +2 -0
  61. package/dist/canvas/lod/lod-mode.svelte.js +20 -18
  62. package/dist/canvas/lod/render-all.svelte.js +36 -39
  63. package/dist/canvas/ops/arrange.js +78 -71
  64. package/dist/canvas/ops/ops.js +260 -255
  65. package/dist/canvas/pen/colors.js +37 -35
  66. package/dist/canvas/pen/eraser.js +28 -27
  67. package/dist/canvas/pen/geometry.js +120 -130
  68. package/dist/canvas/pen/index.js +1 -0
  69. package/dist/canvas/pen/ops.js +11 -11
  70. package/dist/canvas/pen/pen-session.js +150 -159
  71. package/dist/canvas/pen/plugin.js +107 -104
  72. package/dist/canvas/pen/shapes.js +73 -68
  73. package/dist/canvas/pen/stroke-path.js +32 -30
  74. package/dist/canvas/pen/types.js +21 -21
  75. package/dist/canvas/pen/ui.js +10 -8
  76. package/dist/canvas/plugin.js +46 -46
  77. package/dist/canvas/screen/glyphs.js +12 -11
  78. package/dist/canvas/screen/labels.js +22 -22
  79. package/dist/canvas/screen/parts.js +98 -111
  80. package/dist/canvas/screen/plugin.js +47 -56
  81. package/dist/canvas/screen/types.js +30 -35
  82. package/dist/canvas/screen/ui.js +7 -7
  83. package/dist/canvas/test-utils.js +14 -14
  84. package/dist/canvas/types.js +1 -0
  85. package/dist/canvas/ui/forward-wheel.js +24 -25
  86. package/dist/canvas/ui/icons.js +64 -131
  87. package/dist/canvas/ui/place-panel.js +58 -49
  88. package/dist/canvas/ui/shortcut-tip.js +14 -12
  89. package/dist/canvas/ui/tools.js +300 -385
  90. package/dist/charts/dashboard/chart.js +4 -0
  91. package/dist/charts/dashboard/data-table.js +11 -0
  92. package/dist/charts/dashboard/windows.js +8 -3
  93. package/dist/charts/edge-fade.js +10 -5
  94. package/dist/charts/hover-band.js +16 -11
  95. package/dist/charts/index.js +0 -1
  96. package/dist/charts/motion.js +11 -7
  97. package/dist/charts/primitives/chart-utils.js +12 -12
  98. package/dist/components/copy/clipboard.js +32 -32
  99. package/dist/components/copy/copied.svelte.js +31 -23
  100. package/dist/components/data-table/data-table.js +39 -39
  101. package/dist/components/docs/index.js +4 -0
  102. package/dist/components/docs/scroll-spy.svelte.js +73 -68
  103. package/dist/components/list-page/filter-section.js +4 -1
  104. package/dist/components/list-page/index.js +2 -0
  105. package/dist/components/list-page/list-table.js +7 -2
  106. package/dist/components/list-page/queue-rail.js +14 -0
  107. package/dist/components/list-page/section-host.js +4 -2
  108. package/dist/components/list-page/step.js +4 -3
  109. package/dist/components/list-page/types.js +1 -0
  110. package/dist/components/picker/trigger.js +48 -30
  111. package/dist/components/picker/types.js +1 -0
  112. package/dist/components/segmented/collapse.js +38 -27
  113. package/dist/components/segmented/index.js +8 -0
  114. package/dist/components/segmented/segmented.js +67 -42
  115. package/dist/components/star-rating-input/index.js +3 -1
  116. package/dist/components/table-view/types.js +1 -0
  117. package/dist/components/tags-input/tags.js +81 -69
  118. package/dist/components/theme/index.js +4 -0
  119. package/dist/components/toolbar/index.js +4 -0
  120. package/dist/components/toolbar/toolbar-trigger.js +14 -1
  121. package/dist/editor/anchors.js +17 -13
  122. package/dist/editor/block-id.js +52 -51
  123. package/dist/editor/block-ops.js +108 -80
  124. package/dist/editor/doc-blocks.js +155 -166
  125. package/dist/editor/extensions.js +139 -180
  126. package/dist/editor/index.js +4 -0
  127. package/dist/editor/markdown-test-helpers.js +4 -2
  128. package/dist/editor/markdown.js +420 -408
  129. package/dist/editor/serializer.js +204 -200
  130. package/dist/editor/slash-items.js +89 -101
  131. package/dist/editor/youtube-url.js +25 -20
  132. package/dist/effects/animated-beam/path-calculator.svelte.js +43 -38
  133. package/dist/effects/animated-beam/types.js +1 -0
  134. package/dist/effects/border-beam/css.js +92 -78
  135. package/dist/effects/border-beam/presets.js +1705 -1720
  136. package/dist/effects/border-beam/register-properties.js +43 -33
  137. package/dist/effects/border-beam/types.js +1 -0
  138. package/dist/effects/image-comparison/compare.js +3 -2
  139. package/dist/effects/image-reveal/types.js +1 -0
  140. package/dist/effects/magnetic/magnetic.js +58 -52
  141. package/dist/effects/reveal/observe.js +50 -40
  142. package/dist/effects/reveal/types.js +1 -0
  143. package/dist/effects/shader-gradient/color.js +36 -29
  144. package/dist/effects/shader-gradient/renderer.js +384 -295
  145. package/dist/effects/shader-gradient/shaders.js +31 -0
  146. package/dist/effects/shader-gradient/types.js +1 -0
  147. package/dist/icons/apple-symbol-exports.js +99 -124
  148. package/dist/icons/sf-symbols.js +357 -461
  149. package/dist/icons/trades.js +43 -28
  150. package/dist/inbox/index.js +1 -0
  151. package/dist/inbox/logic.js +126 -122
  152. package/dist/inbox/types.js +43 -43
  153. package/dist/lib/arrive.js +19 -12
  154. package/dist/lib/icon-types.js +1 -0
  155. package/dist/lib/initials.js +16 -10
  156. package/dist/lib/layout.js +5 -0
  157. package/dist/lib/panel.js +13 -0
  158. package/dist/lib/result.js +22 -22
  159. package/dist/lib/sliding-pill.js +59 -44
  160. package/dist/lib/theme-transition.js +43 -43
  161. package/dist/lib/tone.js +31 -24
  162. package/dist/lib/utils.js +3 -1
  163. package/dist/onboarding/define-tours.js +24 -23
  164. package/dist/onboarding/onboarding.svelte.js +16 -12
  165. package/dist/onboarding/pick.js +10 -6
  166. package/dist/onboarding/stores.js +40 -41
  167. package/dist/onboarding/wait.js +24 -23
  168. package/dist/panel/side-panel-types.js +1 -0
  169. package/dist/record/demo-data.js +459 -486
  170. package/dist/record/fields.js +129 -131
  171. package/dist/record/format.js +70 -82
  172. package/dist/record/gutter.js +1 -0
  173. package/dist/record/kinds.js +95 -105
  174. package/dist/record/logic.js +238 -219
  175. package/dist/record/types.js +49 -48
  176. package/dist/shadcn/accordion/index.js +3 -1
  177. package/dist/shadcn/alert/index.js +3 -1
  178. package/dist/shadcn/alert-dialog/index.js +3 -1
  179. package/dist/shadcn/attachment/index.js +3 -1
  180. package/dist/shadcn/avatar/index.js +3 -1
  181. package/dist/shadcn/breadcrumb/index.js +3 -1
  182. package/dist/shadcn/bubble/index.js +3 -1
  183. package/dist/shadcn/button/index.js +3 -1
  184. package/dist/shadcn/calendar/index.js +3 -1
  185. package/dist/shadcn/card/index.js +3 -1
  186. package/dist/shadcn/checkbox/index.js +3 -1
  187. package/dist/shadcn/code-block/index.js +3 -1
  188. package/dist/shadcn/collapsible/index.js +3 -1
  189. package/dist/shadcn/command/index.js +4 -1
  190. package/dist/shadcn/context-menu/index.js +3 -1
  191. package/dist/shadcn/dialog/index.js +3 -1
  192. package/dist/shadcn/dropdown-menu/menu-item.js +40 -20
  193. package/dist/shadcn/empty/index.js +3 -1
  194. package/dist/shadcn/field/index.js +3 -1
  195. package/dist/shadcn/hooks/highlighter.js +77 -75
  196. package/dist/shadcn/hooks/is-mobile.svelte.js +3 -3
  197. package/dist/shadcn/hooks/use-highlighted-code.svelte.js +62 -62
  198. package/dist/shadcn/input/index.js +3 -1
  199. package/dist/shadcn/kbd/index.js +3 -1
  200. package/dist/shadcn/label/index.js +3 -1
  201. package/dist/shadcn/marker/index.js +3 -1
  202. package/dist/shadcn/message/index.js +3 -1
  203. package/dist/shadcn/message-scroller/index.js +3 -1
  204. package/dist/shadcn/popover/index.js +3 -1
  205. package/dist/shadcn/progress/index.js +3 -1
  206. package/dist/shadcn/progress/tone.js +8 -7
  207. package/dist/shadcn/radio-group/index.js +3 -1
  208. package/dist/shadcn/scroll-area/index.js +3 -1
  209. package/dist/shadcn/select/index.js +3 -1
  210. package/dist/shadcn/separator/index.js +3 -1
  211. package/dist/shadcn/sheet/index.js +3 -1
  212. package/dist/shadcn/sidebar/context.svelte.js +42 -28
  213. package/dist/shadcn/sidebar/index.js +3 -1
  214. package/dist/shadcn/skeleton/index.js +3 -1
  215. package/dist/shadcn/slider/index.js +3 -1
  216. package/dist/shadcn/switch/index.js +3 -1
  217. package/dist/shadcn/table/index.js +3 -1
  218. package/dist/shadcn/tabs/index.js +3 -1
  219. package/dist/shadcn/textarea/index.js +3 -1
  220. package/dist/shadcn/toggle/index.js +3 -1
  221. package/dist/shadcn/toggle-group/index.js +3 -1
  222. package/dist/shadcn/tooltip/context.js +7 -2
  223. package/dist/shadcn/tooltip/index.js +3 -1
  224. package/dist/shadcn/trace/index.js +3 -1
  225. package/dist/slides/bridges.js +65 -59
  226. package/dist/slides/deck-controller.svelte.js +210 -207
  227. package/dist/slides/export/capture.js +66 -71
  228. package/dist/slides/export/ffmpeg.js +76 -72
  229. package/dist/slides/export/index.js +3 -0
  230. package/dist/slides/export/launch.js +9 -6
  231. package/dist/slides/export/pdf.js +60 -54
  232. package/dist/slides/export/types.js +1 -0
  233. package/dist/slides/labels.js +24 -24
  234. package/dist/slides/layout.js +39 -41
  235. package/dist/slides/model.js +9 -7
  236. package/dist/slides/navigation.js +78 -73
  237. package/dist/slides/types.js +2 -2
  238. package/dist/status/define-statuses.js +18 -14
  239. package/dist/status/status-types.js +1 -0
  240. package/dist/test-setup.js +24 -20
  241. package/dist/thread/logic.js +73 -77
  242. package/dist/thread/types.js +21 -21
  243. package/dist/work/board-order.js +94 -90
  244. package/dist/work/dates.js +16 -9
  245. package/dist/work/detail/detail-types.js +18 -18
  246. package/dist/work/docs/edited.js +32 -29
  247. package/dist/work/docs/index.js +4 -0
  248. package/dist/work/docs/labels.js +34 -34
  249. package/dist/work/docs/menu.js +113 -106
  250. package/dist/work/docs/outline.js +39 -29
  251. package/dist/work/gantt/logic.js +211 -235
  252. package/dist/work/inbox/inbox-logic.js +71 -75
  253. package/dist/work/inbox/inbox-statuses.js +75 -72
  254. package/dist/work/inbox/inbox-types.js +1 -0
  255. package/dist/work/kanban-drag.js +27 -22
  256. package/dist/work/kanban-types.js +1 -0
  257. package/dist/work/presets/sales.js +36 -32
  258. package/dist/work/reorder.js +9 -8
  259. package/dist/work/task-types.js +1 -0
  260. package/dist/work/time/duration.js +51 -46
  261. package/dist/work/time/time-types.js +23 -23
  262. package/dist/work/use-kanban-dnd.svelte.js +126 -125
  263. package/package.json +3 -1
  264. package/src/components/list-page/master-detail.svelte +3 -1
  265. package/src/components/list-page/rail-row.svelte +4 -4
  266. package/src/components/tags-input/tags-input.svelte +2 -0
  267. package/src/editor/markdown.ts +30 -35
@@ -1,148 +1,146 @@
1
+ // Turns a module's field declarations and a record's values into rows to draw.
1
2
  import { formatDay, formatMoney } from "./format.js";
3
+ /** A choice or a multi: the types that carry a list of options. */
2
4
  function hasOptions(field) {
3
- return field.type === "choice" || field.type === "multi";
5
+ return field.type === "choice" || field.type === "multi";
4
6
  }
5
7
  function isEmpty(value) {
6
- return value == null || value === "" || Array.isArray(value) && value.length === 0;
8
+ return value == null || value === "" || Array.isArray(value) && value.length === 0;
7
9
  }
10
+ /** A field shows unless its `showWhen` names a field that doesn't hold the wanted value. */
8
11
  export function isFieldVisible(field, values) {
9
- const rule = field.showWhen;
10
- if (!rule) return true;
11
- const current = values[rule.key];
12
- if (rule.equals === undefined) return !isEmpty(current);
13
- const wanted = Array.isArray(rule.equals) ? rule.equals : [
14
- rule.equals
15
- ];
16
- const held = Array.isArray(current) ? current : current == null ? [] : [
17
- String(current)
18
- ];
19
- return held.some((v)=>wanted.includes(v));
12
+ const rule = field.showWhen;
13
+ if (!rule) return true;
14
+ const current = values[rule.key];
15
+ if (rule.equals === undefined) return !isEmpty(current);
16
+ const wanted = Array.isArray(rule.equals) ? rule.equals : [rule.equals];
17
+ const held = Array.isArray(current) ? current : current == null ? [] : [String(current)];
18
+ return held.some((v) => wanted.includes(v));
20
19
  }
20
+ /**
21
+ * The options a picker offers: an archived one is left out unless it is what the
22
+ * record already holds, so an old value still reads and is never chosen again.
23
+ */
21
24
  export function pickableOptions(field, value) {
22
- const held = Array.isArray(value) ? value : value == null ? [] : [
23
- String(value)
24
- ];
25
- return (hasOptions(field) ? field.options : []).filter((o)=>!o.archived || held.includes(o.key));
25
+ const held = Array.isArray(value) ? value : value == null ? [] : [String(value)];
26
+ return (hasOptions(field) ? field.options : []).filter((o) => !o.archived || held.includes(o.key));
26
27
  }
28
+ /**
29
+ * Visible fields bucketed by section in the declared order. A section with no
30
+ * field is not drawn. Fields with no or an unknown section go last under no heading.
31
+ */
27
32
  export function groupFields(fields, sections, values) {
28
- const visible = fields.filter((f)=>isFieldVisible(f, values));
29
- const known = new Set(sections.map((s)=>s.key));
30
- const groups = sections.map((s)=>({
31
- key: s.key,
32
- label: s.label,
33
- fields: visible.filter((f)=>f.section === s.key)
34
- })).filter((g)=>g.fields.length > 0);
35
- const loose = visible.filter((f)=>!f.section || !known.has(f.section));
36
- if (loose.length > 0) groups.push({
37
- key: "",
38
- label: null,
39
- fields: loose
40
- });
41
- return groups;
33
+ const visible = fields.filter((f) => isFieldVisible(f, values));
34
+ const known = new Set(sections.map((s) => s.key));
35
+ const groups = sections.map((s) => ({
36
+ key: s.key,
37
+ label: s.label,
38
+ fields: visible.filter((f) => f.section === s.key)
39
+ })).filter((g) => g.fields.length > 0);
40
+ const loose = visible.filter((f) => !f.section || !known.has(f.section));
41
+ if (loose.length > 0) groups.push({
42
+ key: "",
43
+ label: null,
44
+ fields: loose
45
+ });
46
+ return groups;
42
47
  }
48
+ /** What the editor hands back, as the type the field declares. Empty text is null. */
43
49
  export function parseSaved(field, raw) {
44
- if (Array.isArray(raw)) return raw;
45
- const text = raw.trim();
46
- if (text === "") return null;
47
- if (field.type === "money") {
48
- const n = Number(text.replace(",", "."));
49
- return Number.isFinite(n) ? n : null;
50
- }
51
- return text;
50
+ if (Array.isArray(raw)) return raw;
51
+ const text = raw.trim();
52
+ if (text === "") return null;
53
+ if (field.type === "money") {
54
+ const n = Number(text.replace(",", "."));
55
+ return Number.isFinite(n) ? n : null;
56
+ }
57
+ return text;
52
58
  }
53
59
  const INPUT = {
54
- text: "text",
55
- longtext: "longtext",
56
- choice: "select",
57
- multi: "multi",
58
- money: "number",
59
- date: "date",
60
- url: "url",
61
- email: "email",
62
- timezone: "text",
63
- tags: "tags"
60
+ text: "text",
61
+ longtext: "longtext",
62
+ choice: "select",
63
+ multi: "multi",
64
+ money: "number",
65
+ date: "date",
66
+ url: "url",
67
+ email: "email",
68
+ timezone: "text",
69
+ tags: "tags"
64
70
  };
71
+ /** One field and its value as an `InfoRow`. */
65
72
  export function fieldRow(field, value, ctx = {}) {
66
- const row = {
67
- id: field.key,
68
- label: field.label,
69
- input: INPUT[field.type],
70
- placeholder: field.placeholder,
71
- required: field.required
72
- };
73
- const optionOf = (key)=>hasOptions(field) ? field.options.find((o)=>o.key === key) : undefined;
74
- if (!isEmpty(value)) {
75
- switch(field.type){
76
- case "choice":
77
- {
78
- const option = optionOf(String(value));
79
- row.value = option?.label ?? String(value);
80
- row.tone = option?.tone;
81
- break;
82
- }
83
- case "multi":
84
- {
85
- const keys = Array.isArray(value) ? value : [
86
- String(value)
87
- ];
88
- row.chips = keys.map((k)=>({
89
- label: optionOf(k)?.label ?? k,
90
- tone: optionOf(k)?.tone
91
- }));
92
- break;
93
- }
94
- case "tags":
95
- {
96
- const labels = Array.isArray(value) ? value : [
97
- String(value)
98
- ];
99
- row.chips = labels.map((label)=>({
100
- label
101
- }));
102
- break;
103
- }
104
- case "money":
105
- row.value = formatMoney(Number(value), field.currency ?? ctx.currency, ctx.locale);
106
- break;
107
- case "date":
108
- row.value = formatDay(String(value), ctx.locale);
109
- break;
110
- case "url":
111
- row.value = String(value).replace(/^https?:\/\/(www\.)?/, "").replace(/\/$/, "");
112
- row.href = String(value);
113
- break;
114
- case "email":
115
- row.value = String(value);
116
- row.href = `mailto:${value}`;
117
- break;
118
- case "text":
119
- case "longtext":
120
- case "timezone":
121
- row.value = String(value);
122
- break;
123
- default:
124
- return field;
125
- }
126
- }
127
- if (field.type === "choice" || field.type === "multi") {
128
- row.options = pickableOptions(field, value).map((o)=>({
129
- value: o.key,
130
- label: o.label,
131
- tone: o.tone
132
- }));
133
- row.editValue = Array.isArray(value) ? value : value == null ? field.type === "multi" ? [] : "" : String(value);
134
- } else if (!isEmpty(value)) {
135
- row.editValue = String(value);
136
- }
137
- if (field.type === "tags") {
138
- row.tags = {
139
- options: ctx.tagOptions?.[field.key] ?? (field.options === "existing-values" ? [] : field.options),
140
- allowCreate: field.allowCreate,
141
- max: field.max
142
- };
143
- row.editValue = Array.isArray(value) ? value : [];
144
- }
145
- const onSave = ctx.onSave;
146
- if (onSave) row.onSave = (raw)=>onSave(field.key, parseSaved(field, raw));
147
- return row;
73
+ const row = {
74
+ id: field.key,
75
+ label: field.label,
76
+ input: INPUT[field.type],
77
+ placeholder: field.placeholder,
78
+ required: field.required
79
+ };
80
+ const optionOf = (key) => hasOptions(field) ? field.options.find((o) => o.key === key) : undefined;
81
+ if (!isEmpty(value)) {
82
+ switch (field.type) {
83
+ case "choice": {
84
+ const option = optionOf(String(value));
85
+ row.value = option?.label ?? String(value);
86
+ row.tone = option?.tone;
87
+ break;
88
+ }
89
+ case "multi": {
90
+ const keys = Array.isArray(value) ? value : [String(value)];
91
+ row.chips = keys.map((k) => ({
92
+ label: optionOf(k)?.label ?? k,
93
+ tone: optionOf(k)?.tone
94
+ }));
95
+ break;
96
+ }
97
+ case "tags": {
98
+ const labels = Array.isArray(value) ? value : [String(value)];
99
+ row.chips = labels.map((label) => ({ label }));
100
+ break;
101
+ }
102
+ case "money":
103
+ row.value = formatMoney(Number(value), field.currency ?? ctx.currency, ctx.locale);
104
+ break;
105
+ case "date":
106
+ row.value = formatDay(String(value), ctx.locale);
107
+ break;
108
+ case "url":
109
+ row.value = String(value).replace(/^https?:\/\/(www\.)?/, "").replace(/\/$/, "");
110
+ row.href = String(value);
111
+ break;
112
+ case "email":
113
+ row.value = String(value);
114
+ row.href = `mailto:${value}`;
115
+ break;
116
+ case "text":
117
+ case "longtext":
118
+ case "timezone":
119
+ row.value = String(value);
120
+ break;
121
+ default: return field;
122
+ }
123
+ }
124
+ if (field.type === "choice" || field.type === "multi") {
125
+ row.options = pickableOptions(field, value).map((o) => ({
126
+ value: o.key,
127
+ label: o.label,
128
+ tone: o.tone
129
+ }));
130
+ row.editValue = Array.isArray(value) ? value : value == null ? field.type === "multi" ? [] : "" : String(value);
131
+ } else if (!isEmpty(value)) {
132
+ row.editValue = String(value);
133
+ }
134
+ if (field.type === "tags") {
135
+ row.tags = {
136
+ // "existing-values" is the host's to fill: only it can see the other records.
137
+ options: ctx.tagOptions?.[field.key] ?? (field.options === "existing-values" ? [] : field.options),
138
+ allowCreate: field.allowCreate,
139
+ max: field.max
140
+ };
141
+ row.editValue = Array.isArray(value) ? value : [];
142
+ }
143
+ const onSave = ctx.onSave;
144
+ if (onSave) row.onSave = (raw) => onSave(field.key, parseSaved(field, raw));
145
+ return row;
148
146
  }
@@ -1,97 +1,85 @@
1
+ // Display formatting through Intl, so a locale string is all a host passes.
2
+ // A date-only value ("2026-10-26") is a calendar day, read in UTC so no zone shifts it.
1
3
  const DAY_ONLY = /^\d{4}-\d{2}-\d{2}$/;
2
4
  function parse(iso) {
3
- return DAY_ONLY.test(iso) ? {
4
- date: new Date(`${iso}T00:00:00Z`),
5
- timeZone: "UTC"
6
- } : {
7
- date: new Date(iso)
8
- };
5
+ return DAY_ONLY.test(iso) ? {
6
+ date: new Date(`${iso}T00:00:00Z`),
7
+ timeZone: "UTC"
8
+ } : { date: new Date(iso) };
9
9
  }
10
+ /** "Mon 26 Oct". */
10
11
  export function formatDay(iso, locale = "en-GB") {
11
- const { date, timeZone } = parse(iso);
12
- return new Intl.DateTimeFormat(locale, {
13
- weekday: "short",
14
- day: "numeric",
15
- month: "short",
16
- timeZone
17
- }).format(date);
12
+ const { date, timeZone } = parse(iso);
13
+ return new Intl.DateTimeFormat(locale, {
14
+ weekday: "short",
15
+ day: "numeric",
16
+ month: "short",
17
+ timeZone
18
+ }).format(date);
18
19
  }
20
+ /** "Sat 24 to Sun 25 Oct" in one month, two full days across months, one day when the ends match. */
19
21
  export function formatRange(start, end, locale = "en-GB") {
20
- if (!end || end.slice(0, 10) === start.slice(0, 10)) return formatDay(start, locale);
21
- const a = parse(start);
22
- const b = parse(end);
23
- const sameMonth = a.date.getUTCFullYear() === b.date.getUTCFullYear() && a.date.getUTCMonth() === b.date.getUTCMonth();
24
- if (!sameMonth) return `${formatDay(start, locale)} to ${formatDay(end, locale)}`;
25
- const part = (d)=>new Intl.DateTimeFormat(locale, {
26
- weekday: "short",
27
- day: "numeric",
28
- timeZone: "UTC"
29
- }).format(d);
30
- const month = new Intl.DateTimeFormat(locale, {
31
- month: "short",
32
- timeZone: "UTC"
33
- }).format(b.date);
34
- return `${part(a.date)} to ${part(b.date)} ${month}`;
22
+ if (!end || end.slice(0, 10) === start.slice(0, 10)) return formatDay(start, locale);
23
+ const a = parse(start);
24
+ const b = parse(end);
25
+ const sameMonth = a.date.getUTCFullYear() === b.date.getUTCFullYear() && a.date.getUTCMonth() === b.date.getUTCMonth();
26
+ if (!sameMonth) return `${formatDay(start, locale)} to ${formatDay(end, locale)}`;
27
+ const part = (d) => new Intl.DateTimeFormat(locale, {
28
+ weekday: "short",
29
+ day: "numeric",
30
+ timeZone: "UTC"
31
+ }).format(d);
32
+ const month = new Intl.DateTimeFormat(locale, {
33
+ month: "short",
34
+ timeZone: "UTC"
35
+ }).format(b.date);
36
+ return `${part(a.date)} to ${part(b.date)} ${month}`;
35
37
  }
38
+ /** "Oct 21" without a weekday, for a timeline day header. */
36
39
  export function formatDayHeader(day, locale = "en-GB") {
37
- return new Intl.DateTimeFormat(locale, {
38
- weekday: "short",
39
- day: "numeric",
40
- month: "short",
41
- year: "numeric",
42
- timeZone: "UTC"
43
- }).format(new Date(`${day}T00:00:00Z`));
40
+ return new Intl.DateTimeFormat(locale, {
41
+ weekday: "short",
42
+ day: "numeric",
43
+ month: "short",
44
+ year: "numeric",
45
+ timeZone: "UTC"
46
+ }).format(new Date(`${day}T00:00:00Z`));
44
47
  }
48
+ /** "14:30" in the viewer's zone, or the given one. */
45
49
  export function formatTime(iso, locale = "en-GB", timeZone) {
46
- return new Intl.DateTimeFormat(locale, {
47
- hour: "2-digit",
48
- minute: "2-digit",
49
- timeZone
50
- }).format(new Date(iso));
50
+ return new Intl.DateTimeFormat(locale, {
51
+ hour: "2-digit",
52
+ minute: "2-digit",
53
+ timeZone
54
+ }).format(new Date(iso));
51
55
  }
56
+ /** "3 days ago", "in 2 weeks", "now". */
52
57
  export function formatAge(iso, now = Date.now(), locale = "en-GB") {
53
- const seconds = Math.round((new Date(iso).getTime() - new Date(now).getTime()) / 1000);
54
- const abs = Math.abs(seconds);
55
- const rtf = new Intl.RelativeTimeFormat(locale, {
56
- numeric: "auto",
57
- style: "long"
58
- });
59
- const units = [
60
- [
61
- 31_536_000,
62
- "year"
63
- ],
64
- [
65
- 2_592_000,
66
- "month"
67
- ],
68
- [
69
- 604_800,
70
- "week"
71
- ],
72
- [
73
- 86_400,
74
- "day"
75
- ],
76
- [
77
- 3_600,
78
- "hour"
79
- ],
80
- [
81
- 60,
82
- "minute"
83
- ]
84
- ];
85
- for (const [size, unit] of units){
86
- if (abs >= size) return rtf.format(Math.round(seconds / size), unit);
87
- }
88
- return rtf.format(0, "second");
58
+ const seconds = Math.round((new Date(iso).getTime() - new Date(now).getTime()) / 1e3);
59
+ const abs = Math.abs(seconds);
60
+ const rtf = new Intl.RelativeTimeFormat(locale, {
61
+ numeric: "auto",
62
+ style: "long"
63
+ });
64
+ const units = [
65
+ [31536e3, "year"],
66
+ [2592e3, "month"],
67
+ [604800, "week"],
68
+ [86400, "day"],
69
+ [3600, "hour"],
70
+ [60, "minute"]
71
+ ];
72
+ for (const [size, unit] of units) {
73
+ if (abs >= size) return rtf.format(Math.round(seconds / size), unit);
74
+ }
75
+ return rtf.format(0, "second");
89
76
  }
77
+ /** "€12,000". `compact` gives "€12K". */
90
78
  export function formatMoney(amount, currency = "EUR", locale = "en-GB", compact = false) {
91
- return new Intl.NumberFormat(locale, {
92
- style: "currency",
93
- currency,
94
- maximumFractionDigits: compact || Number.isInteger(amount) ? 0 : 2,
95
- notation: compact ? "compact" : "standard"
96
- }).format(amount);
79
+ return new Intl.NumberFormat(locale, {
80
+ style: "currency",
81
+ currency,
82
+ maximumFractionDigits: compact || Number.isInteger(amount) ? 0 : 2,
83
+ notation: compact ? "compact" : "standard"
84
+ }).format(amount);
97
85
  }
@@ -1 +1,2 @@
1
+ /** The record's horizontal padding, shared by the header, banner, tabs and body so they line up. */
1
2
  export const gutterClass = "px-4 @3xl:px-6";
@@ -1,110 +1,100 @@
1
1
  import { CheckCircle, ClipboardText, CurrencyCircleDollar, EnvelopeSimple, File, Flag, Gavel, NavigationArrow, NotePencil, Phone, Receipt, Signpost, UsersThree } from "phosphor-svelte";
2
+ /** The kit's event kinds, drawn in plain English. A module adds its own beside them. */
2
3
  export const DEFAULT_RECORD_KINDS = [
3
- {
4
- key: "call",
5
- label: "Call",
6
- icon: Phone
7
- },
8
- {
9
- key: "meeting",
10
- label: "Meeting",
11
- icon: UsersThree
12
- },
13
- {
14
- key: "email",
15
- label: "Email",
16
- icon: EnvelopeSimple
17
- },
18
- {
19
- key: "form",
20
- label: "Form",
21
- icon: ClipboardText
22
- },
23
- {
24
- key: "note",
25
- label: "Note",
26
- icon: NotePencil
27
- },
28
- {
29
- key: "status",
30
- label: "Where we are",
31
- icon: NavigationArrow
32
- },
33
- {
34
- key: "stage",
35
- label: "Stage",
36
- icon: Signpost
37
- },
38
- {
39
- key: "task.done",
40
- label: "Task done",
41
- icon: CheckCircle,
42
- tone: "success"
43
- },
44
- {
45
- key: "milestone.done",
46
- label: "Step done",
47
- icon: Flag,
48
- tone: "success"
49
- },
50
- {
51
- key: "invoice.sent",
52
- label: "Invoice",
53
- icon: Receipt
54
- },
55
- {
56
- key: "payment",
57
- label: "Payment",
58
- icon: CurrencyCircleDollar,
59
- tone: "success"
60
- },
61
- {
62
- key: "decision",
63
- label: "Decision",
64
- icon: Gavel
65
- },
66
- {
67
- key: "file",
68
- label: "File",
69
- icon: File
70
- }
4
+ {
5
+ key: "call",
6
+ label: "Call",
7
+ icon: Phone
8
+ },
9
+ {
10
+ key: "meeting",
11
+ label: "Meeting",
12
+ icon: UsersThree
13
+ },
14
+ {
15
+ key: "email",
16
+ label: "Email",
17
+ icon: EnvelopeSimple
18
+ },
19
+ {
20
+ key: "form",
21
+ label: "Form",
22
+ icon: ClipboardText
23
+ },
24
+ {
25
+ key: "note",
26
+ label: "Note",
27
+ icon: NotePencil
28
+ },
29
+ {
30
+ key: "status",
31
+ label: "Where we are",
32
+ icon: NavigationArrow
33
+ },
34
+ {
35
+ key: "stage",
36
+ label: "Stage",
37
+ icon: Signpost
38
+ },
39
+ {
40
+ key: "task.done",
41
+ label: "Task done",
42
+ icon: CheckCircle,
43
+ tone: "success"
44
+ },
45
+ {
46
+ key: "milestone.done",
47
+ label: "Step done",
48
+ icon: Flag,
49
+ tone: "success"
50
+ },
51
+ {
52
+ key: "invoice.sent",
53
+ label: "Invoice",
54
+ icon: Receipt
55
+ },
56
+ {
57
+ key: "payment",
58
+ label: "Payment",
59
+ icon: CurrencyCircleDollar,
60
+ tone: "success"
61
+ },
62
+ {
63
+ key: "decision",
64
+ label: "Decision",
65
+ icon: Gavel
66
+ },
67
+ {
68
+ key: "file",
69
+ label: "File",
70
+ icon: File
71
+ }
71
72
  ];
73
+ /** The chips over the timeline. Kinds a record has none of are not drawn. */
72
74
  export const DEFAULT_RECORD_FILTERS = [
73
- {
74
- key: "calls",
75
- label: "Calls",
76
- kinds: [
77
- "call",
78
- "meeting"
79
- ]
80
- },
81
- {
82
- key: "notes",
83
- label: "Notes",
84
- kinds: [
85
- "note"
86
- ]
87
- },
88
- {
89
- key: "money",
90
- label: "Money",
91
- kinds: [
92
- "invoice.sent",
93
- "payment"
94
- ]
95
- },
96
- {
97
- key: "stage",
98
- label: "Stage",
99
- kinds: [
100
- "stage"
101
- ]
102
- },
103
- {
104
- key: "status",
105
- label: "Status",
106
- kinds: [
107
- "status"
108
- ]
109
- }
75
+ {
76
+ key: "calls",
77
+ label: "Calls",
78
+ kinds: ["call", "meeting"]
79
+ },
80
+ {
81
+ key: "notes",
82
+ label: "Notes",
83
+ kinds: ["note"]
84
+ },
85
+ {
86
+ key: "money",
87
+ label: "Money",
88
+ kinds: ["invoice.sent", "payment"]
89
+ },
90
+ {
91
+ key: "stage",
92
+ label: "Stage",
93
+ kinds: ["stage"]
94
+ },
95
+ {
96
+ key: "status",
97
+ label: "Status",
98
+ kinds: ["status"]
99
+ }
110
100
  ];