@quillmark/svelte 0.17.0 → 0.19.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.
- package/README.md +1 -1
- package/dist/core/codec/index.d.ts +0 -3
- package/dist/core/rebind.svelte.js +5 -8
- package/dist/preset/index.d.ts +1 -0
- package/dist/preview/Preview.svelte +14 -19
- package/dist/preview/Preview.svelte.d.ts +3 -3
- package/dist/preview/index.d.ts +0 -1
- package/dist/visual/ArrayField.svelte +48 -49
- package/dist/visual/ArrayField.svelte.d.ts +1 -2
- package/dist/visual/BooleanField.svelte +9 -14
- package/dist/visual/BooleanField.svelte.d.ts +0 -1
- package/dist/visual/Card.svelte +5 -18
- package/dist/visual/CardControls.svelte.d.ts +0 -1
- package/dist/visual/DateField.svelte +14 -17
- package/dist/visual/DateField.svelte.d.ts +0 -1
- package/dist/visual/EnumField.svelte +9 -12
- package/dist/visual/EnumField.svelte.d.ts +0 -1
- package/dist/visual/Field.svelte +8 -9
- package/dist/visual/FieldHint.svelte.d.ts +0 -1
- package/dist/visual/FieldLabel.svelte +1 -2
- package/dist/visual/FormatPopover.svelte +22 -27
- package/dist/visual/FormatPopover.svelte.d.ts +0 -1
- package/dist/visual/MatrixAdd.svelte +1 -2
- package/dist/visual/MatrixAdd.svelte.d.ts +0 -1
- package/dist/visual/MatrixField.svelte +3 -5
- package/dist/visual/MatrixField.svelte.d.ts +0 -1
- package/dist/visual/NumberField.svelte +6 -10
- package/dist/visual/NumberField.svelte.d.ts +0 -1
- package/dist/visual/ObjectField.svelte +9 -13
- package/dist/visual/ObjectField.svelte.d.ts +0 -1
- package/dist/visual/ProseField.svelte +42 -41
- package/dist/visual/ProseField.svelte.d.ts +0 -1
- package/dist/visual/ProseValue.svelte +56 -56
- package/dist/visual/ProseValue.svelte.d.ts +0 -1
- package/dist/visual/SlashMenu.svelte +5 -15
- package/dist/visual/SlashMenu.svelte.d.ts +0 -1
- package/dist/visual/TextField.svelte +5 -9
- package/dist/visual/TextField.svelte.d.ts +0 -1
- package/dist/visual/TipsCard.svelte +10 -11
- package/dist/visual/TipsCard.svelte.d.ts +0 -1
- package/dist/visual/VisualEditorInner.svelte +6 -14
- package/dist/visual/VisualEditorInner.svelte.d.ts +0 -1
- package/dist/visual/index.d.ts +0 -1
- package/dist/visual/structure.d.ts +4 -0
- package/dist/visual/structure.js +12 -0
- package/package.json +20 -16
- package/dist/visual/synced.svelte.d.ts +0 -15
- package/dist/visual/synced.svelte.js +0 -34
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
<script lang="ts">
|
|
16
16
|
import { Select } from 'bits-ui';
|
|
17
17
|
import Icon from './icons/Icon.svelte';
|
|
18
|
-
import { syncedLocal } from './synced.svelte.js';
|
|
19
18
|
import { wording } from './strings.js';
|
|
20
19
|
import './controls.css';
|
|
21
20
|
|
|
@@ -67,23 +66,21 @@
|
|
|
67
66
|
// the like), so it never collides with a real option.
|
|
68
67
|
const UNSET = '__qm_unset__';
|
|
69
68
|
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
// the package's rather than the primitive's.
|
|
74
|
-
const local = syncedLocal(() => value ?? UNSET);
|
|
69
|
+
// Driven controlled (`value` + `onValueChange`, never `bind:`) so reconciliation
|
|
70
|
+
// stays the package's rather than the primitive's.
|
|
71
|
+
let local = $derived(value ?? UNSET);
|
|
75
72
|
|
|
76
73
|
/** The blank has no glyph of its own: the quill's `ui.blank_title` stands in for
|
|
77
74
|
* it, else an em dash. */
|
|
78
75
|
const dash = (v: string | undefined) => v || blankTitle || '—';
|
|
79
76
|
|
|
80
|
-
const unset = $derived(local
|
|
77
|
+
const unset = $derived(local === UNSET);
|
|
81
78
|
/** The ghost is a member the render prints, rather than a word for the blank or for
|
|
82
79
|
* `none`: it draws at the default rung (theme.css). */
|
|
83
80
|
const ghostsMember = $derived(!optional && !!fallback);
|
|
84
81
|
const ghostText = $derived(optional ? t.strings.optionalGhost : dash(fallback));
|
|
85
82
|
/** What the closed trigger shows: the pick, or the ghosted default while unset. */
|
|
86
|
-
const shown = $derived(unset ? ghostText : dash(local
|
|
83
|
+
const shown = $derived(unset ? ghostText : dash(local));
|
|
87
84
|
|
|
88
85
|
/** The root to portal into: `document.body` would escape the consumer's dials
|
|
89
86
|
* along with the editor's subtree. `undefined` falls back to bits-ui's default. */
|
|
@@ -99,10 +96,10 @@
|
|
|
99
96
|
<Select.Root
|
|
100
97
|
type="single"
|
|
101
98
|
allowDeselect={false}
|
|
102
|
-
value={local
|
|
99
|
+
value={local}
|
|
103
100
|
onValueChange={(v) => {
|
|
104
101
|
if (v == null) return;
|
|
105
|
-
local
|
|
102
|
+
local = v;
|
|
106
103
|
// Sentinel → unset (parent `removeField`, default renders); any real pick
|
|
107
104
|
// (incl. the default value) → a genuine write.
|
|
108
105
|
onCommit(v === UNSET ? undefined : v);
|
|
@@ -152,12 +149,12 @@
|
|
|
152
149
|
policy when it is the one selected: a listbox whose selected value
|
|
153
150
|
has no row shows nothing for what the document says, and offers none
|
|
154
151
|
for the primitive to mark, type-ahead to, or key onto
|
|
155
|
-
(VISUAL_EDITOR §"Enum policy"). Off `local
|
|
152
|
+
(VISUAL_EDITOR §"Enum policy"). Off `local`, not `value`: the
|
|
156
153
|
row that exists is the row the control has selected, through the
|
|
157
154
|
window where an own pick has not reconciled back. -->
|
|
158
155
|
{#each values as v (v)}
|
|
159
156
|
{@const allowed = optionAllowed?.(v) !== false}
|
|
160
|
-
{#if allowed || enumDisallowed === 'disable' || v === local
|
|
157
|
+
{#if allowed || enumDisallowed === 'disable' || v === local}
|
|
161
158
|
<Select.Item
|
|
162
159
|
class="qm-menu-item qm-select-item"
|
|
163
160
|
value={v}
|
package/dist/visual/Field.svelte
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
isContainer,
|
|
39
39
|
optionalCell,
|
|
40
40
|
printedText,
|
|
41
|
+
renderedBoolean,
|
|
41
42
|
stringifyGhost,
|
|
42
43
|
titleText
|
|
43
44
|
} from './structure.js';
|
|
@@ -235,12 +236,10 @@
|
|
|
235
236
|
* an address to name. `el` is the field-wide box, which a landing at depth does not
|
|
236
237
|
* wash: that lane hands back its own row (`leaves.ts`).
|
|
237
238
|
*/
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (field.control === 'prose' || !controlEl || !leaves) return;
|
|
239
|
+
function registerControl(wrapper: HTMLElement): (() => void) | undefined {
|
|
240
|
+
if (field.control === 'prose' || !leaves) return;
|
|
241
241
|
const key = leafKey;
|
|
242
242
|
const registry = leaves;
|
|
243
|
-
const wrapper = controlEl;
|
|
244
243
|
registry.registerControl(key, {
|
|
245
244
|
focus: focusControl,
|
|
246
245
|
focusPath: isContainer(field.control) ? focusPath : undefined,
|
|
@@ -249,7 +248,7 @@
|
|
|
249
248
|
}
|
|
250
249
|
});
|
|
251
250
|
return () => registry.unregisterControl(key);
|
|
252
|
-
}
|
|
251
|
+
}
|
|
253
252
|
|
|
254
253
|
/**
|
|
255
254
|
* A form control has no controller to report its focus through, so the wrapper
|
|
@@ -262,7 +261,7 @@
|
|
|
262
261
|
const reportFocus = $derived(field.control === 'prose' ? undefined : () => onFocus?.(addr));
|
|
263
262
|
</script>
|
|
264
263
|
|
|
265
|
-
<div class=
|
|
264
|
+
<div class={['qm-field', { cell: span === 'cell' }]}>
|
|
266
265
|
{#if !ownsLabel && !headed}
|
|
267
266
|
<FieldLabel
|
|
268
267
|
label={field.label}
|
|
@@ -279,7 +278,7 @@
|
|
|
279
278
|
rule below holds why). One markup for both spans; a field owning its row nests a
|
|
280
279
|
level and measures the same. -->
|
|
281
280
|
<div class="qm-field-stack">
|
|
282
|
-
<div class="qm-field-control"
|
|
281
|
+
<div class="qm-field-control" {@attach registerControl} onfocusin={reportFocus}>
|
|
283
282
|
{#if field.control === 'prose'}
|
|
284
283
|
<ProseField
|
|
285
284
|
{quill}
|
|
@@ -342,7 +341,7 @@
|
|
|
342
341
|
/>
|
|
343
342
|
{:else if field.control === 'boolean'}
|
|
344
343
|
<BooleanField
|
|
345
|
-
value={value
|
|
344
|
+
value={renderedBoolean(value)}
|
|
346
345
|
fallback={ghost as boolean | undefined}
|
|
347
346
|
{optional}
|
|
348
347
|
id={domIds.control}
|
|
@@ -362,7 +361,7 @@
|
|
|
362
361
|
{:else if field.control === 'array'}
|
|
363
362
|
<ArrayField
|
|
364
363
|
bind:this={arrayEl}
|
|
365
|
-
|
|
364
|
+
{value}
|
|
366
365
|
fallback={Array.isArray(field.schema.default) ? field.schema.default : undefined}
|
|
367
366
|
items={field.schema.items}
|
|
368
367
|
layout={arrayLayout(field.schema)}
|
|
@@ -65,8 +65,7 @@
|
|
|
65
65
|
label is not a tab stop, and the control it hands focus to is already the
|
|
66
66
|
keyboard's own way in. -->
|
|
67
67
|
<label
|
|
68
|
-
class=
|
|
69
|
-
class:targeted={controlId != null || onActivate != null}
|
|
68
|
+
class={['qm-field-label', { targeted: controlId != null || onActivate != null }]}
|
|
70
69
|
{id}
|
|
71
70
|
for={controlId}
|
|
72
71
|
onclick={onActivate}
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
selection the command is supposed to act on.
|
|
53
53
|
-->
|
|
54
54
|
<script lang="ts">
|
|
55
|
+
import { onDestroy } from 'svelte';
|
|
55
56
|
import { wording } from './strings.js';
|
|
56
57
|
|
|
57
58
|
// The surface's words, ambient from the editor root; the package's English
|
|
@@ -105,7 +106,6 @@
|
|
|
105
106
|
/** Whether the selection carried a link when the prompt was raised. */
|
|
106
107
|
let linkPresent = $state(false);
|
|
107
108
|
let linkValue = $state('');
|
|
108
|
-
let linkInputEl = $state<HTMLInputElement | undefined>(undefined);
|
|
109
109
|
let contentEl = $state<HTMLElement | undefined>(undefined);
|
|
110
110
|
/** The root to portal into: `document.body` escapes the editor's subtree and
|
|
111
111
|
* the consumer's dials with it, so a pane-scoped palette misses this surface.
|
|
@@ -212,25 +212,11 @@
|
|
|
212
212
|
deferredSync();
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
document.addEventListener('pointerdown', onPointerDown, true);
|
|
221
|
-
document.addEventListener('pointerup', onPointerUp, true);
|
|
222
|
-
document.addEventListener('pointercancel', onPointerUp, true);
|
|
223
|
-
return () => {
|
|
224
|
-
document.removeEventListener('selectionchange', deferredSync);
|
|
225
|
-
document.removeEventListener('focusout', deferredSync);
|
|
226
|
-
document.removeEventListener('pointerdown', onPointerDown, true);
|
|
227
|
-
document.removeEventListener('pointerup', onPointerUp, true);
|
|
228
|
-
document.removeEventListener('pointercancel', onPointerUp, true);
|
|
229
|
-
// A burst-coalescing rAF may still be in flight at teardown; cancel it so
|
|
230
|
-
// `sync()` never runs against the destroyed component.
|
|
231
|
-
if (pending) cancelAnimationFrame(rafHandle);
|
|
232
|
-
pending = false;
|
|
233
|
-
};
|
|
215
|
+
// A burst-coalescing rAF may still be in flight at teardown; cancel it so
|
|
216
|
+
// `sync()` never runs against the destroyed component.
|
|
217
|
+
onDestroy(() => {
|
|
218
|
+
if (pending) cancelAnimationFrame(rafHandle);
|
|
219
|
+
pending = false;
|
|
234
220
|
});
|
|
235
221
|
|
|
236
222
|
// Any close path that bypasses `sync` (e.g. Escape while the link input holds
|
|
@@ -245,12 +231,10 @@
|
|
|
245
231
|
// surface already open, so without this the URL is typed into the document.
|
|
246
232
|
// Selected rather than merely focused, the input arriving seeded and replacing
|
|
247
233
|
// that value being what the prompt is usually raised for.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
});
|
|
234
|
+
function focusPrompt(input: HTMLInputElement): void {
|
|
235
|
+
input.focus();
|
|
236
|
+
input.select();
|
|
237
|
+
}
|
|
254
238
|
|
|
255
239
|
/** Swallow the button's mousedown so focus/selection never leave the leaf. */
|
|
256
240
|
function keepFocus(e: MouseEvent): void {
|
|
@@ -338,6 +322,17 @@
|
|
|
338
322
|
}
|
|
339
323
|
</script>
|
|
340
324
|
|
|
325
|
+
<!-- The pointer handlers capture, so the gesture is seen whatever a view in the middle of
|
|
326
|
+
it does with the event. `pointercancel` is the release a gesture the browser takes
|
|
327
|
+
over gets. -->
|
|
328
|
+
<svelte:document
|
|
329
|
+
onselectionchange={deferredSync}
|
|
330
|
+
onfocusout={deferredSync}
|
|
331
|
+
onpointerdowncapture={onPointerDown}
|
|
332
|
+
onpointerupcapture={onPointerUp}
|
|
333
|
+
onpointercancelcapture={onPointerUp}
|
|
334
|
+
/>
|
|
335
|
+
|
|
341
336
|
<Popover.Root bind:open>
|
|
342
337
|
<Popover.Portal to={portalTarget}>
|
|
343
338
|
<!-- A flip is measured against the root the surface portals into, which is the box
|
|
@@ -383,7 +378,7 @@
|
|
|
383
378
|
}}
|
|
384
379
|
>
|
|
385
380
|
<input
|
|
386
|
-
|
|
381
|
+
{@attach focusPrompt}
|
|
387
382
|
class="qm-link-input"
|
|
388
383
|
type="text"
|
|
389
384
|
placeholder={t.strings.linkPlaceholder}
|
|
@@ -114,8 +114,7 @@
|
|
|
114
114
|
<!-- The input keeps focus and owns the keys; a press picks without taking it. -->
|
|
115
115
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
116
116
|
<div
|
|
117
|
-
class=
|
|
118
|
-
class:add={option.add}
|
|
117
|
+
class={['qm-menu-item qm-matrix-add-option', { add: option.add }]}
|
|
119
118
|
role="option"
|
|
120
119
|
id={optionId(k)}
|
|
121
120
|
tabindex="-1"
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
/** A native checkbox carries its own state, so a commit the document declines leaves
|
|
192
192
|
* the face ticked over a map that says otherwise, `checked={held(id)}` having nothing
|
|
193
193
|
* new to write. The reassert after the flush is what the styled controls get from
|
|
194
|
-
* their
|
|
194
|
+
* their local, a `$derived` of the value: the document is what the face reads. */
|
|
195
195
|
async function tick(id: string, on: boolean, el: HTMLInputElement): Promise<void> {
|
|
196
196
|
commit(on ? commitMember(map, id, ticked(id)) : unticked(id));
|
|
197
197
|
await flush();
|
|
@@ -333,15 +333,13 @@
|
|
|
333
333
|
</span>
|
|
334
334
|
</div>
|
|
335
335
|
<div
|
|
336
|
-
class=
|
|
337
|
-
class:qm-tracks={compact}
|
|
338
|
-
class:compact
|
|
336
|
+
class={['qm-matrix-roster', { 'qm-tracks': compact, compact }]}
|
|
339
337
|
data-default={defaulted ? '' : undefined}
|
|
340
338
|
bind:this={rosterEl}
|
|
341
339
|
>
|
|
342
340
|
{#each members as m (m.id)}
|
|
343
341
|
{@const on = held(m.id)}
|
|
344
|
-
<div class=
|
|
342
|
+
<div class={['qm-member', { held: on }]} bind:this={memberEls[m.id]}>
|
|
345
343
|
<div class="qm-member-head">
|
|
346
344
|
<!-- A real checkbox with its face drawn here: the UA's face is shadow DOM no
|
|
347
345
|
dial reaches, so the input is `appearance: none` and the box and the
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Content, PathStep, QuillFieldSchema } from '@quillmark/wasm';
|
|
2
2
|
import { type DeepDiagnostic } from './diagnostics.js';
|
|
3
3
|
import type { LandingBox } from './leaves.js';
|
|
4
|
-
import './controls.css';
|
|
5
4
|
interface Props {
|
|
6
5
|
/** The stored sparse map, or undefined while the field is unset. */
|
|
7
6
|
value: Record<string, unknown> | undefined;
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
numbers, and `1.000` as a whole one.
|
|
30
30
|
-->
|
|
31
31
|
<script lang="ts">
|
|
32
|
-
import { syncedLocal } from './synced.svelte.js';
|
|
33
32
|
import './controls.css';
|
|
34
33
|
|
|
35
34
|
interface Props {
|
|
@@ -53,11 +52,8 @@
|
|
|
53
52
|
}
|
|
54
53
|
let { value, integer, fallback, placeholder, label, id, describedBy, onCommit }: Props = $props();
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// back in (see `syncedLocal`).
|
|
59
|
-
const local = syncedLocal(() => (value != null ? String(value) : (fallback ?? '')));
|
|
60
|
-
const defaulted = $derived(value == null && !!fallback && local.value === fallback);
|
|
55
|
+
let local = $derived(value != null ? String(value) : (fallback ?? ''));
|
|
56
|
+
const defaulted = $derived(value == null && !!fallback && local === fallback);
|
|
61
57
|
|
|
62
58
|
const partial = $derived(
|
|
63
59
|
integer ? /^\s*[+-]?\d*\s*$/ : /^\s*[+-]?\d*\.?\d*(?:[eE][+-]?\d*)?\s*$/
|
|
@@ -77,7 +73,7 @@
|
|
|
77
73
|
// `insertText` lands on the undo stack and fires `input`; `setRangeText` does neither.
|
|
78
74
|
if (document.execCommand?.('insertText', false, data)) return;
|
|
79
75
|
el.setRangeText(data, from, to, 'end');
|
|
80
|
-
local
|
|
76
|
+
local = el.value;
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
// Parse a settled entry and emit it; `local` is owned by `oninput`. Blank →
|
|
@@ -89,7 +85,7 @@
|
|
|
89
85
|
const entry = raw.trim();
|
|
90
86
|
if (entry === '') {
|
|
91
87
|
onCommit(undefined);
|
|
92
|
-
local
|
|
88
|
+
local = el.value = fallback ?? '';
|
|
93
89
|
return;
|
|
94
90
|
}
|
|
95
91
|
// Finite as well: a digit run past `Number.MAX_VALUE` reads as `Infinity`.
|
|
@@ -102,7 +98,7 @@
|
|
|
102
98
|
class="qm-input qm-focus-ring"
|
|
103
99
|
type="text"
|
|
104
100
|
inputmode={integer ? 'numeric' : 'decimal'}
|
|
105
|
-
value={local
|
|
101
|
+
value={local}
|
|
106
102
|
{id}
|
|
107
103
|
{placeholder}
|
|
108
104
|
data-default={defaulted ? '' : undefined}
|
|
@@ -110,7 +106,7 @@
|
|
|
110
106
|
aria-describedby={describedBy}
|
|
111
107
|
onbeforeinput={filter}
|
|
112
108
|
oninput={(e) => {
|
|
113
|
-
local
|
|
109
|
+
local = (e.currentTarget as HTMLInputElement).value;
|
|
114
110
|
}}
|
|
115
111
|
onchange={(e) => commit(e.currentTarget as HTMLInputElement)}
|
|
116
112
|
/>
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
ProseValue}) over the boundary's nested read, and a container property — an `array`,
|
|
6
6
|
an `object`, a `matrix`, a variant — mounts its own control here, at the next rung of
|
|
7
7
|
the same figure. Depth is bounded by what is open, not by the schema: a repeater inside
|
|
8
|
-
a subform is collapsed rows again,
|
|
9
|
-
|
|
8
|
+
a subform is collapsed rows again, so an open row under an open row is one figure two
|
|
9
|
+
rungs in.
|
|
10
10
|
|
|
11
11
|
The nesting is a vertical at `--_qm-border`, the ladder's last stroke: the card's edge,
|
|
12
12
|
an open section's vertical one `--_qm-nest` in where the field is in one, this one a rung
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
obliged,
|
|
49
49
|
optionalCell,
|
|
50
50
|
printedText,
|
|
51
|
+
renderedBoolean,
|
|
51
52
|
shortCell
|
|
52
53
|
} from './structure.js';
|
|
53
54
|
import { splitDeep, unrouted, type DeepDiagnostic } from './diagnostics.js';
|
|
@@ -255,9 +256,7 @@
|
|
|
255
256
|
|
|
256
257
|
<div
|
|
257
258
|
bind:this={rootEl}
|
|
258
|
-
class=
|
|
259
|
-
class:qm-object-bare={bare}
|
|
260
|
-
class:qm-object-flat={flat}
|
|
259
|
+
class={['qm-object', { 'qm-object-bare': bare, 'qm-object-flat': flat }]}
|
|
261
260
|
role="group"
|
|
262
261
|
aria-labelledby={labelledBy}
|
|
263
262
|
aria-describedby={describedBy}
|
|
@@ -286,9 +285,7 @@
|
|
|
286
285
|
(`packable`). A block prose cell spans it too, and keeps its label row. -->
|
|
287
286
|
{@const block = kind === 'prose' && !shortCell(sub)}
|
|
288
287
|
<div
|
|
289
|
-
class=
|
|
290
|
-
class:qm-prop-full={isContainer(kind)}
|
|
291
|
-
class:qm-prop-wide={block}
|
|
288
|
+
class={['qm-object-prop', { 'qm-prop-full': isContainer(kind), 'qm-prop-wide': block }]}
|
|
292
289
|
data-qm-prop={key}
|
|
293
290
|
>
|
|
294
291
|
{#if ids && kind !== 'array' && kind !== 'matrix'}
|
|
@@ -330,7 +327,7 @@
|
|
|
330
327
|
label={named}
|
|
331
328
|
id={ids?.control}
|
|
332
329
|
describedBy={describes}
|
|
333
|
-
value={obj[key]
|
|
330
|
+
value={renderedBoolean(obj[key])}
|
|
334
331
|
fallback={sub.default as boolean | undefined}
|
|
335
332
|
optional={optionalCell(sub)}
|
|
336
333
|
onCommit={(v) => commitProp(key, v)}
|
|
@@ -375,12 +372,11 @@
|
|
|
375
372
|
{:else if kind === 'array'}
|
|
376
373
|
<!-- The repeater owns its label row, the add chip sharing it (`Field` skips
|
|
377
374
|
its own label for one too), so the names are handed down rather than
|
|
378
|
-
drawn here. Its rows are collapsed rows again
|
|
379
|
-
|
|
380
|
-
and one figure. -->
|
|
375
|
+
drawn here. Its rows are collapsed rows again: the depth an open row
|
|
376
|
+
under an open row costs is two rungs of the ladder and one figure. -->
|
|
381
377
|
<ArrayField
|
|
382
378
|
bind:this={nestedEls[key]}
|
|
383
|
-
value={obj[key]
|
|
379
|
+
value={obj[key]}
|
|
384
380
|
fallback={Array.isArray(sub.default) ? sub.default : undefined}
|
|
385
381
|
items={sub.items}
|
|
386
382
|
layout={arrayLayout(sub)}
|
|
@@ -2,7 +2,6 @@ import type { Content, PathStep, QuillFieldSchema } from '@quillmark/wasm';
|
|
|
2
2
|
import { type DeepDiagnostic } from './diagnostics.js';
|
|
3
3
|
import type { LandingBox } from './leaves.js';
|
|
4
4
|
import ObjectField from './ObjectField.svelte';
|
|
5
|
-
import './controls.css';
|
|
6
5
|
interface Props {
|
|
7
6
|
value: Record<string, unknown> | undefined;
|
|
8
7
|
properties: Record<string, QuillFieldSchema> | undefined;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
this leaf's commits with the caret riding the untouched view.
|
|
6
6
|
-->
|
|
7
7
|
<script lang="ts">
|
|
8
|
-
import {
|
|
8
|
+
import { untrack } from 'svelte';
|
|
9
9
|
import { createField, type FieldController, type SlashState } from '../core/codec/index.js';
|
|
10
10
|
import type { LeafRegistry } from './leaves.js';
|
|
11
11
|
import SlashMenu from './SlashMenu.svelte';
|
|
@@ -82,7 +82,6 @@
|
|
|
82
82
|
diagnostics
|
|
83
83
|
}: Props = $props();
|
|
84
84
|
|
|
85
|
-
let containerEl: HTMLDivElement | undefined = $state();
|
|
86
85
|
let held = $state(false);
|
|
87
86
|
const uid = $props.id();
|
|
88
87
|
const heldId = `${uid}-held`;
|
|
@@ -108,41 +107,43 @@
|
|
|
108
107
|
controller?.focus();
|
|
109
108
|
}
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
110
|
+
function mountField(container: HTMLDivElement): () => void {
|
|
111
|
+
return untrack(() => {
|
|
112
|
+
const mounted = createField({
|
|
113
|
+
doc,
|
|
114
|
+
quill,
|
|
115
|
+
addr,
|
|
116
|
+
container,
|
|
117
|
+
inline,
|
|
118
|
+
plaintext,
|
|
119
|
+
label,
|
|
120
|
+
labelledBy,
|
|
121
|
+
describedBy,
|
|
122
|
+
fallback,
|
|
123
|
+
placeholder,
|
|
124
|
+
placeholderUntilEdit,
|
|
125
|
+
tableStrings: () => t.strings,
|
|
126
|
+
onSlash: (next) => {
|
|
127
|
+
slash = next;
|
|
128
|
+
},
|
|
129
|
+
onFocus,
|
|
130
|
+
onCaretMove,
|
|
131
|
+
onChange,
|
|
132
|
+
onError,
|
|
133
|
+
heldNoteId: heldId,
|
|
134
|
+
onHold: (next) => {
|
|
135
|
+
held = next;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
controller = mounted;
|
|
139
|
+
leaves?.registerProse(leafKey, mounted);
|
|
140
|
+
return () => {
|
|
141
|
+
leaves?.unregisterProse(leafKey);
|
|
142
|
+
mounted.destroy();
|
|
143
|
+
controller = undefined;
|
|
144
|
+
};
|
|
138
145
|
});
|
|
139
|
-
|
|
140
|
-
return () => {
|
|
141
|
-
leaves?.unregisterProse(leafKey);
|
|
142
|
-
controller?.destroy();
|
|
143
|
-
controller = undefined;
|
|
144
|
-
};
|
|
145
|
-
});
|
|
146
|
+
}
|
|
146
147
|
|
|
147
148
|
// A retype does not remount the leaf (its key is the card's session id), so the new
|
|
148
149
|
// kind's ghost and default are pushed into the live view rather than paid for with
|
|
@@ -162,11 +163,11 @@
|
|
|
162
163
|
</script>
|
|
163
164
|
|
|
164
165
|
<div
|
|
165
|
-
|
|
166
|
-
class=
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
{@attach mountField}
|
|
167
|
+
class={[
|
|
168
|
+
'qm-prose',
|
|
169
|
+
{ 'qm-control-box': !unframed, 'qm-focus-ring-within': !unframed, 'qm-prose-block': block }
|
|
170
|
+
]}
|
|
170
171
|
data-leaf-key={leafKey}
|
|
171
172
|
>
|
|
172
173
|
{#if held}
|