@dorsk/tsumikit 0.44.0 → 0.45.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.
@@ -9,6 +9,10 @@
9
9
  /** Muted, right-aligned secondary text (e.g. "62%"). */
10
10
  hint?: string;
11
11
  disabled?: boolean;
12
+ /** Section heading this option belongs under. Options sharing a group are
13
+ * emitted inside one `<optgroup label={group}>`, in first-seen group order.
14
+ * Ungrouped options render at the top level. */
15
+ group?: string;
12
16
  };
13
17
  </script>
14
18
 
@@ -42,6 +46,7 @@
42
46
  import type { HTMLSelectAttributes } from 'svelte/elements';
43
47
  import Icon from './Icon.svelte';
44
48
  import { getFieldContext, warnUnlabelled } from '../../field-context';
49
+ import { sectionOptions } from '../../select-options';
45
50
 
46
51
  // `size` shadows the native option-count attribute (unused in token layouts) to
47
52
  // expose the sm|md height scale instead.
@@ -99,14 +104,26 @@
99
104
  const selected = $derived(options?.find((o) => o.value === value));
100
105
  const hasFace = $derived(!!options && variant !== 'ghost');
101
106
 
107
+ const sections = $derived(sectionOptions(options ?? []));
108
+
102
109
  const optionText = (o: SelectOption) =>
103
110
  [o.emoji, o.label, o.hint && `· ${o.hint}`].filter(Boolean).join(' ');
104
111
  </script>
105
112
 
