@adecore/ui 0.39.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/dist/AccentSwatches.js +2 -3
  2. package/dist/Banner.js +1 -6
  3. package/dist/ChoiceCards.js +0 -5
  4. package/dist/ColorSwatch.js +1 -4
  5. package/dist/ColorWheelPicker.js +3 -6
  6. package/dist/DockShell.js +3 -5
  7. package/dist/EmptyState.js +0 -1
  8. package/dist/ErrorBoundary.js +1 -5
  9. package/dist/Field.js +2 -6
  10. package/dist/FileIcon.js +2 -4
  11. package/dist/Icon.js +1 -2
  12. package/dist/IconButton.js +1 -1
  13. package/dist/IconPicker.js +3 -7
  14. package/dist/Input.js +1 -2
  15. package/dist/Kbd.js +1 -3
  16. package/dist/LazyBoundary.js +2 -3
  17. package/dist/Meter.js +0 -1
  18. package/dist/MiddleTruncate.js +2 -6
  19. package/dist/PanelEmpty.js +0 -1
  20. package/dist/PanelHeader.js +1 -4
  21. package/dist/Pill.js +1 -6
  22. package/dist/ProjectSwitcher.js +1 -1
  23. package/dist/PromptDialog.js +5 -8
  24. package/dist/RelativeTime.js +1 -1
  25. package/dist/SegmentBar.js +1 -2
  26. package/dist/Segmented.js +2 -5
  27. package/dist/Select.js +1 -4
  28. package/dist/ShortcutHints.js +2 -3
  29. package/dist/Slider.js +0 -1
  30. package/dist/SlidingColumn.js +2 -4
  31. package/dist/SplitButton.js +1 -5
  32. package/dist/TextMenu.js +1 -5
  33. package/dist/Tile.js +0 -4
  34. package/dist/Toasts.js +1 -9
  35. package/dist/Tooltip.js +1 -1
  36. package/dist/UIProvider.js +0 -5
  37. package/dist/Waveform.js +4 -2
  38. package/dist/Wipe.js +1 -6
  39. package/dist/ZoomControls.js +0 -1
  40. package/dist/clipboard.js +2 -5
  41. package/dist/combobox/parts.js +3 -9
  42. package/dist/command-palette/parts.js +5 -14
  43. package/dist/dialog/parts.js +5 -10
  44. package/dist/dialog-layer.js +2 -3
  45. package/dist/error-boundary.js +4 -20
  46. package/dist/file-icon.js +4 -9
  47. package/dist/file-tree/empty-lines.js +2 -8
  48. package/dist/file-tree/use-empty-lines.js +1 -5
  49. package/dist/file-tree/use-slots.js +6 -10
  50. package/dist/format/datetime.js +32 -29
  51. package/dist/format/duration.js +2 -10
  52. package/dist/format/locale.js +19 -39
  53. package/dist/format/number.js +9 -22
  54. package/dist/format/regions.js +4 -7
  55. package/dist/icon-picker.js +5 -14
  56. package/dist/key-value-list/parts.js +1 -5
  57. package/dist/lazy.js +4 -19
  58. package/dist/locales.js +2 -6
  59. package/dist/menu/parts.js +61 -133
  60. package/dist/middle-truncate.js +2 -3
  61. package/dist/placement.js +0 -1
  62. package/dist/popover/parts.js +1 -5
  63. package/dist/popup-layer.js +2 -4
  64. package/dist/prefetch.js +2 -5
  65. package/dist/selection.js +1 -5
  66. package/dist/settings/MasterDetail.js +1 -7
  67. package/dist/settings/SettingsDialog.js +1 -2
  68. package/dist/settings/SettingsRow.js +2 -5
  69. package/dist/settings/SettingsSection.js +1 -1
  70. package/dist/shortcut-hints.js +1 -5
  71. package/dist/shortcut.js +1 -2
  72. package/dist/tabs/overflow.js +1 -6
  73. package/dist/tabs/parts.js +69 -95
  74. package/dist/testing/dedupe.js +5 -7
  75. package/dist/tint.js +3 -5
  76. package/dist/toast-store.js +2 -10
  77. package/dist/tree/use-tree-shift.js +2 -6
  78. package/dist/useColumnResize.js +9 -10
  79. package/dist/useNow.js +2 -9
  80. package/dist/workspace/DropPreview.js +1 -5
  81. package/dist/workspace/preview.js +4 -9
  82. package/package.json +9 -9
  83. package/src/AccentSwatches.tsx +9 -3
  84. package/src/Banner.tsx +5 -11
  85. package/src/Button.tsx +4 -0
  86. package/src/Checkbox.tsx +3 -0
  87. package/src/ChoiceCards.tsx +12 -11
  88. package/src/CloseButton.tsx +1 -0
  89. package/src/ColorSwatch.tsx +7 -8
  90. package/src/ColorWheelPicker.tsx +28 -12
  91. package/src/ColumnResizeHandle.tsx +1 -0
  92. package/src/CopyButton.tsx +6 -0
  93. package/src/DisabledReason.tsx +1 -0
  94. package/src/DockShell.tsx +11 -10
  95. package/src/EmptyState.tsx +10 -6
  96. package/src/ErrorBoundary.tsx +13 -10
  97. package/src/Field.tsx +11 -13
  98. package/src/FileIcon.tsx +4 -4
  99. package/src/Icon.tsx +2 -4
  100. package/src/IconButton.tsx +14 -5
  101. package/src/IconPicker.tsx +30 -15
  102. package/src/Input.tsx +11 -9
  103. package/src/Kbd.tsx +8 -7
  104. package/src/LazyBoundary.tsx +3 -3
  105. package/src/Meter.tsx +7 -2
  106. package/src/MiddleTruncate.tsx +15 -6
  107. package/src/PanelEmpty.tsx +9 -7
  108. package/src/PanelHeader.tsx +2 -5
  109. package/src/Pill.tsx +7 -8
  110. package/src/ProjectSwitcher.tsx +16 -6
  111. package/src/PromptDialog.tsx +31 -26
  112. package/src/RelativeTime.tsx +6 -1
  113. package/src/SegmentBar.tsx +22 -11
  114. package/src/Segmented.tsx +16 -11
  115. package/src/Select.tsx +7 -4
  116. package/src/SelectSearch.tsx +3 -0
  117. package/src/ShortcutHints.tsx +24 -3
  118. package/src/Skeleton.tsx +1 -0
  119. package/src/Slider.tsx +4 -1
  120. package/src/SlidingColumn.tsx +18 -14
  121. package/src/Spinner.tsx +1 -0
  122. package/src/SplitButton.tsx +8 -8
  123. package/src/Stepper.tsx +6 -0
  124. package/src/Switch.tsx +2 -0
  125. package/src/TextMenu.tsx +2 -5
  126. package/src/Tile.tsx +2 -6
  127. package/src/Toasts.tsx +8 -12
  128. package/src/Tooltip.tsx +10 -5
  129. package/src/UIProvider.tsx +7 -7
  130. package/src/Waveform.tsx +41 -12
  131. package/src/Wipe.tsx +5 -6
  132. package/src/ZoomControls.tsx +5 -2
  133. package/src/clipboard.ts +4 -5
  134. package/src/combobox/parts.tsx +18 -14
  135. package/src/command-palette/context.ts +2 -0
  136. package/src/command-palette/index.parts.ts +1 -0
  137. package/src/command-palette/parts.tsx +20 -14
  138. package/src/command-palette/virtual.tsx +21 -0
  139. package/src/dialog/parts.tsx +10 -13
  140. package/src/dialog-layer.ts +5 -3
  141. package/src/error-boundary.ts +7 -20
  142. package/src/file-icon.ts +8 -9
  143. package/src/file-tree/empty-lines.ts +24 -8
  144. package/src/file-tree/helpers.ts +34 -0
  145. package/src/file-tree/highlight.ts +19 -0
  146. package/src/file-tree/index.parts.ts +1 -0
  147. package/src/file-tree/parts.tsx +41 -5
  148. package/src/file-tree/use-empty-lines.ts +22 -5
  149. package/src/file-tree/use-file-tree-shift.tsx +10 -0
  150. package/src/file-tree/use-file-tree.ts +2 -0
  151. package/src/file-tree/use-slots.ts +77 -12
  152. package/src/format/datetime.ts +42 -17
  153. package/src/format/duration.ts +28 -10
  154. package/src/format/index.ts +3 -0
  155. package/src/format/locale.ts +33 -42
  156. package/src/format/number.ts +21 -27
  157. package/src/format/regions.ts +4 -7
  158. package/src/icon-picker.ts +23 -16
  159. package/src/index.ts +8 -0
  160. package/src/key-value-list/parts.tsx +1 -5
  161. package/src/lazy-landings.ts +2 -0
  162. package/src/lazy.tsx +23 -19
  163. package/src/locales.ts +2 -6
  164. package/src/match.ts +14 -0
  165. package/src/menu/parts.tsx +30 -55
  166. package/src/middle-truncate.ts +12 -3
  167. package/src/modality.ts +4 -0
  168. package/src/placement.ts +2 -1
  169. package/src/platform.ts +2 -0
  170. package/src/popover/parts.tsx +3 -5
  171. package/src/popup-layer.ts +6 -4
  172. package/src/prefetch.ts +18 -5
  173. package/src/project-switcher-row.tsx +2 -0
  174. package/src/segmented-keys.ts +6 -0
  175. package/src/select-shared.ts +2 -0
  176. package/src/selection.ts +5 -5
  177. package/src/settings/ConfirmDialog.tsx +3 -0
  178. package/src/settings/MasterDetail.tsx +3 -9
  179. package/src/settings/SettingsDialog.tsx +27 -9
  180. package/src/settings/SettingsRow.tsx +16 -10
  181. package/src/settings/SettingsSection.tsx +10 -5
  182. package/src/settings/index.ts +3 -0
  183. package/src/settings/target.ts +1 -0
  184. package/src/shortcut-hints.ts +10 -5
  185. package/src/shortcut.ts +26 -6
  186. package/src/tabs/overflow.ts +8 -6
  187. package/src/tabs/parts.tsx +50 -26
  188. package/src/testing/dedupe.ts +27 -7
  189. package/src/testing/fake-source.ts +1 -0
  190. package/src/tint.ts +3 -5
  191. package/src/toast-store.ts +20 -14
  192. package/src/tree/parts.tsx +8 -0
  193. package/src/tree/shift.ts +6 -0
  194. package/src/tree/use-tree-shift.tsx +23 -7
  195. package/src/useAsyncAction.ts +8 -0
  196. package/src/useColumnResize.ts +24 -15
  197. package/src/useContentSize.ts +5 -0
  198. package/src/useMeasuredWidth.ts +1 -0
  199. package/src/useNow.ts +6 -11
  200. package/src/workspace/DropPreview.tsx +3 -5
  201. package/src/workspace/SplitView.tsx +109 -0
  202. package/src/workspace/TabStrip.tsx +42 -0
  203. package/src/workspace/Workspace.tsx +5 -0
  204. package/src/workspace/context.ts +1 -0
  205. package/src/workspace/geometry.ts +49 -0
  206. package/src/workspace/model.ts +72 -0
  207. package/src/workspace/preview.ts +15 -15
