@happyvertical/smrt-svelte 0.55.0 → 0.55.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 (79) hide show
  1. package/README.md +38 -6
  2. package/agents/components.md +44 -1
  3. package/agents/workspace.md +77 -2
  4. package/dist/components/app/AppShell.svelte +122 -24
  5. package/dist/components/app/AppShell.svelte.d.ts +25 -2
  6. package/dist/components/app/AppShell.svelte.d.ts.map +1 -1
  7. package/dist/components/app/DockToggles.svelte +103 -0
  8. package/dist/components/app/DockToggles.svelte.d.ts +8 -0
  9. package/dist/components/app/DockToggles.svelte.d.ts.map +1 -0
  10. package/dist/components/app/dock-toggle.d.ts +14 -0
  11. package/dist/components/app/dock-toggle.d.ts.map +1 -0
  12. package/dist/components/app/dock-toggle.js +1 -0
  13. package/dist/components/app/index.d.ts +2 -0
  14. package/dist/components/app/index.d.ts.map +1 -1
  15. package/dist/components/app/index.js +1 -0
  16. package/dist/components/board/Board.svelte +92 -394
  17. package/dist/components/board/Board.svelte.d.ts.map +1 -1
  18. package/dist/components/forms/RelationInput.svelte +379 -0
  19. package/dist/components/forms/RelationInput.svelte.d.ts +75 -0
  20. package/dist/components/forms/RelationInput.svelte.d.ts.map +1 -0
  21. package/dist/components/forms/index.d.ts +2 -1
  22. package/dist/components/forms/index.d.ts.map +1 -1
  23. package/dist/components/forms/index.js +1 -0
  24. package/dist/components/forms/types.d.ts +9 -0
  25. package/dist/components/forms/types.d.ts.map +1 -1
  26. package/dist/components/sortable/Sortable.svelte +359 -0
  27. package/dist/components/sortable/Sortable.svelte.d.ts +26 -0
  28. package/dist/components/sortable/Sortable.svelte.d.ts.map +1 -0
  29. package/dist/components/sortable/controller.svelte.d.ts +152 -0
  30. package/dist/components/sortable/controller.svelte.d.ts.map +1 -0
  31. package/dist/components/sortable/controller.svelte.js +453 -0
  32. package/dist/components/sortable/index.d.ts +5 -0
  33. package/dist/components/sortable/index.d.ts.map +1 -0
  34. package/dist/components/sortable/index.js +2 -0
  35. package/dist/components/sortable/types.d.ts +80 -0
  36. package/dist/components/sortable/types.d.ts.map +1 -0
  37. package/dist/components/sortable/types.js +1 -0
  38. package/dist/components/workspace/admin-shell/AdminShell.svelte +62 -3
  39. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +5 -0
  40. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
  41. package/dist/components/workspace/admin-shell/PhoneTopBar.svelte +2 -1
  42. package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte +196 -0
  43. package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts +17 -0
  44. package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts.map +1 -0
  45. package/dist/components/workspace/admin-shell/TenantNav.svelte +69 -3
  46. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
  47. package/dist/components/workspace/admin-shell/dock.d.ts +37 -0
  48. package/dist/components/workspace/admin-shell/dock.d.ts.map +1 -0
  49. package/dist/components/workspace/admin-shell/dock.js +25 -0
  50. package/dist/components/workspace/admin-shell/layout-context.d.ts +7 -0
  51. package/dist/components/workspace/admin-shell/layout-context.d.ts.map +1 -0
  52. package/dist/components/workspace/admin-shell/layout-context.js +17 -0
  53. package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts +69 -0
  54. package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts.map +1 -0
  55. package/dist/components/workspace/admin-shell/layout-controller.svelte.js +91 -0
  56. package/dist/components/workspace/admin-shell/layout.d.ts +120 -0
  57. package/dist/components/workspace/admin-shell/layout.d.ts.map +1 -0
  58. package/dist/components/workspace/admin-shell/layout.js +402 -0
  59. package/dist/components/workspace/admin-shell/settings.d.ts +6 -4
  60. package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
  61. package/dist/components/workspace/admin-shell/settings.js +14 -4
  62. package/dist/components/workspace/admin-shell/state.svelte.d.ts +59 -1
  63. package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
  64. package/dist/components/workspace/admin-shell/state.svelte.js +146 -2
  65. package/dist/components/workspace/admin-shell/types.d.ts +52 -0
  66. package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
  67. package/dist/components/workspace/index.d.ts +6 -1
  68. package/dist/components/workspace/index.d.ts.map +1 -1
  69. package/dist/components/workspace/index.js +5 -0
  70. package/dist/i18n/server.d.ts +1 -0
  71. package/dist/i18n/server.d.ts.map +1 -1
  72. package/dist/i18n/server.js +1 -0
  73. package/dist/i18n/strings.sortable.d.ts +13 -0
  74. package/dist/i18n/strings.sortable.d.ts.map +1 -0
  75. package/dist/i18n/strings.sortable.js +13 -0
  76. package/dist/i18n/strings.workspace.d.ts +13 -0
  77. package/dist/i18n/strings.workspace.d.ts.map +1 -1
  78. package/dist/i18n/strings.workspace.js +13 -0
  79. package/package.json +26 -15
