@kerfjs/ui 5.0.0-beta.103 → 5.0.0-beta.104

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.
@@ -1,4 +1,4 @@
1
- import { signal } from 'kerfjs';
1
+ import { signal, computed, batch } from 'kerfjs';
2
2
 
3
3
  // src/components/forms/token-search-field/model/token-search-model.ts
4
4
  var escapePattern = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -51,19 +51,33 @@ function createTokenSearchModel({
51
51
  })
52
52
  });
53
53
  const editorRevision = signal(0);
54
+ let lastStructure;
55
+ const structure = computed(() => {
56
+ const { query, tokens } = state.value;
57
+ const next = {
58
+ hasQuery: query.length > 0,
59
+ tokenValues: tokens.map((token) => token.value),
60
+ editorRevision: editorRevision.value
61
+ };
62
+ if (lastStructure?.hasQuery === next.hasQuery && lastStructure.editorRevision === next.editorRevision && lastStructure.tokenValues.length === next.tokenValues.length && lastStructure.tokenValues.every(
63
+ (value, index) => value === next.tokenValues[index]
64
+ ))
65
+ return lastStructure;
66
+ return lastStructure = next;
67
+ });
54
68
  const suggestions = signal([]);
55
- const result = signal(evaluate?.(state.value));
69
+ const result = signal(evaluate?.(state.peek()));
56
70
  let activeStart;
57
71
  let activeRule;
