@quillmark/svelte 0.8.0 → 0.10.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.
@@ -116,7 +116,10 @@
116
116
  if (ids.length === n) return;
117
117
  if (ids.length < n) ids = [...ids, ...seq.take(n - ids.length)];
118
118
  else {
119
- for (const id of ids.slice(n)) delete els[id];
119
+ for (const id of ids.slice(n)) {
120
+ delete els[id];
121
+ delete rowEls[id];
122
+ }
120
123
  ids = ids.slice(0, n);
121
124
  }
122
125
  });
@@ -138,6 +141,11 @@
138
141
  // {@link Card} keeps its header/panel refs in.
139
142
  const els: Record<string, { focus: () => void; setCaret?: (pos: number) => void } | undefined> =
140
143
  $state({});
144
+ // The rows' own boxes, on the same key and dropped on the same paths: what an
145
+ // element landing blooms in ({@link focusElement}), and what an object row's
146
+ // summary is found through. A row is a box whatever its element type, so the two
147
+ // element shapes register alike.
148
+ const rowEls: Record<string, HTMLElement | undefined> = $state({});
141
149
  let addEl: HTMLButtonElement | undefined = $state();
142
150
  let rootEl: HTMLElement | undefined = $state();
143
151
  let rowsEl: HTMLElement | undefined = $state();
@@ -218,6 +226,7 @@
218
226
  const next = ids.filter((_, i) => i !== k);
219
227
  ids = next;
220
228
  delete els[dropped];
229
+ delete rowEls[dropped];
221
230
  // The open row can be the one removed; `openId` is cleared with it rather than
222
231
  // left naming an element that has gone.
223
232
  if (openId === dropped) openId = undefined;
@@ -241,13 +250,7 @@
241
250
  * the row's own summary otherwise — a collapsed row's control is its summary. */
242
251
  function focusObjectRow(id: string): void {
243
252
  if (id === openId && openObjEl) return openObjEl.focus();
244
- // Matched on the dataset: spelling an id into a selector needs `CSS.escape`,
245
- // which the test DOM does not carry.
246
- const rows = rowsEl?.querySelectorAll<HTMLElement>('[data-qm-el]') ?? [];
247
- for (const row of rows) {
248
- if (row.dataset.qmEl === id)
249
- return row.querySelector<HTMLElement>('.qm-element-summary')?.focus();
250
- }
253
+ rowEls[id]?.querySelector<HTMLElement>('.qm-element-summary')?.focus();
251
254
  }
252
255
  /** The box an arrival wash blooms in (`leaves.ts`, `core/bloom.ts`): the elements,
253
256
  * not the header above them. This component owns the field's label, so the wrapper
@@ -266,21 +269,33 @@
266
269
  *
267
270
  * An absent `pos` is the placement rung, exactly as on `Landing`. A row that takes
268
271
  * no offset gets the bare focus: the JSON element, whose textarea has no coordinate
269
- * to spend one in, and a `string` element, which the compile never addresses. */
270
- export function focusElement(k: number, pos?: number): void {
272
+ * to spend one in, and a `string` element, which the compile never addresses.
273
+ *
274
+ * The row's box comes back with the landing: the arrival wash is the address's own
275
+ * granularity, and one row is what an element address named. A fallback to the
276
+ * field answers `undefined`, which is the field's box again (`leaves.ts`). */
277
+ export function focusElement(k: number, pos?: number): HTMLElement | undefined {
278
+ const id = ids[k];
271
279
  // A collapsed row holds no control for a caret to land in, so opening it is part
272
280
  // of the landing rather than something the user does first: a preview click that
273
281
  // resolves into an element has to arrive somewhere the caret can sit.
274
282
  if (control === 'object') {
275
- const id = ids[k];
276
- if (id === undefined) return focus();
283
+ if (id === undefined) {
284
+ focus();
285
+ return undefined;
286
+ }
277
287
  openId = id;
278
- return void focusAfterFlush(id);
288
+ void focusAfterFlush(id);
289
+ return rowEls[id];
279
290
  }
280
- const el = els[ids[k]];
281
- if (!el) return focus();
282
- if (pos != null && el.setCaret) return el.setCaret(pos);
283
- el.focus();
291
+ const el = id === undefined ? undefined : els[id];
292
+ if (!el) {
293
+ focus();
294
+ return undefined;
295
+ }
296
+ if (pos != null && el.setCaret) el.setCaret(pos);
297
+ else el.focus();
298
+ return rowEls[id];
284
299
  }
285
300
  /** Focus element `id` after the flush, never in the same tick: a mutation commits
286
301
  * the array by value, so the parent re-derives and the row does not exist until
@@ -362,7 +377,7 @@
362
377
  {#if control === 'object'}
363
378
  {@const open = openId === id}
364
379
  {@const shown = elementTitle(k)}
365
- <div class="qm-array-row qm-element" class:open data-qm-el={id}>
380
+ <div class="qm-array-row qm-element" class:open bind:this={rowEls[id]}>
366
381
  <!-- The head is the row in collapsed form, and it is a box: the element IS
367
382
  a value, the way the enum trigger is, and the remove slab's grammar
368
383
  (the box's two end-side corners) needs corners to take. So a list of
@@ -404,7 +419,7 @@
404
419
  {/if}
405
420
  </div>
406
421
  {:else}
407
- <div class="qm-array-row">
422
+ <div class="qm-array-row" bind:this={rowEls[id]}>
408
423
  {#if control === 'prose'}
409
424
  <ProseValue
410
425
  bind:this={els[id]}
@@ -477,6 +492,10 @@
477
492
  position: relative;
478
493
  display: grid;
479
494
  grid-template-columns: minmax(0, 1fr);
495
+ /* The corners the row's wash takes, an element landing blooming here rather than
496
+ over the whole list (`focusElement`): the row draws no box of its own, so the
497
+ radius it lends the wash is the one its element's box draws. */
498
+ border-radius: var(--_qm-radius-inner);
480
499
  }