106
113
  {#snippet optionList()}
107
114
  {#if options}
108
- {#each options as o (o.value)}
109
- <option value={o.value} disabled={o.disabled}>{optionText(o)}</option>
115
+ {#each sections as section, i (section.group ?? `-${i}`)}
116
+ {#if section.group === undefined}
117
+ {#each section.options as o (o.value)}
118
+ <option value={o.value} disabled={o.disabled}>{optionText(o)}</option>
119
+ {/each}
120
+ {:else}
121
+ <optgroup label={section.group}>
122
+ {#each section.options as o (o.value)}
123
+ <option value={o.value} disabled={o.disabled}>{optionText(o)}</option>
124
+ {/each}
125
+ </optgroup>
126
+ {/if}
110
127
  {/each}
111
128
  {:else}
112
129
  {@render children?.()}
@@ -251,7 +268,8 @@
251
268
  .select.has-face {
252
269
  color: transparent;
253
270
  }
254
- .select.has-face option {
271
+ .select.has-face option,
272
+ .select.has-face optgroup {
255
273
  color: var(--text);
256
274
  background: var(--bg);
257
275
  }
@@ -7,6 +7,10 @@ export type SelectOption = {
7
7
  /** Muted, right-aligned secondary text (e.g. "62%"). */
8
8
  hint?: string;
9
9
  disabled?: boolean;
10
+ /** Section heading this option belongs under. Options sharing a group are
11
+ * emitted inside one `<optgroup label={group}>`, in first-seen group order.
12
+ * Ungrouped options render at the top level. */
13
+ group?: string;
10
14
  };
11
15
  import type { ControlSize } from '../../size';
12
16
  import type { Snippet } from 'svelte';
@@ -64,8 +64,10 @@
64
64
  variant?: TriggerVariant;
65
65
  tone?: TriggerTone;
66
66
  size?: TriggerSize;
67
- /** Shared square box scale (`--box-xs/sm/md/lg`) for an icon-only trigger;
68
- * the default trigger is the `md` 2.25rem box. */
67
+ /** Shared square box scale (`--box-xs/sm/md/lg`) for an icon-only trigger,
68
+ * pinning the square exactly. The default trigger floors at the `sm` box
69
+ * and grows with its content; override that floor from `triggerClass`
70
+ * with `--pop-box`. */
69
71
  box?: 'xs' | 'sm' | 'md' | 'lg';
70
72
  /** Use the shared `--control-height` toolbar/composer contract. */
71
73
  control?: boolean;
@@ -184,13 +186,16 @@
184
186
  /* Default trigger chrome. Zero specificity (`:where`) so any consumer class
185
187
  (triggerClass) overrides it; `:not(.bare)` keeps it off bare triggers such
186
188
  as the Timestamp <time>, which stay plain inline text. */
189
+ /* The square is one knob, `--pop-box`: the `box` prop sets it inline, and a
190
+ consumer's `triggerClass` can set it too (`--pop-box: var(--box-xs)`) — a
191
+ plain width/height there would not have beaten a `min-*` floor. */
187
192
  :where(.pop-trigger:not(.bare)) {
188
193
  display: inline-flex;
189
194
  align-items: center;
190
195
  justify-content: center;
191
- min-height: var(--box-md);
192
- min-width: var(--box-md);
193
- padding: var(--sp-2);
196
+ min-height: var(--pop-box, var(--box-sm));
197
+ min-width: var(--pop-box, var(--box-sm));
198
+ padding: var(--sp-1);
194
199
  border: 1px solid transparent;
195
200
  border-radius: var(--r-md);
196
201
  background: transparent;
@@ -28,8 +28,10 @@ type $$ComponentProps = {
28
28
  variant?: TriggerVariant;
29
29
  tone?: TriggerTone;
30
30
  size?: TriggerSize;
31
- /** Shared square box scale (`--box-xs/sm/md/lg`) for an icon-only trigger;
32
- * the default trigger is the `md` 2.25rem box. */
31
+ /** Shared square box scale (`--box-xs/sm/md/lg`) for an icon-only trigger,
32
+ * pinning the square exactly. The default trigger floors at the `sm` box
33
+ * and grows with its content; override that floor from `triggerClass`
34
+ * with `--pop-box`. */
33
35
  box?: 'xs' | 'sm' | 'md' | 'lg';
34
36
  /** Use the shared `--control-height` toolbar/composer contract. */
35
37
  control?: boolean;
package/dist/index.d.ts CHANGED
@@ -83,6 +83,7 @@ export { default as FilterSearchBar } from './components/organisms/FilterSearchB
83
83
  export { FIELD_KEY, type FieldContext, getFieldContext, setFieldContext, warnUnlabelled, } from './field-context';
84
84
  export * as filterQuery from './query';
85
85
  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';
86
+ export { type OptionSection, sectionOptions } from './select-options';
86
87
  export type { ControlSize } from './size';
87
88
  export { fontScale, SCALE_LEVELS, type ScaleLevel } from './stores/fontscale.svelte';
88
89
  export { type Mode, THEMES, theme } from './stores/theme.svelte';
package/dist/index.js CHANGED
@@ -94,6 +94,7 @@ export { FIELD_KEY, getFieldContext, setFieldContext, warnUnlabelled, } from './
94
94
  // ---- query core (headless: schema / parser / AST / suggest / compilers) ----
95
95
  export * as filterQuery from './query';
96
96
  export { activeToken, compilePredicate, defaultOperator, filters, findField, freeText, OPERATORS, operatorByCode, operatorById, operatorsFor, parse, resolveValues, serialize, serializeFilter, suggest, toSql, walk, } from './query';
97
+ export { sectionOptions } from './select-options';
97
98
  export { fontScale, SCALE_LEVELS } from './stores/fontscale.svelte';
98
99
  // ---- stores / actions ----
99
100
  export { THEMES, theme } from './stores/theme.svelte';
@@ -0,0 +1,6 @@
1
+ import type { SelectOption } from './components/atoms/Select.svelte';
2
+ export type OptionSection = {
3
+ group?: string;
4
+ options: SelectOption[];
5
+ };
6
+ export declare function sectionOptions(options: SelectOption[]): OptionSection[];
@@ -0,0 +1,22 @@
1
+ export function sectionOptions(options) {
2
+ const sections = [];
3
+ const byGroup = new Map();
4
+ for (const option of options) {
5
+ if (option.group === undefined) {
6
+ const tail = sections.at(-1);
7
+ if (tail && tail.group === undefined)
8
+ tail.options.push(option);
9
+ else
10
+ sections.push({ options: [option] });
11
+ continue;
12
+ }
13
+ let section = byGroup.get(option.group);
14
+ if (!section) {
15
+ section = { group: option.group, options: [] };
16
+ byGroup.set(option.group, section);
17
+ sections.push(section);
18
+ }
19
+ section.options.push(option);
20
+ }
21
+ return sections;
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dorsk/tsumikit",
3
- "version": "0.44.0",
3
+ "version": "0.45.0",
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",