@@ -5,7 +5,9 @@
5
5
  * server or `$app/*` imports. Pair with the server `load`/`actions` from
6
6
  * `@happyvertical/smrt-app-runtime/sveltekit`.
7
7
  */
8
+ export { type ShellLayout, ShellLayoutController, ShellLayoutEditor, tryUseShellLayout, useShellLayout, } from '../workspace/index.js';
8
9
  export { default as AppShell } from './AppShell.svelte';
10
+ export type { DockToggle } from './dock-toggle.js';
9
11
  export { default as OwnerSetupForm } from './OwnerSetupForm.svelte';
10
12
  export type { OwnerSetupData, OwnerSetupFormResult, } from './owner-setup-types.js';
11
13
  export { default as RuntimeDiagnosticsWebMcp } from './RuntimeDiagnosticsWebMcp.svelte';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/app/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,YAAY,EACV,cAAc,EACd,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AACxF,OAAO,EACL,kCAAkC,EAClC,KAAK,uCAAuC,EAC5C,4BAA4B,EAC5B,oCAAoC,EACpC,KAAK,6BAA6B,EAClC,gCAAgC,GACjC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/app/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,KAAK,WAAW,EAChB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,YAAY,EACV,cAAc,EACd,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AACxF,OAAO,EACL,kCAAkC,EAClC,KAAK,uCAAuC,EAC5C,4BAA4B,EAC5B,oCAAoC,EACpC,KAAK,6BAA6B,EAClC,gCAAgC,GACjC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC"}
@@ -5,6 +5,7 @@
5
5
  * server or `$app/*` imports. Pair with the server `load`/`actions` from
6
6
  * `@happyvertical/smrt-app-runtime/sveltekit`.
7
7
  */
8
+ export { ShellLayoutController, ShellLayoutEditor, tryUseShellLayout, useShellLayout, } from '../workspace/index.js';
8
9
  export { default as AppShell } from './AppShell.svelte';
9
10
  export { default as OwnerSetupForm } from './OwnerSetupForm.svelte';
10
11
  export { default as RuntimeDiagnosticsWebMcp } from './RuntimeDiagnosticsWebMcp.svelte';
@@ -1,31 +1,20 @@
1
1
  <script lang="ts" generics="Card extends BoardCard, Column extends BoardColumn">
2
2
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
3
  import { BROWSER } from 'esm-env';
4
- import { tick, untrack } from 'svelte';
4
+ import { untrack } from 'svelte';
5
5
  import { M } from '../../i18n/strings.board.js';
6
+ import {
7
+ createSortable,
8
+ type SortableAnnouncement,
9
+ type SortableMove,
10
+ } from '../sortable/controller.svelte.js';
6
11
  import type {
7
12
  BoardCard,
8
13
  BoardColumn,
9
14
  BoardMoveIntent,
10
- BoardPosition,
11
15
  BoardProps,
12
16
  } from './types.js';
