@quillmark/svelte 0.13.0 → 0.14.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/NOTICE +3 -2
- package/dist/core/codec/field.d.ts +3 -4
- package/dist/core/codec/field.js +1 -1
- package/dist/visual/Card.svelte +1 -0
- package/dist/visual/DateField.svelte +96 -13
- package/dist/visual/DateField.svelte.d.ts +5 -2
- package/dist/visual/Field.svelte +9 -7
- package/dist/visual/NumberField.svelte +43 -16
- package/dist/visual/ObjectField.svelte +1 -0
- package/dist/visual/ProseField.svelte +1 -1
- package/dist/visual/ProseField.svelte.d.ts +1 -1
- package/dist/visual/icons/nodes.d.ts +28 -0
- package/dist/visual/icons/nodes.js +14 -1
- package/dist/visual/strings.d.ts +3 -0
- package/dist/visual/strings.js +1 -0
- package/dist/visual/structure.js +11 -12
- package/package.json +1 -1
package/NOTICE
CHANGED
|
@@ -6,9 +6,10 @@ Copyright 2026 Nibs
|
|
|
6
6
|
This package contains SVG glyph geometry derived from Lucide, carried in source
|
|
7
7
|
rather than installed.
|
|
8
8
|
|
|
9
|
-
`visual/icons/nodes.ts` holds
|
|
9
|
+
`visual/icons/nodes.ts` holds nineteen verbatim from @lucide/svelte 1.28.0:
|
|
10
10
|
chevron-right, arrow-right, chevron-down, chevron-up, bold, italic, underline,
|
|
11
|
-
strikethrough, code, link, hash, info, plus, x, trash-2
|
|
11
|
+
strikethrough, code, link, hash, info, plus, x, trash-2, minus, check, calendar
|
|
12
|
+
and calendar-check.
|
|
12
13
|
|
|
13
14
|
`core/codec/table-view.ts` holds a plus and four text-alignment glyphs derived
|
|
14
15
|
from an earlier release of the same project.
|
|
@@ -71,10 +71,9 @@ export interface CreateFieldOpts {
|
|
|
71
71
|
* a body's invitation. The initial value; {@link FieldController.setPlaceholder}
|
|
72
72
|
* moves it after mount. Empty/absent shows no ghost. */
|
|
73
73
|
placeholder?: string;
|
|
74
|
-
/** The placeholder
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* whenever the leaf is empty. */
|
|
74
|
+
/** The placeholder goes at the leaf's first edit: the edit answers the leaf, and
|
|
75
|
+
* emptied it holds an empty answer, which prints empty. Absent, the placeholder
|
|
76
|
+
* returns whenever the leaf is empty. */
|
|
78
77
|
placeholderUntilEdit?: boolean;
|
|
79
78
|
/** Ghost text shown on the empty leaf until its first edit: an unset field's
|
|
80
79
|
* `example:`, at rest where there is no placeholder and in the placeholder's stead
|
package/dist/core/codec/field.js
CHANGED
|
@@ -598,7 +598,7 @@ function pastAtomPlugin() {
|
|
|
598
598
|
* `data-placeholder` is drawn at rest, and `data-example` where there is none, and in
|
|
599
599
|
* its stead while the view holds the focus. The example is stamped only until the
|
|
600
600
|
* state's first edit, after which the leaf has been answered, and so is a placeholder
|
|
601
|
-
*
|
|
601
|
+
* under `placeholderUntilEdit`. An inline leaf's
|
|
602
602
|
* ghost keeps to the one line the leaf is (`qm-prose-placeholder-line`).
|
|
603
603
|
*
|
|
604
604
|
* The texts are read per decoration pass rather than closed over, so moving a ghost is
|
package/dist/visual/Card.svelte
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
A `date` (or `datetime`) field → a styled segmented date field on bits-ui. The
|
|
3
|
-
stored value is a string
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
stored value is a string, `YYYY-MM-DD` or `today`; a cleared control commits
|
|
4
|
+
`undefined` (the unset rung): the parent removes the field, and the quill's
|
|
5
|
+
`default:` applies. The value-object a date field lowers to is a render-time
|
|
6
|
+
concern: the editor only sees the stored string.
|
|
7
|
+
|
|
8
|
+
`today` is the render date rather than a written one (canon `SCHEMAS.md`), so its
|
|
9
|
+
digits take the default rung: they print, and nothing wrote them. The digits are
|
|
10
|
+
the local date, which is what `resolve` and a session opened without a date both
|
|
11
|
+
render. The Today toggle writes `today`, and pressed, writes those digits; `T` in a
|
|
12
|
+
segment writes `today` too. An edit to a segment writes a whole date, the other
|
|
13
|
+
segments the render date's. A `default: today` presses the toggle at the default
|
|
14
|
+
rung while the field is unset.
|
|
8
15
|
|
|
9
16
|
Styled rather than a native `<input type="date">`: that control's calendar popup
|
|
10
17
|
is UA-owned and reaches no dial. `DateField` (segments, no
|
|
@@ -41,14 +48,20 @@
|
|
|
41
48
|
-->
|
|
42
49
|
<script lang="ts">
|
|
43
50
|
import { DateField as BitsDateField } from 'bits-ui';
|
|
44
|
-
import { parseDate, type DateValue } from '@internationalized/date';
|
|
51
|
+
import { getLocalTimeZone, parseDate, today, type DateValue } from '@internationalized/date';
|
|
52
|
+
import Icon from './icons/Icon.svelte';
|
|
53
|
+
import { wording } from './strings.js';
|
|
45
54
|
import { syncedLocal } from './synced.svelte.js';
|
|
46
55
|
import './controls.css';
|
|
47
56
|
|
|
57
|
+
const t = wording();
|
|
58
|
+
const TODAY = 'today';
|
|
59
|
+
const renderDate = today(getLocalTimeZone());
|
|
60
|
+
|
|
48
61
|
interface Props {
|
|
49
62
|
value: string | undefined;
|
|
50
|
-
/** The
|
|
51
|
-
*
|
|
63
|
+
/** The declared `default:` (`YYYY-MM-DD` or `today`): parsed for display only,
|
|
64
|
+
* shown while unset, never written. */
|
|
52
65
|
fallback?: string;
|
|
53
66
|
/** Accessible name for a field nothing else names: an object property, whose
|
|
54
67
|
* name is the field label plus the property's. A field's own date takes
|
|
@@ -65,9 +78,12 @@
|
|
|
65
78
|
labelledBy?: string;
|
|
66
79
|
/** The parked `description` (FieldLabel): announced after the name. */
|
|
67
80
|
describedBy?: string;
|
|
81
|
+
/** A `datetime`: `today` is no value of its type, so it draws no toggle and
|
|
82
|
+
* takes no `T`. */
|
|
83
|
+
datetime?: boolean;
|
|
68
84
|
onCommit: (v: string | undefined) => void;
|
|
69
85
|
}
|
|
70
|
-
let { value, fallback, label, labelledBy, describedBy, onCommit }: Props = $props();
|
|
86
|
+
let { value, fallback, label, labelledBy, describedBy, datetime, onCommit }: Props = $props();
|
|
71
87
|
|
|
72
88
|
let wrapEl: HTMLElement | undefined = $state();
|
|
73
89
|
/** Take the caret: what the label click, and a parent placing focus here, calls.
|
|
@@ -83,6 +99,7 @@
|
|
|
83
99
|
// throws on a malformed authored value: an empty field is the honest render.
|
|
84
100
|
function toDateValue(s: string): DateValue | undefined {
|
|
85
101
|
if (!s) return undefined;
|
|
102
|
+
if (s === TODAY) return renderDate;
|
|
86
103
|
try {
|
|
87
104
|
return parseDate(s);
|
|
88
105
|
} catch {
|
|
@@ -110,6 +127,27 @@
|
|
|
110
127
|
// a boolean so the substitution below narrows on the one fact it needs.
|
|
111
128
|
const ghost = $derived(local.value === '' ? fallbackDate : undefined);
|
|
112
129
|
const seated = $derived(value == null && seat && local.value !== '');
|
|
130
|
+
/** Who put `today` here, while the field follows the render date. */
|
|
131
|
+
const follows = $derived(
|
|
132
|
+
local.value === TODAY ? 'authored' : value == null && fallback === TODAY ? 'default' : undefined
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
function write(next: string): void {
|
|
136
|
+
local.value = next;
|
|
137
|
+
seat = false;
|
|
138
|
+
onCommit(next);
|
|
139
|
+
}
|
|
140
|
+
function toggle(): void {
|
|
141
|
+
write(follows ? renderDate.toString() : TODAY);
|
|
142
|
+
}
|
|
143
|
+
function shortcut(e: KeyboardEvent): void {
|
|
144
|
+
if (datetime || e.key.toLowerCase() !== 't' || e.ctrlKey || e.metaKey || e.altKey) return;
|
|
145
|
+
const seg = (e.target as HTMLElement).dataset.segment;
|
|
146
|
+
if (!seg || seg === 'literal') return;
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
e.stopPropagation();
|
|
149
|
+
write(TODAY);
|
|
150
|
+
}
|
|
113
151
|
|
|
114
152
|
/** Focus moving between segments is neither an entry nor a leaving. */
|
|
115
153
|
const within = (e: FocusEvent): boolean => !!wrapEl?.contains(e.relatedTarget as Node | null);
|
|
@@ -152,7 +190,13 @@
|
|
|
152
190
|
}
|
|
153
191
|
</script>
|
|
154
192
|
|
|
155
|
-
<span
|
|
193
|
+
<span
|
|
194
|
+
class="qm-date-wrap"
|
|
195
|
+
bind:this={wrapEl}
|
|
196
|
+
onfocusin={enter}
|
|
197
|
+
onfocusout={leave}
|
|
198
|
+
onkeydowncapture={shortcut}
|
|
199
|
+
>
|
|
156
200
|
<BitsDateField.Root
|
|
157
201
|
value={parsed}
|
|
158
202
|
placeholder={fallbackDate}
|
|
@@ -160,9 +204,9 @@
|
|
|
160
204
|
// `CalendarDate.toString()` is exactly `YYYY-MM-DD`. A cleared or
|
|
161
205
|
// half-typed field yields undefined: the unset rung. The primitive reports
|
|
162
206
|
// its own value again as a segment loses focus, which is no edit, and over a
|
|
163
|
-
// seated default would write it.
|
|
207
|
+
// seated default or `today` would write it.
|
|
164
208
|
const next = d?.toString() ?? '';
|
|
165
|
-
if (next ===
|
|
209
|
+
if (next === (parsed?.toString() ?? '')) return;
|
|
166
210
|
local.value = next;
|
|
167
211
|
if (!d) seat = false;
|
|
168
212
|
onCommit(d?.toString());
|
|
@@ -179,7 +223,7 @@
|
|
|
179
223
|
aria-labelledby={labelledBy}
|
|
180
224
|
aria-describedby={describedBy}
|
|
181
225
|
data-ghosted={ghost ? '' : undefined}
|
|
182
|
-
data-default={seated ? '' : undefined}
|
|
226
|
+
data-default={seated || follows ? '' : undefined}
|
|
183
227
|
>
|
|
184
228
|
{#snippet children({ segments })}
|
|
185
229
|
<!-- Keyed by index: `part` repeats (the `literal` separators between
|
|
@@ -194,6 +238,17 @@
|
|
|
194
238
|
{shown.text}
|
|
195
239
|
</BitsDateField.Segment>
|
|
196
240
|
{/each}
|
|
241
|
+
{#if !datetime}
|
|
242
|
+
<button
|
|
243
|
+
type="button"
|
|
244
|
+
class="qm-chip qm-tap-floor qm-focus-ring qm-date-today"
|
|
245
|
+
aria-pressed={!!follows}
|
|
246
|
+
data-default={follows === 'default' ? '' : undefined}
|
|
247
|
+
onclick={toggle}
|
|
248
|
+
>
|
|
249
|
+
<Icon name={follows ? 'calendar-check' : 'calendar'} />{t.strings.dateToday}
|
|
250
|
+
</button>
|
|
251
|
+
{/if}
|
|
197
252
|
{/snippet}
|
|
198
253
|
</BitsDateField.Input>
|
|
199
254
|
</BitsDateField.Root>
|
|
@@ -235,4 +290,32 @@
|
|
|
235
290
|
.qm-date-wrap :global(.qm-date[data-default] .qm-date-segment) {
|
|
236
291
|
color: var(--_qm-ink-default);
|
|
237
292
|
}
|
|
293
|
+
/* The toggle's state is its fill and its glyph, never the pointer: a hover fill
|
|
294
|
+
would read as pressed. Off, it rests hidden where a pointer can reveal it, still
|
|
295
|
+
holding its width so the digits never shift. It sits inside the box's line, so
|
|
296
|
+
the box stands as tall as the inputs beside it. */
|
|
297
|
+
.qm-date-today {
|
|
298
|
+
margin-inline-start: auto;
|
|
299
|
+
padding-block: var(--_qm-space-half);
|
|
300
|
+
line-height: 1;
|
|
301
|
+
border-radius: var(--_qm-radius-inner);
|
|
302
|
+
}
|
|
303
|
+
.qm-date-today:hover {
|
|
304
|
+
background: transparent;
|
|
305
|
+
color: var(--_qm-ink-label);
|
|
306
|
+
}
|
|
307
|
+
.qm-date-today[aria-pressed='true'] {
|
|
308
|
+
background: var(--_qm-accent-tint);
|
|
309
|
+
color: var(--_qm-ink);
|
|
310
|
+
}
|
|
311
|
+
.qm-date-today[data-default] {
|
|
312
|
+
background: transparent;
|
|
313
|
+
box-shadow: inset 0 0 0 var(--_qm-border-width) var(--_qm-border);
|
|
314
|
+
color: var(--_qm-ink-default);
|
|
315
|
+
}
|
|
316
|
+
@media (hover: hover) {
|
|
317
|
+
.qm-date-wrap:not(:hover, :focus-within) .qm-date-today[aria-pressed='false'] {
|
|
318
|
+
opacity: 0;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
238
321
|
</style>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import './controls.css';
|
|
2
2
|
interface Props {
|
|
3
3
|
value: string | undefined;
|
|
4
|
-
/** The
|
|
5
|
-
*
|
|
4
|
+
/** The declared `default:` (`YYYY-MM-DD` or `today`): parsed for display only,
|
|
5
|
+
* shown while unset, never written. */
|
|
6
6
|
fallback?: string;
|
|
7
7
|
/** Accessible name for a field nothing else names: an object property, whose
|
|
8
8
|
* name is the field label plus the property's. A field's own date takes
|
|
@@ -19,6 +19,9 @@ interface Props {
|
|
|
19
19
|
labelledBy?: string;
|
|
20
20
|
/** The parked `description` (FieldLabel): announced after the name. */
|
|
21
21
|
describedBy?: string;
|
|
22
|
+
/** A `datetime`: `today` is no value of its type, so it draws no toggle and
|
|
23
|
+
* takes no `T`. */
|
|
24
|
+
datetime?: boolean;
|
|
22
25
|
onCommit: (v: string | undefined) => void;
|
|
23
26
|
}
|
|
24
27
|
declare const DateField: import("svelte").Component<Props, {
|
package/dist/visual/Field.svelte
CHANGED
|
@@ -115,9 +115,9 @@
|
|
|
115
115
|
|
|
116
116
|
// What an unset field shows of its `default:`: the resolved value (provenance,
|
|
117
117
|
// `source === 'default'`). `ghost` is the raw typed value (enum/number/boolean
|
|
118
|
-
// fallbacks, a content leaf's `Content`), `defaultStr` its string form
|
|
119
|
-
//
|
|
120
|
-
//
|
|
118
|
+
// fallbacks, a content leaf's `Content`), `defaultStr` its string form; an
|
|
119
|
+
// object-valued default has none. An array draws its declared literal instead,
|
|
120
|
+
// which is what a take writes (`ArrayField`).
|
|
121
121
|
// A default that prints stands in the control as the value it is, at the default
|
|
122
122
|
// rung, and the first edit takes it (VISUAL_EDITOR §"The commitment ladder"). Where
|
|
123
123
|
// nothing prints, a control draws words: the `none` an optional cell prints, and
|
|
@@ -127,9 +127,10 @@
|
|
|
127
127
|
field.control === 'prose' ? titleText(ghost) || undefined : stringifyGhost(ghost)
|
|
128
128
|
);
|
|
129
129
|
const printed = $derived(printedText(defaultStr));
|
|
130
|
-
// A text or
|
|
131
|
-
// the row stops naming a default once the field is written, and emptying a
|
|
132
|
-
// field still has to know that the default prints.
|
|
130
|
+
// A text, number or date control reads its default off the schema, not the resolved
|
|
131
|
+
// row: the row stops naming a default once the field is written, and emptying a
|
|
132
|
+
// written field still has to know that the default prints. A date's `default: today`
|
|
133
|
+
// resolves to digits, and the control has to know it follows the render date.
|
|
133
134
|
const declared = $derived(printedText(field.schema.default));
|
|
134
135
|
const optional = $derived(optionalCell(field.schema));
|
|
135
136
|
const none = $derived(optional && value == null ? t.strings.optionalGhost : undefined);
|
|
@@ -350,7 +351,8 @@
|
|
|
350
351
|
<DateField
|
|
351
352
|
bind:this={dateEl}
|
|
352
353
|
value={value as string | undefined}
|
|
353
|
-
fallback={
|
|
354
|
+
fallback={stringifyGhost(field.schema.default)}
|
|
355
|
+
datetime={baseType(field.schema) === 'datetime'}
|
|
354
356
|
labelledBy={domIds.label}
|
|
355
357
|
{describedBy}
|
|
356
358
|
onCommit={onCommitScalar}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
A `number` / `integer` field → numeric input (fixture `font_size` = 11.5).
|
|
3
3
|
Commits at `change` (blur/Enter), not per keystroke: a partial numeric entry
|
|
4
|
-
(`-`, `1
|
|
4
|
+
(`-`, `1.`) is never a document state worth a boundary round-trip, and
|
|
5
5
|
committing it live flashes a coercion diagnostic + `console.error` on every
|
|
6
6
|
intermediate prefix, announced by `DiagnosticList`'s `role="status"` live
|
|
7
7
|
region.
|
|
@@ -12,15 +12,21 @@
|
|
|
12
12
|
so a blank entry commits `undefined`, the unset rung: the parent removes the field,
|
|
13
13
|
the engine renders the default, and the input holds it again.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
`
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
The entry grammar is plain decimal: an optional sign and digits, and for a `number`
|
|
16
|
+
a `.` and an exponent, the form `String()` gives a value too small or large for
|
|
17
|
+
digits. A keystroke that leaves the text outside a prefix of it does not land, and in
|
|
18
|
+
a `number` a `,` lands as `.`, the decimal key of a comma locale's keypad. A deletion
|
|
19
|
+
always lands, so a stored value outside the grammar (`abc`) can be cleared, and a
|
|
20
|
+
paste, a drop or an IME composition lands as it comes, judged when it settles.
|
|
21
|
+
`type="text"`, not `type="number"`: a native number input sanitizes an invalid
|
|
22
|
+
string to `""` rather than showing it, and steps on a scroll-wheel. `inputmode`
|
|
23
|
+
keeps the numeric mobile keyboard.
|
|
24
|
+
|
|
25
|
+
A settled entry in the grammar commits as a number. Anything else forwards the raw
|
|
26
|
+
string to `onCommit`: the boundary's `writer.set` coercion is the judge (throws a
|
|
27
|
+
`QuillmarkError` the parent turns into a field diagnostic, VISUAL_EDITOR
|
|
28
|
+
§Diagnostics). `Number()` alone is no gate: it reads `0x1F` and `Infinity` as
|
|
29
|
+
numbers, and `1.000` as a whole one.
|
|
24
30
|
-->
|
|
25
31
|
<script lang="ts">
|
|
26
32
|
import { syncedLocal } from './synced.svelte.js';
|
|
@@ -53,22 +59,42 @@
|
|
|
53
59
|
const local = syncedLocal(() => (value != null ? String(value) : (fallback ?? '')));
|
|
54
60
|
const defaulted = $derived(value == null && !!fallback && local.value === fallback);
|
|
55
61
|
|
|
62
|
+
const partial = $derived(
|
|
63
|
+
integer ? /^\s*[+-]?\d*\s*$/ : /^\s*[+-]?\d*\.?\d*(?:[eE][+-]?\d*)?\s*$/
|
|
64
|
+
);
|
|
65
|
+
const whole = $derived(integer ? /^[+-]?\d+$/ : /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/);
|
|
66
|
+
|
|
67
|
+
function filter(e: InputEvent): void {
|
|
68
|
+
if (e.inputType !== 'insertText' || e.data == null) return;
|
|
69
|
+
const el = e.currentTarget as HTMLInputElement;
|
|
70
|
+
const from = el.selectionStart ?? el.value.length;
|
|
71
|
+
const to = el.selectionEnd ?? el.value.length;
|
|
72
|
+
const data = !integer && e.data === ',' ? '.' : e.data;
|
|
73
|
+
const next = el.value.slice(0, from) + data + el.value.slice(to);
|
|
74
|
+
if (!partial.test(next)) return e.preventDefault();
|
|
75
|
+
if (data === e.data) return;
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
// `insertText` lands on the undo stack and fires `input`; `setRangeText` does neither.
|
|
78
|
+
if (document.execCommand?.('insertText', false, data)) return;
|
|
79
|
+
el.setRangeText(data, from, to, 'end');
|
|
80
|
+
local.value = el.value;
|
|
81
|
+
}
|
|
82
|
+
|
|
56
83
|
// Parse a settled entry and emit it; `local` is owned by `oninput`. Blank →
|
|
57
84
|
// `undefined`, and the input takes the default back: nothing else reconciles it, the
|
|
58
85
|
// projection having read the default throughout. Onto the element as well, since the
|
|
59
86
|
// attribute Svelte last wrote may already be the default, and it writes no repeat.
|
|
60
87
|
function commit(el: HTMLInputElement): void {
|
|
61
88
|
const raw = el.value;
|
|
62
|
-
|
|
89
|
+
const entry = raw.trim();
|
|
90
|
+
if (entry === '') {
|
|
63
91
|
onCommit(undefined);
|
|
64
92
|
local.value = el.value = fallback ?? '';
|
|
65
93
|
return;
|
|
66
94
|
}
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const n = Number(raw);
|
|
71
|
-
onCommit(Number.isNaN(n) ? raw : n);
|
|
95
|
+
// Finite as well: a digit run past `Number.MAX_VALUE` reads as `Infinity`.
|
|
96
|
+
const n = whole.test(entry) ? Number(entry) : NaN;
|
|
97
|
+
onCommit(Number.isFinite(n) ? n : raw);
|
|
72
98
|
}
|
|
73
99
|
</script>
|
|
74
100
|
|
|
@@ -82,6 +108,7 @@
|
|
|
82
108
|
data-default={defaulted ? '' : undefined}
|
|
83
109
|
aria-label={id ? undefined : label}
|
|
84
110
|
aria-describedby={describedBy}
|
|
111
|
+
onbeforeinput={filter}
|
|
85
112
|
oninput={(e) => {
|
|
86
113
|
local.value = (e.currentTarget as HTMLInputElement).value;
|
|
87
114
|
}}
|
|
@@ -338,6 +338,7 @@
|
|
|
338
338
|
describedBy={describes}
|
|
339
339
|
value={obj[key] as string | undefined}
|
|
340
340
|
fallback={sub.default != null ? String(sub.default) : undefined}
|
|
341
|
+
datetime={baseType(sub) === 'datetime'}
|
|
341
342
|
onCommit={(v) => commitProp(key, v)}
|
|
342
343
|
/>
|
|
343
344
|
{:else if kind === 'text'}
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
/** Ghost shown on the empty leaf: the `none` an optional field prints unset, or
|
|
44
44
|
* nothing, and a body's always text (`resolveBodyGhost`). */
|
|
45
45
|
placeholder?: string;
|
|
46
|
-
/**
|
|
46
|
+
/** The placeholder goes at the leaf's first edit (`createField`). */
|
|
47
47
|
placeholderUntilEdit?: boolean;
|
|
48
48
|
/** Ghost shown on the empty leaf until its first edit, and in the placeholder's
|
|
49
49
|
* stead while the leaf holds the focus: an unset field's `example:`
|
|
@@ -31,7 +31,7 @@ interface Props {
|
|
|
31
31
|
/** Ghost shown on the empty leaf: the `none` an optional field prints unset, or
|
|
32
32
|
* nothing, and a body's always text (`resolveBodyGhost`). */
|
|
33
33
|
placeholder?: string;
|
|
34
|
-
/**
|
|
34
|
+
/** The placeholder goes at the leaf's first edit (`createField`). */
|
|
35
35
|
placeholderUntilEdit?: boolean;
|
|
36
36
|
/** Ghost shown on the empty leaf until its first edit, and in the placeholder's
|
|
37
37
|
* stead while the leaf holds the focus: an unset field's `example:`
|
|
@@ -119,6 +119,34 @@ export declare const ICONS: {
|
|
|
119
119
|
readonly check: readonly [readonly ["path", {
|
|
120
120
|
readonly d: "M20 6 9 17l-5-5";
|
|
121
121
|
}]];
|
|
122
|
+
readonly calendar: readonly [readonly ["path", {
|
|
123
|
+
readonly d: "M8 2v3";
|
|
124
|
+
}], readonly ["path", {
|
|
125
|
+
readonly d: "M16 2v3";
|
|
126
|
+
}], readonly ["rect", {
|
|
127
|
+
readonly x: "3";
|
|
128
|
+
readonly y: "3";
|
|
129
|
+
readonly width: "18";
|
|
130
|
+
readonly height: "18";
|
|
131
|
+
readonly rx: "2";
|
|
132
|
+
}], readonly ["path", {
|
|
133
|
+
readonly d: "M3 9h18";
|
|
134
|
+
}]];
|
|
135
|
+
readonly 'calendar-check': readonly [readonly ["path", {
|
|
136
|
+
readonly d: "M8 2v3";
|
|
137
|
+
}], readonly ["path", {
|
|
138
|
+
readonly d: "M16 2v3";
|
|
139
|
+
}], readonly ["rect", {
|
|
140
|
+
readonly x: "3";
|
|
141
|
+
readonly y: "3";
|
|
142
|
+
readonly width: "18";
|
|
143
|
+
readonly height: "18";
|
|
144
|
+
readonly rx: "2";
|
|
145
|
+
}], readonly ["path", {
|
|
146
|
+
readonly d: "M3 9h18";
|
|
147
|
+
}], readonly ["path", {
|
|
148
|
+
readonly d: "m9 15 2 2 4-4";
|
|
149
|
+
}]];
|
|
122
150
|
};
|
|
123
151
|
/** The closed set. A name that is not here is a compile error at the call site,
|
|
124
152
|
* which is what a string key buys over thirteen imported components. */
|
|
@@ -64,5 +64,18 @@ export const ICONS = {
|
|
|
64
64
|
['path', { d: 'M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2' }]
|
|
65
65
|
],
|
|
66
66
|
minus: [['path', { d: 'M5 12h14' }]],
|
|
67
|
-
check: [['path', { d: 'M20 6 9 17l-5-5' }]]
|
|
67
|
+
check: [['path', { d: 'M20 6 9 17l-5-5' }]],
|
|
68
|
+
calendar: [
|
|
69
|
+
['path', { d: 'M8 2v3' }],
|
|
70
|
+
['path', { d: 'M16 2v3' }],
|
|
71
|
+
['rect', { x: '3', y: '3', width: '18', height: '18', rx: '2' }],
|
|
72
|
+
['path', { d: 'M3 9h18' }]
|
|
73
|
+
],
|
|
74
|
+
'calendar-check': [
|
|
75
|
+
['path', { d: 'M8 2v3' }],
|
|
76
|
+
['path', { d: 'M16 2v3' }],
|
|
77
|
+
['rect', { x: '3', y: '3', width: '18', height: '18', rx: '2' }],
|
|
78
|
+
['path', { d: 'M3 9h18' }],
|
|
79
|
+
['path', { d: 'm9 15 2 2 4-4' }]
|
|
80
|
+
]
|
|
68
81
|
};
|
package/dist/visual/strings.d.ts
CHANGED
|
@@ -47,6 +47,9 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
|
|
|
47
47
|
enumUnsetTag: string;
|
|
48
48
|
/** The count in the matrix's label row: how many of the roster are held. */
|
|
49
49
|
matrixHeld: (held: number, total: number) => string;
|
|
50
|
+
/** The date field's toggle, pressed while the field holds `today`: the render
|
|
51
|
+
* date rather than a written one. */
|
|
52
|
+
dateToday: string;
|
|
50
53
|
/** The required marker's accessible name; the glyph itself is a `*`. */
|
|
51
54
|
fieldRequired: string;
|
|
52
55
|
/** What an unset optional cell (`t?`) ghosts at rest: the `none` it prints
|
package/dist/visual/strings.js
CHANGED
|
@@ -37,6 +37,7 @@ export const DEFAULT_VISUAL_STRINGS = {
|
|
|
37
37
|
elementUntitled: (label, index) => `${label} ${index}`,
|
|
38
38
|
enumUnsetTag: 'default',
|
|
39
39
|
matrixHeld: (held, total) => `${held} of ${total} held`,
|
|
40
|
+
dateToday: 'Today',
|
|
40
41
|
fieldRequired: 'required',
|
|
41
42
|
optionalGhost: 'None',
|
|
42
43
|
proseHeld: 'Holds more than this field can edit here.',
|
package/dist/visual/structure.js
CHANGED
|
@@ -323,21 +323,20 @@ export function shortCell(sub) {
|
|
|
323
323
|
return (kind === 'text' || kind === 'enum' || kind === 'number' || kind === 'boolean' || kind === 'date');
|
|
324
324
|
}
|
|
325
325
|
/**
|
|
326
|
-
* The first short text cell's words
|
|
327
|
-
* `
|
|
328
|
-
*
|
|
329
|
-
*
|
|
326
|
+
* The first short text cell's words — the first `string`, or inline `richtext` /
|
|
327
|
+
* `plaintext`, in declaration order — read through {@link titleText}; `undefined` where
|
|
328
|
+
* there is no such cell or it is empty. The cell is picked by the schema alone, so an
|
|
329
|
+
* instance is named by one cell whatever the others hold. What names an instance from
|
|
330
|
+
* its own values: a collapsed row, and a card whose kind declares no `title`.
|
|
330
331
|
*/
|
|
331
332
|
function firstShortText(cells, values) {
|
|
332
|
-
|
|
333
|
+
const named = Object.entries(cells ?? {}).find(([, sub]) => {
|
|
333
334
|
const kind = controlKind(sub);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
return undefined;
|
|
335
|
+
return kind === 'text' || (kind === 'prose' && shortCell(sub));
|
|
336
|
+
});
|
|
337
|
+
if (!named)
|
|
338
|
+
return undefined;
|
|
339
|
+
return titleText(ownValue(values, named[0])).trim() || undefined;
|
|
341
340
|
}
|
|
342
341
|
/** A collapsed row's own words ({@link firstShortText}); `undefined` while the row has
|
|
343
342
|
* nothing to say for itself. */
|