@dorsk/tsumikit 0.17.0 → 0.18.1

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.
@@ -297,6 +297,25 @@
297
297
  border-color: var(--ok);
298
298
  filter: brightness(1.08);
299
299
  }
300
+ /* Other tones on a primary action tint the accent fill rather than repaint the
301
+ text, so the on-accent foreground stays readable (bare tone-as-text would be
302
+ accent-on-accent). success keeps its own filled guard above. */
303
+ .btn-primary.btn-tone-accent,
304
+ .btn-primary.btn-tone-info,
305
+ .btn-primary.btn-tone-warn,
306
+ .btn-primary.btn-tone-danger {
307
+ background: color-mix(in srgb, var(--btn-tone) 35%, var(--accent));
308
+ border-color: color-mix(in srgb, var(--btn-tone) 35%, var(--accent));
309
+ color: var(--text-on-accent);
310
+ }
311
+ .btn-primary.btn-tone-accent:hover:not(:disabled),
312
+ .btn-primary.btn-tone-info:hover:not(:disabled),
313
+ .btn-primary.btn-tone-warn:hover:not(:disabled),
314
+ .btn-primary.btn-tone-danger:hover:not(:disabled) {
315
+ background: color-mix(in srgb, var(--btn-tone) 35%, var(--accent));
316
+ border-color: color-mix(in srgb, var(--btn-tone) 35%, var(--accent));
317
+ filter: brightness(1.08);
318
+ }
300
319
 
301
320
  /* Icon-only buttons (IconButton). Square box = consistent 2.25rem tap target. */