13
17
 
14
- interface DragState {
15
- cardId: string;
16
- source: BoardPosition;
17
- target: BoardPosition;
18
- mode: 'keyboard' | 'pointer';
19
- }
20
-
21
- interface PointerSession {
22
- cardId: string;
23
- pointerId: number;
24
- startX: number;
25
- startY: number;
26
- active: boolean;
27
- }
28
-
29
18
  let {
30
19
  columns,
31
20
  cards,
@@ -50,13 +39,7 @@ let root: HTMLElement;
50
39
  let localCards = $state<Card[]>(untrack(() => [...defaultCards]));
51
40
  let optimisticCards = $state<Card[] | undefined>();
52
41
  let lastAuthoritativeCards = untrack(() => cards);
53
- let drag = $state<DragState | undefined>();
54
42
  let collapsed = $state<Set<string>>(new Set());
55
- let announcement = $state('');
56
- let focusCardId = $state<string | undefined>();
57
- let suppressedSelectionCardId = $state<string | undefined>();
58
- let movePending = $state(false);
59
- let pointerSession = $state<PointerSession | undefined>();
60
43
 
61
44
  // A new owner array is an explicit reconciliation point. Do not clear an
62
45
  // optimistic move merely because this component has re-rendered.
@@ -80,23 +63,6 @@ const cardsByColumn = $derived.by(() => {
80
63
  }
81
64
  return next;
82
65
  });
83
- const movable = $derived(
84
- (cards === undefined || onmove !== undefined) && !movePending,
85
- );
86
-
87
- $effect(() => {
88
- if (!BROWSER) return;
89
- if (!focusCardId) return;
90
- const cardId = focusCardId;
91
- focusCardId = undefined;
92
- tick().then(() => {
93
- Array.from(
94
- root.querySelectorAll<HTMLButtonElement>('[data-smrt-board-card-id]'),
95
- )
96
- .find((element) => element.dataset.smrtBoardCardId === cardId)
97
- ?.focus();
98
- });
99
- });
100
66
 
