@dorsk/tsumikit 0.51.0 → 0.52.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 +4 -1
- package/dist/components/layouts/NavBar.svelte +1 -1
- package/dist/components/molecules/FilterInput.svelte +32 -8
- package/dist/components/molecules/FilterInput.svelte.d.ts +11 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/query/index.d.ts +1 -0
- package/dist/query/index.js +1 -0
- package/dist/query/single.d.ts +15 -0
- package/dist/query/single.js +43 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -267,7 +267,10 @@ hooks on head/row/cell; `responsive="stack"` turns rows into cards below
|
|
|
267
267
|
prefix, the `<table>` stays a table for assistive tech), FilterSearchBar /
|
|
268
268
|
FilterInput (`size="sm"` compact bar on `--control-height-compact`,
|
|
269
269
|
`shape="pill"`, `surface`, `hotkey="/"` focuses the input from anywhere
|
|
270
|
-
outside an editable element, `showHotkey` renders the `<kbd>` hint, `grow
|
|
270
|
+
outside an editable element, `showHotkey` renders the `<kbd>` hint, `grow`;
|
|
271
|
+
`key="cwd"` puts FilterInput in single-key mode — `value` is the bare value
|
|
272
|
+
instead of a `key:"value"` query, no key prefix in the box, the `placeholder`
|
|
273
|
+
shows while empty, and completion still runs through that field's provider).
|
|
271
274
|
|
|
272
275
|
**Layouts:** AppShell (responsive header/sidebar/main/footer — persistent
|
|
273
276
|
sidebar on desktop, overlay drawer on mobile, optionally resizable;
|
|
@@ -43,13 +43,15 @@
|
|
|
43
43
|
import { autoQuoteEdit, backspaceEmptyQuotes, closingQuoteExit } from '../../query/edit';
|
|
44
44
|
import { parse } from '../../query/parser';
|
|
45
45
|
import { type Schema } from '../../query/schema';
|
|
46
|
+
import { singleQuery, suggestSingle } from '../../query/single';
|
|
46
47
|
import { suggest, type SuggestState } from '../../query/suggest';
|
|
47
48
|
import { getFieldContext, warnUnlabelled } from '../../field-context';
|
|
48
49
|
|
|
49
50
|
let {
|
|
50
51
|
schema,
|
|
52
|
+
key,
|
|
51
53
|
value = $bindable(''),
|
|
52
|
-
placeholder
|
|
54
|
+
placeholder,
|
|
53
55
|
autoQuote = true,
|
|
54
56
|
showClear = true,
|
|
55
57
|
icon = 'search',
|
|
@@ -70,13 +72,23 @@
|
|
|
70
72
|
style: styleProp = '',
|
|
71
73
|
}: {
|
|
72
74
|
schema: Schema;
|
|
73
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Single-key mode: the name (or alias) of the ONE schema field being
|
|
77
|
+
* edited. `value` then holds the bare value instead of a `key:"value"`
|
|
78
|
+
* query — no key prefix in the box, the placeholder shows while empty and
|
|
79
|
+
* completion still runs through that field's provider. The equivalent query
|
|
80
|
+
* is synthesised for `onchange` and the snippet context.
|
|
81
|
+
*/
|
|
82
|
+
key?: string;
|
|
83
|
+
/** The raw textual query, or the bare value in single-key mode (bindable). */
|
|
74
84
|
value?: string;
|
|
85
|
+
/** Defaults to a sample query, or to nothing in single-key mode. */
|
|
75
86
|
placeholder?: string;
|
|
76
87
|
/**
|
|
77
88
|
* When a string field's value step opens (`title:`), auto-insert a `""`
|
|
78
89
|
* pair with the caret inside so multi-word values stay together; Tab exits
|
|
79
90
|
* the quotes. Set false for bare typing where spaces split the value.
|
|
91
|
+
* Always off in single-key mode, which quotes nothing.
|
|
80
92
|
*/
|
|
81
93
|
autoQuote?: boolean;
|
|
82
94
|
/** Show the trailing clear (✕) button when the field is non-empty. */
|
|
@@ -147,8 +159,12 @@
|
|
|
147
159
|
return () => document.removeEventListener('keydown', onHotkey);
|
|
148
160
|
});
|
|
149
161
|
|
|
162
|
+
const single = $derived(!!key);
|
|
163
|
+
const quoting = $derived(autoQuote && !single);
|
|
164
|
+
const hint = $derived(placeholder ?? (single ? '' : 'artist:"Daft Punk" AND year>=2000'));
|
|
165
|
+
|
|
150
166
|
// Parsed view (drives the onchange AST + the snippet context).
|
|
151
|
-
const ast = $derived(parse(value, schema));
|
|
167
|
+
const ast = $derived(parse(key ? singleQuery(schema, key, value) : value, schema));
|
|
152
168
|
const chips = $derived(filters(ast));
|
|
153
169
|
const text = $derived(freeText(ast));
|
|
154
170
|
|
|
@@ -183,7 +199,7 @@
|
|
|
183
199
|
}
|
|
184
200
|
|
|
185
201
|
function oninput(e: Event) {
|
|
186
|
-
if (
|
|
202
|
+
if (quoting && el && (e as InputEvent).inputType?.startsWith('insert')) {
|
|
187
203
|
const pos = el.selectionStart ?? value.length;
|
|
188
204
|
const edit = autoQuoteEdit(schema, value, pos);
|
|
189
205
|
if (edit) {
|
|
@@ -200,7 +216,7 @@
|
|
|
200
216
|
if (!el) return;
|
|
201
217
|
const pos = el.selectionStart ?? value.length;
|
|
202
218
|
const id = ++reqId;
|
|
203
|
-
const next = await suggest(schema, value, pos);
|
|
219
|
+
const next = key ? await suggestSingle(schema, key, value) : await suggest(schema, value, pos);
|
|
204
220
|
if (id !== reqId) return; // a newer keystroke won
|
|
205
221
|
menu = next;
|
|
206
222
|
active = 0;
|
|
@@ -217,7 +233,7 @@
|
|
|
217
233
|
let caret = s.caret;
|
|
218
234
|
// A field/operator pick that opens a string field's value step gets the
|
|
219
235
|
// same auto-quotes as manual typing.
|
|
220
|
-
if (
|
|
236
|
+
if (quoting && s.advance) {
|
|
221
237
|
const edit = autoQuoteEdit(schema, value, caret);
|
|
222
238
|
if (edit) {
|
|
223
239
|
value = edit.value;
|
|
@@ -236,7 +252,7 @@
|
|
|
236
252
|
}
|
|
237
253
|
|
|
238
254
|
function onkeydown(e: KeyboardEvent) {
|
|
239
|
-
if (
|
|
255
|
+
if (quoting && el) {
|
|
240
256
|
const pos = el.selectionStart ?? value.length;
|
|
241
257
|
if (e.key === 'Tab') {
|
|
242
258
|
// Inside auto-quotes Tab exits them (takes priority over accepting a
|
|
@@ -292,6 +308,14 @@
|
|
|
292
308
|
}
|
|
293
309
|
|
|
294
310
|
function removeChip(span: [number, number]) {
|
|
311
|
+
// Single-key spans index the synthesised query, not the box: the only
|
|
312
|
+
// clause there is the value itself, so removing it empties the field.
|
|
313
|
+
if (single) {
|
|
314
|
+
value = '';
|
|
315
|
+
onsubmit?.('');
|
|
316
|
+
queueMicrotask(() => el?.focus());
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
295
319
|
// Splice the clause out, plus trailing spaces to avoid doubles.
|
|
296
320
|
const [a, b] = span;
|
|
297
321
|
let end = b;
|
|
@@ -332,7 +356,7 @@
|
|
|
332
356
|
aria-invalid={ariaInvalid ?? (field?.invalid ? 'true' : undefined)}
|
|
333
357
|
spellcheck="false"
|
|
334
358
|
autocomplete="off"
|
|
335
|
-
{
|
|
359
|
+
placeholder={hint}
|
|
336
360
|
{oninput}
|
|
337
361
|
onclick={refresh}
|
|
338
362
|
onkeyup={(e) => {
|
|
@@ -24,13 +24,23 @@ import { type IconName } from '../atoms/Icon.svelte';
|
|
|
24
24
|
import { type Schema } from '../../query/schema';
|
|
25
25
|
type $$ComponentProps = {
|
|
26
26
|
schema: Schema;
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Single-key mode: the name (or alias) of the ONE schema field being
|
|
29
|
+
* edited. `value` then holds the bare value instead of a `key:"value"`
|
|
30
|
+
* query — no key prefix in the box, the placeholder shows while empty and
|
|
31
|
+
* completion still runs through that field's provider. The equivalent query
|
|
32
|
+
* is synthesised for `onchange` and the snippet context.
|
|
33
|
+
*/
|
|
34
|
+
key?: string;
|
|
35
|
+
/** The raw textual query, or the bare value in single-key mode (bindable). */
|
|
28
36
|
value?: string;
|
|
37
|
+
/** Defaults to a sample query, or to nothing in single-key mode. */
|
|
29
38
|
placeholder?: string;
|
|
30
39
|
/**
|
|
31
40
|
* When a string field's value step opens (`title:`), auto-insert a `""`
|
|
32
41
|
* pair with the caret inside so multi-word values stay together; Tab exits
|
|
33
42
|
* the quotes. Set false for bare typing where spaces split the value.
|
|
43
|
+
* Always off in single-key mode, which quotes nothing.
|
|
34
44
|
*/
|
|
35
45
|
autoQuote?: boolean;
|
|
36
46
|
/** Show the trailing clear (✕) button when the field is non-empty. */
|
package/dist/index.d.ts
CHANGED
|
@@ -87,7 +87,7 @@ export { default as FilterSearchBar } from './components/organisms/FilterSearchB
|
|
|
87
87
|
export { EMOJI_GROUPS, type EmojiEntry, type EmojiGroup, searchEmoji } from './emoji';
|
|
88
88
|
export { FIELD_KEY, type FieldContext, getFieldContext, setFieldContext, warnUnlabelled, } from './field-context';
|
|
89
89
|
export * as filterQuery from './query';
|
|
90
|
-
export { type AndNode, activeToken, compilePredicate, defaultOperator, type ExprNode, type FieldDef, type FieldType, type FilterNode, filters, findField, freeText, type LeafNode, type NotNode, OPERATORS, type Operator, type OperatorId, type OrNode, operatorByCode, operatorById, operatorsFor, parse, type Query, type QueryNode, resolveValues, type Schema, type Suggestion, type SuggestKind, type SuggestState, serialize, serializeFilter, suggest, type TextNode, toSql, type ValueContext, type ValueOption, type ValueProvider, walk, } from './query';
|
|
90
|
+
export { type AndNode, activeToken, compilePredicate, defaultOperator, type ExprNode, type FieldDef, type FieldType, type FilterNode, filters, findField, freeText, type LeafNode, type NotNode, OPERATORS, type Operator, type OperatorId, type OrNode, operatorByCode, operatorById, operatorsFor, parse, type Query, type QueryNode, resolveValues, type Schema, type Suggestion, type SuggestKind, type SuggestState, serialize, serializeFilter, singleQuery, suggest, suggestSingle, type TextNode, toSql, type ValueContext, type ValueOption, type ValueProvider, walk, } from './query';
|
|
91
91
|
export { type OptionSection, sectionOptions } from './select-options';
|
|
92
92
|
export type { ControlSize } from './size';
|
|
93
93
|
export { fontScale, SCALE_LEVELS, type ScaleLevel } from './stores/fontscale.svelte';
|
package/dist/index.js
CHANGED
|
@@ -98,7 +98,7 @@ export { EMOJI_GROUPS, searchEmoji } from './emoji';
|
|
|
98
98
|
export { FIELD_KEY, getFieldContext, setFieldContext, warnUnlabelled, } from './field-context';
|
|
99
99
|
// ---- query core (headless: schema / parser / AST / suggest / compilers) ----
|
|
100
100
|
export * as filterQuery from './query';
|
|
101
|
-
export { activeToken, compilePredicate, defaultOperator, filters, findField, freeText, OPERATORS, operatorByCode, operatorById, operatorsFor, parse, resolveValues, serialize, serializeFilter, suggest, toSql, walk, } from './query';
|
|
101
|
+
export { activeToken, compilePredicate, defaultOperator, filters, findField, freeText, OPERATORS, operatorByCode, operatorById, operatorsFor, parse, resolveValues, serialize, serializeFilter, singleQuery, suggest, suggestSingle, toSql, walk, } from './query';
|
|
102
102
|
export { sectionOptions } from './select-options';
|
|
103
103
|
export { fontScale, SCALE_LEVELS } from './stores/fontscale.svelte';
|
|
104
104
|
// ---- stores / actions ----
|
package/dist/query/index.d.ts
CHANGED
|
@@ -5,5 +5,6 @@ export { parse } from './parser';
|
|
|
5
5
|
export { compilePredicate, serialize, serializeFilter, toSql } from './query';
|
|
6
6
|
export type { FieldDef, FieldType, Operator, OperatorId, Schema, ValueContext, ValueOption, ValueProvider, } from './schema';
|
|
7
7
|
export { defaultOperator, findField, OPERATORS, operatorByCode, operatorById, operatorsFor, resolveValues, } from './schema';
|
|
8
|
+
export { singleQuery, suggestSingle } from './single';
|
|
8
9
|
export type { Suggestion, SuggestKind, SuggestState } from './suggest';
|
|
9
10
|
export { activeToken, suggest } from './suggest';
|
package/dist/query/index.js
CHANGED
|
@@ -7,4 +7,5 @@ export { autoQuoteEdit, backspaceEmptyQuotes, closingQuoteExit, insideQuoteAtCar
|
|
|
7
7
|
export { parse } from './parser';
|
|
8
8
|
export { compilePredicate, serialize, serializeFilter, toSql } from './query';
|
|
9
9
|
export { defaultOperator, findField, OPERATORS, operatorByCode, operatorById, operatorsFor, resolveValues, } from './schema';
|
|
10
|
+
export { singleQuery, suggestSingle } from './single';
|
|
10
11
|
export { activeToken, suggest } from './suggest';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Schema } from './schema';
|
|
2
|
+
import type { SuggestState } from './suggest';
|
|
3
|
+
/**
|
|
4
|
+
* The textual query a bare `value` stands for, using `key`'s default operator —
|
|
5
|
+
* `"/srv/my app"` under key `cwd` becomes `cwd:"/srv/my app"`. Empty values (and
|
|
6
|
+
* unknown keys) yield an empty query so the AST stays empty rather than parsing
|
|
7
|
+
* a half-written clause.
|
|
8
|
+
*/
|
|
9
|
+
export declare function singleQuery(schema: Schema, key: string, value: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Value suggestions for `key` given the whole box as the fragment. The span
|
|
12
|
+
* always covers the entire value, so accepting an item replaces it outright —
|
|
13
|
+
* there is no field or operator step to advance to.
|
|
14
|
+
*/
|
|
15
|
+
export declare function suggestSingle(schema: Schema, key: string, value: string): Promise<SuggestState | null>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Single-key mode — the headless half of FilterInput's `key` prop: the box
|
|
3
|
+
// holds the bare value (`/srv/app`, not `cwd:"/srv/app"`), completing through
|
|
4
|
+
// the field's own provider, and the equivalent query is synthesised only for
|
|
5
|
+
// the `inline` / `below` snippets and the `onchange` AST.
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
7
|
+
import { defaultOperator, findField, resolveValues } from './schema';
|
|
8
|
+
function quoteIfNeeded(v) {
|
|
9
|
+
return /[\s,()]/.test(v) ? `"${v}"` : v;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The textual query a bare `value` stands for, using `key`'s default operator —
|
|
13
|
+
* `"/srv/my app"` under key `cwd` becomes `cwd:"/srv/my app"`. Empty values (and
|
|
14
|
+
* unknown keys) yield an empty query so the AST stays empty rather than parsing
|
|
15
|
+
* a half-written clause.
|
|
16
|
+
*/
|
|
17
|
+
export function singleQuery(schema, key, value) {
|
|
18
|
+
const field = findField(schema, key);
|
|
19
|
+
if (!field || !value)
|
|
20
|
+
return '';
|
|
21
|
+
return `${field.name}${defaultOperator(field).code}${quoteIfNeeded(value)}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Value suggestions for `key` given the whole box as the fragment. The span
|
|
25
|
+
* always covers the entire value, so accepting an item replaces it outright —
|
|
26
|
+
* there is no field or operator step to advance to.
|
|
27
|
+
*/
|
|
28
|
+
export async function suggestSingle(schema, key, value) {
|
|
29
|
+
const field = findField(schema, key);
|
|
30
|
+
if (!field)
|
|
31
|
+
return null;
|
|
32
|
+
const span = [0, value.length];
|
|
33
|
+
const options = await resolveValues(field, value, { rawQuery: value, span, caret: value.length });
|
|
34
|
+
if (options.length === 0)
|
|
35
|
+
return null;
|
|
36
|
+
const items = options.map((o) => ({
|
|
37
|
+
label: o.label,
|
|
38
|
+
hint: o.hint,
|
|
39
|
+
insert: o.value,
|
|
40
|
+
caret: o.value.length,
|
|
41
|
+
}));
|
|
42
|
+
return { kind: 'value', span, items };
|
|
43
|
+
}
|
package/package.json
CHANGED