@@ -5,20 +5,17 @@ const HIDE_AFTER = 900;
5
5
 
6
6
  const NEAR_BOTTOM = 16;
7
7
 
8
- /* Where a tree draws its rows, and how far a row reaches past the edge it may use. */
9
8
  export interface TreeShiftSource {
10
9
  /* The node whose changes add, remove or rename rows; null while the tree has not drawn it yet. */
11
10
  root(frame: HTMLElement): ParentNode | null;
11
+
12
12
  rows(root: ParentNode): readonly HTMLElement[];
13
+
13
14
  /* How many pixels of the row lie past its edge at no shift; null for a row that is not laid out. */
14
15
  need(row: HTMLElement, shift: number, gap: number, range: Range): number | null;
15
16
  }
16
17
 
17
- /*
18
- * Rows that are wider than the tree slide sideways together, by a shift every row reads from the
19
- * frame's `--adecore-tree-shift`, with a thin bar that shows while a person scrolls or nears it. A
20
- * shift instead of a scroll keeps every row's background and the vertical scroll where they are.
21
- */
18
+ /* A shift every row reads from `--adecore-tree-shift` instead of a scroll keeps every row's background and the vertical scroll where they are. */
22
19
  export function useTreeShift(source: TreeShiftSource, resetKey?: string): { attach(node: HTMLElement | null): void; bar: ReactElement } {
23
20
  const [frame, setFrame] = useState<HTMLElement | null>(null);
24
21
  const [track, setTrack] = useState<HTMLDivElement | null>(null);
@@ -28,6 +25,7 @@ export function useTreeShift(source: TreeShiftSource, resetKey?: string): { atta
28
25
  if (frame === null || track === null || thumb === null) {
29
26
  return;
30
27
  }
28
+
31
29
  const range = document.createRange();
32
30
  let root: ParentNode | null = null;
33
31
  let exact = 0;
@@ -40,10 +38,12 @@ export function useTreeShift(source: TreeShiftSource, resetKey?: string): { atta
40
38
 
41
39
  const draw = (): void => {
42
40
  const shift = Math.round(exact);
41
+
43
42
  if (shift !== drawn) {
44
43
  drawn = shift;
45
44
  frame.style.setProperty(SHIFT_PROPERTY, `${shift}px`);
46
45
  }
46
+
47
47
  const { left, width } = shiftThumb(shift, max, track.clientWidth, frame.clientWidth);
48
48
  thumb.style.left = `${left}px`;
49
49
  thumb.style.width = `${width}px`;
@@ -52,13 +52,17 @@ export function useTreeShift(source: TreeShiftSource, resetKey?: string): { atta
52
52
 
53
53
  const measure = (): void => {
54
54
  pending = 0;
55
+
55
56
  if (root === null) {
56
57
  root = source.root(frame);
58
+
57
59
  if (root === null) {
58
60
  return;
59
61
  }
62
+
60
63
  rows.observe(root, { childList: true, subtree: true, characterData: true });
61
64
  }
65
+
62
66
  const drawnRows = source.rows(root);
63
67
  const gap = drawnRows.length === 0 ? 0 : Number.parseFloat(getComputedStyle(drawnRows[0]!).columnGap) || 0;
64
68
  max = maxShift(drawnRows.map((row) => source.need(row, drawn, gap, range)).filter((need) => need !== null));
@@ -80,26 +84,33 @@ export function useTreeShift(source: TreeShiftSource, resetKey?: string): { atta
80
84
 
81
85
  const onWheel = (event: WheelEvent): void => {
82
86
  const delta = sidewaysDelta(event, frame.clientWidth);
87
+
83
88
  if (delta === 0 || max === 0) {
84
89
  return;
85
90
  }
91
+
86
92
  scrolling = true;
87
93
  window.clearTimeout(hideTimer);
94
+
88
95
  hideTimer = window.setTimeout(() => {
89
96
  scrolling = false;
90
97
  draw();
91
98
  }, HIDE_AFTER);
99
+
92
100
  const next = clampShift(exact + delta, max);
101
+
93
102
  if (next !== exact) {
94
103
  event.preventDefault();
95
104
  exact = next;
96
105
  }
106
+
97
107
  draw();
98
108
  };
99
109
 
100
110
  const onPointerMove = (event: PointerEvent): void => {
101
111
  const bottom = track.getBoundingClientRect().bottom;
102
112
  const isNear = event.clientY >= bottom - NEAR_BOTTOM;
113
+
103
114
  if (isNear !== near) {
104
115
  near = isNear;
105
116
  draw();
@@ -143,21 +154,26 @@ export function useTreeShift(source: TreeShiftSource, resetKey?: string): { atta
143
154
  return { attach: setFrame, bar };
144
155
  }
145
156
 
146
- /* The rows of `Tree.Root`: everything a row holds slides, so a row reaches as far as its last part. */
157
+ /* Everything a row of `Tree.Root` holds slides, so a row reaches as far as its last part. */
147
158
  export const TREE_ROWS: TreeShiftSource = {
148
159
  root: (frame) => frame,
149
160
  rows: (root) => [...root.querySelectorAll<HTMLElement>('.adecore-tree-row')],
150
161
  need: (row, shift) => {
151
162
  const box = row.getBoundingClientRect();
163
+
152
164
  if (box.width === 0) {
153
165
  return null;
154
166
  }
167
+
155
168
  const parts = [...row.children].filter((child) => !child.classList.contains('adecore-tree-guide'));
169
+
156
170
  if (parts.length === 0) {
157
171
  return null;
158
172
  }
173
+
159
174
  const reach = Math.max(...parts.map((part) => part.getBoundingClientRect().right));
160
175
  const limit = box.right - (Number.parseFloat(getComputedStyle(row).paddingInlineEnd) || 0);
176
+
161
177
  return shiftNeed(reach, limit, shift);
162
178
  }
163
179
  };
@@ -4,10 +4,13 @@ import { messageOf } from './error-message.ts';
4
4
  export interface AsyncAction {
5
5
  busy: boolean;
6
6
  failure: string | null;
7
+
7
8
  /* Says whether the work went through, so a caller closes or steps on only on the way out. */
8
9
  run(work: () => Promise<unknown>): Promise<boolean>;
10
+
9
11
  /* A refusal a surface can see for itself, which reads in the same line as one that came back. */
10
12
  fail(message: string): void;
13
+
11
14
  clear(): void;
12
15
  }
13
16
 
@@ -15,18 +18,23 @@ export interface AsyncAction {
15
18
  export function useAsyncAction(fallback?: string): AsyncAction {
16
19
  const [busy, setBusy] = useState(false);
17
20
  const [failure, setFailure] = useState<string | null>(null);
21
+
18
22
  const run = async (work: () => Promise<unknown>): Promise<boolean> => {
19
23
  setBusy(true);
20
24
  setFailure(null);
25
+
21
26
  try {
22
27
  await work();
28
+
23
29
  return true;
24
30
  } catch (e) {
25
31
  setFailure(messageOf(e, fallback));
32
+
26
33
  return false;
27
34
  } finally {
28
35
  setBusy(false);
29
36
  }
30
37
  };
38
+
31
39
  return { busy, failure, run, fail: setFailure, clear: () => setFailure(null) };
32
40
  }
@@ -1,37 +1,39 @@
1
1
  import type { PointerEvent as ReactPointerEvent, RefObject } from 'react';
2
2
 
3
- /* Which edge the column hangs from, and with it the axis the drag runs along. A column pinned to
4
- the window's right edge grows as the pointer moves left; one that starts at its own left edge
5
- grows as it moves right, and the same holds for a row between `top` and `bottom`. */
3
+ /* The edge the column hangs from, and with it the axis the drag runs along. A column pinned to the right edge grows as the pointer moves left. */
6
4
  export type ColumnEdge = 'left' | 'right' | 'top' | 'bottom';
7
5
 
8
6
  export interface ColumnResizeOptions {
9
- /* What the column is wide, or the row is tall, right now, in whole pixels. */
7
+ /* In whole pixels. */
10
8
  size: number;
9
+
11
10
  min: number;
12
11
  from: ColumnEdge;
12
+
13
13
  /* Read at drag time, so a window resize between two drags is taken into account. */
14
14
  max?(): number;
15
- /* Where the dragged size goes; called on every move, so the column follows the pointer. */
15
+
16
+ /* Called on every move. */
16
17
  onSize(size: number): void;
17
18
  }
18
19
 
20
+ const DRAG_ENDS = ['pointerup', 'pointercancel', 'lostpointercapture'] as const;
21
+
19
22
  export interface ColumnResize {
20
23
  startResize(event: ReactPointerEvent<HTMLElement>): void;
21
24
  }
22
25
 
23
26
  export const clampColumnSize = (options: Pick<ColumnResizeOptions, 'min' | 'max'>, size: number): number => {
24
27
  const max = Math.max(options.min, options.max?.() ?? Number.MAX_SAFE_INTEGER);
28
+
25
29
  return Math.max(options.min, Math.min(Math.round(size), max));
26
30
  };
27
31
 
28
- /* Where the pointer sits on the axis this edge belongs to. */
29
32
  const along = (from: ColumnEdge, event: { clientX: number; clientY: number }): number => (from === 'left' || from === 'right' ? event.clientX : event.clientY);
30
33
 
31
34
  /*
32
- * One resizable column or row: a handle that drags the size its owner keeps. The handle takes the
33
- * pointer capture, so the drag survives leaving the few pixels it is wide, and `[data-resizing]` on
34
- * the column turns off any transition on that size for as long as it lasts.
35
+ * The handle takes the pointer capture, so the drag survives leaving the few pixels it is wide, and
36
+ * `[data-resizing]` on the column turns off any transition on that size for as long as it lasts.
35
37
  */
36
38
  export const useColumnResize = (ref: RefObject<HTMLElement | null>, options: ColumnResizeOptions): ColumnResize => {
37
39
  const startResize = (event: ReactPointerEvent<HTMLElement>): void => {
@@ -44,25 +46,32 @@ export const useColumnResize = (ref: RefObject<HTMLElement | null>, options: Col
44
46
  /* The far edge grows as the pointer comes towards it; the near edge grows as it goes away. */
45
47
  const growsTowardsAnchor = options.from === 'right' || options.from === 'bottom';
46
48
  column?.setAttribute('data-resizing', 'true');
49
+
47
50
  const onMove = (move: PointerEvent): void => {
48
51
  const delta = along(options.from, move) - start;
49
52
  options.onSize(clampColumnSize(options, size + (growsTowardsAnchor ? -delta : delta)));
50
53
  };
54
+
51
55
  const onUp = (): void => {
52
56
  handle.removeEventListener('pointermove', onMove);
53
- handle.removeEventListener('pointerup', onUp);
54
- handle.removeEventListener('pointercancel', onUp);
55
- handle.removeEventListener('lostpointercapture', onUp);
57
+
58
+ for (const type of DRAG_ENDS) {
59
+ handle.removeEventListener(type, onUp);
60
+ }
61
+
56
62
  if (handle.hasPointerCapture(event.pointerId)) {
57
63
  handle.releasePointerCapture(event.pointerId);
58
64
  }
65
+
59
66
  column?.removeAttribute('data-resizing');
60
67
  };
68
+
61
69
  handle.setPointerCapture(event.pointerId);
62
70
  handle.addEventListener('pointermove', onMove);
63
- handle.addEventListener('pointerup', onUp);
64
- handle.addEventListener('pointercancel', onUp);
65
- handle.addEventListener('lostpointercapture', onUp);
71
+
72
+ for (const type of DRAG_ENDS) {
73
+ handle.addEventListener(type, onUp);
74
+ }
66
75
  };
67
76
 
68
77
  return { startResize };
@@ -10,18 +10,23 @@ export function useContentSize(): [(node: HTMLElement | null) => void, ContentSi
10
10
  const [size, setSize] = useState<ContentSize>({ width: 0, height: 0 });
11
11
  const observer = useRef<ResizeObserver | null>(null);
12
12
  useEffect(() => () => observer.current?.disconnect(), []);
13
+
13
14
  const measure = (node: HTMLElement | null): void => {
14
15
  observer.current?.disconnect();
16
+
15
17
  if (node === null) {
16
18
  return;
17
19
  }
20
+
18
21
  const update = (width: number, height: number): void => {
19
22
  const next = { width: Math.round(width), height: Math.round(height) };
20
23
  setSize((current) => (current.width === next.width && current.height === next.height ? current : next));
21
24
  };
25
+
22
26
  update(node.clientWidth, node.clientHeight);
23
27
  observer.current = new ResizeObserver(([entry]) => update(entry!.contentRect.width, entry!.contentRect.height));
24
28
  observer.current.observe(node);
25
29
  };
30
+
26
31
  return [measure, size];
27
32
  }
@@ -3,5 +3,6 @@ import { useContentSize } from './useContentSize.ts';
3
3
  /* A plot draws in real pixels rather than a stretched view box, so what it draws lands on whole ones. */
4
4
  export function useMeasuredWidth(): [(node: HTMLElement | null) => void, number] {
5
5
  const [measure, size] = useContentSize();
6
+
6
7
  return [measure, size.width];
7
8
  }
package/src/useNow.ts CHANGED
@@ -1,10 +1,6 @@
1
1
  import { useCallback, useEffect, useRef, useState, type RefObject } from 'react';
2
2
 
3
- /*
4
- * The clock a surface reads to say how long something has been running. One timer for the whole
5
- * surface, ticking only while there is something to count, and starting at the time it was mounted
6
- * so a panel that comes back mid-session does not read zero for a second first.
7
- */
3
+ /* Starts at the time it was mounted, so a panel that comes back mid-session does not read zero for a second first. */
8
4
  export const useNow = (intervalMs: number, ticking = true): number => {
9
5
  const [now, setNow] = useState(Date.now);
10
6
 
@@ -12,17 +8,16 @@ export const useNow = (intervalMs: number, ticking = true): number => {
12
8
  if (!ticking) {
13
9
  return;
14
10
  }
11
+
15
12
  const timer = window.setInterval(() => setNow(Date.now()), intervalMs);
13
+
16
14
  return () => window.clearInterval(timer);
17
15
  }, [intervalMs, ticking]);
18
16
 
19
17
  return now;
20
18
  };
21
19
 
22
- /*
23
- * A timer on one line of a long thread. The text is written straight into the node, because a chat
24
- * that re-renders every second while an agent works re-renders every row it is drawing with it.
25
- */
20
+ /* Written straight into the node: a thread that re-renders every second re-renders every row it draws with it. */
26
21
  export const useTickingText = (render: () => string, intervalMs = 1000): RefObject<HTMLSpanElement | null> => {
27
22
  const ref = useRef<HTMLSpanElement>(null);
28
23
  const latest = useRef(render);
@@ -35,11 +30,11 @@ export const useTickingText = (render: () => string, intervalMs = 1000): RefObje
35
30
 
36
31
  useEffect(() => {
37
32
  const timer = window.setInterval(tick, intervalMs);
33
+
38
34
  return () => window.clearInterval(timer);
39
35
  }, [intervalMs, tick]);
40
36
 
41
- /* After every render as well, so the first one writes the line and a language or region change
42
- lands on it without waiting for the next tick. */
37
+ /* After every render as well, so a language or region change lands without waiting for the next tick. */
43
38
  useEffect(() => {
44
39
  latest.current = render;
45
40
  tick();
@@ -5,19 +5,17 @@ import { dropPreviewPath, type DropPreviewShape } from './preview.ts';
5
5
  export interface DropPreviewProps {
6
6
  /* Where the drop would land, in the coordinates of the box the preview fills; null draws nothing. */
7
7
  shape: DropPreviewShape | null;
8
+
8
9
  className?: string;
9
10
  ref?: Ref<SVGSVGElement>;
10
11
  }
11
12
 
12
- /*
13
- * The outline `SplitView` draws where a drag would land, for an app with a drag layer of its own. It
14
- * fills its nearest positioned ancestor. One shape moves into the next as a transition of its path,
15
- * and the first shape after a null appears in place.
16
- */
13
+ /* For an app with a drag layer of its own. It fills its nearest positioned ancestor; the first shape after a null appears in place. */
17
14
  export function DropPreview({ shape, className, ref }: DropPreviewProps) {
18
15
  if (shape === null) {
19
16
  return null;
20
17
  }
18
+
21
19
  return (
22
20
  <svg ref={ref} className={clsx('ade-drop-preview', className)} width="100%" height="100%" aria-hidden="true">
23
21
  <path style={{ d: `path("${dropPreviewPath(shape)}")` }} />