101
67
  function laneListId(index: number): string {
102
68
  return `${instanceId}-lane-${index}`;
@@ -117,32 +83,17 @@ function findColumn(columnId: string): Column | undefined {
117
83
  return columns.find((column) => column.id === columnId);
118
84
  }
119
85
 
120
- function positionFor(cardId: string): BoardPosition | undefined {
121
- const item = findCard(cardId);
122
- if (!item) return undefined;
123
- const columnId = getCardColumnId(item);
124
- const index = cardsInColumn(columnId).findIndex(
125
- (candidate) => candidate.id === cardId,
126
- );
127
- return index < 0 ? undefined : { columnId, index };
128
- }
129
-
130
- function clamp(index: number, max: number): number {
131
- return Math.max(0, Math.min(index, max));
132
- }
133
-
134
- function createIntent(
135
- state: DragState,
86
+ function intentFor(
87
+ move: SortableMove,
136
88
  ): BoardMoveIntent<Card, Column> | undefined {
137
- const movingCard = findCard(state.cardId);
138
- const sourceColumn = findColumn(state.source.columnId);
139
- const targetColumn = findColumn(state.target.columnId);
140
- if (!movingCard || !sourceColumn || !targetColumn || targetColumn.disabled)
141
- return undefined;
89
+ const movingCard = findCard(move.itemId);
90
+ const sourceColumn = findColumn(move.source.containerId);
91
+ const targetColumn = findColumn(move.target.containerId);
92
+ if (!movingCard || !sourceColumn || !targetColumn) return undefined;
142
93
  return {
143
94
  card: movingCard,
144
- source: state.source,
145
- target: state.target,
95
+ source: { columnId: move.source.containerId, index: move.source.index },
96
+ target: { columnId: move.target.containerId, index: move.target.index },
146
97
  sourceColumn,
147
98
  targetColumn,
148
99
  };
@@ -176,327 +127,74 @@ function moveCards(intent: BoardMoveIntent<Card, Column>): Card[] {
176
127
  ];
177
128
  }
178
129
 
179
- function pickup(cardId: string, mode: DragState['mode']): void {
180
- if (!movable) return;
181
- const source = positionFor(cardId);
182
- const movingCard = findCard(cardId);
183
- if (!source || !movingCard) return;
184
- drag = { cardId, source, target: { ...source }, mode };
185
- announcement = t(M['ui.board.pickup'], {
186
- card: getCardLabel(movingCard),
187
- destinations: columns
188
- .filter((column) => !column.disabled)
189
- .map((column) => column.label)
190
- .join(', '),
191
- });
192
- }
193
-
194
- function announcePosition(state: DragState): void {
195
- const movingCard = findCard(state.cardId);
196
- const column = findColumn(state.target.columnId);
197
- if (!movingCard || !column) return;
198
- const count = cardsInColumn(column.id, state.cardId).length + 1;
199
- announcement = t(M['ui.board.position'], {
200
- card: getCardLabel(movingCard),
201
- column: column.label,
202
- position: state.target.index + 1,
203
- count,
204
- });
205
- }
206
-
207
- function moveKeyboardTarget(
208
- direction: 'horizontal' | 'vertical',
209
- delta: number,
210
- ): void {
211
- if (drag?.mode !== 'keyboard') return;
212
- const target = { ...drag.target };
213
- if (direction === 'vertical') {
214
- if (!allowSameColumnReorder) return;
215
- target.index = clamp(
216
- target.index + delta,
217
- cardsInColumn(target.columnId, drag.cardId).length,
218
- );
219
- } else {
220
- const currentIndex = columns.findIndex(
221
- (column) => column.id === target.columnId,
222
- );
223
- const candidate = columns[currentIndex + delta];
224
- if (!candidate) return;
225
- if (candidate.disabled) {
226
- announcement = t(M['ui.board.unavailable_column'], {
227
- column: candidate.label,
130
+ function message(announcement: SortableAnnouncement): string {
131
+ switch (announcement.type) {
132
+ case 'pickup':
133
+ return t(M['ui.board.pickup'], {
134
+ card: announcement.item,
135
+ destinations: announcement.destinations.join(', '),
228
136
  });
229
- return;
230
- }
231
- target.columnId = candidate.id;
232
- target.index = clamp(
233
- target.index,
234
- cardsInColumn(candidate.id, drag.cardId).length,
235
- );
236
- }
237
- drag = { ...drag, target };
238
- announcePosition(drag);
239
- }
240
-
241
- async function drop(): Promise<void> {
242
- if (!drag) return;
243
- const state = drag;
244
- const intent = createIntent(state);
245
- drag = undefined;
246
- suppressSelection(state.cardId);
247
- if (!intent) return;
248
- const changed =
249
- intent.source.columnId !== intent.target.columnId ||
250
- intent.source.index !== intent.target.index;
251
- if (!changed) {
252
- focusCardId = intent.card.id;
253
- return;
254
- }
255
- const reordered = moveCards(intent);
256
- expandColumn(intent.target.columnId);
257
- if (cards !== undefined && optimistic) optimisticCards = reordered;
258
- movePending = true;
259
- try {
260
- await onmove?.(intent);
261
- } catch {
262
- optimisticCards = undefined;
263
- announcement = t(M['ui.board.move_failed'], {
264
- card: getCardLabel(intent.card),
265
- });
266
- focusCardId = intent.card.id;
267
- return;
268
- } finally {
269
- movePending = false;
270
- }
271
- if (cards === undefined) localCards = reordered;
272
- const count =
273
- cardsInColumn(intent.target.columnId, intent.card.id).length + 1;
274
- announcement = t(M['ui.board.drop'], {
275
- card: getCardLabel(intent.card),
276
- column: intent.targetColumn.label,
277
- position: intent.target.index + 1,
278
- count,
279
- });
280
- focusCardId = intent.card.id;
281
- }
282
-
283
- function cancel(announce = true): void {
284
- if (!drag) return;
285
- const movingCard = findCard(drag.cardId);
286
- if (movingCard && announce) {
287
- announcement = t(M['ui.board.cancel'], { card: getCardLabel(movingCard) });
288
- }
289
- if (movingCard) {
290
- focusCardId = movingCard.id;
291
- }
292
- suppressSelection(drag.cardId);
293
- drag = undefined;
294
- }
295
-
296
- function suppressSelection(cardId: string): void {
297
- suppressedSelectionCardId = cardId;
298
- window.setTimeout(() => {
299
- if (suppressedSelectionCardId === cardId)
300
- suppressedSelectionCardId = undefined;
301
- }, 0);
302
- }
303
-
304
- function selectCard(item: Card): void {
305
- // Keyboard pickup/drop and native drag gestures can both dispatch a click
306
- // after their key/pointer sequence. Consume it so a move never doubles as
307
- // an application-level selection.
308
- if (suppressedSelectionCardId === item.id) {
309
- suppressedSelectionCardId = undefined;
310
- return;
137
+ case 'position':
138
+ return t(M['ui.board.position'], {
139
+ card: announcement.item,
140
+ column: announcement.container,
141
+ position: announcement.position,
142
+ count: announcement.count,
143
+ });
144
+ case 'drop':
145
+ return t(M['ui.board.drop'], {
146
+ card: announcement.item,
147
+ column: announcement.container,
148
+ position: announcement.position,
149
+ count: announcement.count,
150
+ });
151
+ case 'unavailable':
152
+ return t(M['ui.board.unavailable_column'], {
153
+ column: announcement.container,
154
+ });
155
+ case 'failed':
156
+ return t(M['ui.board.move_failed'], { card: announcement.item });
157
+ case 'cancel':
158
+ return t(M['ui.board.cancel'], { card: announcement.item });
311
159
  }
312
- if (!drag) onselect?.(item);
313
160
  }
314
161
 
315
- function handleCardKeydown(event: KeyboardEvent, cardId: string): void {
316
- if (!drag) {
317
- if (event.key === ' ' || event.key === 'Enter') {
318
- if (!movable) return;
319
- event.preventDefault();
320
- pickup(cardId, 'keyboard');
162
+ const sortable = createSortable({
163
+ root: () => root,
164
+ selectors: {
165
+ container: '[data-smrt-board-column-id]',
166
+ containerKey: 'smrtBoardColumnId',
167
+ item: '[data-smrt-board-card-id]',
168
+ itemKey: 'smrtBoardCardId',
169
+ },
170
+ containers: () => columns,
171
+ itemIds: (columnId) => cardsInColumn(columnId).map((item) => item.id),
172
+ itemLabel: (cardId) => {
173
+ const found = findCard(cardId);
174
+ return found ? getCardLabel(found) : undefined;
175
+ },
176
+ allowSameContainerReorder: () => allowSameColumnReorder,
177
+ enabled: () => cards === undefined || onmove !== undefined,
178
+ announce: message,
179
+ async commit(move) {
180
+ const intent = intentFor(move);
181
+ if (!intent) return;
182
+ const reordered = moveCards(intent);
183
+ expandColumn(intent.target.columnId);
184
+ if (cards !== undefined && optimistic) optimisticCards = reordered;
185
+ try {
186
+ await onmove?.(intent);
187
+ } catch (error) {
188
+ optimisticCards = undefined;
189
+ throw error;
321
190
  }
322
- return;
323
- }
324
- if (drag.cardId !== cardId || drag.mode !== 'keyboard') return;
325
- if (event.key === 'Escape') {
326
- event.preventDefault();
327
- cancel();
328
- } else if (event.key === ' ' || event.key === 'Enter') {
329
- event.preventDefault();
330
- void drop();
331
- } else if (event.key === 'ArrowLeft') {
332
- event.preventDefault();
333
- moveKeyboardTarget('horizontal', -1);
334
- } else if (event.key === 'ArrowRight') {
335
- event.preventDefault();
336
- moveKeyboardTarget('horizontal', 1);
337
- } else if (event.key === 'ArrowUp') {
338
- event.preventDefault();
339
- moveKeyboardTarget('vertical', -1);
340
- } else if (event.key === 'ArrowDown') {
341
- event.preventDefault();
342
- moveKeyboardTarget('vertical', 1);
343
- }
344
- }
345
-
346
- function handleDragStart(event: DragEvent, cardId: string): void {
347
- if (!movable) {
348
- event.preventDefault();
349
- return;
350
- }
351
- pickup(cardId, 'pointer');
352
- event.dataTransfer?.setData('text/plain', cardId);
353
- if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move';
354
- }
355
-
356
- function handlePointerDown(event: PointerEvent, cardId: string): void {
357
- if (!movable || event.button !== 0) return;
358
- pointerSession = {
359
- cardId,
360
- pointerId: event.pointerId,
361
- startX: event.clientX,
362
- startY: event.clientY,
363
- active: false,
364
- };
365
- const target = event.currentTarget as HTMLElement;
366
- target.setPointerCapture?.(event.pointerId);
367
- }
368
-
369
- function elementAtPointer(event: PointerEvent): Element | null {
370
- return (
371
- document.elementFromPoint?.(event.clientX, event.clientY) ??
372
- (event.target as Element | null)
373
- );
374
- }
375
-
376
- function setPointerTargetFromElement(
377
- element: Element | null,
378
- clientY: number,
379
- ): Column | undefined {
380
- // Column ids belong to the consumer's domain and may repeat in another
381
- // Board instance. Pointer hit-testing must never escape this Board's root.
382
- if (!element || !root.contains(element)) return undefined;
383
- const lane = element?.closest<HTMLElement>('[data-smrt-board-column-id]');
384
- const columnId = lane?.dataset.smrtBoardColumnId;
385
- if (!columnId) return undefined;
386
- const column = findColumn(columnId);
387
- if (!column || column.disabled) return column;
388
- const cardElement = element?.closest<HTMLElement>(
389
- '[data-smrt-board-card-id]',
390
- );
391
- if (!cardElement) {
392
- pointerTarget(columnId, cardsInColumn(columnId, drag?.cardId).length);
393
- return column;
394
- }
395
- const rawIndex = cardsInColumn(columnId).findIndex(
396
- (item) => item.id === cardElement.dataset.smrtBoardCardId,
397
- );
398
- if (rawIndex < 0) return column;
399
- const rect = cardElement.getBoundingClientRect();
400
- const before = clientY < rect.top + rect.height / 2;
401
- let targetIndex = rawIndex + (before ? 0 : 1);
402
- if (drag?.source.columnId === columnId && targetIndex > drag.source.index) {
403
- targetIndex -= 1;
404
- }
405
- pointerTarget(columnId, targetIndex);
406
- return column;
407
- }
408
-
409
- function finishPointerSession(event: PointerEvent, cancelled = false): void {
410
- const session = pointerSession;
411
- pointerSession = undefined;
412
- const target = event.currentTarget as HTMLElement;
413
- if (target.hasPointerCapture?.(event.pointerId))
414
- target.releasePointerCapture(event.pointerId);
415
- if (!session?.active || drag?.cardId !== session.cardId) return;
416
- if (cancelled) {
417
- cancel();
418
- return;
419
- }
420
- const column = setPointerTargetFromElement(
421
- elementAtPointer(event),
422
- event.clientY,
423
- );
424
- if (column?.disabled) {
425
- announcement = t(M['ui.board.unavailable_column'], {
426
- column: column.label,
427
- });
428
- cancel(false);
429
- return;
430
- }
431
- if (!column) {
432
- cancel();
433
- return;
434
- }
435
- void drop();
436
- }
437
-
438
- function handlePointerMove(event: PointerEvent): void {
439
- const session = pointerSession;
440
- if (!session || session.pointerId !== event.pointerId) return;
441
- if (!session.active) {
442
- const distance = Math.hypot(
443
- event.clientX - session.startX,
444
- event.clientY - session.startY,
445
- );
446
- if (distance < 6) return;
447
- pickup(session.cardId, 'pointer');
448
- pointerSession = { ...session, active: true };
449
- }
450
- event.preventDefault();
451
- setPointerTargetFromElement(elementAtPointer(event), event.clientY);
452
- }
453
-
454
- function pointerTarget(columnId: string, index: number): void {
455
- if (drag?.mode !== 'pointer') return;
456
- if (!allowSameColumnReorder && columnId === drag.source.columnId) {
457
- drag = { ...drag, target: { ...drag.source } };
458
- return;
459
- }
460
- const max = cardsInColumn(columnId, drag.cardId).length;
461
- drag = { ...drag, target: { columnId, index: clamp(index, max) } };
462
- }
463
-
464
- function handleCardDrop(
465
- event: DragEvent,
466
- columnId: string,
467
- rawIndex: number,
468
- ): void {
469
- event.preventDefault();
470
- if (drag?.mode !== 'pointer') return;
471
- const targetColumn = findColumn(columnId);
472
- if (targetColumn?.disabled) {
473
- announcement = t(M['ui.board.unavailable_column'], {
474
- column: targetColumn.label,
475
- });
476
- cancel(false);
477
- return;
478
- }
479
- const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
480
- const before = event.clientY < rect.top + rect.height / 2;
481
- let targetIndex = rawIndex + (before ? 0 : 1);
482
- if (drag.source.columnId === columnId && targetIndex > drag.source.index) {
483
- targetIndex -= 1;
484
- }
485
- pointerTarget(columnId, targetIndex);
486
- void drop();
487
- }
191
+ if (cards === undefined) localCards = reordered;
192
+ },
193
+ });
488
194
 
489
- function handleLaneDrop(event: DragEvent, column: Column): void {
490
- event.preventDefault();
491
- if (column.disabled) {
492
- announcement = t(M['ui.board.unavailable_column'], {
493
- column: column.label,
494
- });
495
- cancel(false);
496
- return;
497
- }
498
- pointerTarget(column.id, cardsInColumn(column.id, drag?.cardId).length);
499
- void drop();
195
+ function selectCard(item: Card): void {
196
+ if (sortable.consumeClick(item.id)) return;
197
+ if (!sortable.drag) onselect?.(item);
500
198
  }
501
199
 
502
200
  function expandColumn(columnId: string): void {
@@ -551,32 +249,32 @@ function toggleColumn(columnId: string): void {
551
249
  role="list"
552
250
  data-smrt-board-column-id={column.id}
553
251
  ondragover={(event) => event.preventDefault()}
554
- ondrop={(event) => handleLaneDrop(event, column)}
252
+ ondrop={(event) => sortable.dropOnContainer(event, column.id)}
555
253
  >
556
254
  {#each items as item, index (item.id)}
557
255
  <div role="listitem">
558
256
  <!-- raw-primitive-allow: native button owns keyboard pickup and HTML drag/drop -->
559
257
  <button
560
258
  type="button"
561
- class:smrt-board__card--dragging={drag?.cardId === item.id}
562
- class:smrt-board__card--touch-drag={movable}
259
+ class:smrt-board__card--dragging={sortable.drag?.itemId === item.id}
260
+ class:smrt-board__card--touch-drag={sortable.movable}
563
261
  class="smrt-board__card"
564
262
  data-smrt-board-card-id={item.id}
565
- draggable={movable && pointerSession === undefined}
566
- aria-pressed={drag?.cardId === item.id}
567
- aria-describedby={drag?.cardId === item.id ? liveId : undefined}
263
+ draggable={sortable.nativeDraggable}
264
+ aria-pressed={sortable.drag?.itemId === item.id}
265
+ aria-describedby={sortable.drag?.itemId === item.id ? liveId : undefined}
568
266
  onclick={() => selectCard(item)}
569
- onkeydown={(event) => handleCardKeydown(event, item.id)}
570
- onpointerdown={(event) => handlePointerDown(event, item.id)}
571
- onpointermove={handlePointerMove}
572
- onpointerup={(event) => finishPointerSession(event)}
573
- onpointercancel={(event) => finishPointerSession(event, true)}
574
- ondragstart={(event) => handleDragStart(event, item.id)}
575
- ondragend={() => { if (drag?.mode === 'pointer') cancel(); }}
267
+ onkeydown={(event) => sortable.keydown(event, item.id)}
268
+ onpointerdown={(event) => sortable.pointerDown(event, item.id)}
269
+ onpointermove={(event) => sortable.pointerMove(event)}
270
+ onpointerup={(event) => sortable.pointerUp(event)}
271
+ onpointercancel={(event) => sortable.pointerCancel(event)}
272
+ ondragstart={(event) => sortable.dragStart(event, item.id)}
273
+ ondragend={() => sortable.dragEnd()}
576
274
  ondragover={(event) => event.preventDefault()}
577
- ondrop={(event) => handleCardDrop(event, column.id, index)}
275
+ ondrop={(event) => sortable.dropOnItem(event, column.id, index)}
578
276
  >
579
- {@render card({ card: item, column, index, isDragging: drag?.cardId === item.id })}
277
+ {@render card({ card: item, column, index, isDragging: sortable.drag?.itemId === item.id })}
580
278
  </button>
581
279
  </div>
582
280
  {:else}
@@ -589,7 +287,7 @@ function toggleColumn(columnId: string): void {
589
287
  </section>
590
288
  {/each}
591
289
  </div>
592
- <div id={liveId} class="smrt-board__live" aria-live="assertive" aria-atomic="true">{announcement}</div>
290
+ <div id={liveId} class="smrt-board__live" aria-live="assertive" aria-atomic="true">{sortable.announcement}</div>
593
291
  </section>
594
292
 
595
293
  <style>
@@ -1 +1 @@
1
- {"version":3,"file":"Board.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/board/Board.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EAGX,UAAU,EACX,MAAM,YAAY,CAAC;AACpB,iBAAS,QAAQ,CAAC,IAAI,SAAS,SAAS,EAAE,MAAM,SAAS,WAAW;WA2iBvC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC;;;;;EAA4E;AACjI,cAAM,iBAAiB,CAAC,IAAI,SAAS,SAAS,EAAC,MAAM,SAAS,WAAW;IACrE,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAC1D,MAAM,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC5D,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAC1D,QAAQ;IACR,OAAO;CACV;AAED,UAAU,qBAAqB;IAC3B,KAAK,IAAI,SAAS,SAAS,EAAC,MAAM,SAAS,WAAW,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IACxe,CAAC,IAAI,SAAS,SAAS,EAAC,MAAM,SAAS,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC/L,YAAY,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,GAAG,EAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CACrE;AACD,QAAA,MAAM,KAAK,EAAE,qBAAmC,CAAC;AAC/B,KAAK,KAAK,CAAC,IAAI,SAAS,SAAS,EAAC,MAAM,SAAS,WAAW,IAAI,YAAY,CAAC,OAAO,KAAK,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,CAAC;AAC1G,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"Board.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/board/Board.svelte.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EAEX,UAAU,EACX,MAAM,YAAY,CAAC;AACpB,iBAAS,QAAQ,CAAC,IAAI,SAAS,SAAS,EAAE,MAAM,SAAS,WAAW;WA0PvC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC;;;;;EAA4E;AACjI,cAAM,iBAAiB,CAAC,IAAI,SAAS,SAAS,EAAC,MAAM,SAAS,WAAW;IACrE,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAC1D,MAAM,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC5D,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IAC1D,QAAQ;IACR,OAAO;CACV;AAED,UAAU,qBAAqB;IAC3B,KAAK,IAAI,SAAS,SAAS,EAAC,MAAM,SAAS,WAAW,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IACxe,CAAC,IAAI,SAAS,SAAS,EAAC,MAAM,SAAS,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC/L,YAAY,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,GAAG,EAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CACrE;AACD,QAAA,MAAM,KAAK,EAAE,qBAAmC,CAAC;AAC/B,KAAK,KAAK,CAAC,IAAI,SAAS,SAAS,EAAC,MAAM,SAAS,WAAW,IAAI,YAAY,CAAC,OAAO,KAAK,CAAC,IAAI,EAAC,MAAM,CAAC,CAAC,CAAC;AAC1G,eAAe,KAAK,CAAC"}