@chelone/core 0.6.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/dist/assistant/assistant-session.d.ts +6 -0
  2. package/dist/assistant/assistant-session.js +204 -181
  3. package/dist/assistant/echo-transport.js +26 -29
  4. package/dist/assistant/http-transport.d.ts +10 -0
  5. package/dist/assistant/http-transport.js +98 -0
  6. package/dist/assistant/types.d.ts +1 -7
  7. package/dist/auth/access-control.d.ts +13 -5
  8. package/dist/auth/access-control.js +25 -21
  9. package/dist/auth/auth-options.d.ts +23 -17
  10. package/dist/auth/auth-options.js +82 -48
  11. package/dist/auth/permission-matrix.d.ts +12 -0
  12. package/dist/auth/permission-matrix.js +67 -0
  13. package/dist/auth/types.d.ts +14 -4
  14. package/dist/block-editor/block-document.d.ts +64 -0
  15. package/dist/block-editor/block-document.js +147 -0
  16. package/dist/block-editor/data-blocks.d.ts +63 -0
  17. package/dist/block-editor/data-blocks.js +117 -0
  18. package/dist/chart/chart-data.js +46 -44
  19. package/dist/command-menu/hotkey.js +58 -47
  20. package/dist/command-menu/recents.d.ts +4 -0
  21. package/dist/command-menu/recents.js +12 -11
  22. package/dist/command-menu/registry.d.ts +9 -5
  23. package/dist/command-menu/registry.js +36 -46
  24. package/dist/controller/calendar-dates.d.ts +12 -0
  25. package/dist/controller/calendar-dates.js +94 -113
  26. package/dist/controller/calendar-events.js +93 -88
  27. package/dist/controller/client-list-controller.d.ts +2 -2
  28. package/dist/controller/client-list-controller.js +251 -254
  29. package/dist/controller/data-table-columns.d.ts +2 -0
  30. package/dist/controller/data-table-columns.js +50 -56
  31. package/dist/controller/deep-equal.d.ts +1 -0
  32. package/dist/controller/deep-equal.js +14 -0
  33. package/dist/controller/filter-toggle.js +36 -44
  34. package/dist/controller/in-place-editor.d.ts +43 -0
  35. package/dist/controller/in-place-editor.js +43 -0
  36. package/dist/controller/infinite-list-controller.js +219 -189
  37. package/dist/controller/inspector.d.ts +55 -0
  38. package/dist/controller/inspector.js +70 -0
  39. package/dist/controller/job-monitor.d.ts +10 -4
  40. package/dist/controller/job-monitor.js +46 -47
  41. package/dist/controller/kanban-board.js +45 -24
  42. package/dist/controller/list-controller.d.ts +8 -5
  43. package/dist/controller/list-controller.js +447 -400
  44. package/dist/controller/list-params.js +96 -87
  45. package/dist/controller/pivot-grid.d.ts +4 -4
  46. package/dist/controller/pivot-grid.js +89 -95
  47. package/dist/controller/prev-next.d.ts +2 -2
  48. package/dist/controller/prev-next.js +74 -54
  49. package/dist/controller/record-controllers.d.ts +14 -6
  50. package/dist/controller/record-controllers.js +288 -257
  51. package/dist/controller/record-diff.d.ts +21 -0
  52. package/dist/controller/record-diff.js +50 -0
  53. package/dist/controller/record-timeline.d.ts +5 -5
  54. package/dist/controller/record-timeline.js +42 -30
  55. package/dist/controller/record-tree.js +57 -47
  56. package/dist/controller/record-workflow.d.ts +38 -16
  57. package/dist/controller/record-workflow.js +85 -64
  58. package/dist/controller/reference-many-controller.d.ts +2 -0
  59. package/dist/controller/reference-many-controller.js +199 -192
  60. package/dist/controller/reference-one-controller.d.ts +1 -1
  61. package/dist/controller/reference-one-controller.js +63 -56
  62. package/dist/controller/saved-queries.js +104 -91
  63. package/dist/controller/translate-fallback.js +10 -6
  64. package/dist/csv/export.d.ts +4 -3
  65. package/dist/csv/export.js +43 -40
  66. package/dist/csv/import-controller.d.ts +4 -0
  67. package/dist/csv/import-controller.js +189 -169
  68. package/dist/csv/match-headers.js +29 -22
  69. package/dist/csv/parse-with.js +60 -54
  70. package/dist/csv/run-import.js +69 -72
  71. package/dist/csv/validate-rows.d.ts +1 -1
  72. package/dist/csv/validate-rows.js +65 -52
  73. package/dist/data/cache-ops.d.ts +5 -4
  74. package/dist/data/cache-ops.js +208 -188
  75. package/dist/data/combine-data-providers.js +25 -23
  76. package/dist/data/data-provider-log.d.ts +45 -0
  77. package/dist/data/data-provider-log.js +93 -0
  78. package/dist/data/errors.js +14 -10
  79. package/dist/data/fake-provider.d.ts +6 -3
  80. package/dist/data/fake-provider.js +135 -114
  81. package/dist/data/fetch-utils.js +61 -44
  82. package/dist/data/filter-branch.d.ts +5 -0
  83. package/dist/data/filter-branch.js +10 -0
  84. package/dist/data/get-many-aggregate.js +84 -76
  85. package/dist/data/http-error.js +17 -12
  86. package/dist/data/keys.js +50 -36
  87. package/dist/data/mutation-options.d.ts +14 -9
  88. package/dist/data/mutation-options.js +93 -109
  89. package/dist/data/query-options.js +73 -68
  90. package/dist/data/remove-empty.d.ts +3 -0
  91. package/dist/data/remove-empty.js +11 -0
  92. package/dist/data/undoable.d.ts +2 -2
  93. package/dist/data/undoable.js +190 -174
  94. package/dist/data/with-lifecycle-callbacks.d.ts +45 -17
  95. package/dist/data/with-lifecycle-callbacks.js +66 -52
  96. package/dist/environment/application-update-store.js +58 -55
  97. package/dist/environment/offline-store.js +21 -21
  98. package/dist/form/array-binding.d.ts +30 -11
  99. package/dist/form/array-binding.js +184 -166
  100. package/dist/form/choice-ids.d.ts +7 -0
  101. package/dist/form/choice-ids.js +11 -0
  102. package/dist/form/choices-controller.d.ts +22 -2
  103. package/dist/form/choices-controller.js +315 -275
  104. package/dist/form/date-format.d.ts +38 -0
  105. package/dist/form/date-format.js +89 -0
  106. package/dist/form/dirty-values.d.ts +2 -2
  107. package/dist/form/dirty-values.js +22 -11
  108. package/dist/form/duration.d.ts +22 -0
  109. package/dist/form/duration.js +49 -0
  110. package/dist/form/edit-create-form.d.ts +12 -5
  111. package/dist/form/edit-create-form.js +243 -188
  112. package/dist/form/event-value.d.ts +6 -0
  113. package/dist/form/event-value.js +17 -0
  114. package/dist/form/field-error.d.ts +14 -0
  115. package/dist/form/field-error.js +28 -0
  116. package/dist/form/field-name.d.ts +20 -0
  117. package/dist/form/field-name.js +6 -0
  118. package/dist/form/filter-form-values.js +63 -49
  119. package/dist/form/form-controller.d.ts +9 -1
  120. package/dist/form/form-controller.js +165 -160
  121. package/dist/form/form-groups.d.ts +4 -3
  122. package/dist/form/form-groups.js +79 -79
  123. package/dist/form/form-handle.d.ts +175 -0
  124. package/dist/form/form-handle.js +68 -0
  125. package/dist/form/input-binding.d.ts +56 -31
  126. package/dist/form/input-binding.js +171 -118
  127. package/dist/form/input-values.d.ts +140 -0
  128. package/dist/form/input-values.js +206 -0
  129. package/dist/form/json-text.d.ts +11 -0
  130. package/dist/form/json-text.js +62 -37
  131. package/dist/form/save-pipeline.d.ts +6 -8
  132. package/dist/form/save-pipeline.js +91 -82
  133. package/dist/form/set-form-values.d.ts +6 -0
  134. package/dist/form/set-form-values.js +10 -0
  135. package/dist/form/source.js +97 -65
  136. package/dist/form/text-of.d.ts +2 -0
  137. package/dist/form/text-of.js +7 -0
  138. package/dist/form/validators.d.ts +58 -19
  139. package/dist/form/validators.js +97 -71
  140. package/dist/geo/bbox.d.ts +5 -4
  141. package/dist/geo/bbox.js +69 -48
  142. package/dist/geo/bounds.d.ts +14 -3
  143. package/dist/geo/bounds.js +98 -68
  144. package/dist/geo/constraints.js +88 -93
  145. package/dist/geo/coordinates.d.ts +10 -0
  146. package/dist/geo/coordinates.js +44 -23
  147. package/dist/geo/editor.d.ts +13 -13
  148. package/dist/geo/editor.js +176 -153
  149. package/dist/geo/geocoding.d.ts +4 -4
  150. package/dist/geo/geocoding.js +38 -41
  151. package/dist/geo/geojson.d.ts +12 -74
  152. package/dist/geo/geojson.js +52 -40
  153. package/dist/geo/geometry-math.d.ts +4 -4
  154. package/dist/geo/geometry-math.js +100 -93
  155. package/dist/geo/index.d.ts +9 -8
  156. package/dist/geo/map-layers.d.ts +42 -7
  157. package/dist/geo/map-layers.js +88 -62
  158. package/dist/geo/map-style.d.ts +12 -0
  159. package/dist/geo/map-style.js +43 -32
  160. package/dist/geo/messages.js +48 -45
  161. package/dist/geo/nominatim.js +88 -80
  162. package/dist/geo/osm-presets.d.ts +4 -3
  163. package/dist/geo/osm-presets.js +108 -86
  164. package/dist/geo/osm-tags.js +14 -12
  165. package/dist/geo/overpass.d.ts +14 -2
  166. package/dist/geo/overpass.js +83 -90
  167. package/dist/geo/shape-value.d.ts +9 -5
  168. package/dist/geo/shape-value.js +169 -146
  169. package/dist/geo/snap-to-roads.d.ts +3 -3
  170. package/dist/geo/snap-to-roads.js +55 -50
  171. package/dist/geo/throttle.js +36 -39
  172. package/dist/guards.d.ts +44 -0
  173. package/dist/guards.js +77 -0
  174. package/dist/i18n/default-messages.js +34 -29
  175. package/dist/i18n/english-messages.d.ts +172 -0
  176. package/dist/i18n/english-messages.js +359 -189
  177. package/dist/i18n/i18n.d.ts +2 -2
  178. package/dist/i18n/i18n.js +47 -60
  179. package/dist/i18n/inflection.d.ts +34 -0
  180. package/dist/i18n/inflection.js +82 -0
  181. package/dist/i18n/messages.d.ts +10 -2
  182. package/dist/i18n/messages.js +17 -4
  183. package/dist/i18n/missing-keys.d.ts +4 -0
  184. package/dist/i18n/missing-keys.js +70 -62
  185. package/dist/i18n/translatable.d.ts +16 -1
  186. package/dist/i18n/translatable.js +40 -47
  187. package/dist/index.d.ts +87 -39
  188. package/dist/index.js +54 -301
  189. package/dist/json/deep-equal.d.ts +8 -0
  190. package/dist/json/deep-equal.js +24 -0
  191. package/dist/json/strip-json-comments.js +38 -42
  192. package/dist/noop.d.ts +2 -0
  193. package/dist/noop.js +4 -0
  194. package/dist/notification/error-message.d.ts +2 -0
  195. package/dist/notification/error-message.js +10 -0
  196. package/dist/notification/notification-store.d.ts +3 -2
  197. package/dist/notification/notification-store.js +41 -45
  198. package/dist/onboarding-tour/placement.d.ts +2 -0
  199. package/dist/onboarding-tour/placement.js +65 -78
  200. package/dist/onboarding-tour/tour-controller.js +46 -54
  201. package/dist/realtime/add-events-for-mutations.js +75 -81
  202. package/dist/realtime/announce-lock.js +16 -13
  203. package/dist/realtime/broadcast-channel-transport.js +65 -59
  204. package/dist/realtime/copy-members.js +22 -14
  205. package/dist/realtime/in-memory-lock-provider.js +40 -48
  206. package/dist/realtime/live.js +95 -101
  207. package/dist/realtime/lock-options.js +16 -16
  208. package/dist/realtime/lock-session.js +91 -78
  209. package/dist/realtime/memory-transport.js +25 -25
  210. package/dist/realtime/presence.js +120 -122
  211. package/dist/realtime/realtime-data-provider.d.ts +10 -0
  212. package/dist/realtime/realtime-data-provider.js +45 -42
  213. package/dist/realtime/reconnect-backoff.js +17 -0
  214. package/dist/realtime/sse-transport.d.ts +31 -0
  215. package/dist/realtime/sse-transport.js +235 -0
  216. package/dist/realtime/status-store.js +23 -28
  217. package/dist/realtime/topic-hub.js +62 -71
  218. package/dist/realtime/topics.js +10 -12
  219. package/dist/realtime/types.js +10 -12
  220. package/dist/realtime/unread-count.js +21 -21
  221. package/dist/realtime/websocket-transport.d.ts +32 -0
  222. package/dist/realtime/websocket-transport.js +306 -0
  223. package/dist/resource/describe-resource.d.ts +2 -0
  224. package/dist/resource/describe-resource.js +247 -219
  225. package/dist/resource/get-at-path.js +29 -14
  226. package/dist/resource/identifier.d.ts +18 -0
  227. package/dist/resource/identifier.js +27 -0
  228. package/dist/resource/record.d.ts +4 -0
  229. package/dist/resource/record.js +10 -0
  230. package/dist/resource/register.d.ts +7 -0
  231. package/dist/resource/registry-store.d.ts +45 -15
  232. package/dist/resource/registry-store.js +67 -60
  233. package/dist/resource/registry.d.ts +10 -0
  234. package/dist/resource/registry.js +8 -4
  235. package/dist/resource/source-path.d.ts +23 -2
  236. package/dist/rich-text/rich-text-extras.d.ts +134 -0
  237. package/dist/rich-text/rich-text-extras.js +181 -0
  238. package/dist/rich-text/rich-text-image.d.ts +124 -0
  239. package/dist/rich-text/rich-text-image.js +163 -0
  240. package/dist/rich-text/rich-text.d.ts +6 -0
  241. package/dist/rich-text/rich-text.js +125 -88
  242. package/dist/router/location.d.ts +24 -4
  243. package/dist/router/location.js +43 -18
  244. package/dist/router/match.js +47 -39
  245. package/dist/router/paths.js +40 -34
  246. package/dist/router/record-from-location.d.ts +1 -1
  247. package/dist/router/record-from-location.js +25 -17
  248. package/dist/store/adapters.js +51 -55
  249. package/dist/store/micro-store.js +57 -59
  250. package/dist/store/shallow-equal.d.ts +12 -0
  251. package/dist/store/shallow-equal.js +20 -0
  252. package/dist/store/store.js +54 -57
  253. package/dist/ui-defaults.d.ts +15 -0
  254. package/dist/ui-defaults.js +17 -0
  255. package/package.json +13 -11