302
321
  .btn-icon {
@@ -0,0 +1,111 @@
1
+ <script lang="ts" module>
2
+ export interface BreadcrumbItem {
3
+ label: string;
4
+ href?: string;
5
+ }
6
+ </script>
7
+
8
+ <script lang="ts">
9
+ // Navigation trail (e.g. "Musique > Artist > Album > Track"). The last item —
10
+ // or any item without an `href` — renders unlinked and is marked
11
+ // aria-current="page". When the trail exceeds `maxItems` the middle collapses
12
+ // to an ellipsis, always keeping the first item and the tail. `separator` is a
13
+ // snippet for custom markup, else a char via the `char` prop.
14
+ import type { Snippet } from 'svelte';
15
+ import Icon from '../atoms/Icon.svelte';
16
+
17
+ let {
18
+ items,
19
+ char,
20
+ separator,
21
+ maxItems = 0
22
+ }: {
23
+ items: BreadcrumbItem[];
24
+ /** Separator character; ignored when the `separator` snippet is set. */
25
+ char?: string;
26
+ separator?: Snippet;
27
+ /** Collapse the middle to an ellipsis when the trail is longer than this
28
+ * (0 = never collapse). Keeps the first item and the last `maxItems - 1`. */
29
+ maxItems?: number;
30
+ } = $props();
31
+
32
+ type Crumb = BreadcrumbItem | { ellipsis: true };
33
+
34
+ const shown = $derived<Crumb[]>(
35
+ maxItems > 0 && items.length > maxItems
36
+ ? [items[0], { ellipsis: true }, ...items.slice(items.length - (maxItems - 1))]
37
+ : items
38
+ );
39
+ </script>
40
+
41
+ <nav aria-label="breadcrumb" data-tsu="Breadcrumb">
42
+ <ol class="crumbs">
43
+ {#each shown as item, i (i)}
44
+ <li class="crumb">
45
+ {#if 'ellipsis' in item}
46
+ <span class="ellipsis" aria-hidden="true">…</span>
47
+ {:else if item.href && i < shown.length - 1}
48
+ <a class="link" href={item.href}>{item.label}</a>
49
+ {:else}
50
+ <span class="current" aria-current="page">{item.label}</span>
51
+ {/if}
52
+ {#if i < shown.length - 1}
53
+ <span class="sep" aria-hidden="true">
54
+ {#if separator}{@render separator()}{:else if char}{char}{:else}<Icon
55
+ name="chevron-right"
56
+ size={14}
57
+ />{/if}
58
+ </span>
59
+ {/if}
60
+ </li>
61
+ {/each}
62
+ </ol>
63
+ </nav>
64
+
65
+ <style>
66
+ .crumbs {
67
+ display: flex;
68
+ flex-wrap: wrap;
69
+ align-items: center;
70
+ gap: var(--sp-1);
71
+ margin: 0;
72
+ padding: 0;
73
+ list-style: none;
74
+ font-size: var(--fs-sm);
75
+ }
76
+ .crumb {
77
+ display: inline-flex;
78
+ align-items: center;
79
+ gap: var(--sp-1);
80
+ min-width: 0;
81
+ }
82
+ .link {
83
+ color: var(--text-muted);
84
+ text-decoration: none;
85
+ white-space: nowrap;
86
+ overflow: hidden;
87
+ text-overflow: ellipsis;
88
+ max-width: 16ch;
89
+ transition: color 0.12s var(--ease);
90
+ }
91
+ .link:hover {
92
+ color: var(--text);
93
+ text-decoration: underline;
94
+ }
95
+ .current {
96
+ color: var(--text);
97
+ font-weight: var(--fw-medium);
98
+ white-space: nowrap;
99
+ overflow: hidden;
100
+ text-overflow: ellipsis;
101
+ max-width: 24ch;
102
+ }
103
+ .ellipsis {
104
+ color: var(--text-muted);
105
+ }
106
+ .sep {
107
+ display: inline-flex;
108
+ align-items: center;
109
+ color: var(--text-subtle, var(--text-muted));
110
+ }
111
+ </style>
@@ -0,0 +1,17 @@
1
+ export interface BreadcrumbItem {
2
+ label: string;
3
+ href?: string;
4
+ }
5
+ import type { Snippet } from 'svelte';
6
+ type $$ComponentProps = {
7
+ items: BreadcrumbItem[];
8
+ /** Separator character; ignored when the `separator` snippet is set. */
9
+ char?: string;
10
+ separator?: Snippet;
11
+ /** Collapse the middle to an ellipsis when the trail is longer than this
12
+ * (0 = never collapse). Keeps the first item and the last `maxItems - 1`. */
13
+ maxItems?: number;
14
+ };
15
+ declare const Breadcrumb: import("svelte").Component<$$ComponentProps, {}, "">;
16
+ type Breadcrumb = ReturnType<typeof Breadcrumb>;
17
+ export default Breadcrumb;
package/dist/index.d.ts CHANGED
@@ -26,6 +26,7 @@ export { default as NavSection } from './components/layouts/NavSection.svelte';
26
26
  export { default as ResizablePanel } from './components/layouts/ResizablePanel.svelte';
27
27
  export { default as Stack } from './components/layouts/Stack.svelte';
28
28
  export { type AccordionItem, default as Accordion, } from './components/molecules/Accordion.svelte';
29
+ export { type BreadcrumbItem, default as Breadcrumb, } from './components/molecules/Breadcrumb.svelte';
29
30
  export { default as CodeBlock } from './components/molecules/CodeBlock.svelte';
30
31
  export { default as CopyButton } from './components/molecules/CopyButton.svelte';
31
32
  export { default as Dropzone } from './components/molecules/Dropzone.svelte';
@@ -52,7 +53,7 @@ export { default as Tooltip } from './components/molecules/Tooltip.svelte';
52
53
  export { default as Truncate } from './components/molecules/Truncate.svelte';
53
54
  export { type Column, default as DataTable } from './components/organisms/DataTable.svelte';
54
55
  export { default as FilterSearchBar } from './components/organisms/FilterSearchBar.svelte';
55
- 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 ValueOption, type ValueProvider, walk, } from './query';
56
+ 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';
56
57
  export { fontScale, SCALE_LEVELS, type ScaleLevel } from './stores/fontscale.svelte';
57
58
  export { type Mode, THEMES, theme } from './stores/theme.svelte';
58
59
  export { type Toast, type ToastTone, toasts } from './stores/toast.svelte';
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ export { default as NavSection } from './components/layouts/NavSection.svelte';
33
33
  export { default as ResizablePanel } from './components/layouts/ResizablePanel.svelte';
34
34
  export { default as Stack } from './components/layouts/Stack.svelte';
35
35
  export { default as Accordion, } from './components/molecules/Accordion.svelte';
36
+ export { default as Breadcrumb, } from './components/molecules/Breadcrumb.svelte';
36
37
  export { default as CodeBlock } from './components/molecules/CodeBlock.svelte';
37
38
  export { default as CopyButton } from './components/molecules/CopyButton.svelte';
38
39
  export { default as Dropzone } from './components/molecules/Dropzone.svelte';
@@ -3,7 +3,7 @@ export { filters, freeText, walk } from './ast';
3
3
  export { autoQuoteEdit, backspaceEmptyQuotes, closingQuoteExit, insideQuoteAtCaret, } from './edit';
4
4
  export { parse } from './parser';
5
5
  export { compilePredicate, serialize, serializeFilter, toSql } from './query';
6
- export type { FieldDef, FieldType, Operator, OperatorId, Schema, ValueOption, ValueProvider, } from './schema';
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
8
  export type { Suggestion, SuggestKind, SuggestState } from './suggest';
9
9
  export { activeToken, suggest } from './suggest';
@@ -19,8 +19,22 @@ export interface ValueOption {
19
19
  /** Optional secondary line (e.g. an email under a username). */
20
20
  hint?: string;
21
21
  }
22
+ /**
23
+ * Extra context handed to a {@link ValueProvider} alongside the value fragment:
24
+ * the full raw query, the active replacement span (`[start, end)` offsets of the
25
+ * text a chosen suggestion would replace) and the caret position. Optional so
26
+ * existing providers that only read the fragment keep working unchanged.
27
+ */
28
+ export interface ValueContext {
29
+ /** The full query string the fragment was extracted from. */
30
+ rawQuery: string;
31
+ /** `[start, end)` offsets of the token region a suggestion would replace. */
32
+ span: [number, number];
33
+ /** Absolute caret index within `rawQuery`. */
34
+ caret: number;
35
+ }
22
36
  /** Resolves candidate values for a field given the user's partial input. */
23
- export type ValueProvider = (query: string) => ValueOption[] | Promise<ValueOption[]>;
37
+ export type ValueProvider = (query: string, context?: ValueContext) => ValueOption[] | Promise<ValueOption[]>;
24
38
  export interface FieldDef {
25
39
  /** Canonical name, used in the serialised query (e.g. `artist`). */
26
40
  name: string;
@@ -57,4 +71,4 @@ export declare function findField(schema: Schema, token: string): FieldDef | und
57
71
  export declare function operatorByCode(code: string): Operator | undefined;
58
72
  export declare function operatorById(id: OperatorId): Operator | undefined;
59
73
  /** Resolve a field's value options (sync wrapper that always returns a promise). */
60
- export declare function resolveValues(field: FieldDef, query: string): Promise<ValueOption[]>;
74
+ export declare function resolveValues(field: FieldDef, query: string, context?: ValueContext): Promise<ValueOption[]>;
@@ -45,9 +45,9 @@ export function operatorById(id) {
45
45
  return OPERATORS.find((op) => op.id === id);
46
46
  }
47
47
  /** Resolve a field's value options (sync wrapper that always returns a promise). */
48
- export async function resolveValues(field, query) {
48
+ export async function resolveValues(field, query, context) {
49
49
  if (field.provider)
50
- return field.provider(query);
50
+ return field.provider(query, context);
51
51
  const opts = field.options ?? [];
52
52
  const q = query.toLowerCase();
53
53
  return q ? opts.filter((o) => o.label.toLowerCase().includes(q)) : opts;
@@ -1,4 +1,4 @@
1
- import { type Schema } from './schema';
1
+ import { type Schema } from './schema.ts';
2
2
  export type SuggestKind = 'field' | 'operator' | 'value';
3
3
  export interface Suggestion {
4
4
  /** What to show on the row. */
@@ -6,7 +6,7 @@
6
6
  //
7
7
  // This is pure logic, deliberately UI-free so the Svelte organism stays dumb.
8
8
  // ─────────────────────────────────────────────────────────────────────────
9
- import { findField, operatorsFor, resolveValues, } from './schema';
9
+ import { findField, operatorsFor, resolveValues, } from './schema.ts';
10
10
  // Longest first so `>=`/`!:` win over `>`/`:`.
11
11
  const OP_CODES = ['!:', '!=', '>=', '<=', ':', '=', '>', '<'];
12
12
  /**
@@ -164,7 +164,8 @@ export async function suggest(schema, s, pos) {
164
164
  if (!field)
165
165
  return null;
166
166
  // Operator present → suggest values (async; filtered by what's typed).
167
- const opts = await resolveValues(field, unquote(value));
167
+ const context = { rawQuery: s, span, caret: pos };
168
+ const opts = await resolveValues(field, unquote(value), context);
168
169
  if (opts.length === 0)
169
170
  return null;
170
171
  return buildValueState(field, opCode, span, opts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorsk/tsumikit",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "Minimal, dependency-free Svelte 5 + pure-CSS UI kit. Token-driven atoms, molecules & layouts with theming out of the box.",
5
5
  "type": "module",
6
6
  "license": "MIT",