@cbcruk/highlight-kit 0.1.0 → 0.2.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 +212 -8
- package/dist/core-DDCXN2b_.d.ts +527 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/react.d.ts +203 -1
- package/dist/react.js +300 -2
- package/dist/value-range-CH2vegQi.js +707 -0
- package/package.json +1 -1
- package/dist/core-B8g04jvB.d.ts +0 -168
- package/dist/core-LUnH63zG.js +0 -306
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
//#region src/tokenize.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Pure tokenizer: a string plus an ordered rule list in, character spans out.
|
|
4
|
+
*
|
|
5
|
+
* Nothing here touches the DOM, the `CSS.highlights` registry, or the
|
|
6
|
+
* `OpaqueRange` API, so the overlap rules — the part that is easy to get wrong —
|
|
7
|
+
* are testable without a browser. {@link ./value-range.ts} turns the spans this
|
|
8
|
+
* produces into live ranges inside an `<input>` or `<textarea>`.
|
|
9
|
+
*/
|
|
10
|
+
/** One pattern and the highlight name its matches are registered under. */
|
|
11
|
+
interface TokenRule {
|
|
12
|
+
/** CSS `::highlight()` name for this rule's matches. Rules may share a name. */
|
|
13
|
+
name: string;
|
|
14
|
+
/**
|
|
15
|
+
* Text or expression to match. A `RegExp` keeps its own flags, so
|
|
16
|
+
* `caseSensitive` and `wholeWord` are ignored for one.
|
|
17
|
+
*/
|
|
18
|
+
pattern: string | RegExp;
|
|
19
|
+
/**
|
|
20
|
+
* Case sensitive match. Ignored for `RegExp` patterns.
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
caseSensitive?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Match whole words only (wraps the pattern in `\b`). Ignored for `RegExp`
|
|
26
|
+
* patterns.
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
wholeWord?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Stacking order of this rule's highlight name against other names. When
|
|
32
|
+
* several rules share a name the highest value wins.
|
|
33
|
+
* @default 0
|
|
34
|
+
*/
|
|
35
|
+
priority?: number;
|
|
36
|
+
}
|
|
37
|
+
/** One match: a half-open character span `[start, end)` and its rule. */
|
|
38
|
+
interface Token {
|
|
39
|
+
/** The matching rule's highlight name. */
|
|
40
|
+
readonly name: string;
|
|
41
|
+
/** Start offset, in UTF-16 code units into the tokenized string. */
|
|
42
|
+
readonly start: number;
|
|
43
|
+
/** End offset, exclusive. */
|
|
44
|
+
readonly end: number;
|
|
45
|
+
/** Index of the rule in the rule list that produced this token. */
|
|
46
|
+
readonly rule: number;
|
|
47
|
+
}
|
|
48
|
+
/** How matches covering the same characters are resolved. */
|
|
49
|
+
type OverlapStrategy = 'first' | 'all';
|
|
50
|
+
/** Options for {@link tokenizeValue}. */
|
|
51
|
+
interface TokenizeOptions {
|
|
52
|
+
/**
|
|
53
|
+
* How matches covering the same characters resolve.
|
|
54
|
+
*
|
|
55
|
+
* - `'first'` — scan left to right. At each position the earliest rule in the
|
|
56
|
+
* list that matches *there* wins, and the scan jumps past its match, so a
|
|
57
|
+
* comment rule swallows keywords inside the comment. Tokens never overlap.
|
|
58
|
+
* - `'all'` — every rule scans the whole string independently and every match
|
|
59
|
+
* is emitted, overlaps included. Layering is left to `priority`.
|
|
60
|
+
*
|
|
61
|
+
* @default 'first'
|
|
62
|
+
*/
|
|
63
|
+
overlap?: OverlapStrategy;
|
|
64
|
+
}
|
|
65
|
+
/** The spans of one highlight name, ready to hand to a controller. */
|
|
66
|
+
interface NamedSpans {
|
|
67
|
+
/** CSS `::highlight()` name. */
|
|
68
|
+
name: string;
|
|
69
|
+
/** Highest `priority` among the rules that contributed to this name. */
|
|
70
|
+
priority: number;
|
|
71
|
+
/** Half-open `[start, end)` character spans, in the order tokenized. */
|
|
72
|
+
spans: ReadonlyArray<{
|
|
73
|
+
start: number;
|
|
74
|
+
end: number;
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Match `rules` against `value` and return the character spans of every match.
|
|
79
|
+
*
|
|
80
|
+
* Offsets are UTF-16 code unit indices into `value`, the same units
|
|
81
|
+
* `selectionStart` uses, so they can be handed straight to
|
|
82
|
+
* `createValueRange()` when `value` came from `element.value`.
|
|
83
|
+
*
|
|
84
|
+
* Rule order is precedence: see {@link TokenizeOptions.overlap}.
|
|
85
|
+
*
|
|
86
|
+
* @param value - Text to scan, typically `element.value`
|
|
87
|
+
* @param rules - Ordered rules; earlier rules win under `'first'`
|
|
88
|
+
* @returns Tokens sorted by start offset. Empty when `value` or `rules` is empty
|
|
89
|
+
*
|
|
90
|
+
* @example A comment rule swallowing keywords inside it
|
|
91
|
+
* ```ts
|
|
92
|
+
* import { tokenizeValue } from '@cbcruk/highlight-kit'
|
|
93
|
+
*
|
|
94
|
+
* tokenizeValue('// return 1', [
|
|
95
|
+
* { name: 'comment', pattern: /\/\/.*$/m },
|
|
96
|
+
* { name: 'keyword', pattern: /\breturn\b/ },
|
|
97
|
+
* ])
|
|
98
|
+
* // [{ name: 'comment', start: 0, end: 11, rule: 0 }]
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @example Independent patterns, overlaps kept
|
|
102
|
+
* ```ts
|
|
103
|
+
* import { tokenizeValue } from '@cbcruk/highlight-kit'
|
|
104
|
+
*
|
|
105
|
+
* tokenizeValue('status:open', [
|
|
106
|
+
* { name: 'field', pattern: /\w+:/ },
|
|
107
|
+
* { name: 'all', pattern: /\w+/ },
|
|
108
|
+
* ], { overlap: 'all' })
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
declare function tokenizeValue(value: string, rules: readonly TokenRule[], options?: TokenizeOptions): Token[];
|
|
112
|
+
/**
|
|
113
|
+
* Collapse tokens into one entry per highlight name.
|
|
114
|
+
*
|
|
115
|
+
* Several rules may share a name; the resulting `priority` is the highest one
|
|
116
|
+
* any contributing rule declared, since a controller tracks a single priority
|
|
117
|
+
* per name.
|
|
118
|
+
*
|
|
119
|
+
* @param tokens - Tokens from {@link tokenizeValue}
|
|
120
|
+
* @param rules - The same rule list those tokens were produced from
|
|
121
|
+
* @returns One entry per name that matched at least once, in first-match order
|
|
122
|
+
*/
|
|
123
|
+
declare function groupTokens(tokens: readonly Token[], rules: readonly TokenRule[]): NamedSpans[];
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/value-range.d.ts
|
|
126
|
+
/** Form controls that can expose ranges over their value. */
|
|
127
|
+
type ValueRangeElement = HTMLInputElement | HTMLTextAreaElement;
|
|
128
|
+
/**
|
|
129
|
+
* A live range over a form control's value — the `OpaqueRange` returned by
|
|
130
|
+
* `createValueRange()`.
|
|
131
|
+
*
|
|
132
|
+
* Offsets are UTF-16 code unit indices into `element.value`, the same units as
|
|
133
|
+
* `selectionStart`/`selectionEnd`, and they shift as the value is edited.
|
|
134
|
+
*
|
|
135
|
+
* This is deliberately *not* typed as an `AbstractRange`. Shipping this API
|
|
136
|
+
* moved `startContainer`/`endContainer` off `AbstractRange` onto a new
|
|
137
|
+
* `NodeRange` interface, and on a value range both read `undefined`. Declaring
|
|
138
|
+
* them as `Node` would be a lie, so they are absent here: use
|
|
139
|
+
* {@link ValueRange.getBoundingClientRect} for geometry and `element.value` for
|
|
140
|
+
* text. There is also no `toString()` and no constructor.
|
|
141
|
+
*/
|
|
142
|
+
interface ValueRange {
|
|
143
|
+
/** Whether the range is empty. A collapsed range is never painted. */
|
|
144
|
+
readonly collapsed: boolean;
|
|
145
|
+
/** Start offset into the control's value, in UTF-16 code units. */
|
|
146
|
+
readonly startOffset: number;
|
|
147
|
+
/** End offset into the control's value, exclusive. */
|
|
148
|
+
readonly endOffset: number;
|
|
149
|
+
/** Client rects of the range, one per line box it covers. */
|
|
150
|
+
getClientRects(): DOMRectList;
|
|
151
|
+
/** Bounding box of the range. Empty once the range is disconnected. */
|
|
152
|
+
getBoundingClientRect(): DOMRect;
|
|
153
|
+
/**
|
|
154
|
+
* Stop tracking edits and collapse to offset 0, releasing the control's
|
|
155
|
+
* reference to this range.
|
|
156
|
+
*
|
|
157
|
+
* Optional because it ships in Chromium but is absent from the spec pull
|
|
158
|
+
* requests, so another engine may implement `OpaqueRange` without it.
|
|
159
|
+
*/
|
|
160
|
+
disconnect?(): void;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Whether this browser implements `createValueRange()`.
|
|
164
|
+
*
|
|
165
|
+
* True only says the *engine* supports the API. It does not say a given element
|
|
166
|
+
* does — the method exists on `HTMLInputElement.prototype` for every input
|
|
167
|
+
* type and throws at call time for unsupported ones. Use
|
|
168
|
+
* {@link supportsValueRange} for a specific element.
|
|
169
|
+
*/
|
|
170
|
+
declare function isValueRangeSupported(): boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Whether `element` can produce value ranges: a `<textarea>`, or an `<input>`
|
|
173
|
+
* whose `type` is `text`, `search`, `tel`, `url`, or `password`.
|
|
174
|
+
*
|
|
175
|
+
* @param element - Element to test; `null`/`undefined` returns false
|
|
176
|
+
*/
|
|
177
|
+
declare function supportsValueRange(element: Element | null | undefined): element is ValueRangeElement;
|
|
178
|
+
/**
|
|
179
|
+
* Narrow a highlight range to a value range.
|
|
180
|
+
*
|
|
181
|
+
* Tests for "not a DOM `Range`", so it also answers whether reading
|
|
182
|
+
* `startContainer` is safe — on a value range it is `undefined`.
|
|
183
|
+
*/
|
|
184
|
+
declare function isValueRange(range: HighlightRange): range is ValueRange;
|
|
185
|
+
/**
|
|
186
|
+
* Create live ranges over `element`'s value for each of `spans`.
|
|
187
|
+
*
|
|
188
|
+
* Offsets are clamped into `[0, element.value.length]` rather than throwing the
|
|
189
|
+
* `IndexSizeError` the API raises for out-of-range offsets, and spans that
|
|
190
|
+
* clamp to nothing are dropped — a collapsed range is never painted.
|
|
191
|
+
*
|
|
192
|
+
* Every returned range holds a reference from `element` until
|
|
193
|
+
* {@link disconnectValueRanges} releases it.
|
|
194
|
+
*
|
|
195
|
+
* @param element - A control {@link supportsValueRange} accepts
|
|
196
|
+
* @param spans - Half-open `[start, end)` character spans
|
|
197
|
+
* @returns One range per non-empty span. Empty when the element is unsupported
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* import { createValueRanges, highlights } from '@cbcruk/highlight-kit'
|
|
202
|
+
*
|
|
203
|
+
* const textarea = document.querySelector('textarea')!
|
|
204
|
+
* const ranges = createValueRanges(textarea, [{ start: 0, end: 5 }])
|
|
205
|
+
* highlights.set('note', 'my-source', ranges)
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
declare function createValueRanges(element: ValueRangeElement, spans: ReadonlyArray<{
|
|
209
|
+
start: number;
|
|
210
|
+
end: number;
|
|
211
|
+
}>): ValueRange[];
|
|
212
|
+
/**
|
|
213
|
+
* Release `ranges` so their control stops tracking them.
|
|
214
|
+
*
|
|
215
|
+
* Safe to call twice, and on ranges already auto-disconnected by the element
|
|
216
|
+
* being removed or its `type` changing.
|
|
217
|
+
*/
|
|
218
|
+
declare function disconnectValueRanges(ranges: Iterable<ValueRange>): void;
|
|
219
|
+
/** Options shared by {@link createValueRangeRegistry} and its wrappers. */
|
|
220
|
+
interface ValueRangeBindingOptions {
|
|
221
|
+
/** The control whose value the ranges point into. */
|
|
222
|
+
element: ValueRangeElement;
|
|
223
|
+
/** Controller the ranges are registered with. @default the shared singleton */
|
|
224
|
+
controller?: HighlightController;
|
|
225
|
+
/** This binding's identity within each highlight name. @default a fresh symbol */
|
|
226
|
+
sourceId?: SourceId;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Owns one generation of value ranges on a control and swaps it atomically.
|
|
230
|
+
*
|
|
231
|
+
* This is the piece that makes repeated re-matching safe. A control retains
|
|
232
|
+
* every range `createValueRange()` returned and shifts all of their offsets on
|
|
233
|
+
* each edit, so re-registering without releasing the ranges from the previous
|
|
234
|
+
* pass leaves a growing pile of live ranges behind — invisible, because
|
|
235
|
+
* `Highlight` no longer holds them, but still updated on every keystroke.
|
|
236
|
+
* {@link ValueRangeRegistry.commit} disconnects the outgoing generation for you.
|
|
237
|
+
*/
|
|
238
|
+
interface ValueRangeRegistry {
|
|
239
|
+
/** Whether the element can actually produce value ranges. */
|
|
240
|
+
readonly supported: boolean;
|
|
241
|
+
/** Highlight names this registry currently has ranges registered under. */
|
|
242
|
+
readonly names: readonly string[];
|
|
243
|
+
/**
|
|
244
|
+
* Replace everything this registry owns with ranges for `groups`.
|
|
245
|
+
*
|
|
246
|
+
* Names the registry held but `groups` omits are unregistered. The outgoing
|
|
247
|
+
* ranges are disconnected only after the new ones are committed, so the
|
|
248
|
+
* highlight never blanks for a frame.
|
|
249
|
+
*/
|
|
250
|
+
commit(groups: readonly NamedSpans[]): void;
|
|
251
|
+
/** Unregister every name and disconnect every range this registry created. */
|
|
252
|
+
dispose(): void;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Create a registry that keeps one generation of value ranges on `element`.
|
|
256
|
+
*
|
|
257
|
+
* Use it when you compute spans yourself; {@link createValueHighlighter} layers
|
|
258
|
+
* the tokenizer and an `input` listener on top.
|
|
259
|
+
*
|
|
260
|
+
* @example Marking a single span and replacing it later
|
|
261
|
+
* ```ts
|
|
262
|
+
* import { createValueRangeRegistry } from '@cbcruk/highlight-kit'
|
|
263
|
+
*
|
|
264
|
+
* const registry = createValueRangeRegistry({ element: textarea })
|
|
265
|
+
* registry.commit([{ name: 'note', priority: 0, spans: [{ start: 0, end: 4 }] }])
|
|
266
|
+
* registry.commit([{ name: 'note', priority: 0, spans: [{ start: 6, end: 9 }] }])
|
|
267
|
+
* registry.dispose()
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
declare function createValueRangeRegistry({ element, controller, sourceId }: ValueRangeBindingOptions): ValueRangeRegistry;
|
|
271
|
+
/** Options for {@link createValueHighlighter}. */
|
|
272
|
+
interface ValueHighlighterOptions extends ValueRangeBindingOptions {
|
|
273
|
+
/** Ordered token rules. Earlier rules win; see {@link TokenRule}. */
|
|
274
|
+
rules: readonly TokenRule[];
|
|
275
|
+
/**
|
|
276
|
+
* How matches covering the same characters resolve.
|
|
277
|
+
* @default 'first'
|
|
278
|
+
*/
|
|
279
|
+
overlap?: OverlapStrategy;
|
|
280
|
+
/**
|
|
281
|
+
* Re-tokenize on the element's own `input` events. Turn off to drive
|
|
282
|
+
* {@link ValueHighlighter.refresh} yourself.
|
|
283
|
+
* @default true
|
|
284
|
+
*/
|
|
285
|
+
observe?: boolean;
|
|
286
|
+
}
|
|
287
|
+
/** A running tokenizer bound to one form control. */
|
|
288
|
+
interface ValueHighlighter {
|
|
289
|
+
/** Whether the element can actually produce value ranges. */
|
|
290
|
+
readonly supported: boolean;
|
|
291
|
+
/** Highlight names that currently have at least one match. */
|
|
292
|
+
readonly names: readonly string[];
|
|
293
|
+
/**
|
|
294
|
+
* Re-tokenize the element's current value and re-register.
|
|
295
|
+
*
|
|
296
|
+
* Call this after assigning `element.value` programmatically: a whole-value
|
|
297
|
+
* assignment collapses every live range to offset 0 and fires no `input`
|
|
298
|
+
* event, so nothing else would notice.
|
|
299
|
+
*/
|
|
300
|
+
refresh(): void;
|
|
301
|
+
/** Unregister every name and disconnect every range this highlighter created. */
|
|
302
|
+
dispose(): void;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Keep a form control's value tokenized and highlighted.
|
|
306
|
+
*
|
|
307
|
+
* Tokenizes once on creation and then on every `input` event, registering one
|
|
308
|
+
* highlight name per rule name and releasing the previous generation of ranges.
|
|
309
|
+
*
|
|
310
|
+
* Does nothing when the browser or the element is unsupported, so calling it
|
|
311
|
+
* unconditionally is safe — read {@link ValueHighlighter.supported} to decide
|
|
312
|
+
* whether to render a fallback.
|
|
313
|
+
*
|
|
314
|
+
* @example Search-query syntax in a text input
|
|
315
|
+
* ```ts
|
|
316
|
+
* import {
|
|
317
|
+
* createValueHighlighter,
|
|
318
|
+
* injectHighlightStyles,
|
|
319
|
+
* } from '@cbcruk/highlight-kit'
|
|
320
|
+
*
|
|
321
|
+
* injectHighlightStyles({
|
|
322
|
+
* field: { color: '#2563eb' },
|
|
323
|
+
* quoted: { backgroundColor: '#dbeafe' },
|
|
324
|
+
* })
|
|
325
|
+
*
|
|
326
|
+
* const input = document.querySelector('input')!
|
|
327
|
+
* const highlighter = createValueHighlighter({
|
|
328
|
+
* element: input,
|
|
329
|
+
* rules: [
|
|
330
|
+
* { name: 'quoted', pattern: /"[^"]*"/ },
|
|
331
|
+
* { name: 'field', pattern: /\b\w+:/ },
|
|
332
|
+
* ],
|
|
333
|
+
* })
|
|
334
|
+
*
|
|
335
|
+
* highlighter.dispose()
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
declare function createValueHighlighter({ rules, overlap, observe, ...binding }: ValueHighlighterOptions): ValueHighlighter;
|
|
339
|
+
/**
|
|
340
|
+
* Scroll `element` just far enough for `range` to be visible inside it.
|
|
341
|
+
*
|
|
342
|
+
* `scrollIntoView()` is not an option: a value range has no node to call it on.
|
|
343
|
+
* This adjusts the control's own `scrollTop`/`scrollLeft` from the range's
|
|
344
|
+
* client rect, and unlike `setSelectionRange()` it leaves the user's selection
|
|
345
|
+
* and focus alone.
|
|
346
|
+
*
|
|
347
|
+
* No-op for a collapsed or disconnected range, whose rect is empty.
|
|
348
|
+
*/
|
|
349
|
+
declare function scrollValueRangeIntoView(element: ValueRangeElement, range: ValueRange): void;
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/core.d.ts
|
|
352
|
+
/**
|
|
353
|
+
* Either kind of range a highlight can hold.
|
|
354
|
+
*
|
|
355
|
+
* `Highlight` is setlike over `AbstractRange`, so DOM ranges and value ranges
|
|
356
|
+
* may share one highlight name. Only a value range lacks
|
|
357
|
+
* `startContainer`/`endContainer`; narrow with `isValueRange` before reading
|
|
358
|
+
* them.
|
|
359
|
+
*/
|
|
360
|
+
type HighlightRange = Range | ValueRange;
|
|
361
|
+
/** Options controlling how a string pattern is matched against text. */
|
|
362
|
+
interface MatchOptions {
|
|
363
|
+
/**
|
|
364
|
+
* Case sensitive match. Ignored for `RegExp` patterns, which keep their own flags.
|
|
365
|
+
* @default false
|
|
366
|
+
*/
|
|
367
|
+
caseSensitive?: boolean;
|
|
368
|
+
/**
|
|
369
|
+
* Match whole words only (wraps the pattern in `\b`). Ignored for `RegExp` patterns.
|
|
370
|
+
* @default false
|
|
371
|
+
*/
|
|
372
|
+
wholeWord?: boolean;
|
|
373
|
+
}
|
|
374
|
+
/** Reactive state of one highlight name, as exposed to subscribers. */
|
|
375
|
+
interface HighlightSnapshot {
|
|
376
|
+
/** Whether this name currently has any registered ranges */
|
|
377
|
+
readonly active: boolean;
|
|
378
|
+
/** Number of ranges registered under this name */
|
|
379
|
+
readonly count: number;
|
|
380
|
+
}
|
|
381
|
+
/** A source contributing ranges, keyed by a unique id (e.g. React's useId) */
|
|
382
|
+
type SourceId = string | symbol;
|
|
383
|
+
/**
|
|
384
|
+
* Where reconciled ranges are written. The controller's bookkeeping always
|
|
385
|
+
* runs; only this side effect is swappable (CSS registry, no-op for tests/SSR).
|
|
386
|
+
*/
|
|
387
|
+
interface HighlightSink {
|
|
388
|
+
/** Replace the registered highlight for `name` with `ranges`. */
|
|
389
|
+
commit(name: string, ranges: HighlightRange[], priority: number): void;
|
|
390
|
+
/** Drop the registered highlight for `name`. */
|
|
391
|
+
remove(name: string): void;
|
|
392
|
+
/**
|
|
393
|
+
* When this returns false the controller skips bookkeeping entirely, so
|
|
394
|
+
* snapshots stay empty. Omit it to always track ranges.
|
|
395
|
+
*/
|
|
396
|
+
isSupported?(): boolean;
|
|
397
|
+
}
|
|
398
|
+
/** Options for {@link createHighlightController}. */
|
|
399
|
+
interface HighlightControllerOptions {
|
|
400
|
+
/** Where reconciled ranges are written. Defaults to {@link createCssHighlightSink}. */
|
|
401
|
+
sink?: HighlightSink;
|
|
402
|
+
}
|
|
403
|
+
/** Whether the CSS Custom Highlight API (`CSS.highlights` and `Highlight`) is available. */
|
|
404
|
+
declare function isHighlightSupported(): boolean;
|
|
405
|
+
/**
|
|
406
|
+
* Collect the text nodes under an element that contain non-whitespace text.
|
|
407
|
+
*
|
|
408
|
+
* Whitespace-only nodes (newlines, indentation between elements) are skipped,
|
|
409
|
+
* so this is suited to pattern matching, not to offset math against `textContent`.
|
|
410
|
+
*/
|
|
411
|
+
declare function getTextNodes(root: Node): Text[];
|
|
412
|
+
/**
|
|
413
|
+
* Compute Range objects for every match of `pattern` within `root`.
|
|
414
|
+
* Pure: returns ranges, does not touch the registry.
|
|
415
|
+
*/
|
|
416
|
+
declare function computeRanges(root: Element, pattern: string | RegExp, options?: MatchOptions): Range[];
|
|
417
|
+
/**
|
|
418
|
+
* Map flat character offsets onto Range objects. Useful when you already know positions.
|
|
419
|
+
*
|
|
420
|
+
* Offsets count over `root.textContent`, whitespace-only text nodes (newlines,
|
|
421
|
+
* indentation between elements) included, so positions computed from
|
|
422
|
+
* `textContent` or on a server against the same text line up. Unlike
|
|
423
|
+
* {@link computeRanges}, a span may cross text node boundaries; it yields one
|
|
424
|
+
* range per text node it touches. Out-of-bounds parts are ignored.
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* ```ts
|
|
428
|
+
* import { rangesFromOffsets } from '@cbcruk/highlight-kit'
|
|
429
|
+
*
|
|
430
|
+
* const el = document.querySelector('#article')!
|
|
431
|
+
* const start = el.textContent!.indexOf('wisdom')
|
|
432
|
+
* rangesFromOffsets(el, [{ start, end: start + 'wisdom'.length }])
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
declare function rangesFromOffsets(root: Element, spans: ReadonlyArray<{
|
|
436
|
+
start: number;
|
|
437
|
+
end: number;
|
|
438
|
+
}>): Range[];
|
|
439
|
+
/** Sink that writes to the document-global `CSS.highlights` registry. */
|
|
440
|
+
declare function createCssHighlightSink(): HighlightSink;
|
|
441
|
+
/** Side-effect-free sink: bookkeeping still runs, nothing is painted. */
|
|
442
|
+
declare function createNoopSink(): HighlightSink;
|
|
443
|
+
/**
|
|
444
|
+
* Tracks ranges per highlight name and source, and writes their union to a sink.
|
|
445
|
+
*
|
|
446
|
+
* Multiple sources may contribute to one name; each change reconciles the name
|
|
447
|
+
* into a single highlight and notifies `subscribe` listeners. Create instances
|
|
448
|
+
* with {@link createHighlightController} or use the shared {@link highlights}.
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* ```ts
|
|
452
|
+
* import { computeRanges, highlights } from '@cbcruk/highlight-kit'
|
|
453
|
+
*
|
|
454
|
+
* const el = document.querySelector('#article')!
|
|
455
|
+
* highlights.set('search', 'my-source', computeRanges(el, 'wisdom'))
|
|
456
|
+
* highlights.getSnapshot('search') // { active: true, count: ... }
|
|
457
|
+
* highlights.remove('search', 'my-source')
|
|
458
|
+
* ```
|
|
459
|
+
*/
|
|
460
|
+
declare class HighlightController {
|
|
461
|
+
#private;
|
|
462
|
+
/** Create a controller that writes to `sink` (the CSS registry sink by default). */
|
|
463
|
+
constructor({ sink }?: HighlightControllerOptions);
|
|
464
|
+
/**
|
|
465
|
+
* Whether the sink reports support. `true` when the sink has no `isSupported`.
|
|
466
|
+
* When `false`, {@link HighlightController.set} is a no-op.
|
|
467
|
+
*/
|
|
468
|
+
get supported(): boolean;
|
|
469
|
+
/** useSyncExternalStore: stable identity (arrow field on the instance). */
|
|
470
|
+
subscribe: (listener: () => void) => (() => void);
|
|
471
|
+
/** Snapshot for a given highlight name (referentially stable). */
|
|
472
|
+
getSnapshot: (name: string) => HighlightSnapshot;
|
|
473
|
+
/** Snapshots of every active name (referentially stable between changes). */
|
|
474
|
+
getSnapshots: () => Readonly<Record<string, HighlightSnapshot>>;
|
|
475
|
+
/** SSR / unsupported: constant empty snapshot. */
|
|
476
|
+
getServerSnapshot: () => HighlightSnapshot;
|
|
477
|
+
/** SSR / unsupported: constant empty record. */
|
|
478
|
+
getServerSnapshots: () => Readonly<Record<string, HighlightSnapshot>>;
|
|
479
|
+
/** Union of every source's ranges currently registered under `name`. */
|
|
480
|
+
getRanges(name: string): HighlightRange[];
|
|
481
|
+
/**
|
|
482
|
+
* Register/replace a source's ranges under a name, then reconcile.
|
|
483
|
+
* `priority` applies to the whole name; the most recent call wins.
|
|
484
|
+
*/
|
|
485
|
+
set(name: string, sourceId: SourceId, ranges: HighlightRange[], priority?: number): void;
|
|
486
|
+
/** Remove a single source's contribution to a name. */
|
|
487
|
+
remove(name: string, sourceId: SourceId): void;
|
|
488
|
+
/** Drop a name entirely, regardless of sources. */
|
|
489
|
+
clear(name: string): void;
|
|
490
|
+
/** Drop everything this controller manages. */
|
|
491
|
+
clearAll(): void;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Create an isolated controller. Useful for tests (with {@link createNoopSink})
|
|
495
|
+
* or for scoping subscriptions via the React `HighlightProvider`. Highlight
|
|
496
|
+
* *names* are still document-global once painted by a CSS sink.
|
|
497
|
+
*/
|
|
498
|
+
declare function createHighlightController(options?: HighlightControllerOptions): HighlightController;
|
|
499
|
+
/** The shared singleton. */
|
|
500
|
+
declare const highlights: HighlightController;
|
|
501
|
+
/**
|
|
502
|
+
* Build `::highlight(name)` CSS rules from a style map without injecting them.
|
|
503
|
+
*
|
|
504
|
+
* camelCase property names are converted to kebab-case; values are written as is.
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* ```ts
|
|
508
|
+
* import { generateHighlightCSS } from '@cbcruk/highlight-kit'
|
|
509
|
+
*
|
|
510
|
+
* generateHighlightCSS({ search: { backgroundColor: 'yellow' } })
|
|
511
|
+
* // '::highlight(search) {\n background-color: yellow;\n}'
|
|
512
|
+
* ```
|
|
513
|
+
*/
|
|
514
|
+
declare function generateHighlightCSS(styles: Record<string, Partial<CSSStyleDeclaration>>): string;
|
|
515
|
+
/**
|
|
516
|
+
* Append a `<style>` element with `::highlight()` rules to `document.head`.
|
|
517
|
+
*
|
|
518
|
+
* An existing element with the same `id` is removed first, so calling it again
|
|
519
|
+
* replaces the previous rules.
|
|
520
|
+
*
|
|
521
|
+
* @param styles - Style declarations keyed by highlight name
|
|
522
|
+
* @param id - `id` of the `<style>` element
|
|
523
|
+
* @returns The inserted `<style>` element
|
|
524
|
+
*/
|
|
525
|
+
declare function injectHighlightStyles(styles: Record<string, Partial<CSSStyleDeclaration>>, id?: string): HTMLStyleElement;
|
|
526
|
+
//#endregion
|
|
527
|
+
export { scrollValueRangeIntoView as A, ValueRangeRegistry as C, disconnectValueRanges as D, createValueRanges as E, TokenRule as F, TokenizeOptions as I, groupTokens as L, NamedSpans as M, OverlapStrategy as N, isValueRange as O, Token as P, tokenizeValue as R, ValueRangeElement as S, createValueRangeRegistry as T, rangesFromOffsets as _, HighlightSnapshot as a, ValueRange as b, computeRanges as c, createNoopSink as d, generateHighlightCSS as f, isHighlightSupported as g, injectHighlightStyles as h, HighlightSink as i, supportsValueRange as j, isValueRangeSupported as k, createCssHighlightSink as l, highlights as m, HighlightControllerOptions as n, MatchOptions as o, getTextNodes as p, HighlightRange as r, SourceId as s, HighlightController as t, createHighlightController as u, ValueHighlighter as v, createValueHighlighter as w, ValueRangeBindingOptions as x, ValueHighlighterOptions as y };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { type HighlightController, HighlightControllerOptions, HighlightSink, HighlightSnapshot, MatchOptions, SourceId, computeRanges, createCssHighlightSink, createHighlightController, createNoopSink, generateHighlightCSS, getTextNodes, highlights, injectHighlightStyles, isHighlightSupported, rangesFromOffsets };
|
|
1
|
+
import { A as scrollValueRangeIntoView, C as ValueRangeRegistry, D as disconnectValueRanges, E as createValueRanges, F as TokenRule, I as TokenizeOptions, L as groupTokens, M as NamedSpans, N as OverlapStrategy, O as isValueRange, P as Token, R as tokenizeValue, S as ValueRangeElement, T as createValueRangeRegistry, _ as rangesFromOffsets, a as HighlightSnapshot, b as ValueRange, c as computeRanges, d as createNoopSink, f as generateHighlightCSS, g as isHighlightSupported, h as injectHighlightStyles, i as HighlightSink, j as supportsValueRange, k as isValueRangeSupported, l as createCssHighlightSink, m as highlights, n as HighlightControllerOptions, o as MatchOptions, p as getTextNodes, r as HighlightRange, s as SourceId, t as HighlightController, u as createHighlightController, v as ValueHighlighter, w as createValueHighlighter, x as ValueRangeBindingOptions, y as ValueHighlighterOptions } from "./core-DDCXN2b_.js";
|
|
2
|
+
export { type HighlightController, HighlightControllerOptions, HighlightRange, HighlightSink, HighlightSnapshot, MatchOptions, NamedSpans, OverlapStrategy, SourceId, Token, TokenRule, TokenizeOptions, ValueHighlighter, ValueHighlighterOptions, ValueRange, ValueRangeBindingOptions, ValueRangeElement, ValueRangeRegistry, computeRanges, createCssHighlightSink, createHighlightController, createNoopSink, createValueHighlighter, createValueRangeRegistry, createValueRanges, disconnectValueRanges, generateHighlightCSS, getTextNodes, groupTokens, highlights, injectHighlightStyles, isHighlightSupported, isValueRange, isValueRangeSupported, rangesFromOffsets, scrollValueRangeIntoView, supportsValueRange, tokenizeValue };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { computeRanges, createCssHighlightSink, createHighlightController, createNoopSink, generateHighlightCSS, getTextNodes, highlights, injectHighlightStyles, isHighlightSupported, rangesFromOffsets };
|
|
1
|
+
import { _ as highlights, a as isValueRange, b as rangesFromOffsets, c as supportsValueRange, d as computeRanges, f as createCssHighlightSink, g as getTextNodes, h as generateHighlightCSS, i as disconnectValueRanges, l as groupTokens, m as createNoopSink, n as createValueRangeRegistry, o as isValueRangeSupported, p as createHighlightController, r as createValueRanges, s as scrollValueRangeIntoView, t as createValueHighlighter, u as tokenizeValue, v as injectHighlightStyles, y as isHighlightSupported } from "./value-range-CH2vegQi.js";
|
|
2
|
+
export { computeRanges, createCssHighlightSink, createHighlightController, createNoopSink, createValueHighlighter, createValueRangeRegistry, createValueRanges, disconnectValueRanges, generateHighlightCSS, getTextNodes, groupTokens, highlights, injectHighlightStyles, isHighlightSupported, isValueRange, isValueRangeSupported, rangesFromOffsets, scrollValueRangeIntoView, supportsValueRange, tokenizeValue };
|