@@ -0,0 +1,117 @@
1
+ import { z } from "zod";
2
+ // src/block-editor/data-blocks.ts
3
+ var dataBlockNames = [
4
+ "referenceRecord",
5
+ "recordList",
6
+ "chart"
7
+ ];
8
+ var referenceRecordSchema = z.object({
9
+ resource: z.string().default(""),
10
+ id: z.union([z.string(), z.number()]).nullable().default(null),
11
+ fields: z.array(z.string()).optional(),
12
+ layout: z.enum(["card", "inline"]).default("card")
13
+ });
14
+ var recordListSchema = z.object({
15
+ resource: z.string().default(""),
16
+ perPage: z.number().int().min(1).max(50).default(5),
17
+ fields: z.array(z.string()).optional()
18
+ });
19
+ var chartSchema = z.object({
20
+ resource: z.string().default(""),
21
+ type: z.enum([
22
+ "bar",
23
+ "line",
24
+ "pie"
25
+ ]).default("bar"),
26
+ category: z.string().default(""),
27
+ value: z.string().default(""),
28
+ aggregate: z.enum([
29
+ "count",
30
+ "sum",
31
+ "avg"
32
+ ]).default("count"),
33
+ perPage: z.number().int().min(1).max(1e3).default(100)
34
+ });
35
+ var referenceRecordBlockSpec = {
36
+ name: "referenceRecord",
37
+ label: "Reference record",
38
+ labelKey: "chelone.block_editor.referenceRecord.label",
39
+ group: "data",
40
+ keywords: [
41
+ "record",
42
+ "relation",
43
+ "embed",
44
+ "reference"
45
+ ],
46
+ description: "Embed a live record",
47
+ descriptionKey: "chelone.block_editor.referenceRecord.description",
48
+ schema: referenceRecordSchema
49
+ };
50
+ var recordListBlockSpec = {
51
+ name: "recordList",
52
+ label: "Record list",
53
+ labelKey: "chelone.block_editor.recordList.label",
54
+ group: "data",
55
+ keywords: [
56
+ "list",
57
+ "table",
58
+ "records",
59
+ "collection"
60
+ ],
61
+ description: "Embed a live list from a resource",
62
+ descriptionKey: "chelone.block_editor.recordList.description",
63
+ schema: recordListSchema
64
+ };
65
+ var chartBlockSpec = {
66
+ name: "chart",
67
+ label: "Chart",
68
+ labelKey: "chelone.block_editor.chart.label",
69
+ group: "data",
70
+ keywords: [
71
+ "chart",
72
+ "graph",
73
+ "bar",
74
+ "line",
75
+ "pie",
76
+ "analytics"
77
+ ],
78
+ description: "Aggregate a resource into a chart",
79
+ descriptionKey: "chelone.block_editor.chart.description",
80
+ schema: chartSchema
81
+ };
82
+ /** The fields a data block shows: the given ones, else every key but `id`, cut to `limit`. */
83
+ function pickDataBlockFields(record, fields, limit) {
84
+ const picked = fields !== void 0 && fields.length > 0 ? [...fields] : Object.keys(record).filter((key) => key !== "id");
85
+ return limit === void 0 ? picked : picked.slice(0, limit);
86
+ }
87
+ function formatDataBlockValue(value) {
88
+ if (value === null || value === void 0) return "";
89
+ if (typeof value !== "object" && typeof value !== "function") return String(value);
90
+ try {
91
+ return JSON.stringify(value) ?? String(value);
92
+ } catch {
93
+ return String(value);
94
+ }
95
+ }
96
+ /** A record's own value for a key. A key the record does not own reads as absent. */
97
+ var readDataBlockValue = (row, key) => Object.getOwnPropertyDescriptor(row, key)?.value;
98
+ /** Group rows by `String(row[category])` in first-seen order and reduce `value` by the aggregate. */
99
+ function aggregateChartData(rows, { category, value, aggregate }) {
100
+ const groups = /* @__PURE__ */ new Map();
101
+ for (const row of rows) {
102
+ const raw = readDataBlockValue(row, category);
103
+ const name = raw === null || raw === void 0 ? "" : String(raw);
104
+ const group = groups.get(name) ?? {
105
+ count: 0,
106
+ sum: 0
107
+ };
108
+ group.count += 1;
109
+ group.sum += Number(readDataBlockValue(row, value)) || 0;
110
+ groups.set(name, group);
111
+ }
112
+ return [...groups].map(([name, { count, sum }]) => ({
113
+ name,
114
+ value: aggregate === "count" ? count : aggregate === "sum" ? sum : sum / count
115
+ }));
116
+ }
117
+ export { aggregateChartData, chartBlockSpec, dataBlockNames, formatDataBlockValue, pickDataBlockFields, readDataBlockValue, recordListBlockSpec, referenceRecordBlockSpec };
@@ -1,59 +1,61 @@
1
- const DEFAULT_CHART_COLORS = [
2
- "var(--chart-1)",
3
- "var(--chart-2)",
4
- "var(--chart-3)",
5
- "var(--chart-4)",
6
- "var(--chart-5)"
1
+ // src/chart/chart-data.ts
2
+ /** Theme variables the registry ships, in series order. */
3
+ var DEFAULT_CHART_COLORS = [
4
+ "var(--chart-1)",
5
+ "var(--chart-2)",
6
+ "var(--chart-3)",
7
+ "var(--chart-4)",
8
+ "var(--chart-5)"
7
9
  ];
8
- const FALLBACK_COLOR = "var(--chart-1)";
10
+ var FALLBACK_COLOR = "var(--chart-1)";
9
11
  function chartColor(index, colors = DEFAULT_CHART_COLORS) {
10
- const list = colors.length > 0 ? colors : DEFAULT_CHART_COLORS;
11
- const length = list.length;
12
- return list[(index % length + length) % length] ?? FALLBACK_COLOR;
12
+ const list = colors.length > 0 ? colors : DEFAULT_CHART_COLORS;
13
+ const length = list.length;
14
+ return list[(index % length + length) % length] ?? FALLBACK_COLOR;
13
15
  }
14
16
  function toFiniteNumber(value) {
15
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
16
- if (typeof value === "string" && value.trim() !== "") {
17
- const parsed = Number(value);
18
- return Number.isFinite(parsed) ? parsed : null;
19
- }
20
- return null;
17
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
18
+ if (typeof value === "string" && value.trim() !== "") {
19
+ const parsed = Number(value);
20
+ return Number.isFinite(parsed) ? parsed : null;
21
+ }
22
+ return null;
21
23
  }
22
24
  function toCartesianRows(data, xField, yField) {
23
- return data.map((row, index) => ({
24
- index,
25
- x: row[xField],
26
- y: toFiniteNumber(row[yField])
27
- }));
25
+ return data.map((row, index) => ({
26
+ index,
27
+ x: row[xField],
28
+ y: toFiniteNumber(row[yField])
29
+ }));
28
30
  }
29
31
  function hasChartValues(rows) {
30
- return rows.some((row) => row.y !== null);
32
+ return rows.some((row) => row.y !== null);
31
33
  }
32
34
  function toLabel(value) {
33
- return value === void 0 || value === null ? "" : String(value);
35
+ return value === void 0 || value === null ? "" : String(value);
34
36
  }
35
37
  function toDonutSlices(data, labelField, valueField) {
36
- const kept = [];
37
- for (const row of data) {
38
- const value = toFiniteNumber(row[valueField]);
39
- if (value !== null && value > 0) {
40
- kept.push({ label: toLabel(row[labelField]), value });
41
- }
42
- }
43
- const total = kept.reduce((sum, slice) => sum + slice.value, 0);
44
- if (total <= 0) return [];
45
- return kept.map((slice) => ({ ...slice, share: slice.value / total }));
38
+ const kept = [];
39
+ for (const row of data) {
40
+ const value = toFiniteNumber(row[valueField]);
41
+ if (value !== null && value > 0) kept.push({
42
+ label: toLabel(row[labelField]),
43
+ value
44
+ });
45
+ }
46
+ const total = kept.reduce((sum, slice) => sum + slice.value, 0);
47
+ if (total <= 0) return [];
48
+ return kept.map((slice) => ({
49
+ ...slice,
50
+ share: slice.value / total
51
+ }));
46
52
  }
53
+ /** `delta` is a ratio (0.05 is 5.0%). Direction follows the sign of `delta`, not of the rounded text. */
47
54
  function getMetricDelta(delta) {
48
- if (delta === void 0 || !Number.isFinite(delta)) return null;
49
- const direction = delta > 0 ? "up" : delta < 0 ? "down" : "flat";
50
- return { direction, text: `${(delta * 100).toFixed(1)}%` };
55
+ if (delta === void 0 || !Number.isFinite(delta)) return null;
56
+ return {
57
+ direction: delta > 0 ? "up" : delta < 0 ? "down" : "flat",
58
+ text: `${(delta * 100).toFixed(1)}%`
59
+ };
51
60
  }
52
- export {
53
- DEFAULT_CHART_COLORS,
54
- chartColor,
55
- getMetricDelta,
56
- hasChartValues,
57
- toCartesianRows,
58
- toDonutSlices
59
- };
61
+ export { DEFAULT_CHART_COLORS, chartColor, getMetricDelta, hasChartValues, toCartesianRows, toDonutSlices };
@@ -1,60 +1,71 @@
1
+ // src/command-menu/hotkey.ts
1
2
  function parseBinding(binding) {
2
- const lower = binding.toLowerCase();
3
- const plus = lower === "+" || lower.endsWith("++");
4
- const parts = (plus ? lower.slice(0, -1) : lower).split("+");
5
- const key = plus ? "+" : parts.pop() ?? "";
6
- return {
7
- key,
8
- mod: parts.includes("mod"),
9
- shift: parts.includes("shift"),
10
- alt: parts.includes("alt")
11
- };
3
+ const lower = binding.toLowerCase();
4
+ const plus = lower === "+" || lower.endsWith("++");
5
+ const parts = (plus ? lower.slice(0, -1) : lower).split("+");
6
+ return {
7
+ key: plus ? "+" : parts.pop() ?? "",
8
+ mod: parts.includes("mod"),
9
+ shift: parts.includes("shift"),
10
+ alt: parts.includes("alt")
11
+ };
12
12
  }
13
- const punctuationCodes = {
14
- "/": "Slash",
15
- ".": "Period",
16
- ",": "Comma",
17
- ";": "Semicolon",
18
- "'": "Quote",
19
- "[": "BracketLeft",
20
- "]": "BracketRight",
21
- "\\": "Backslash",
22
- "-": "Minus",
23
- "=": "Equal",
24
- "`": "Backquote"
13
+ var punctuationCodes = {
14
+ "/": "Slash",
15
+ ".": "Period",
16
+ ",": "Comma",
17
+ ";": "Semicolon",
18
+ "'": "Quote",
19
+ "[": "BracketLeft",
20
+ "]": "BracketRight",
21
+ "\\": "Backslash",
22
+ "-": "Minus",
23
+ "=": "Equal",
24
+ "`": "Backquote"
25
25
  };
26
26
  function codeFor(key) {
27
- if (/^[a-z]$/.test(key)) return `Key${key.toUpperCase()}`;
28
- if (/^[0-9]$/.test(key)) return `Digit${key}`;
29
- return punctuationCodes[key];
27
+ if (/^[a-z]$/.test(key)) return `Key${key.toUpperCase()}`;
28
+ if (/^[0-9]$/.test(key)) return `Digit${key}`;
29
+ return punctuationCodes[key];
30
30
  }
31
+ /**
32
+ * Option and Shift rewrite `event.key` ("÷" for Option+/, "!" for Shift+1), so
33
+ * a chord with either falls back to the physical key, but only when `key` is
34
+ * not already a letter or digit. On a Dvorak layout the physical K key types
35
+ * "t": that stays a "t", so `mod+k` does not fire on it.
36
+ */
31
37
  function keyMatches(event, parsed, eventKey) {
32
- if (eventKey === parsed.key) return true;
33
- if (!(parsed.alt || parsed.shift) || !event.code) return false;
34
- if (/^[a-z0-9]$/i.test(event.key)) return false;
35
- return event.code === codeFor(parsed.key);
38
+ if (eventKey === parsed.key) return true;
39
+ if (!(parsed.alt || parsed.shift) || !event.code) return false;
40
+ if (/^[a-z0-9]$/i.test(event.key)) return false;
41
+ return event.code === codeFor(parsed.key);
36
42
  }
43
+ /**
44
+ * Reads bindings such as `"mod+k"`, `"mod+shift+p"`, `"alt+/"` and `"f1"`.
45
+ * `mod` is Meta or Ctrl. Modifiers match exactly: a binding without `mod`
46
+ * does not fire while Ctrl or Meta is down.
47
+ */
37
48
  function matchesHotkey(event, binding) {
38
- const parsed = parseBinding(binding);
39
- if (!keyMatches(event, parsed, event.key.toLowerCase())) return false;
40
- const modDown = event.metaKey || event.ctrlKey;
41
- return modDown === parsed.mod && event.shiftKey === parsed.shift && event.altKey === parsed.alt;
49
+ const parsed = parseBinding(binding);
50
+ if (!keyMatches(event, parsed, event.key.toLowerCase())) return false;
51
+ return (event.metaKey || event.ctrlKey) === parsed.mod && event.shiftKey === parsed.shift && event.altKey === parsed.alt;
42
52
  }
43
53
  function isTypingTarget(target) {
44
- if (!target) return false;
45
- if (target.isContentEditable) return true;
46
- const tag = target.tagName?.toLowerCase();
47
- return tag === "input" || tag === "textarea" || tag === "select";
54
+ if (!target) return false;
55
+ if (target.isContentEditable) return true;
56
+ const tag = target.tagName?.toLowerCase();
57
+ return tag === "input" || tag === "textarea" || tag === "select";
48
58
  }
59
+ /**
60
+ * `matchesHotkey` plus two guards: nothing fires during IME composition, and a
61
+ * binding with neither `mod` nor `alt` is ignored while `target` is typing, so
62
+ * `/` never opens the menu from a text field.
63
+ */
49
64
  function shouldHandleHotkey(event, binding, target) {
50
- if (event.isComposing) return false;
51
- if (!matchesHotkey(event, binding)) return false;
52
- const { mod, alt } = parseBinding(binding);
53
- if (!mod && !alt && isTypingTarget(target)) return false;
54
- return true;
65
+ if (event.isComposing) return false;
66
+ if (!matchesHotkey(event, binding)) return false;
67
+ const { mod, alt } = parseBinding(binding);
68
+ if (!mod && !alt && isTypingTarget(target)) return false;
69
+ return true;
55
70
  }
56
- export {
57
- isTypingTarget,
58
- matchesHotkey,
59
- shouldHandleHotkey
60
- };
71
+ export { isTypingTarget, matchesHotkey, shouldHandleHotkey };
@@ -1,5 +1,9 @@
1
1
  /** Where the command menu keeps its recents in a `CheloneStore`. */
2
2
  export declare const recentsKey = "command-menu.recents";
3
+ /** Most recents the command menu keeps when `recentsLimit` is not set. */
4
+ export declare const DEFAULT_RECENTS_LIMIT = 10;
5
+ /** Wait after the last keystroke, in milliseconds, when `searchDebounceMs` is not set. */
6
+ export declare const DEFAULT_SEARCH_DEBOUNCE_MS = 250;
3
7
  /**
4
8
  * A page the user visited. It holds no `path`: the link is rebuilt from the
5
9
  * resource and id, so a basename change cannot strand a saved entry.
@@ -1,15 +1,16 @@
1
- const recentsKey = "command-menu.recents";
1
+ // src/command-menu/recents.ts
2
+ /** Where the command menu keeps its recents in a `CheloneStore`. */
3
+ var recentsKey = "command-menu.recents";
4
+ /** Most recents the command menu keeps when `recentsLimit` is not set. */
5
+ var DEFAULT_RECENTS_LIMIT = 10;
6
+ /** Wait after the last keystroke, in milliseconds, when `searchDebounceMs` is not set. */
7
+ var DEFAULT_SEARCH_DEBOUNCE_MS = 250;
2
8
  function sameEntry(a, b) {
3
- return a.type === b.type && a.resource === b.resource && (a.id === void 0 ? void 0 : String(a.id)) === (b.id === void 0 ? void 0 : String(b.id));
9
+ return a.type === b.type && a.resource === b.resource && (a.id === void 0 ? void 0 : String(a.id)) === (b.id === void 0 ? void 0 : String(b.id));
4
10
  }
11
+ /** Puts `entry` first, drops an earlier copy, and cuts to `limit`. */
5
12
  function rememberRecent(recents, entry, limit) {
6
- if (limit < 1) return [];
7
- return [entry, ...recents.filter((other) => !sameEntry(other, entry))].slice(
8
- 0,
9
- limit
10
- );
13
+ if (limit < 1) return [];
14
+ return [entry, ...recents.filter((other) => !sameEntry(other, entry))].slice(0, limit);
11
15
  }
12
- export {
13
- recentsKey,
14
- rememberRecent
15
- };
16
+ export { DEFAULT_RECENTS_LIMIT, DEFAULT_SEARCH_DEBOUNCE_MS, recentsKey, rememberRecent };
@@ -3,7 +3,7 @@ import { type MicroStore } from "../store/micro-store.js";
3
3
  * An extra command in the menu. `label` and `icon` are generic so each adapter
4
4
  * uses its own types (a React node, a Vue component).
5
5
  */
6
- export interface CommandAction<TLabel = string, TIcon = unknown> {
6
+ export interface CommandAction<TLabel, TIcon> {
7
7
  id: string;
8
8
  label: TLabel;
9
9
  icon?: TIcon;
@@ -15,7 +15,7 @@ export interface CommandAction<TLabel = string, TIcon = unknown> {
15
15
  when?: () => boolean;
16
16
  onSelect: () => void;
17
17
  }
18
- export interface CommandRegistry<TLabel = string, TIcon = unknown> {
18
+ export interface CommandRegistry<TLabel, TIcon> {
19
19
  store: MicroStore<{
20
20
  commands: CommandAction<TLabel, TIcon>[];
21
21
  }>;
@@ -28,7 +28,11 @@ export interface CommandRegistry<TLabel = string, TIcon = unknown> {
28
28
  unregister(id: string): void;
29
29
  }
30
30
  export declare const defaultCommandGroup = "actions";
31
- export declare function createCommandRegistry<TLabel = string, TIcon = unknown>(): CommandRegistry<TLabel, TIcon>;
32
- export declare function getVisibleCommands<T extends Pick<CommandAction, "when">>(commands: readonly T[]): T[];
31
+ export declare function createCommandRegistry<TLabel, TIcon>(): CommandRegistry<TLabel, TIcon>;
32
+ export declare function getVisibleCommands<T extends {
33
+ when?: () => boolean;
34
+ }>(commands: readonly T[]): T[];
33
35
  /** Groups in first-seen order. A missing `group` reads as `"actions"`. */
34
- export declare function groupCommands<T extends Pick<CommandAction, "group">>(commands: readonly T[]): [string, T[]][];
36
+ export declare function groupCommands<T extends {
37
+ group?: string;
38
+ }>(commands: readonly T[]): [string, T[]][];
@@ -1,53 +1,43 @@
1
1
  import { createMicroStore } from "../store/micro-store.js";
2
- const defaultCommandGroup = "actions";
2
+ // src/command-menu/registry.ts
3
+ var defaultCommandGroup = "actions";
3
4
  function createCommandRegistry() {
4
- const store = createMicroStore({
5
- commands: []
6
- });
7
- const unregister = (id) => {
8
- store.setState((prev) => ({
9
- commands: prev.commands.filter((command) => command.id !== id)
10
- }));
11
- };
12
- const register = (action) => {
13
- store.setState((prev) => {
14
- const exists = prev.commands.some((command) => command.id === action.id);
15
- return {
16
- commands: exists ? prev.commands.map(
17
- (command) => command.id === action.id ? action : command
18
- ) : [...prev.commands, action]
19
- };
20
- });
21
- return () => {
22
- store.setState((prev) => ({
23
- commands: prev.commands.filter((command) => command !== action)
24
- }));
25
- };
26
- };
27
- return { store, register, unregister };
5
+ const store = createMicroStore({ commands: [] });
6
+ const unregister = (id) => {
7
+ store.setState((prev) => ({ commands: prev.commands.filter((command) => command.id !== id) }));
8
+ };
9
+ const register = (action) => {
10
+ store.setState((prev) => {
11
+ return { commands: prev.commands.some((command) => command.id === action.id) ? prev.commands.map((command) => command.id === action.id ? action : command) : [...prev.commands, action] };
12
+ });
13
+ return () => {
14
+ store.setState((prev) => ({ commands: prev.commands.filter((command) => command !== action) }));
15
+ };
16
+ };
17
+ return {
18
+ store,
19
+ register,
20
+ unregister
21
+ };
28
22
  }
29
23
  function getVisibleCommands(commands) {
30
- return commands.filter((command) => {
31
- try {
32
- return command.when ? command.when() !== false : true;
33
- } catch {
34
- return false;
35
- }
36
- });
24
+ return commands.filter((command) => {
25
+ try {
26
+ return command.when ? command.when() !== false : true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ });
37
31
  }
32
+ /** Groups in first-seen order. A missing `group` reads as `"actions"`. */
38
33
  function groupCommands(commands) {
39
- const groups = /* @__PURE__ */ new Map();
40
- for (const command of commands) {
41
- const group = command.group ?? defaultCommandGroup;
42
- const members = groups.get(group);
43
- if (members) members.push(command);
44
- else groups.set(group, [command]);
45
- }
46
- return [...groups];
34
+ const groups = /* @__PURE__ */ new Map();
35
+ for (const command of commands) {
36
+ const group = command.group ?? "actions";
37
+ const members = groups.get(group);
38
+ if (members) members.push(command);
39
+ else groups.set(group, [command]);
40
+ }
41
+ return [...groups];
47
42
  }
48
- export {
49
- createCommandRegistry,
50
- defaultCommandGroup,
51
- getVisibleCommands,
52
- groupCommands
53
- };
43
+ export { createCommandRegistry, defaultCommandGroup, getVisibleCommands, groupCommands };
@@ -15,6 +15,18 @@ export declare function parseCalendarDate(value: unknown): Date | undefined;
15
15
  export declare function calendarRange(view: CalendarView, anchor: Date, weekStartsOn: CalendarWeekStart): CalendarRange;
16
16
  /** One local midnight per day: adding to the day of the month never skips or repeats a day when the clocks change. */
17
17
  export declare function calendarDays(range: CalendarRange): Date[];
18
+ /** A selected day cell: the whole local day, as ISO strings. */
19
+ export interface CalendarDaySlot {
20
+ startISO: string;
21
+ endISO: string;
22
+ allDay: true;
23
+ }
24
+ /**
25
+ * The slot a whole day is: its local midnight to its last millisecond, the same
26
+ * inclusive end {@link calendarRange} gives a range. A day carrying a time is
27
+ * read as its day, not that instant.
28
+ */
29
+ export declare function calendarDaySlot(day: Date): CalendarDaySlot;
18
30
  export declare function shiftCalendarAnchor(view: CalendarView, anchor: Date, direction: 1 | -1): Date;
19
31
  export declare const calendarDayKey: (date: Date) => string;
20
32
  export interface CalendarLabels {