58
72
  const refreshSuggestions = () => {
59
- const match = activePattern?.exec(state.value.query);
73
+ const match = activePattern?.exec(state.peek().query);
60
74
  const raw = match?.[1];
61
75
  const separator = raw?.indexOf(":") ?? -1;
62
76
  const rule = separator < 0 ? void 0 : byName.get(raw.slice(0, separator).toLowerCase());
63
77
  activeRule = rule;
64
78
  const input = separator < 0 ? "" : unquote(raw.slice(separator + 1));
65
79
  activeStart = match && raw ? match.index + match[0].lastIndexOf(raw) : void 0;
66
- suggestions.value = (rule?.suggest?.(input, state.value) ?? []).flatMap(
80
+ const next = (rule?.suggest?.(input, state.peek()) ?? []).flatMap(
67
81
  (candidate) => {
68
82
  const value = typeof candidate === "string" ? candidate : candidate.value;
69
83
  const token = resolve(`${rule.name}:${quote(value)}`, 0);
@@ -75,12 +89,17 @@ function createTokenSearchModel({
75
89
  ] : [];
76
90
  }
77
91
  );
92
+ const current = suggestions.peek();
93
+ if (next.length !== current.length || next.some(
94
+ (suggestion, index) => suggestion.value !== current[index].value || suggestion.label !== current[index].label
95
+ ))
96
+ suggestions.value = next;
78
97
  };
79
- const publish = (next) => {
98
+ const publish = (next) => batch(() => {
80
99
  state.value = next;
81
100
  result.value = evaluate?.(next);
82
101
  refreshSuggestions();
83
- };
102
+ });
84
103
  refreshSuggestions();
85
104
  const edit = (value, commit2 = false) => {
86
105
  let query = "";
@@ -124,11 +143,11 @@ function createTokenSearchModel({
124
143
  };
125
144
  const commitResolved = (token) => {
126
145
  if (!token || activeStart === void 0) return;
127
- const query = state.value.query.slice(0, activeStart);
128
- publish({ query, tokens: [...state.value.tokens, token] });
146
+ const query = state.peek().query.slice(0, activeStart);
147
+ publish({ query, tokens: [...state.peek().tokens, token] });
129
148
  };
130
149
  const choose = (value) => {
131
- if (activeStart === void 0 || !suggestions.value.some((suggestion) => suggestion.value === value))
150
+ if (activeStart === void 0 || !suggestions.peek().some((suggestion) => suggestion.value === value))
132
151
  return;
133
152
  commitResolved(resolve(value, activeStart));
134
153
  };
@@ -139,35 +158,36 @@ function createTokenSearchModel({
139
158
  return {
140
159
  state,
141
160
  editorRevision,
161
+ structure,
142
162
  suggestions,
143
163
  result,
144
164
  edit,
145
165
  replace(value) {
146
- edit(value);
147
- editorRevision.value++;
166
+ batch(() => {
167
+ edit(value);
168
+ editorRevision.value = editorRevision.peek() + 1;
169
+ });
148
170
  },
149
171
  submit(value) {
150
172
  edit(value, true);
151
- onSubmit?.(state.value, result.value);
173
+ onSubmit?.(state.peek(), result.peek());
152
174
  },
153
175
  choose,
154
176
  commit,
155
177
  expandToken(value) {
156
- const index = state.value.tokens.findIndex(
157
- (token) => token.value === value
158
- );
178
+ const index = state.peek().tokens.findIndex((token) => token.value === value);
159
179
  if (index < 0) return;
160
180
  const offset = Math.max(
161
181
  0,
162
182
  Math.min(
163
- state.value.query.length,
164
- state.value.tokens[index].offset ?? state.value.query.length
183
+ state.peek().query.length,
184
+ state.peek().tokens[index].offset ?? state.peek().query.length
165
185
  )
166
186
  );
167
187
  const insertion = `${value} `;
168
188
  publish({
169
- query: `${state.value.query.slice(0, offset)}${insertion}${state.value.query.slice(offset)}`,
170
- tokens: state.value.tokens.flatMap(
189
+ query: `${state.peek().query.slice(0, offset)}${insertion}${state.peek().query.slice(offset)}`,
190
+ tokens: state.peek().tokens.flatMap(
171
191
  (token, at) => at === index ? [] : [
172
192
  {
173
193
  ...token,
@@ -178,25 +198,25 @@ function createTokenSearchModel({
178
198
  });
179
199
  },
180
200
  remove(value) {
181
- const index = state.value.tokens.findIndex(
182
- (token) => token.value === value
183
- );
201
+ const index = state.peek().tokens.findIndex((token) => token.value === value);
184
202
  if (index < 0) return;
185
203
  publish({
186
- ...state.value,
187
- tokens: state.value.tokens.filter((_, at) => at !== index)
204
+ ...state.peek(),
205
+ tokens: state.peek().tokens.filter((_, at) => at !== index)
188
206
  });
189
207
  },
190
208
  clear() {
191
- publish({ query: "", tokens: [] });
192
- editorRevision.value++;
209
+ batch(() => {
210
+ publish({ query: "", tokens: [] });
211
+ editorRevision.value = editorRevision.peek() + 1;
212
+ });
193
213
  },
194
214
  dismiss() {
195
- suggestions.value = [];
215
+ if (suggestions.peek().length > 0) suggestions.value = [];
196
216
  }
197
217
  };
198
218
  }
199
219
 
200
220
  export { createTokenSearchModel };
201
221
 
202
- //# sourceMappingURL=chunk-IM3GKYOG.js.map
222
+ //# sourceMappingURL=chunk-T5BJBMW7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/forms/token-search-field/model/token-search-model.ts"],"names":["commit"],"mappings":"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","file":"chunk-T5BJBMW7.js","sourcesContent":["import {\n batch,\n computed,\n type ReadonlySignal,\n type Signal,\n signal,\n} from 'kerfjs';\n\nimport type {\n TokenSearchFieldValue,\n TokenSearchToken,\n} from '../token-search-field.js';\n\nexport interface TokenSearchRule {\n /** The word before `:` (for example `tag` or `status`). */\n name: string;\n /** Return a canonical value, or undefined to leave invalid input as text. */\n parse?: (input: string) => string | undefined;\n /** Label for a committed chip. Defaults to `name:input`. */\n label?: (value: string, input: string) => string;\n /** Suggestions for the unfinished value after `name:`, with current committed tokens. */\n suggest?: (\n input: string,\n state: TokenSearchState,\n ) => readonly (string | { value: string; label: string })[];\n}\n\nexport interface TokenSearchResolvedToken extends TokenSearchToken {\n kind: string;\n parsedValue: string;\n}\n\nexport interface TokenSearchState {\n query: string;\n tokens: TokenSearchResolvedToken[];\n}\n\nexport interface TokenSearchSuggestion {\n value: string;\n label: string;\n}\n\n/**\n * The render-relevant shape of a managed field: everything a host render\n * needs to re-run for. Plain text edits do not change it.\n */\nexport interface TokenSearchStructure {\n /** Whether the free text is non-empty (drives clear, placeholder, and collapsed state). */\n hasQuery: boolean;\n /** Committed token values in editor order. */\n tokenValues: readonly string[];\n /** The model's editor revision (programmatic text replacement). */\n editorRevision: number;\n}\n\nexport interface TokenSearchModel<Result = unknown> {\n /**\n * The live query and tokens. Changes on every keystroke: read it with\n * `.peek()` (or in an event handler) to avoid subscribing a render per key.\n */\n state: Signal<TokenSearchState>;\n /** Changes when a model action must replace DOM-owned editor text. */\n editorRevision: Signal<number>;\n /**\n * Changes only on structural edits (a token committed, removed, or\n * reordered; the text becoming empty or non-empty; an editor revision), and\n * keeps the same reference otherwise. `TokenSearchField` subscribes to this\n * instead of `state`, so typing does not re-run the host render.\n */\n structure: ReadonlySignal<TokenSearchStructure>;\n /** Visible suggestions. Publishes only when the list's content changes. */\n suggestions: Signal<readonly TokenSearchSuggestion[]>;\n result: Signal<Result | undefined>;\n edit(value: TokenSearchFieldValue, commit?: boolean): void;\n /** Replace app-owned search state and rebuild the DOM-owned editor text. */\n replace(value: TokenSearchFieldValue): void;\n submit(value: TokenSearchFieldValue): void;\n choose(value: string): void;\n /** Commit a value for the active `name:` prefix, even when it is not suggested. */\n commit(value: string): void;\n expandToken(value: string): void;\n remove(value: string): void;\n clear(): void;\n dismiss(): void;\n}\n\nexport interface TokenSearchModelOptions<Result> {\n rules?: readonly TokenSearchRule[];\n initial?: TokenSearchFieldValue;\n /** Runs after every state change, including initial state. */\n evaluate?: (state: TokenSearchState) => Result;\n onSubmit?: (state: TokenSearchState, result: Result | undefined) => void;\n}\n\nconst escapePattern = (text: string) =>\n text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\nconst quote = (text: string) =>\n /[\\s()\"\\\\]/.test(text)\n ? `\"${text.replaceAll('\\\\', '\\\\\\\\').replaceAll('\"', '\\\\\"')}\"`\n : text;\nconst unquote = (text: string) =>\n text.startsWith('\"') && text.endsWith('\"')\n ? text.slice(1, -1).replace(/\\\\([\"\\\\])/g, '$1')\n : text;\n\n/** Opt-in state, token parsing, suggestions, and evaluation for TokenSearchField. */\nexport function createTokenSearchModel<Result = unknown>({\n rules = [],\n initial = { query: '', tokens: [] },\n evaluate,\n onSubmit,\n}: TokenSearchModelOptions<Result> = {}): TokenSearchModel<Result> {\n const byName = new Map(rules.map((rule) => [rule.name.toLowerCase(), rule]));\n if (\n byName.size !== rules.length ||\n rules.some((rule) => !/^[\\w-]+$/.test(rule.name))\n )\n throw new Error('Token search rule names must be unique words');\n const names = [...byName.keys()].map(escapePattern).join('|');\n const pattern = names\n ? new RegExp(\n `(?:^|[\\\\s(])((?:${names}):(?:\"(?:\\\\\\\\.|[^\"])*\"|[^\"\\\\s()][^\\\\s()]*))(?=$|[\\\\s)])`,\n 'gi',\n )\n : undefined;\n const activePattern = names\n ? new RegExp(\n `(?:^|[\\\\s(])((?:${names}):(?:\"(?:\\\\\\\\.|[^\"])*|[^\\\\s()]*))$`,\n 'i',\n )\n : undefined;\n\n const resolve = (\n raw: string,\n offset: number,\n ): TokenSearchResolvedToken | undefined => {\n const separator = raw.indexOf(':');\n const kind = raw.slice(0, separator).toLowerCase();\n const rule = byName.get(kind);\n if (!rule || separator < 0) return;\n const input = unquote(raw.slice(separator + 1));\n if (!input) return;\n const parsedValue = rule.parse ? rule.parse(input) : input;\n if (parsedValue === undefined) return;\n return {\n kind,\n parsedValue,\n value: `${kind}:${quote(input)}`,\n label: rule.label?.(parsedValue, input) ?? `${kind}:${input}`,\n offset,\n };\n };\n\n const rehydrate = (\n token: TokenSearchToken,\n ): TokenSearchResolvedToken | undefined => {\n const resolved = resolve(token.value, token.offset ?? 0);\n return resolved ? { ...resolved, ...token } : undefined;\n };\n\n const state = signal<TokenSearchState>({\n query: initial.query,\n tokens: initial.tokens.flatMap((token) => {\n const resolved = rehydrate(token);\n return resolved ? [resolved] : [];\n }),\n });\n const editorRevision = signal(0);\n let lastStructure: TokenSearchStructure | undefined;\n const structure = computed<TokenSearchStructure>(() => {\n const { query, tokens } = state.value;\n const next: TokenSearchStructure = {\n hasQuery: query.length > 0,\n tokenValues: tokens.map((token) => token.value),\n editorRevision: editorRevision.value,\n };\n if (\n lastStructure?.hasQuery === next.hasQuery &&\n lastStructure.editorRevision === next.editorRevision &&\n lastStructure.tokenValues.length === next.tokenValues.length &&\n lastStructure.tokenValues.every(\n (value, index) => value === next.tokenValues[index],\n )\n )\n return lastStructure;\n return (lastStructure = next);\n });\n const suggestions = signal<readonly TokenSearchSuggestion[]>([]);\n const result = signal<Result | undefined>(evaluate?.(state.peek()));\n let activeStart: number | undefined;\n let activeRule: TokenSearchRule | undefined;\n\n const refreshSuggestions = () => {\n const match = activePattern?.exec(state.peek().query);\n const raw = match?.[1];\n const separator = raw?.indexOf(':') ?? -1;\n const rule =\n separator < 0\n ? undefined\n : byName.get(raw!.slice(0, separator).toLowerCase());\n activeRule = rule;\n const input = separator < 0 ? '' : unquote(raw!.slice(separator + 1));\n activeStart =\n match && raw ? match.index + match[0].lastIndexOf(raw) : undefined;\n const next = (rule?.suggest?.(input, state.peek()) ?? []).flatMap(\n (candidate) => {\n const value =\n typeof candidate === 'string' ? candidate : candidate.value;\n const token = resolve(`${rule!.name}:${quote(value)}`, 0);\n return token\n ? [\n {\n value: token.value,\n label:\n typeof candidate === 'string' ? token.label : candidate.label,\n },\n ]\n : [];\n },\n );\n const current = suggestions.peek();\n if (\n next.length !== current.length ||\n next.some(\n (suggestion, index) =>\n suggestion.value !== current[index].value ||\n suggestion.label !== current[index].label,\n )\n )\n suggestions.value = next;\n };\n const publish = (next: TokenSearchState) =>\n batch(() => {\n state.value = next;\n result.value = evaluate?.(next);\n refreshSuggestions();\n });\n refreshSuggestions();\n\n const edit = (value: TokenSearchFieldValue, commit = false) => {\n let query = '';\n let cursor = 0;\n const added: TokenSearchResolvedToken[] = [];\n const removed: Array<{ start: number; end: number }> = [];\n for (const match of value.query.matchAll(pattern ?? /(?!) /g)) {\n const raw = match[1];\n const start = match.index + match[0].lastIndexOf(raw);\n const end = start + raw.length;\n if (end === value.query.length && !commit && !/\\s$/.test(value.query))\n continue;\n const token = resolve(raw, query.length + start - cursor);\n if (!token) continue;\n query += value.query.slice(cursor, start);\n token.offset = query.length;\n added.push(token);\n removed.push({ start, end });\n cursor = end;\n }\n const suffix = value.query.slice(cursor);\n query += added.length && /^\\s+$/.test(suffix) ? '' : suffix;\n const existing = value.tokens.flatMap((token) => {\n const resolved = rehydrate(token);\n if (!resolved) return [];\n const offset = token.offset ?? value.query.length;\n return [\n {\n ...resolved,\n offset:\n offset -\n removed.reduce(\n (total, range) =>\n total + Math.max(0, Math.min(offset, range.end) - range.start),\n 0,\n ),\n },\n ];\n });\n const tokens = [...existing, ...added].sort(\n (left, right) => (left.offset ?? 0) - (right.offset ?? 0),\n );\n publish({ query, tokens });\n };\n\n const commitResolved = (token: TokenSearchResolvedToken | undefined) => {\n if (!token || activeStart === undefined) return;\n const query = state.peek().query.slice(0, activeStart);\n publish({ query, tokens: [...state.peek().tokens, token] });\n };\n const choose = (value: string) => {\n if (\n activeStart === undefined ||\n !suggestions.peek().some((suggestion) => suggestion.value === value)\n )\n return;\n commitResolved(resolve(value, activeStart));\n };\n const commit = (value: string) => {\n if (!activeRule || activeStart === undefined) return;\n commitResolved(resolve(`${activeRule.name}:${quote(value)}`, activeStart));\n };\n\n return {\n state,\n editorRevision,\n structure,\n suggestions,\n result,\n edit,\n replace(value) {\n batch(() => {\n edit(value);\n editorRevision.value = editorRevision.peek() + 1;\n });\n },\n submit(value) {\n edit(value, true);\n onSubmit?.(state.peek(), result.peek());\n },\n choose,\n commit,\n expandToken(value) {\n const index = state\n .peek()\n .tokens.findIndex((token) => token.value === value);\n if (index < 0) return;\n const offset = Math.max(\n 0,\n Math.min(\n state.peek().query.length,\n state.peek().tokens[index].offset ?? state.peek().query.length,\n ),\n );\n const insertion = `${value} `;\n publish({\n query: `${state.peek().query.slice(0, offset)}${insertion}${state.peek().query.slice(offset)}`,\n tokens: state.peek().tokens.flatMap((token, at) =>\n at === index\n ? []\n : [\n {\n ...token,\n offset:\n (token.offset ?? 0) + (at > index ? insertion.length : 0),\n },\n ],\n ),\n });\n },\n remove(value) {\n const index = state\n .peek()\n .tokens.findIndex((token) => token.value === value);\n if (index < 0) return;\n publish({\n ...state.peek(),\n tokens: state.peek().tokens.filter((_, at) => at !== index),\n });\n },\n clear() {\n batch(() => {\n publish({ query: '', tokens: [] });\n editorRevision.value = editorRevision.peek() + 1;\n });\n },\n dismiss() {\n if (suggestions.peek().length > 0) suggestions.value = [];\n },\n };\n}\n"]}
@@ -54,14 +54,12 @@ function TokenSearchField({
54
54
  editorAttributes = {},
55
55
  slot
56
56
  }) {
57
- if (model) {
58
- query = model.state.value.query;
59
- tokens = model.state.value.tokens;
60
- }
57
+ const structure = model?.structure.value;
58
+ if (model) ({ query, tokens } = model.state.peek());
61
59
  const suggestions = model?.suggestions.value ?? [];
62
60
  const parts = orderedParts(query, tokens);
63
61
  const tokenKey = tokens.map((token) => token.value).join("|");
64
- const key = model ? `${id}@${String(revision ?? "")}:${model.editorRevision.value}:${tokenKey}` : revision === void 0 ? `${id}:${tokenKey}` : `${id}@${String(revision)}:${tokenKey}`;
62
+ const key = structure ? `${id}@${String(revision ?? "")}:${structure.editorRevision}:${tokenKey}` : revision === void 0 ? `${id}:${tokenKey}` : `${id}@${String(revision)}:${tokenKey}`;
65
63
  const resolvedExpanded = !collapsible || expanded || query.length > 0 || tokens.length > 0;
66
64
  const control = /* @__PURE__ */ jsx(
67
65
  "div",
@@ -297,4 +295,4 @@ function placeTokenSearchCaret(editor, offset) {
297
295
 
298
296
  export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField };
299
297
 
300
- //# sourceMappingURL=chunk-MF6ZUUJZ.js.map
298
+ //# sourceMappingURL=chunk-ZJC5ND3M.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/forms/token-search-field/token-search-field.tsx"],"names":[],"mappings":"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","file":"chunk-ZJC5ND3M.js","sourcesContent":["import type { SafeHtml } from 'kerfjs';\nimport { Search, X } from 'lucide';\n\nimport type { KerfUiContent } from '../../../shared/content/semantic-content.js';\nimport { LucideIcon } from '../../media/lucide-icon/lucide-icon.js';\nimport type { TokenSearchModel } from './model/token-search-model.js';\n\nexport interface TokenSearchToken {\n value: string;\n label: string;\n offset?: number;\n accessibleLabel?: string;\n}\n\n/**\n * Domain `data-*` metadata for a trailing action button. The component owns\n * `data-action` (pass `action`) and its state attributes.\n */\nexport type TokenSearchTrailingActionAttributes = Readonly<\n Record<`data-${string}`, string | undefined> & {\n 'data-action'?: never;\n 'data-component'?: never;\n 'data-key'?: never;\n }\n>;\n\ninterface TokenSearchTrailingActionBase {\n /** Decorative glyph, such as a `LucideIcon`. */\n icon: SafeHtml;\n /** Accessible name and tooltip, in title style (\"Search Help\"). */\n label: string;\n /** Delegated `data-action` value. */\n action: string;\n id?: string;\n /** `aria-controls`: the id of the surface this button opens or toggles. */\n controls?: string;\n /** `aria-haspopup` for a button that opens a dialog, menu, or listbox. */\n haspopup?: 'dialog' | 'menu' | 'listbox' | 'true';\n /** Domain `data-*` metadata for the app's delegated handler. */\n attributes?: TokenSearchTrailingActionAttributes;\n}\n\n/**\n * A compact icon button in the field's trailing slot, sized and styled like\n * the clear button. Pass `expanded` for a disclosure (a help surface it opens)\n * or `pressed` for a toggle; the button shows the state with its hover fill.\n */\nexport type TokenSearchTrailingAction = TokenSearchTrailingActionBase &\n (\n | { expanded?: boolean; pressed?: never }\n | { expanded?: never; pressed?: boolean }\n );\n\nexport type TokenSearchEditorAttributes = Readonly<\n Record<`data-${string}`, string | undefined> & {\n 'data-component'?: never;\n 'data-key'?: never;\n 'data-morph-skip'?: never;\n 'data-token-search-editor'?: never;\n 'data-token-count'?: never;\n 'data-placeholder'?: never;\n }\n>;\n\ninterface TokenSearchFieldBaseProps {\n id: string;\n label: string;\n query?: string;\n tokens?: readonly TokenSearchToken[];\n /** Opt-in grammar, suggestion, and evaluation state. Overrides `query` and `tokens`. */\n model?: TokenSearchModel;\n /**\n * Editor identity for a programmatic text replacement. The editor's text is\n * DOM-owned between token changes, so a new `query` with the same tokens\n * does not re-render it (that would reset the caret on every keystroke).\n * Change `revision` when the app replaces the text itself — reseeding a\n * persistent dialog's field on reopen, applying a saved search — and the\n * editor is rebuilt from `query` and `tokens`.\n */\n revision?: string | number;\n placeholder?: string;\n tokenPlaceholder?: string;\n disabled?: boolean;\n autofocus?: boolean;\n /** Let an expanded collapsible field consume the available inline width. */\n fill?: boolean;\n leading?: KerfUiContent;\n editAction?: string;\n removeAction?: string;\n clearAction?: string;\n clearLabel?: string;\n /** Icon for the clear button. Default: the Lucide `x` glyph token chips also use. */\n clearIcon?: SafeHtml;\n className?: string;\n editorAttributes?: TokenSearchEditorAttributes;\n /** Native named-slot assignment when composed inside a web component. */\n slot?: string;\n}\n\ntype TokenSearchTrailingProps =\n | { trailing?: KerfUiContent; trailingAction?: never }\n | { trailing?: never; trailingAction: TokenSearchTrailingAction };\n\ntype TokenSearchPresentationProps =\n | {\n /** Standalone field chrome or the inset visual layer of a configured toolbar group. */\n presentation?: 'standalone' | 'toolbar-group';\n hint?: never;\n required?: never;\n }\n | {\n /** Visible form label and optional hint around the full-width standalone control. */\n presentation: 'form-field';\n hint?: string;\n /** Marks the editor required; the app validates its value. */\n required?: boolean;\n };\n\ntype TokenSearchCollapsibleProps =\n | {\n /** Allow an empty field to render as one iconic action. */\n collapsible: true;\n /** Keep an empty collapsible field open while the application owns focus. */\n expanded?: boolean;\n expandAction?: string;\n expandLabel?: string;\n }\n | {\n collapsible?: false;\n expanded?: never;\n expandAction?: never;\n expandLabel?: never;\n };\n\nexport type TokenSearchFieldProps = TokenSearchFieldBaseProps &\n TokenSearchCollapsibleProps &\n TokenSearchTrailingProps &\n TokenSearchPresentationProps;\n\nexport interface TokenSearchFieldValue {\n query: string;\n tokens: TokenSearchToken[];\n}\n\ntype TokenSearchPart =\n { kind: 'text'; value: string } | { kind: 'token'; token: TokenSearchToken };\n\nfunction orderedParts(\n query: string,\n tokens: readonly TokenSearchToken[],\n): TokenSearchPart[] {\n const ordered = tokens\n .map((token, index) => ({\n token,\n index,\n offset: Math.max(0, Math.min(query.length, token.offset ?? query.length)),\n }))\n .sort(\n (left, right) => left.offset - right.offset || left.index - right.index,\n );\n const parts: TokenSearchPart[] = [];\n let cursor = 0;\n for (const { token, offset } of ordered) {\n parts.push({ kind: 'text', value: query.slice(cursor, offset) });\n parts.push({ kind: 'token', token: { ...token, offset } });\n cursor = offset;\n }\n parts.push({ kind: 'text', value: query.slice(cursor) });\n return parts;\n}\n\nfunction CloseIcon() {\n return <LucideIcon icon={X} name=\"x\" />;\n}\n\nexport function TokenSearchField({\n id,\n label,\n query = '',\n tokens = [],\n model,\n revision,\n placeholder = 'Search',\n tokenPlaceholder = 'Add search…',\n disabled = false,\n autofocus = false,\n fill = false,\n collapsible = false,\n expanded = false,\n expandAction = 'expand-token-search',\n expandLabel,\n leading,\n trailing,\n trailingAction,\n presentation = 'standalone',\n hint,\n required = false,\n editAction = 'edit-search-token',\n removeAction = 'remove-search-token',\n clearAction = 'clear-token-search',\n clearLabel = 'Clear Search',\n clearIcon,\n className = '',\n editorAttributes = {},\n slot,\n}: TokenSearchFieldProps) {\n // A managed model subscribes the host render to `structure` (token,\n // emptiness, and revision changes) only. The live text is DOM-owned between\n // structural changes, so it is peeked, not read: typing must not re-run the\n // host render on every keystroke.\n const structure = model?.structure.value;\n if (model) ({ query, tokens } = model.state.peek());\n const suggestions = model?.suggestions.value ?? [];\n const parts = orderedParts(query, tokens);\n const tokenKey = tokens.map((token) => token.value).join('|');\n const key = structure\n ? `${id}@${String(revision ?? '')}:${structure.editorRevision}:${tokenKey}`\n : revision === undefined\n ? `${id}:${tokenKey}`\n : `${id}@${String(revision)}:${tokenKey}`;\n const resolvedExpanded =\n !collapsible || expanded || query.length > 0 || tokens.length > 0;\n const control = (\n <div\n class={`kui-token-search ${presentation === 'form-field' ? '' : className}`.trim()}\n data-component=\"token-search-field\"\n data-token-search-id={id}\n data-disabled={String(disabled)}\n data-collapsible={String(collapsible)}\n data-expanded={String(resolvedExpanded)}\n data-fill={String(fill)}\n data-has-trailing={String(Boolean(trailing || trailingAction))}\n data-presentation={presentation}\n data-placeholder={tokens.length ? tokenPlaceholder : placeholder}\n data-placeholder-visible={String(\n query.length === 0 && tokens.length === 0,\n )}\n slot={slot}\n >\n {!resolvedExpanded ? (\n <button\n type=\"button\"\n class=\"kui-token-search__expand\"\n data-action={expandAction}\n aria-label={expandLabel ?? label}\n title={expandLabel ?? label}\n disabled={disabled}\n >\n <LucideIcon icon={Search} name=\"search\" />\n </button>\n ) : (\n <>\n <span class=\"kui-token-search__leading\" aria-hidden=\"true\">\n {leading ?? <LucideIcon icon={Search} name=\"search\" />}\n </span>\n <div\n {...editorAttributes}\n class=\"kui-token-search__editor\"\n data-key={key}\n data-morph-skip\n data-token-search-editor={id}\n data-token-count={tokens.length}\n data-placeholder={tokens.length ? tokenPlaceholder : placeholder}\n role=\"searchbox\"\n aria-label={presentation === 'form-field' ? undefined : label}\n aria-labelledby={\n presentation === 'form-field' ? `${id}-label` : undefined\n }\n aria-describedby={\n presentation === 'form-field' && hint ? `${id}-hint` : undefined\n }\n aria-required={required ? 'true' : undefined}\n aria-disabled={disabled ? 'true' : undefined}\n contenteditable={disabled ? 'false' : 'true'}\n spellcheck=\"false\"\n autofocus={autofocus}\n >\n {parts.map((part) =>\n part.kind === 'text' ? (\n <span\n data-token-search-text\n data-empty={String(part.value.length === 0)}\n >\n {part.value || (tokens.length ? '\\u200b' : '')}\n </span>\n ) : (\n <span\n class=\"kui-token-search__token\"\n contenteditable=\"false\"\n data-component=\"token-search-token\"\n data-token-value={part.token.value}\n >\n <button\n type=\"button\"\n class=\"kui-token-search__token-edit\"\n data-action={editAction}\n data-token-value={part.token.value}\n aria-label={`Edit ${part.token.accessibleLabel ?? part.token.label}`}\n disabled={disabled}\n >\n {part.token.label}\n </button>\n <button\n type=\"button\"\n class=\"kui-token-search__token-remove\"\n data-action={removeAction}\n data-token-value={part.token.value}\n aria-label={`Remove ${part.token.accessibleLabel ?? part.token.label}`}\n disabled={disabled}\n >\n <CloseIcon />\n </button>\n </span>\n ),\n )}\n </div>\n {(query || tokens.length > 0) && (\n <button\n type=\"button\"\n class=\"kui-token-search__clear\"\n data-action={clearAction}\n aria-label={clearLabel}\n title={clearLabel}\n disabled={disabled}\n >\n {clearIcon ?? <CloseIcon />}\n </button>\n )}\n {trailingAction ? (\n <button\n {...trailingAction.attributes}\n type=\"button\"\n class=\"kui-token-search__trailing-action\"\n id={trailingAction.id}\n data-action={trailingAction.action}\n aria-label={trailingAction.label}\n aria-expanded={\n trailingAction.expanded === undefined\n ? undefined\n : String(trailingAction.expanded)\n }\n aria-pressed={\n trailingAction.pressed === undefined\n ? undefined\n : String(trailingAction.pressed)\n }\n aria-controls={trailingAction.controls}\n aria-haspopup={trailingAction.haspopup}\n title={trailingAction.label}\n disabled={disabled}\n >\n {trailingAction.icon}\n </button>\n ) : trailing ? (\n <span class=\"kui-token-search__trailing\">{trailing}</span>\n ) : null}\n {!disabled && suggestions.length > 0 && (\n <div\n class=\"kui-token-search__suggestions\"\n data-token-search-keep-open\n >\n {suggestions.map((suggestion) => (\n <button\n type=\"button\"\n class=\"kui-token-search__suggestion\"\n data-token-search-suggestion={suggestion.value}\n >\n {suggestion.label}\n </button>\n ))}\n </div>\n )}\n </>\n )}\n </div>\n );\n if (presentation !== 'form-field') return control;\n return (\n <div\n class={`kui-token-search__field ${className}`.trim()}\n data-token-search-form-field={id}\n >\n <div\n class=\"kui-token-search__field-label\"\n id={`${id}-label`}\n data-token-search-form-label={id}\n >\n {label}\n {required && (\n <span class=\"kui-token-search__field-required\" aria-hidden=\"true\">\n *\n </span>\n )}\n </div>\n {control}\n {hint && (\n <div class=\"kui-token-search__field-hint\" id={`${id}-hint`}>\n {hint}\n </div>\n )}\n </div>\n );\n}\n\n/** Read editable text and ordered token offsets from a rendered TokenSearchField editor. */\nexport function readTokenSearchField(\n editor: HTMLElement,\n knownTokens: readonly TokenSearchToken[] = [],\n): TokenSearchFieldValue {\n let query = '';\n const tokens: TokenSearchToken[] = [];\n const visit = (node: Node): void => {\n if (node.nodeType === Node.TEXT_NODE) {\n query += (node.textContent ?? '')\n .replaceAll('\\u00a0', ' ')\n .replaceAll('\\u200b', '');\n return;\n }\n if (!(node instanceof HTMLElement)) return;\n if (node.matches('[data-component=\"token-search-token\"]')) {\n const value = node.dataset.tokenValue;\n const known = knownTokens.find((token) => token.value === value);\n if (known) tokens.push({ ...known, offset: query.length });\n else if (value)\n tokens.push({ value, label: value, offset: query.length });\n return;\n }\n if (node.tagName === 'BR') {\n query += ' ';\n return;\n }\n for (const child of node.childNodes) visit(child);\n };\n for (const child of editor.childNodes) visit(child);\n return { query, tokens };\n}\n\n/** Focus an editor and place its caret at a text offset, skipping atomic token chips. */\nexport function placeTokenSearchCaret(\n editor: HTMLElement,\n offset?: number,\n): void {\n editor.focus({ preventScroll: true });\n const selection = globalThis.getSelection();\n if (!selection) return;\n const range = document.createRange();\n if (offset === undefined) {\n range.selectNodeContents(editor);\n range.collapse(false);\n } else {\n let remaining = Math.max(0, offset);\n let found = false;\n const walker = document.createTreeWalker(editor, NodeFilter.SHOW_TEXT, {\n acceptNode: (node) =>\n node.parentElement?.closest('[data-component=\"token-search-token\"]')\n ? NodeFilter.FILTER_REJECT\n : NodeFilter.FILTER_ACCEPT,\n });\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n const source = node.textContent ?? '';\n const length = source.replaceAll('\\u200b', '').length;\n if (remaining <= length) {\n range.setStart(\n node,\n source === '\\u200b' ? 0 : Math.min(remaining, source.length),\n );\n range.collapse(true);\n found = true;\n break;\n }\n remaining -= length;\n }\n if (!found) {\n range.selectNodeContents(editor);\n range.collapse(false);\n }\n }\n selection.removeAllRanges();\n selection.addRange(range);\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -36,7 +36,7 @@ export { FileDropZone, FileDropZoneProps, FilePickerTrigger, FilePickerTriggerPr
36
36
  export { FilePickerSelection, WireFilePickersOptions, fileMatchesAccept, wireFilePickers } from './wire-file-pickers.js';
37
37
  export { SegmentedControl, SegmentedControlAppearance, SegmentedControlChoice, SegmentedControlLayout, SegmentedControlProps, SegmentedControlShape, SegmentedControlSize } from './segmented-control.js';
38
38
  export { Select, SelectChoice, SelectFocusRingOwner, SelectPresentation, SelectProps, SelectSelectedPresentation, SelectSize } from './select.js';
39
- export { T as TokenSearchEditorAttributes, a as TokenSearchField, b as TokenSearchFieldProps, c as TokenSearchFieldValue, d as TokenSearchModel, e as TokenSearchModelOptions, f as TokenSearchResolvedToken, g as TokenSearchRule, h as TokenSearchState, i as TokenSearchSuggestion, j as TokenSearchToken, k as TokenSearchTrailingAction, l as TokenSearchTrailingActionAttributes, m as createTokenSearchModel, p as placeTokenSearchCaret, r as readTokenSearchField } from './token-search-model-aYR0YAei.js';
39
+ export { T as TokenSearchEditorAttributes, a as TokenSearchField, b as TokenSearchFieldProps, c as TokenSearchFieldValue, d as TokenSearchModel, e as TokenSearchModelOptions, f as TokenSearchResolvedToken, g as TokenSearchRule, h as TokenSearchState, i as TokenSearchStructure, j as TokenSearchSuggestion, k as TokenSearchToken, l as TokenSearchTrailingAction, m as TokenSearchTrailingActionAttributes, n as createTokenSearchModel, p as placeTokenSearchCaret, r as readTokenSearchField } from './token-search-model-JA1i8z4D.js';
40
40
  export { TokenSearchCollapsibleOptions, TokenSearchEdit, TokenSearchFieldsHandle, TokenSearchSubmit, WireTokenSearchFieldsOptions, wireTokenSearchFields } from './wire-token-search-fields.js';
41
41
  export { ColumnTrack, ColumnTrackLayout, ColumnTrackProps } from './column-track.js';
42
42
  export { ContentSwap, ContentSwapDirection, ContentSwapProps, ContentSwapSide, ContentSwapTransition } from './content-swap.js';
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ export { wireToolbarVisibility } from './chunk-2GOCOLQL.js';
2
2
  export { wireOverlaySheets } from './chunk-5NQF35VO.js';
3
3
  export { wireResizableRegions } from './chunk-GS2QD3N7.js';
4
4
  export { reorderTabs, wireTabBars } from './chunk-K4AT3V3J.js';
5
- export { wireTokenSearchFields } from './chunk-PKL6QJKK.js';
5
+ export { wireTokenSearchFields } from './chunk-CR5U5FV6.js';
6
6
 
7
7
  export { wireScrollDividers } from './chunk-OZ6X76G5.js';
8
8
  export { wireComboboxes } from './chunk-FDZNYD4C.js';
@@ -11,7 +11,7 @@ export { wireCornerStatus } from './chunk-ZKKO4MHC.js';
11
11
  export { fileMatchesAccept, wireFilePickers } from './chunk-2MSPNRWJ.js';
12
12
  export { wireListFocusScroll } from './chunk-QDSZTQXL.js';
13
13
  export { reorderListItems, wireListReorder } from './chunk-MJ5RU6XT.js';
14
- export { createTokenSearchModel } from './chunk-IM3GKYOG.js';
14
+ export { createTokenSearchModel } from './chunk-T5BJBMW7.js';
15
15
  export { ValueTable, ValueTableRow } from './chunk-DZJ4GBSP.js';
16
16
  export { StateBanner } from './chunk-FNZQOUZ5.js';
17
17
  export { SummaryRow } from './chunk-4UB5Z3ZZ.js';
@@ -40,7 +40,7 @@ export { CornerStatus } from './chunk-G3J7JDP6.js';
40
40
  export { openAnchoredSurface, openAnchoredSurfaceAt } from './chunk-3WYREXW4.js';
41
41
  export { AppTab } from './chunk-TGLUDQP2.js';
42
42
  export { BackdropImage } from './chunk-SCDWURBO.js';
43
- export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-MF6ZUUJZ.js';
43
+ export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-ZJC5ND3M.js';
44
44
  export { ResizableRegion, clampRegionSize, resizeRegionFromPointer } from './chunk-Y6KT7ZTR.js';
45
45
  export { Row } from './chunk-YQ2AC4E2.js';
46
46
  export { ListItem, ListItemLink } from './chunk-ZYU5WRC3.js';
@@ -355,6 +355,21 @@
355
355
  margin-inline-start: auto;
356
356
  }
357
357
 
358
+ /* A lone zone spans every column track, so an absent (empty, hidden) zone's
359
+ track and the column gap before it never narrow it: leading-only content
360
+ set to fill the row reaches the toolbar's inline-end inset, and
361
+ trailing-only content its inline-start inset. */
362
+ .kui-toolbar[data-has-center="false"]:has(> .kui-toolbar__trailing:empty)
363
+ > .kui-toolbar__leading,
364
+ .kui-toolbar:has(> .kui-toolbar__leading:empty):has(
365
+ > .kui-toolbar__trailing:empty
366
+ )
367
+ > .kui-toolbar__center,
368
+ .kui-toolbar[data-has-center="false"]:has(> .kui-toolbar__leading:empty)
369
+ > .kui-toolbar__trailing {
370
+ grid-column: 1 / -1;
371
+ }
372
+
358
373
  .kui-toolbar[data-has-center="false"] .kui-toolbar__center,
359
374
  .kui-toolbar__leading:empty,
360
375
  .kui-toolbar__trailing:empty {
@@ -355,6 +355,21 @@
355
355
  margin-inline-start: auto;
356
356
  }
357
357
 
358
+ /* A lone zone spans every column track, so an absent (empty, hidden) zone's
359
+ track and the column gap before it never narrow it: leading-only content
360
+ set to fill the row reaches the toolbar's inline-end inset, and
361
+ trailing-only content its inline-start inset. */
362
+ .kui-toolbar[data-has-center="false"]:has(> .kui-toolbar__trailing:empty)
363
+ > .kui-toolbar__leading,
364
+ .kui-toolbar:has(> .kui-toolbar__leading:empty):has(
365
+ > .kui-toolbar__trailing:empty
366
+ )
367
+ > .kui-toolbar__center,
368
+ .kui-toolbar[data-has-center="false"]:has(> .kui-toolbar__leading:empty)
369
+ > .kui-toolbar__trailing {
370
+ grid-column: 1 / -1;
371
+ }
372
+
358
373
  .kui-toolbar[data-has-center="false"] .kui-toolbar__center,
359
374
  .kui-toolbar__leading:empty,
360
375
  .kui-toolbar__trailing:empty {
@@ -1,3 +1,3 @@
1
- export { T as TokenSearchEditorAttributes, a as TokenSearchField, b as TokenSearchFieldProps, c as TokenSearchFieldValue, j as TokenSearchToken, k as TokenSearchTrailingAction, l as TokenSearchTrailingActionAttributes, p as placeTokenSearchCaret, r as readTokenSearchField } from './token-search-model-aYR0YAei.js';
1
+ export { T as TokenSearchEditorAttributes, a as TokenSearchField, b as TokenSearchFieldProps, c as TokenSearchFieldValue, k as TokenSearchToken, l as TokenSearchTrailingAction, m as TokenSearchTrailingActionAttributes, p as placeTokenSearchCaret, r as readTokenSearchField } from './token-search-model-JA1i8z4D.js';
2
2
  import 'kerfjs';
3
3
  import './semantic-content-BbzjvSu9.js';
@@ -1,4 +1,4 @@
1
- export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-MF6ZUUJZ.js';
1
+ export { TokenSearchField, placeTokenSearchCaret, readTokenSearchField } from './chunk-ZJC5ND3M.js';
2
2
 
3
3
 
4
4
 
@@ -1,4 +1,4 @@
1
- import { SafeHtml, Signal } from 'kerfjs';
1
+ import { SafeHtml, Signal, ReadonlySignal } from 'kerfjs';
2
2
  import { K as KerfUiContent } from './semantic-content-BbzjvSu9.js';
3
3
 
4
4
  interface TokenSearchToken {
@@ -153,10 +153,34 @@ interface TokenSearchSuggestion {
153
153
  value: string;
154
154
  label: string;
155
155
  }
156
+ /**
157
+ * The render-relevant shape of a managed field: everything a host render
158
+ * needs to re-run for. Plain text edits do not change it.
159
+ */
160
+ interface TokenSearchStructure {
161
+ /** Whether the free text is non-empty (drives clear, placeholder, and collapsed state). */
162
+ hasQuery: boolean;
163
+ /** Committed token values in editor order. */
164
+ tokenValues: readonly string[];
165
+ /** The model's editor revision (programmatic text replacement). */
166
+ editorRevision: number;
167
+ }
156
168
  interface TokenSearchModel<Result = unknown> {
169
+ /**
170
+ * The live query and tokens. Changes on every keystroke: read it with
171
+ * `.peek()` (or in an event handler) to avoid subscribing a render per key.
172
+ */
157
173
  state: Signal<TokenSearchState>;
158
174
  /** Changes when a model action must replace DOM-owned editor text. */
159
175
  editorRevision: Signal<number>;
176
+ /**
177
+ * Changes only on structural edits (a token committed, removed, or
178
+ * reordered; the text becoming empty or non-empty; an editor revision), and
179
+ * keeps the same reference otherwise. `TokenSearchField` subscribes to this
180
+ * instead of `state`, so typing does not re-run the host render.
181
+ */
182
+ structure: ReadonlySignal<TokenSearchStructure>;
183
+ /** Visible suggestions. Publishes only when the list's content changes. */
160
184
  suggestions: Signal<readonly TokenSearchSuggestion[]>;
161
185
  result: Signal<Result | undefined>;
162
186
  edit(value: TokenSearchFieldValue, commit?: boolean): void;
@@ -181,4 +205,4 @@ interface TokenSearchModelOptions<Result> {
181
205
  /** Opt-in state, token parsing, suggestions, and evaluation for TokenSearchField. */
182
206
  declare function createTokenSearchModel<Result = unknown>({ rules, initial, evaluate, onSubmit, }?: TokenSearchModelOptions<Result>): TokenSearchModel<Result>;
183
207
 
184
- export { type TokenSearchEditorAttributes as T, TokenSearchField as a, type TokenSearchFieldProps as b, type TokenSearchFieldValue as c, type TokenSearchModel as d, type TokenSearchModelOptions as e, type TokenSearchResolvedToken as f, type TokenSearchRule as g, type TokenSearchState as h, type TokenSearchSuggestion as i, type TokenSearchToken as j, type TokenSearchTrailingAction as k, type TokenSearchTrailingActionAttributes as l, createTokenSearchModel as m, placeTokenSearchCaret as p, readTokenSearchField as r };
208
+ export { type TokenSearchEditorAttributes as T, TokenSearchField as a, type TokenSearchFieldProps as b, type TokenSearchFieldValue as c, type TokenSearchModel as d, type TokenSearchModelOptions as e, type TokenSearchResolvedToken as f, type TokenSearchRule as g, type TokenSearchState as h, type TokenSearchStructure as i, type TokenSearchSuggestion as j, type TokenSearchToken as k, type TokenSearchTrailingAction as l, type TokenSearchTrailingActionAttributes as m, createTokenSearchModel as n, placeTokenSearchCaret as p, readTokenSearchField as r };
@@ -1,3 +1,3 @@
1
- export { d as TokenSearchModel, e as TokenSearchModelOptions, f as TokenSearchResolvedToken, g as TokenSearchRule, h as TokenSearchState, i as TokenSearchSuggestion, m as createTokenSearchModel } from './token-search-model-aYR0YAei.js';
1
+ export { d as TokenSearchModel, e as TokenSearchModelOptions, f as TokenSearchResolvedToken, g as TokenSearchRule, h as TokenSearchState, i as TokenSearchStructure, j as TokenSearchSuggestion, n as createTokenSearchModel } from './token-search-model-JA1i8z4D.js';
2
2
  import 'kerfjs';
3
3
  import './semantic-content-BbzjvSu9.js';
@@ -1,3 +1,3 @@
1
- export { createTokenSearchModel } from './chunk-IM3GKYOG.js';
1
+ export { createTokenSearchModel } from './chunk-T5BJBMW7.js';
2
2
 
3
3
  //# sourceMappingURL=token-search-model.js.map
@@ -3,7 +3,7 @@ import { wireWorkbench } from './chunk-QLA25V2K.js';
3
3
  import { wireScrollDividers } from './chunk-OZ6X76G5.js';
4
4
  import { loadUxReviewProject } from './chunk-6C6DLP3W.js';
5
5
 
6
- import { readTokenSearchField } from './chunk-MF6ZUUJZ.js';
6
+ import { readTokenSearchField } from './chunk-ZJC5ND3M.js';
7
7
 
8
8
 
9
9
 
@@ -1,5 +1,5 @@
1
1
  import { Signal } from 'kerfjs';
2
- import { d as TokenSearchModel } from './token-search-model-aYR0YAei.js';
2
+ import { d as TokenSearchModel } from './token-search-model-JA1i8z4D.js';
3
3
  import './semantic-content-BbzjvSu9.js';
4
4
 
5
5
  interface TokenSearchSubmit {
@@ -1,4 +1,4 @@
1
- export { wireTokenSearchFields } from './chunk-PKL6QJKK.js';
1
+ export { wireTokenSearchFields } from './chunk-CR5U5FV6.js';
2
2
 
3
3
 
4
4
 
@@ -514,7 +514,7 @@ still owns the controlled `value` and localized action labels.
514
514
  hidden compact replacement and keeps compact overlays exclusive by default.
515
515
  - `TokenSearchField` is a structured editor. A native input or `wa-input` is the right answer for ordinary text.
516
516
 
517
- For common structured search, use `createTokenSearchModel` from `@kerfjs/ui/token-search-model`, pass the model to `TokenSearchField`, and register it under the field id in `wireTokenSearchFields(root, { models })`. Rules parse `name:value` expressions and provide suggestions; the model owns chips, selection, removal, clear, and an optional evaluation result. Omit `model` and `models` for full manual control.
517
+ For common structured search, use `createTokenSearchModel` from `@kerfjs/ui/token-search-model`, pass the model to `TokenSearchField`, and register it under the field id in `wireTokenSearchFields(root, { models })`. Rules parse `name:value` expressions and provide suggestions; the model owns chips, selection, removal, clear, and an optional evaluation result. The field subscribes only to `model.structure`, so typing plain text does not re-run the host render; read `model.state.peek()` in the host's render and bind `model.result` as a signal hole instead of reading `model.state.value`. Omit `model` and `models` for full manual control.
518
518
 
519
519
  In a form, use `TokenSearchField presentation="form-field"` with its `label`,
520
520
  optional `hint`, and `required` state. It renders a full-width control beside
@@ -575,7 +575,10 @@ application markup. `ToolbarText` is the standard identity/title; use
575
575
  hover treatment, or overflow behavior. A standalone primary `wa-button` keeps
576
576
  its own Web Awesome chrome and fits naturally in the trailing zone. Bare
577
577
  controls retain their own styling and accessibility responsibilities; the
578
- toolbar only positions them within the zone.
578
+ toolbar only positions them within the zone. A toolbar with a single zone (only
579
+ `leading`, only `trailing`, or only `center`) gives that zone the whole content
580
+ box: an absent zone leaves no empty track or gap, so content set to fill the row
581
+ reaches the opposite inset.
579
582
  For a title that opens an editor, set `action` on a direct `ToolbarText`: it
580
583
  renders a native button that keeps its content width when possible and
581
584
  ellipsizes before an adjacent status chip when the leading zone narrows. The
package/llms.txt CHANGED
@@ -610,6 +610,10 @@ headingLevel`, `TokenSearchField clearIcon`, `ContentItem frame="framed"` for a
610
610
  A sibling helper can call `model.commit(value)` for the active `name:` prefix
611
611
  even when that value is not suggested; invalid values and missing prefixes
612
612
  leave state unchanged.
613
+ A managed field subscribes the host render only to `model.structure`
614
+ (committed tokens, empty or non-empty text, editor revision), so typing plain
615
+ text never re-runs it; in the host's own render read `model.state.peek()` and
616
+ bind `model.result` as a signal hole instead of reading `model.state.value`.
613
617
  The one richer case is `wireTokenSearchFields`, which manages the
614
618
  collapsible field's expand/collapse/focus by default (on, per-behavior opt-out) and
615
619
  exposes the `expanded` signal on its handle — bind it in render or hand in your own
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kerfjs/ui",
3
- "version": "5.0.0-beta.103",
3
+ "version": "5.0.0-beta.104",
4
4
  "description": "Accessible, composable UI primitives for kerf applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/forms/token-search-field/model/token-search-model.ts"],"names":["commit"],"mappings":"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","file":"chunk-IM3GKYOG.js","sourcesContent":["import { type Signal, signal } from 'kerfjs';\n\nimport type {\n TokenSearchFieldValue,\n TokenSearchToken,\n} from '../token-search-field.js';\n\nexport interface TokenSearchRule {\n /** The word before `:` (for example `tag` or `status`). */\n name: string;\n /** Return a canonical value, or undefined to leave invalid input as text. */\n parse?: (input: string) => string | undefined;\n /** Label for a committed chip. Defaults to `name:input`. */\n label?: (value: string, input: string) => string;\n /** Suggestions for the unfinished value after `name:`, with current committed tokens. */\n suggest?: (\n input: string,\n state: TokenSearchState,\n ) => readonly (string | { value: string; label: string })[];\n}\n\nexport interface TokenSearchResolvedToken extends TokenSearchToken {\n kind: string;\n parsedValue: string;\n}\n\nexport interface TokenSearchState {\n query: string;\n tokens: TokenSearchResolvedToken[];\n}\n\nexport interface TokenSearchSuggestion {\n value: string;\n label: string;\n}\n\nexport interface TokenSearchModel<Result = unknown> {\n state: Signal<TokenSearchState>;\n /** Changes when a model action must replace DOM-owned editor text. */\n editorRevision: Signal<number>;\n suggestions: Signal<readonly TokenSearchSuggestion[]>;\n result: Signal<Result | undefined>;\n edit(value: TokenSearchFieldValue, commit?: boolean): void;\n /** Replace app-owned search state and rebuild the DOM-owned editor text. */\n replace(value: TokenSearchFieldValue): void;\n submit(value: TokenSearchFieldValue): void;\n choose(value: string): void;\n /** Commit a value for the active `name:` prefix, even when it is not suggested. */\n commit(value: string): void;\n expandToken(value: string): void;\n remove(value: string): void;\n clear(): void;\n dismiss(): void;\n}\n\nexport interface TokenSearchModelOptions<Result> {\n rules?: readonly TokenSearchRule[];\n initial?: TokenSearchFieldValue;\n /** Runs after every state change, including initial state. */\n evaluate?: (state: TokenSearchState) => Result;\n onSubmit?: (state: TokenSearchState, result: Result | undefined) => void;\n}\n\nconst escapePattern = (text: string) =>\n text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\nconst quote = (text: string) =>\n /[\\s()\"\\\\]/.test(text)\n ? `\"${text.replaceAll('\\\\', '\\\\\\\\').replaceAll('\"', '\\\\\"')}\"`\n : text;\nconst unquote = (text: string) =>\n text.startsWith('\"') && text.endsWith('\"')\n ? text.slice(1, -1).replace(/\\\\([\"\\\\])/g, '$1')\n : text;\n\n/** Opt-in state, token parsing, suggestions, and evaluation for TokenSearchField. */\nexport function createTokenSearchModel<Result = unknown>({\n rules = [],\n initial = { query: '', tokens: [] },\n evaluate,\n onSubmit,\n}: TokenSearchModelOptions<Result> = {}): TokenSearchModel<Result> {\n const byName = new Map(rules.map((rule) => [rule.name.toLowerCase(), rule]));\n if (\n byName.size !== rules.length ||\n rules.some((rule) => !/^[\\w-]+$/.test(rule.name))\n )\n throw new Error('Token search rule names must be unique words');\n const names = [...byName.keys()].map(escapePattern).join('|');\n const pattern = names\n ? new RegExp(\n `(?:^|[\\\\s(])((?:${names}):(?:\"(?:\\\\\\\\.|[^\"])*\"|[^\"\\\\s()][^\\\\s()]*))(?=$|[\\\\s)])`,\n 'gi',\n )\n : undefined;\n const activePattern = names\n ? new RegExp(\n `(?:^|[\\\\s(])((?:${names}):(?:\"(?:\\\\\\\\.|[^\"])*|[^\\\\s()]*))$`,\n 'i',\n )\n : undefined;\n\n const resolve = (\n raw: string,\n offset: number,\n ): TokenSearchResolvedToken | undefined => {\n const separator = raw.indexOf(':');\n const kind = raw.slice(0, separator).toLowerCase();\n const rule = byName.get(kind);\n if (!rule || separator < 0) return;\n const input = unquote(raw.slice(separator + 1));\n if (!input) return;\n const parsedValue = rule.parse ? rule.parse(input) : input;\n if (parsedValue === undefined) return;\n return {\n kind,\n parsedValue,\n value: `${kind}:${quote(input)}`,\n label: rule.label?.(parsedValue, input) ?? `${kind}:${input}`,\n offset,\n };\n };\n\n const rehydrate = (\n token: TokenSearchToken,\n ): TokenSearchResolvedToken | undefined => {\n const resolved = resolve(token.value, token.offset ?? 0);\n return resolved ? { ...resolved, ...token } : undefined;\n };\n\n const state = signal<TokenSearchState>({\n query: initial.query,\n tokens: initial.tokens.flatMap((token) => {\n const resolved = rehydrate(token);\n return resolved ? [resolved] : [];\n }),\n });\n const editorRevision = signal(0);\n const suggestions = signal<readonly TokenSearchSuggestion[]>([]);\n const result = signal<Result | undefined>(evaluate?.(state.value));\n let activeStart: number | undefined;\n let activeRule: TokenSearchRule | undefined;\n\n const refreshSuggestions = () => {\n const match = activePattern?.exec(state.value.query);\n const raw = match?.[1];\n const separator = raw?.indexOf(':') ?? -1;\n const rule =\n separator < 0\n ? undefined\n : byName.get(raw!.slice(0, separator).toLowerCase());\n activeRule = rule;\n const input = separator < 0 ? '' : unquote(raw!.slice(separator + 1));\n activeStart =\n match && raw ? match.index + match[0].lastIndexOf(raw) : undefined;\n suggestions.value = (rule?.suggest?.(input, state.value) ?? []).flatMap(\n (candidate) => {\n const value =\n typeof candidate === 'string' ? candidate : candidate.value;\n const token = resolve(`${rule!.name}:${quote(value)}`, 0);\n return token\n ? [\n {\n value: token.value,\n label:\n typeof candidate === 'string' ? token.label : candidate.label,\n },\n ]\n : [];\n },\n );\n };\n const publish = (next: TokenSearchState) => {\n state.value = next;\n result.value = evaluate?.(next);\n refreshSuggestions();\n };\n refreshSuggestions();\n\n const edit = (value: TokenSearchFieldValue, commit = false) => {\n let query = '';\n let cursor = 0;\n const added: TokenSearchResolvedToken[] = [];\n const removed: Array<{ start: number; end: number }> = [];\n for (const match of value.query.matchAll(pattern ?? /(?!) /g)) {\n const raw = match[1];\n const start = match.index + match[0].lastIndexOf(raw);\n const end = start + raw.length;\n if (end === value.query.length && !commit && !/\\s$/.test(value.query))\n continue;\n const token = resolve(raw, query.length + start - cursor);\n if (!token) continue;\n query += value.query.slice(cursor, start);\n token.offset = query.length;\n added.push(token);\n removed.push({ start, end });\n cursor = end;\n }\n const suffix = value.query.slice(cursor);\n query += added.length && /^\\s+$/.test(suffix) ? '' : suffix;\n const existing = value.tokens.flatMap((token) => {\n const resolved = rehydrate(token);\n if (!resolved) return [];\n const offset = token.offset ?? value.query.length;\n return [\n {\n ...resolved,\n offset:\n offset -\n removed.reduce(\n (total, range) =>\n total + Math.max(0, Math.min(offset, range.end) - range.start),\n 0,\n ),\n },\n ];\n });\n const tokens = [...existing, ...added].sort(\n (left, right) => (left.offset ?? 0) - (right.offset ?? 0),\n );\n publish({ query, tokens });\n };\n\n const commitResolved = (token: TokenSearchResolvedToken | undefined) => {\n if (!token || activeStart === undefined) return;\n const query = state.value.query.slice(0, activeStart);\n publish({ query, tokens: [...state.value.tokens, token] });\n };\n const choose = (value: string) => {\n if (\n activeStart === undefined ||\n !suggestions.value.some((suggestion) => suggestion.value === value)\n )\n return;\n commitResolved(resolve(value, activeStart));\n };\n const commit = (value: string) => {\n if (!activeRule || activeStart === undefined) return;\n commitResolved(resolve(`${activeRule.name}:${quote(value)}`, activeStart));\n };\n\n return {\n state,\n editorRevision,\n suggestions,\n result,\n edit,\n replace(value) {\n edit(value);\n editorRevision.value++;\n },\n submit(value) {\n edit(value, true);\n onSubmit?.(state.value, result.value);\n },\n choose,\n commit,\n expandToken(value) {\n const index = state.value.tokens.findIndex(\n (token) => token.value === value,\n );\n if (index < 0) return;\n const offset = Math.max(\n 0,\n Math.min(\n state.value.query.length,\n state.value.tokens[index].offset ?? state.value.query.length,\n ),\n );\n const insertion = `${value} `;\n publish({\n query: `${state.value.query.slice(0, offset)}${insertion}${state.value.query.slice(offset)}`,\n tokens: state.value.tokens.flatMap((token, at) =>\n at === index\n ? []\n : [\n {\n ...token,\n offset:\n (token.offset ?? 0) + (at > index ? insertion.length : 0),\n },\n ],\n ),\n });\n },\n remove(value) {\n const index = state.value.tokens.findIndex(\n (token) => token.value === value,\n );\n if (index < 0) return;\n publish({\n ...state.value,\n tokens: state.value.tokens.filter((_, at) => at !== index),\n });\n },\n clear() {\n publish({ query: '', tokens: [] });\n editorRevision.value++;\n },\n dismiss() {\n suggestions.value = [];\n },\n };\n}\n"]}