481
500
  /* The end inset the slab stands in, taken off whichever box the element drew:
482
501
  `.qm-input` is the text element and the JSON textarea, `.qm-control-box` the prose
@@ -32,7 +32,7 @@ interface Props {
32
32
  declare const ArrayField: import("svelte").Component<Props, {
33
33
  focus: () => void;
34
34
  washBox: () => HTMLElement | undefined;
35
- focusElement: (k: number, pos?: number) => void;
35
+ focusElement: (k: number, pos?: number) => HTMLElement | undefined;
36
36
  }, "">;
37
37
  type ArrayField = ReturnType<typeof ArrayField>;
38
38
  export default ArrayField;
@@ -147,7 +147,7 @@
147
147
  let arrayEl = $state<
148
148
  | {
149
149
  focus: () => void;
150
- focusElement: (k: number, pos?: number) => void;
150
+ focusElement: (k: number, pos?: number) => HTMLElement | undefined;
151
151
  washBox: () => HTMLElement | undefined;
152
152
  }
153
153
  | undefined
@@ -159,9 +159,11 @@
159
159
  document.getElementById(domIds.control)?.focus();
160
160
  }
161
161
  /** The array's per-ELEMENT landing, for the addresses the preview mints under an
162
- * array field (`leaves.ts`); read at the call, so it tracks the mounted repeater. */
163
- function focusElement(k: number, pos?: number): void {
164
- arrayEl?.focusElement(k, pos);
162
+ * array field (`leaves.ts`); read at the call, so it tracks the mounted repeater.
163
+ * The row it settled in comes back with it, the wash being the landing's own
164
+ * granularity. */
165
+ function focusElement(k: number, pos?: number): HTMLElement | undefined {
166
+ return arrayEl?.focusElement(k, pos);
165
167
  }
166
168
  // Only where `for` cannot reach; the labelable four are the browser's own, and a
167
169
  // second handler over them would be a focus the label already placed.
@@ -181,7 +183,8 @@
181
183
  * that does not remount.
182
184
  *
183
185
  * An array carries the per-element lane as well; no other control has elements for
184
- * an address to name.
186
+ * an address to name. `el` is the field-wide box, which an element landing does not
187
+ * wash: that lane hands back its own row (`leaves.ts`).
185
188
  */
186
189
  let controlEl = $state<HTMLElement | undefined>();
187
190
  $effect(() => {
@@ -640,12 +640,11 @@
640
640
  // did not resolve. `'cluster'` (and an absent granularity: the backend did not
641
641
  // report it, treat as exact) places the caret. An absent `pos` is the placement
642
642
  // rung and reaches the same floor.
643
- land(found, at.granularity === 'segment' ? undefined : at.pos);
644
- // Unconditional: a preview click is one discrete act, and its commonest target
645
- // is the leaf already focused (where landing a caret changes nothing on screen)
646
- // or one off-screen, where the browser's focus-scroll moves the page and leaves
647
- // the caret to be hunted for.
648
- bloomInside(found.control.el);
643
+ // The wash is unconditional, and takes the box the landing settled in: a preview
644
+ // click is one discrete act, and its commonest target is the leaf already focused
645
+ // (where landing a caret changes nothing on screen) or one off-screen, where the
646
+ // browser's focus-scroll moves the page and leaves the caret to be hunted for.
647
+ bloomInside(land(found, at.granularity === 'segment' ? undefined : at.pos));
649
648
  }
650
649
  /** The active leaf's controller: the formatting popover's observation seam.
651
650
  * `undefined` for a focused form control, which holds no marks to toggle. */
@@ -689,16 +688,25 @@
689
688
  * selection outright), which is also the whole of what a click on plate-placed ink
690
689
  * can mean. An element is no `createField` leaf, so the offset goes down the element
691
690
  * lane instead, where what it means is the row control's (`leaves.ts`).
691
+ *
692
+ * Hands back the box the arrival wash blooms in, which is the landing's own
693
+ * granularity rather than the registry's: an element lands in one row and says so
694
+ * over that row, and everything else — the field, and a row the document has since
695
+ * dropped — over the field's box.
692
696
  */
693
- function land(found: Landed, pos: number | undefined): void {
697
+ function land(found: Landed, pos: number | undefined): HTMLElement {
694
698
  if (found.element != null && found.control.focusElement) {
695
- return found.control.focusElement(found.element, pos);
699
+ return found.control.focusElement(found.element, pos) ?? found.control.el;
696
700
  }
697
701
  if (pos != null) {
698
702
  const prose = leaves.prose(found.key);
699
- if (prose) return prose.setCaret(pos);
703
+ if (prose) {
704
+ prose.setCaret(pos);
705
+ return found.control.el;
706
+ }
700
707
  }
701
708
  found.control.focus();
709
+ return found.control.el;
702
710
  }
703
711
 
704
712
  // ── The verbs, as instance exports ──────────────────────────────────────────
@@ -717,8 +725,7 @@
717
725
  export async function focusField(field: DocPath): Promise<void> {
718
726
  const found = await revealLeaf(field);
719
727
  if (!found) return missed(`no mounted field at ${field}`, field);
720
- land(found, undefined);
721
- bloomInside(found.control.el);
728
+ bloomInside(land(found, undefined));
722
729
  }
723
730
  /** Seed a card of `kind` and insert it at `at` (default: the end). Returns the new
724
731
  * card's session key, or `undefined` when the quill seeds no card of that kind. */
@@ -19,8 +19,13 @@ export interface FieldControl {
19
19
  * `Landing`, and what a `pos` means is the row control's, the same way focusing is.
20
20
  * The place rides the call because the registry stays parent-keyed: a per-element
21
21
  * key is positional, in a registry whose doctrine is dodging positional churn.
22
+ *
23
+ * Returns the element's own box, which is what the wash blooms in: the address
24
+ * named one row, and a wash over the whole repeater says the field where the click
25
+ * said the row. `undefined` is the fallback the index past the live list took, and
26
+ * reads as {@link el}.
22
27
  */
23
- focusElement?(index: number, pos?: number): void;
28
+ focusElement?(index: number, pos?: number): HTMLElement | undefined;
24
29
  readonly el: HTMLElement;
25
30
  }
26
31
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillmark/svelte",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Editor, live-preview and source surfaces over a Quillmark WASM session.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -66,7 +66,7 @@
66
66
  "check": "svelte-check --tsconfig ./tsconfig.json"
67
67
  },
68
68
  "peerDependencies": {
69
- "@quillmark/wasm": ">=0.108.3-0",
69
+ "@quillmark/wasm": ">=0.110.0-0",
70
70
  "svelte": "^5.0.0"
71
71
  },
72
72
  "dependencies": {