@fusion.dev/helix-core 0.145.0 → 0.147.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/custom-elements.json +6585 -1920
- package/dist/controllers/aria-forward-controller/aria-forward-controller.d.ts +3 -2
- package/dist/controllers/aria-forward-controller/aria-forward-controller.js +8 -17
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +71 -3
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +50 -9
- package/dist/controllers/chart-pointer-controller/reading-step.d.ts +7 -1
- package/dist/controllers/chart-pointer-controller/reading-step.js +5 -0
- package/dist/controllers/copy-controller/copy-controller.d.ts +4 -1
- package/dist/controllers/copy-controller/copy-controller.js +2 -1
- package/dist/controllers/countdown-controller/countdown-controller.d.ts +4 -1
- package/dist/controllers/countdown-controller/countdown-controller.js +3 -1
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.d.ts +14 -55
- package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +7 -39
- package/dist/controllers/relative-clock-controller/relative-clock-controller.d.ts +44 -0
- package/dist/controllers/relative-clock-controller/relative-clock-controller.js +31 -0
- package/dist/controllers/source-controller/source-controller.d.ts +10 -3
- package/dist/controllers/source-controller/source-controller.js +9 -4
- package/dist/controllers/swipe-controller/swipe-controller.d.ts +10 -1
- package/dist/controllers/swipe-controller/swipe-controller.js +1 -1
- package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +48 -1
- package/dist/controllers/virtualize-controller/virtualize-controller.js +52 -7
- package/dist/events/action.d.ts +12 -0
- package/dist/events/action.js +6 -0
- package/dist/events/activate.d.ts +22 -0
- package/dist/events/activate.js +11 -0
- package/dist/events/aura-fallback.d.ts +12 -0
- package/dist/events/aura-fallback.js +11 -0
- package/dist/events/band.d.ts +13 -0
- package/dist/events/band.js +11 -0
- package/dist/events/before-activate.d.ts +24 -0
- package/dist/events/before-activate.js +11 -0
- package/dist/events/before-change.d.ts +26 -0
- package/dist/events/before-change.js +11 -0
- package/dist/events/before-close.d.ts +6 -0
- package/dist/events/before-close.js +11 -0
- package/dist/events/before-collapse.d.ts +6 -0
- package/dist/events/before-collapse.js +11 -0
- package/dist/events/before-edit.d.ts +6 -0
- package/dist/events/before-edit.js +11 -0
- package/dist/events/before-move.d.ts +17 -0
- package/dist/events/before-move.js +11 -0
- package/dist/events/before-open.d.ts +17 -0
- package/dist/events/before-open.js +11 -0
- package/dist/events/before-panel-resize.d.ts +13 -0
- package/dist/events/before-panel-resize.js +11 -0
- package/dist/events/before-pin.d.ts +6 -0
- package/dist/events/before-pin.js +11 -0
- package/dist/events/before-reconnect.d.ts +14 -0
- package/dist/events/before-reconnect.js +11 -0
- package/dist/events/before-relabel.d.ts +15 -0
- package/dist/events/before-relabel.js +11 -0
- package/dist/events/before-resize.d.ts +6 -0
- package/dist/events/before-resize.js +11 -0
- package/dist/events/before-split.d.ts +12 -0
- package/dist/events/before-split.js +11 -0
- package/dist/events/before-toggle.d.ts +23 -0
- package/dist/events/before-toggle.js +11 -0
- package/dist/events/canvas-edit.d.ts +16 -0
- package/dist/events/canvas-edit.js +11 -0
- package/dist/events/canvas-pan.d.ts +13 -0
- package/dist/events/canvas-pan.js +11 -0
- package/dist/events/canvas-view.d.ts +14 -0
- package/dist/events/canvas-view.js +11 -0
- package/dist/events/canvas-zoom.d.ts +6 -0
- package/dist/events/canvas-zoom.js +11 -0
- package/dist/events/caret.d.ts +17 -0
- package/dist/events/caret.js +11 -0
- package/dist/events/clear.d.ts +11 -0
- package/dist/events/clear.js +6 -0
- package/dist/events/collapse.d.ts +12 -0
- package/dist/events/collapse.js +11 -0
- package/dist/events/complete.d.ts +26 -0
- package/dist/events/complete.js +11 -0
- package/dist/events/connect.d.ts +14 -0
- package/dist/events/connect.js +11 -0
- package/dist/events/console.d.ts +13 -0
- package/dist/events/console.js +11 -0
- package/dist/events/copy-error.d.ts +9 -0
- package/dist/events/copy-error.js +6 -0
- package/dist/events/copy.d.ts +12 -0
- package/dist/events/copy.js +10 -0
- package/dist/events/create.d.ts +18 -0
- package/dist/events/create.js +11 -0
- package/dist/events/details.d.ts +459 -0
- package/dist/events/details.js +1 -0
- package/dist/events/dismiss.d.ts +16 -0
- package/dist/events/dismiss.js +11 -0
- package/dist/events/edit.d.ts +12 -0
- package/dist/events/edit.js +11 -0
- package/dist/events/end.d.ts +9 -0
- package/dist/events/end.js +6 -0
- package/dist/events/events.d.ts +113 -1478
- package/dist/events/events.js +93 -863
- package/dist/events/expand.d.ts +10 -0
- package/dist/events/expand.js +11 -0
- package/dist/events/facet.d.ts +17 -0
- package/dist/events/facet.js +11 -0
- package/dist/events/filter.d.ts +12 -0
- package/dist/events/filter.js +11 -0
- package/dist/events/filtered.d.ts +12 -0
- package/dist/events/filtered.js +11 -0
- package/dist/events/graph-filter.d.ts +10 -0
- package/dist/events/graph-filter.js +11 -0
- package/dist/events/graph-layout.d.ts +10 -0
- package/dist/events/graph-layout.js +11 -0
- package/dist/events/graph-pan.d.ts +9 -0
- package/dist/events/graph-pan.js +11 -0
- package/dist/events/graph-part-change.d.ts +10 -0
- package/dist/events/graph-part-change.js +11 -0
- package/dist/events/graph-ready.d.ts +8 -0
- package/dist/events/graph-ready.js +6 -0
- package/dist/events/graph-signal.d.ts +11 -0
- package/dist/events/graph-signal.js +11 -0
- package/dist/events/graph-zoom.d.ts +6 -0
- package/dist/events/graph-zoom.js +11 -0
- package/dist/events/history.d.ts +16 -0
- package/dist/events/history.js +11 -0
- package/dist/events/load-more.d.ts +12 -0
- package/dist/events/load-more.js +6 -0
- package/dist/events/load.d.ts +12 -0
- package/dist/events/load.js +6 -0
- package/dist/events/map-cluster.d.ts +15 -0
- package/dist/events/map-cluster.js +11 -0
- package/dist/events/map-pan.d.ts +18 -0
- package/dist/events/map-pan.js +11 -0
- package/dist/events/map-part-change.d.ts +16 -0
- package/dist/events/map-part-change.js +11 -0
- package/dist/events/map-ready.d.ts +15 -0
- package/dist/events/map-ready.js +6 -0
- package/dist/events/map-rotate.d.ts +9 -0
- package/dist/events/map-rotate.js +11 -0
- package/dist/events/map-tilt.d.ts +9 -0
- package/dist/events/map-tilt.js +11 -0
- package/dist/events/map-zoom.d.ts +13 -0
- package/dist/events/map-zoom.js +11 -0
- package/dist/events/move.d.ts +11 -0
- package/dist/events/move.js +11 -0
- package/dist/events/names.d.ts +18 -0
- package/dist/events/names.js +13 -0
- package/dist/events/navigate.d.ts +13 -0
- package/dist/events/navigate.js +11 -0
- package/dist/events/next.d.ts +23 -0
- package/dist/events/next.js +11 -0
- package/dist/events/panel-resize.d.ts +12 -0
- package/dist/events/panel-resize.js +11 -0
- package/dist/events/pin.d.ts +12 -0
- package/dist/events/pin.js +11 -0
- package/dist/events/press.d.ts +19 -0
- package/dist/events/press.js +11 -0
- package/dist/events/preview.d.ts +14 -0
- package/dist/events/preview.js +11 -0
- package/dist/events/previous.d.ts +6 -0
- package/dist/events/previous.js +11 -0
- package/dist/events/range-change.d.ts +10 -0
- package/dist/events/range-change.js +11 -0
- package/dist/events/read.d.ts +19 -0
- package/dist/events/read.js +11 -0
- package/dist/events/reconnect.d.ts +6 -0
- package/dist/events/reconnect.js +11 -0
- package/dist/events/reject.d.ts +13 -0
- package/dist/events/reject.js +11 -0
- package/dist/events/relabel.d.ts +6 -0
- package/dist/events/relabel.js +11 -0
- package/dist/events/remove.d.ts +12 -0
- package/dist/events/remove.js +11 -0
- package/dist/events/reorder.d.ts +18 -0
- package/dist/events/reorder.js +11 -0
- package/dist/events/reordered.d.ts +12 -0
- package/dist/events/reordered.js +11 -0
- package/dist/events/resize.d.ts +19 -0
- package/dist/events/resize.js +11 -0
- package/dist/events/resized.d.ts +16 -0
- package/dist/events/resized.js +11 -0
- package/dist/events/sample-end.d.ts +5 -0
- package/dist/events/sample-end.js +6 -0
- package/dist/events/sample-start.d.ts +18 -0
- package/dist/events/sample-start.js +6 -0
- package/dist/events/scrolled.d.ts +32 -0
- package/dist/events/scrolled.js +11 -0
- package/dist/events/select.d.ts +12 -0
- package/dist/events/select.js +11 -0
- package/dist/events/selected.d.ts +12 -0
- package/dist/events/selected.js +11 -0
- package/dist/events/send.d.ts +18 -0
- package/dist/events/send.js +11 -0
- package/dist/events/shortcut.d.ts +13 -0
- package/dist/events/shortcut.js +11 -0
- package/dist/events/sort.d.ts +17 -0
- package/dist/events/sort.js +11 -0
- package/dist/events/sorted.d.ts +15 -0
- package/dist/events/sorted.js +11 -0
- package/dist/events/split.d.ts +12 -0
- package/dist/events/split.js +11 -0
- package/dist/events/toggle.d.ts +15 -0
- package/dist/events/toggle.js +11 -0
- package/dist/events/tour-ask.d.ts +16 -0
- package/dist/events/tour-ask.js +11 -0
- package/dist/events/tour-status.d.ts +14 -0
- package/dist/events/tour-status.js +11 -0
- package/dist/events/typing.d.ts +10 -0
- package/dist/events/typing.js +11 -0
- package/dist/helix-element/helix-element.js +9 -43
- package/dist/helix-element/react-bridge.d.ts +23 -0
- package/dist/helix-element/react-bridge.js +2 -2
- package/dist/helix-form-element/helix-form-element.d.ts +5 -20
- package/dist/helix-form-element/helix-form-element.js +4 -56
- package/dist/index.d.ts +30 -9
- package/dist/index.js +30 -9
- package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +24 -2
- package/dist/mixins/mixin-chart-source/mixin-chart-source.js +23 -1
- package/dist/mixins/mixin-controller-active/mixin-controller-active.js +4 -3
- package/dist/mixins/mixin-controller-checked/mixin-controller-checked.d.ts +5 -2
- package/dist/mixins/mixin-controller-checked/mixin-controller-checked.js +2 -1
- package/dist/mixins/mixin-controller-value/mixin-controller-value.d.ts +4 -2
- package/dist/mixins/mixin-controller-value/mixin-controller-value.js +7 -2
- package/dist/mixins/mixin-dismissible/mixin-dismissible.js +44 -7
- package/dist/mixins/mixin-field/mixin-field.js +2 -2
- package/dist/mixins/mixin-picker-feedback/mixin-picker-feedback.d.ts +42 -0
- package/dist/mixins/mixin-picker-feedback/mixin-picker-feedback.js +46 -0
- package/dist/mixins/mixin-picker-field/mixin-picker-field.d.ts +7 -4
- package/dist/mixins/mixin-picker-field/mixin-picker-field.js +6 -4
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +19 -9
- package/dist/utils/audit-name/audit-name-off.d.ts +10 -0
- package/dist/utils/audit-name/audit-name-off.js +1 -0
- package/dist/utils/audit-name/audit-name.d.ts +26 -8
- package/dist/utils/audit-name/audit-name.js +22 -30
- package/dist/utils/audit-source/audit-source-off.d.ts +10 -0
- package/dist/utils/audit-source/audit-source-off.js +1 -0
- package/dist/utils/audit-source/audit-source.d.ts +19 -0
- package/dist/utils/audit-source/audit-source.js +27 -0
- package/dist/utils/calendar/calendar.js +7 -7
- package/dist/utils/call-listener/call-listener.d.ts +16 -0
- package/dist/utils/call-listener/call-listener.js +17 -0
- package/dist/utils/chord/chord.d.ts +14 -0
- package/dist/utils/chord/chord.js +1 -0
- package/dist/utils/clamp/clamp.d.ts +7 -0
- package/dist/utils/clamp/clamp.js +1 -0
- package/dist/utils/css-color/css-color.js +1 -1
- package/dist/utils/draw-tooltip/draw-tooltip.d.ts +1 -1
- package/dist/utils/draw-tooltip/draw-tooltip.js +3 -1
- package/dist/utils/draw-tooltip/spoken-reading.d.ts +6 -2
- package/dist/utils/draw-tooltip/spoken-reading.js +5 -4
- package/dist/utils/elapsed-time/elapsed-time.d.ts +1 -1
- package/dist/utils/elapsed-time/elapsed-time.js +2 -5
- package/dist/utils/heading-level/heading-level.d.ts +8 -0
- package/dist/utils/heading-level/heading-level.js +4 -0
- package/dist/utils/host-words/host-words.d.ts +16 -0
- package/dist/utils/host-words/host-words.js +13 -0
- package/dist/utils/implicit-submit/implicit-submit.d.ts +25 -0
- package/dist/utils/implicit-submit/implicit-submit.js +52 -0
- package/dist/utils/load-more/load-more.d.ts +25 -0
- package/dist/utils/load-more/load-more.js +1 -0
- package/dist/utils/locale-of/locale-of.d.ts +18 -0
- package/dist/utils/locale-of/locale-of.js +26 -0
- package/dist/utils/name-text/name-text.d.ts +12 -0
- package/dist/utils/name-text/name-text.js +25 -0
- package/dist/utils/nearest/nearest.d.ts +9 -0
- package/dist/utils/nearest/nearest.js +9 -0
- package/dist/utils/number-format/number-format.d.ts +11 -1
- package/dist/utils/number-format/number-format.js +17 -5
- package/dist/utils/own-rule/own-rule.d.ts +16 -0
- package/dist/utils/own-rule/own-rule.js +7 -0
- package/dist/utils/plain-click/plain-click.d.ts +10 -0
- package/dist/utils/plain-click/plain-click.js +1 -0
- package/dist/utils/platform/platform.d.ts +8 -0
- package/dist/utils/platform/platform.js +1 -0
- package/dist/utils/press-type/press-type.d.ts +20 -0
- package/dist/utils/press-type/press-type.js +15 -0
- package/dist/utils/read-series/read-series.d.ts +14 -0
- package/dist/utils/read-series/read-series.js +15 -0
- package/dist/utils/relative-time/relative-time.d.ts +8 -0
- package/dist/utils/relative-time/relative-time.js +5 -2
- package/dist/utils/report-render/report-render.js +2 -14
- package/dist/utils/report-runtime/report-runtime.d.ts +11 -4
- package/dist/utils/report-runtime/report-runtime.js +2 -14
- package/dist/utils/report-usage/report-usage.js +2 -14
- package/dist/utils/row-data/row-data.d.ts +15 -0
- package/dist/utils/row-data/row-data.js +17 -0
- package/dist/utils/row-order/row-order.d.ts +141 -0
- package/dist/utils/row-order/row-order.js +76 -0
- package/dist/utils/scroll-item-into-view/scroll-item-into-view.d.ts +33 -0
- package/dist/utils/scroll-item-into-view/scroll-item-into-view.js +13 -3
- package/dist/utils/slotted-glyph/slotted-glyph.d.ts +33 -0
- package/dist/utils/slotted-glyph/slotted-glyph.js +27 -0
- package/dist/utils/tell-disclosure/tell-disclosure.d.ts +4 -0
- package/dist/utils/tree-arrow/tree-arrow.d.ts +23 -0
- package/dist/utils/tree-arrow/tree-arrow.js +9 -0
- package/dist/utils/typeahead/typeahead.d.ts +75 -0
- package/dist/utils/typeahead/typeahead.js +37 -0
- package/dist/utils/value-as/value-as.d.ts +9 -0
- package/dist/utils/value-as/value-as.js +9 -1
- package/dist/utils/verdict-flags/verdict-flags.d.ts +13 -0
- package/dist/utils/verdict-flags/verdict-flags.js +6 -0
- package/dist/utils/watch-labels/watch-labels.d.ts +16 -0
- package/dist/utils/watch-labels/watch-labels.js +34 -0
- package/dist/utils/words-converter/words-converter.d.ts +14 -0
- package/dist/utils/words-converter/words-converter.js +15 -0
- package/dist/utils/written-list/written-list.d.ts +60 -0
- package/dist/utils/written-list/written-list.js +46 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +29 -5
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter, sort, then window. In that order, over the whole set, every time.
|
|
3
|
+
*
|
|
4
|
+
* **This is the file that stops the classic virtualization bug.** Sorting the
|
|
5
|
+
* rendered window looks completely correct on the first screen and is wrong
|
|
6
|
+
* everywhere else, and it is the natural thing to write once rows are data and
|
|
7
|
+
* only some of them are drawn. Keeping the three operations here, as functions
|
|
8
|
+
* over an array with no knowledge of what is on screen, is what makes that
|
|
9
|
+
* mistake hard to make: nothing in this module can see a window, and `window()`
|
|
10
|
+
* is the last thing applied rather than the first.
|
|
11
|
+
*
|
|
12
|
+
* The order is not a preference. Filtering after sorting sorts rows that are
|
|
13
|
+
* about to be thrown away, which is only slower; paging before either is the bug.
|
|
14
|
+
* Filter narrows, sort arranges what is left, the page is a slice of that, and
|
|
15
|
+
* the count a reader is shown is the length after filtering rather than the
|
|
16
|
+
* length of the data, or the pagination promises pages that are empty.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* A row, which is whatever the consumer's data is.
|
|
20
|
+
*
|
|
21
|
+
* Not `DataRow`: `@fusion.dev/helix-core` already exports one, for a cell read out of a
|
|
22
|
+
* chart series, and the headless barrel re-exports both. Two different `DataRow`s
|
|
23
|
+
* reaching a consumer through one package is an ambiguity TypeScript refuses
|
|
24
|
+
* outright.
|
|
25
|
+
*/
|
|
26
|
+
export type DataRow = Readonly<Record<string, unknown>>;
|
|
27
|
+
/** One column's contribution, reduced to the two functions this module needs. */
|
|
28
|
+
export interface Axis {
|
|
29
|
+
/** The field this axis is named by, matched against a sort spec. */
|
|
30
|
+
field: string;
|
|
31
|
+
/** The value to compare and match on, which is not always what the cell shows. */
|
|
32
|
+
order: (row: DataRow) => unknown;
|
|
33
|
+
}
|
|
34
|
+
/** A parsed `field:direction`. */
|
|
35
|
+
export interface Sort {
|
|
36
|
+
direction: 'asc' | 'desc';
|
|
37
|
+
field: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* `field:asc` and `field:desc`, and nothing else.
|
|
41
|
+
*
|
|
42
|
+
* A bare `field` parses as ascending, because that is what a URL query usually
|
|
43
|
+
* carries and refusing it would make the common case the verbose one. Anything
|
|
44
|
+
* unparseable is no sort rather than an error: this value arrives from a query
|
|
45
|
+
* string as often as from code, and a bad one should leave the table in the
|
|
46
|
+
* order the data came in rather than break the page.
|
|
47
|
+
*/
|
|
48
|
+
export declare const parse: (spec: string) => Sort | undefined;
|
|
49
|
+
/** A `Sort` written back out, so the property and the attribute agree. */
|
|
50
|
+
export declare const format: (sort: Sort | undefined) => string;
|
|
51
|
+
/**
|
|
52
|
+
* What a reader gets on the next click of a sortable heading.
|
|
53
|
+
*
|
|
54
|
+
* Ascending, descending, then none. **The third state is not an omission to be
|
|
55
|
+
* tidied away**: the order rows arrived in is information the data came with,
|
|
56
|
+
* often the server's own relevance ranking, and a two-state cycle makes it
|
|
57
|
+
* unreachable once a reader has sorted once.
|
|
58
|
+
*/
|
|
59
|
+
export declare const cycle: (current: Sort | undefined, field: string) => Sort | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Rows matching a query, across every axis.
|
|
62
|
+
*
|
|
63
|
+
* One query over all columns rather than a predicate per column, which is what a
|
|
64
|
+
* reader means by a search box above a table. Per-column predicates are a
|
|
65
|
+
* different feature and compose with this rather than replacing it.
|
|
66
|
+
*
|
|
67
|
+
* Case-insensitive and substring, on the ordering value rather than the rendered
|
|
68
|
+
* one: a cell showing a formatted date should be findable by the text a reader
|
|
69
|
+
* sees, and a cell showing an icon should be findable by the value behind it.
|
|
70
|
+
* Where those differ, `compare` on the column is what decides, which is the same
|
|
71
|
+
* function sorting uses, so search and sort cannot disagree about what a column
|
|
72
|
+
* holds.
|
|
73
|
+
*/
|
|
74
|
+
export declare const filter: (rows: readonly DataRow[], query: string, axes: readonly Axis[]) => DataRow[];
|
|
75
|
+
/**
|
|
76
|
+
* Rows in the asked-for order, stably.
|
|
77
|
+
*
|
|
78
|
+
* **Stable, and that is load-bearing rather than tidy.** `Array.prototype.sort`
|
|
79
|
+
* is required to be stable since ES2019, so equal rows keep the order they
|
|
80
|
+
* arrived in, which means sorting by one column and then another leaves the
|
|
81
|
+
* first as the tiebreak. A reader who sorts by status and then by name expects
|
|
82
|
+
* exactly that, and an unstable sort would shuffle equal rows on every
|
|
83
|
+
* re-render, which reads as the table twitching.
|
|
84
|
+
*/
|
|
85
|
+
export declare const sort: (rows: readonly DataRow[], spec: Sort | undefined, axes: readonly Axis[]) => DataRow[];
|
|
86
|
+
/**
|
|
87
|
+
* One page of rows, clamped.
|
|
88
|
+
*
|
|
89
|
+
* A page past the end returns the last page rather than nothing, because the
|
|
90
|
+
* usual way to be past the end is a bookmarked link after the result shrank,
|
|
91
|
+
* and an empty table is a worse answer than the last page of a smaller one. The
|
|
92
|
+
* same clamp `Pagination` already applies to what it reports.
|
|
93
|
+
*/
|
|
94
|
+
export declare const window_: (rows: readonly DataRow[], page: number, size: number) => DataRow[];
|
|
95
|
+
/** What arranging produces: the rows in order, with no page or window taken. */
|
|
96
|
+
export interface Arranged {
|
|
97
|
+
/** Rows after filtering and sorting, in the order they are to be drawn. */
|
|
98
|
+
rows: DataRow[];
|
|
99
|
+
/** Rows before filtering, which is what tells a reader a filter is on. */
|
|
100
|
+
total: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Filtering and sorting, with nothing taken off the end.
|
|
104
|
+
*
|
|
105
|
+
* **Split out of `ordered` because a window moves and this does not.** A table
|
|
106
|
+
* drawing a window on a result re-renders every time a scroll reveals a row,
|
|
107
|
+
* and deriving that render from the whole result means filtering and sorting it
|
|
108
|
+
* again each time: 23.7ms at five thousand rows, a frame budget spent three
|
|
109
|
+
* times over before a cell is drawn. A caller that keeps this between renders
|
|
110
|
+
* pays it once per change to the data rather than once per frame.
|
|
111
|
+
*
|
|
112
|
+
* The two steps stay together for the reason `ordered` gives below: sorting a
|
|
113
|
+
* page is the classic bug, and one function doing both is what makes calling
|
|
114
|
+
* them in the wrong order impossible.
|
|
115
|
+
*/
|
|
116
|
+
export declare const arranged: (rows: readonly DataRow[], axes: readonly Axis[], { query, spec }: {
|
|
117
|
+
query: string;
|
|
118
|
+
spec: string;
|
|
119
|
+
}) => Arranged;
|
|
120
|
+
/** What one pass over the data produces: the window, and the counts around it. */
|
|
121
|
+
export interface Ordered {
|
|
122
|
+
/** Rows after filtering, before paging. The number a pagination must page. */
|
|
123
|
+
matched: number;
|
|
124
|
+
/** The rows to draw. */
|
|
125
|
+
rows: DataRow[];
|
|
126
|
+
/** Rows before filtering, which is what tells a reader a filter is on. */
|
|
127
|
+
total: number;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The whole pass, in the one order that is correct.
|
|
131
|
+
*
|
|
132
|
+
* Exported as a single function so a caller cannot get the order wrong by
|
|
133
|
+
* calling the three in the sequence that reads naturally, which is the sequence
|
|
134
|
+
* that pages before it sorts.
|
|
135
|
+
*/
|
|
136
|
+
export declare const ordered: (rows: readonly DataRow[], axes: readonly Axis[], { page, query, size, spec }: {
|
|
137
|
+
page: number;
|
|
138
|
+
query: string;
|
|
139
|
+
size: number;
|
|
140
|
+
spec: string;
|
|
141
|
+
}) => Ordered;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const parse = (spec) => {
|
|
2
|
+
const trimmed = spec.trim();
|
|
3
|
+
if (trimmed === '') {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const [field = '', direction = 'asc'] = trimmed.split(':');
|
|
7
|
+
if (field === '' || (direction !== 'asc' && direction !== 'desc')) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
return { direction, field };
|
|
11
|
+
};
|
|
12
|
+
export const format = (sort) => sort ? `${sort.field}:${sort.direction}` : '';
|
|
13
|
+
export const cycle = (current, field) => {
|
|
14
|
+
if (current?.field !== field) {
|
|
15
|
+
return { direction: 'asc', field };
|
|
16
|
+
}
|
|
17
|
+
return current.direction === 'asc' ? { direction: 'desc', field } : undefined;
|
|
18
|
+
};
|
|
19
|
+
export const filter = (rows, query, axes) => {
|
|
20
|
+
const needle = query.trim().toLocaleLowerCase();
|
|
21
|
+
if (needle === '') {
|
|
22
|
+
return [...rows];
|
|
23
|
+
}
|
|
24
|
+
return rows.filter((row) => axes.some((axis) => text(axis.order(row)).toLocaleLowerCase().includes(needle)));
|
|
25
|
+
};
|
|
26
|
+
export const sort = (rows, spec, axes) => {
|
|
27
|
+
const axis = spec ? axes.find((one) => one.field === spec.field) : undefined;
|
|
28
|
+
if (!spec || !axis) {
|
|
29
|
+
return [...rows];
|
|
30
|
+
}
|
|
31
|
+
const sign = spec.direction === 'asc' ? 1 : -1;
|
|
32
|
+
return [...rows].sort((left, right) => compare(axis.order(left), axis.order(right), sign));
|
|
33
|
+
};
|
|
34
|
+
export const window_ = (rows, page, size) => {
|
|
35
|
+
if (size <= 0) {
|
|
36
|
+
return [...rows];
|
|
37
|
+
}
|
|
38
|
+
const pages = Math.max(1, Math.ceil(rows.length / size));
|
|
39
|
+
const wanted = Math.min(pages, Math.max(1, Math.floor(page) || 1));
|
|
40
|
+
const from = (wanted - 1) * size;
|
|
41
|
+
return rows.slice(from, from + size);
|
|
42
|
+
};
|
|
43
|
+
const text = (value) => {
|
|
44
|
+
if (value === null || value === undefined) {
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
if (value instanceof Date) {
|
|
48
|
+
return value.toISOString();
|
|
49
|
+
}
|
|
50
|
+
return String(value);
|
|
51
|
+
};
|
|
52
|
+
const compare = (left, right, sign) => {
|
|
53
|
+
const leftMissing = left === null || left === undefined || left === '';
|
|
54
|
+
const rightMissing = right === null || right === undefined || right === '';
|
|
55
|
+
if (leftMissing || rightMissing) {
|
|
56
|
+
return leftMissing && rightMissing ? 0 : leftMissing ? 1 : -1;
|
|
57
|
+
}
|
|
58
|
+
if (typeof left === 'number' && typeof right === 'number') {
|
|
59
|
+
return sign * (left - right);
|
|
60
|
+
}
|
|
61
|
+
if (typeof left === 'boolean' && typeof right === 'boolean') {
|
|
62
|
+
return sign * (Number(left) - Number(right));
|
|
63
|
+
}
|
|
64
|
+
if (left instanceof Date && right instanceof Date) {
|
|
65
|
+
return sign * (left.getTime() - right.getTime());
|
|
66
|
+
}
|
|
67
|
+
return (sign * text(left).localeCompare(text(right), undefined, { numeric: true, sensitivity: 'base' }));
|
|
68
|
+
};
|
|
69
|
+
export const arranged = (rows, axes, { query, spec }) => ({
|
|
70
|
+
rows: sort(filter(rows, query, axes), parse(spec), axes),
|
|
71
|
+
total: rows.length,
|
|
72
|
+
});
|
|
73
|
+
export const ordered = (rows, axes, { page, query, size, spec }) => {
|
|
74
|
+
const { rows: inOrder, total } = arranged(rows, axes, { query, spec });
|
|
75
|
+
return { matched: inOrder.length, rows: window_(inOrder, page, size), total };
|
|
76
|
+
};
|
|
@@ -7,4 +7,37 @@ export interface ScrollItemOptions {
|
|
|
7
7
|
/** Whether to animate. Ignored where the reader asked for less motion. */
|
|
8
8
|
smooth?: boolean;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* The nearest ancestor that actually scrolls, crossing shadow boundaries.
|
|
12
|
+
*
|
|
13
|
+
* **Not `item.offsetParent`**, which is the nearest *positioned* ancestor and
|
|
14
|
+
* has nothing whatever to do with overflow. The two coincide often enough to
|
|
15
|
+
* look right, because a scroller frequently is positioned, and they come apart
|
|
16
|
+
* exactly where it matters: put anything `position: relative` between the item
|
|
17
|
+
* and the box that scrolls, and the util scrolls the thing in between, which
|
|
18
|
+
* does not scroll, and reports nothing.
|
|
19
|
+
*
|
|
20
|
+
* The walk crosses shadow boundaries through the host, because the item is
|
|
21
|
+
* almost always in one and `parentElement` is null at the root. Without that
|
|
22
|
+
* step, a component whose scroller is its own `[part]` would find nothing.
|
|
23
|
+
*
|
|
24
|
+
* It stops before the document. Scrolling the page is the one thing this whole
|
|
25
|
+
* util exists not to do, so finding nothing inside the component and returning
|
|
26
|
+
* null is the honest answer: the caller no-ops rather than taking the address
|
|
27
|
+
* bar with it.
|
|
28
|
+
*/
|
|
29
|
+
export declare const scrollingAncestor: (item: HTMLElement, options?: ScrollerOptions) => HTMLElement | null;
|
|
30
|
+
/** Where `scrollingAncestor` looks, and what it takes for a scroller. */
|
|
31
|
+
export interface ScrollerOptions {
|
|
32
|
+
/** The axis whose overflow counts, or either where absent. */
|
|
33
|
+
readonly axis?: 'x' | 'y';
|
|
34
|
+
/**
|
|
35
|
+
* Only a box whose content is larger than it, along `axis`: one that can
|
|
36
|
+
* scroll now, rather than one that would once it fills. Outline asks this of
|
|
37
|
+
* the box its links and its sections scroll in.
|
|
38
|
+
*/
|
|
39
|
+
readonly overflowing?: boolean;
|
|
40
|
+
/** Where the walk stops, unread: the document's body where absent. */
|
|
41
|
+
readonly stop?: Element | null;
|
|
42
|
+
}
|
|
10
43
|
export declare const scrollItemIntoView: (item: HTMLElement, options?: ScrollItemOptions) => void;
|
|
@@ -1,15 +1,25 @@
|
|
|
1
|
-
const scrollingAncestor = (item) => {
|
|
2
|
-
const stop = item.ownerDocument.body;
|
|
1
|
+
export const scrollingAncestor = (item, options = {}) => {
|
|
2
|
+
const { axis, overflowing = false, stop = item.ownerDocument.body } = options;
|
|
3
3
|
let node = climb(item);
|
|
4
4
|
while (node && node !== stop) {
|
|
5
5
|
const style = getComputedStyle(node);
|
|
6
|
-
|
|
6
|
+
const overflow = axis === 'x'
|
|
7
|
+
? style.overflowX
|
|
8
|
+
: axis === 'y'
|
|
9
|
+
? style.overflowY
|
|
10
|
+
: `${style.overflowX} ${style.overflowY}`;
|
|
11
|
+
if (/\b(auto|scroll|overlay)\b/.test(overflow) && (!overflowing || overflows(node, axis))) {
|
|
7
12
|
return node;
|
|
8
13
|
}
|
|
9
14
|
node = climb(node);
|
|
10
15
|
}
|
|
11
16
|
return null;
|
|
12
17
|
};
|
|
18
|
+
const overflows = (node, axis) => {
|
|
19
|
+
const tall = node.scrollHeight > node.clientHeight;
|
|
20
|
+
const wide = node.scrollWidth > node.clientWidth;
|
|
21
|
+
return axis === 'x' ? wide : axis === 'y' ? tall : tall || wide;
|
|
22
|
+
};
|
|
13
23
|
const climb = (node) => {
|
|
14
24
|
if (node.parentElement) {
|
|
15
25
|
return node.parentElement;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A glyph a consumer slots once and a component draws in many places: the
|
|
3
|
+
* marks of a rating, the remove icon on every row of a file list.
|
|
4
|
+
*
|
|
5
|
+
* A slotted element can be assigned to one slot only, so a component that
|
|
6
|
+
* draws it in several places draws copies. **One copy per place, kept**, so a
|
|
7
|
+
* render does not make them again: a drag renders on every pointer move, and
|
|
8
|
+
* handing Lit a fresh node each time swaps every copy on every frame.
|
|
9
|
+
*/
|
|
10
|
+
export declare class SlottedGlyph {
|
|
11
|
+
/** The copies made so far, by the place each is drawn in. */
|
|
12
|
+
private readonly copies;
|
|
13
|
+
/** The element slotted last, which every copy is made from. */
|
|
14
|
+
private source;
|
|
15
|
+
/** Whether a glyph is slotted, so the component draws copies of it. */
|
|
16
|
+
get slotted(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Reads the slot after a `slotchange`: the first element assigned to it,
|
|
19
|
+
* through any slot it is forwarded by. The copies made of the last one go.
|
|
20
|
+
*/
|
|
21
|
+
read(slot: HTMLSlotElement): void;
|
|
22
|
+
/**
|
|
23
|
+
* Takes `source` as the glyph, or none, as `read` does from a slot: the
|
|
24
|
+
* copies made of the last one go. For the first render, which reads the
|
|
25
|
+
* host's children before any `slotchange` answers.
|
|
26
|
+
*/
|
|
27
|
+
take(source: Element | undefined): void;
|
|
28
|
+
/**
|
|
29
|
+
* The copy for one place, made the first time it is asked for and the same
|
|
30
|
+
* node after, or `undefined` while nothing is slotted.
|
|
31
|
+
*/
|
|
32
|
+
copyFor(place: number | string): Node | undefined;
|
|
33
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class SlottedGlyph {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.copies = new Map();
|
|
4
|
+
}
|
|
5
|
+
get slotted() {
|
|
6
|
+
return this.source !== undefined;
|
|
7
|
+
}
|
|
8
|
+
read(slot) {
|
|
9
|
+
this.take(slot.assignedElements({ flatten: true })[0]);
|
|
10
|
+
}
|
|
11
|
+
take(source) {
|
|
12
|
+
this.source = source;
|
|
13
|
+
this.copies.clear();
|
|
14
|
+
}
|
|
15
|
+
copyFor(place) {
|
|
16
|
+
if (!this.source) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const key = String(place);
|
|
20
|
+
let copy = this.copies.get(key);
|
|
21
|
+
if (!copy) {
|
|
22
|
+
copy = this.source.cloneNode(true);
|
|
23
|
+
this.copies.set(key, copy);
|
|
24
|
+
}
|
|
25
|
+
return copy;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
* menu nothing no
|
|
23
23
|
* ```
|
|
24
24
|
*
|
|
25
|
+
* The popover row holds for the first render. Since then Popover writes
|
|
26
|
+
* `aria-expanded` onto a plain `<button>` trigger from the reader's first
|
|
27
|
+
* press on, after hydration, and calls this only for a Helix trigger.
|
|
28
|
+
*
|
|
25
29
|
* React reports "some attributes of the server rendered HTML didn't match the
|
|
26
30
|
* client properties. This won't be patched up", and the cost is not the console
|
|
27
31
|
* line: it answers a mismatch by discarding the server's HTML for that subtree
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** What a tree row is, for the arrow along the line. */
|
|
2
|
+
export interface TreeArrowRow {
|
|
3
|
+
/** Whether the row holds rows of its own. */
|
|
4
|
+
branch: boolean;
|
|
5
|
+
/** Whether a disabled branch, which neither opens nor shuts. */
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
/** Whether the branch is open. */
|
|
8
|
+
open: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** What the arrow along the line does on a row. */
|
|
11
|
+
export type TreeArrow = 'child' | 'close' | 'open' | 'parent';
|
|
12
|
+
/**
|
|
13
|
+
* What Right or Left does on a tree row, as the APG tree view says: `along` is
|
|
14
|
+
* `1` for the key that points into a branch, where its caret points (Right on
|
|
15
|
+
* a left-to-right page), and `-1` for the other.
|
|
16
|
+
*
|
|
17
|
+
* In: a shut branch opens, and an open one moves to its first child; a leaf
|
|
18
|
+
* does nothing (`undefined`). Out: an open branch shuts, and anything else
|
|
19
|
+
* moves to its parent. A disabled branch neither opens nor shuts. Tree, Object
|
|
20
|
+
* Inspector and Data Table's tree column read their keys through this, and
|
|
21
|
+
* Data Table takes `child` and `undefined` as the next column.
|
|
22
|
+
*/
|
|
23
|
+
export declare const treeArrow: (row: TreeArrowRow, along: -1 | 1) => TreeArrow | undefined;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a press types a character, which is what typeahead reads.
|
|
3
|
+
*
|
|
4
|
+
* One character with no Control, Command or Alt held, so a shortcut such as
|
|
5
|
+
* Control+A is never taken for the letter A. Shift is allowed, because a
|
|
6
|
+
* capital is still a letter.
|
|
7
|
+
*/
|
|
8
|
+
export declare const typesCharacter: (event: KeyboardEvent) => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Finds an item by the letters a reader types at a set, the way a native
|
|
11
|
+
* `<select>` does.
|
|
12
|
+
*
|
|
13
|
+
* Letters typed within `timeout` milliseconds of each other build one search,
|
|
14
|
+
* so typing "ca" moves to "Canada" past "Cambodia". The same letter typed again
|
|
15
|
+
* moves to the next item that starts with it, so pressing "c" three times visits
|
|
16
|
+
* the first three. A pause starts a new search.
|
|
17
|
+
*
|
|
18
|
+
* Matching ignores case and leading spaces, and searches from the reader's
|
|
19
|
+
* position, wrapping at the end. A search that matches nothing moves nothing.
|
|
20
|
+
*
|
|
21
|
+
* Holds only the letters, so one instance serves any set: the caller passes the
|
|
22
|
+
* words of the items and where the reader is on every key.
|
|
23
|
+
*/
|
|
24
|
+
export declare class Typeahead {
|
|
25
|
+
private letters;
|
|
26
|
+
private last;
|
|
27
|
+
private readonly timeout;
|
|
28
|
+
constructor({ timeout }?: {
|
|
29
|
+
timeout?: number;
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Whether a search is under way.
|
|
33
|
+
*
|
|
34
|
+
* A set that chooses on Space asks this first, because a Space typed in the
|
|
35
|
+
* middle of a search is part of the words, as in "New York".
|
|
36
|
+
*/
|
|
37
|
+
typing(now?: number): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Adds a character to the search, and returns the index of the item it
|
|
40
|
+
* matches, or `-1` where none does.
|
|
41
|
+
*
|
|
42
|
+
* `texts` are the words of the items a reader can reach, in order, and
|
|
43
|
+
* `from` is the index of the item the reader is on, or `-1`.
|
|
44
|
+
*/
|
|
45
|
+
match(key: string, texts: readonly string[], from: number, now?: number): number;
|
|
46
|
+
/** Ends the search, so the next letter starts a new one. */
|
|
47
|
+
reset(): void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A typeahead over a set's items: how to read an item's words, and the search
|
|
51
|
+
* that remembers the letters typed so far.
|
|
52
|
+
*
|
|
53
|
+
* What `KeyboardNavigationController` takes as its `typeahead` option. A set
|
|
54
|
+
* builds one with `typeaheadBy`, so the search's code ships only with the sets
|
|
55
|
+
* that type ahead, and Rating, Tabs or Radio, which never do, carry none of it.
|
|
56
|
+
*/
|
|
57
|
+
export interface TypeaheadBy<Item> {
|
|
58
|
+
/** The letters typed so far, matched against the words. */
|
|
59
|
+
readonly search: Typeahead;
|
|
60
|
+
/** The words of an item, which a reader types the start of. */
|
|
61
|
+
readonly words: (item: Item) => string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Turns on typeahead for a set whose items have these words. See
|
|
65
|
+
* `TypeaheadBy`.
|
|
66
|
+
*/
|
|
67
|
+
export declare const typeaheadBy: <Item>(words: (item: Item) => string) => TypeaheadBy<Item>;
|
|
68
|
+
/**
|
|
69
|
+
* Turns on typeahead for a set whose items carry their own `words`, as List's
|
|
70
|
+
* rows, Tag Group's tags and Drilldown's items do. An item with none is
|
|
71
|
+
* passed over.
|
|
72
|
+
*/
|
|
73
|
+
export declare const typeaheadByWords: () => TypeaheadBy<{
|
|
74
|
+
readonly words?: string;
|
|
75
|
+
}>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const typesCharacter = (event) => event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
2
|
+
export class Typeahead {
|
|
3
|
+
constructor({ timeout = 500 } = {}) {
|
|
4
|
+
this.letters = '';
|
|
5
|
+
this.last = 0;
|
|
6
|
+
this.timeout = timeout;
|
|
7
|
+
}
|
|
8
|
+
typing(now = Date.now()) {
|
|
9
|
+
return this.letters !== '' && now - this.last <= this.timeout;
|
|
10
|
+
}
|
|
11
|
+
match(key, texts, from, now = Date.now()) {
|
|
12
|
+
if (!this.typing(now)) {
|
|
13
|
+
this.letters = '';
|
|
14
|
+
}
|
|
15
|
+
this.last = now;
|
|
16
|
+
this.letters += key.toLowerCase();
|
|
17
|
+
const words = texts.map((text) => text.trim().toLowerCase());
|
|
18
|
+
const repeated = [...this.letters].every((letter) => letter === this.letters[0]);
|
|
19
|
+
const needle = repeated ? (this.letters[0] ?? '') : this.letters;
|
|
20
|
+
const start = repeated ? from + 1 : Math.max(from, 0);
|
|
21
|
+
for (let step = 0; step < words.length; step += 1) {
|
|
22
|
+
const at = (start + step) % words.length;
|
|
23
|
+
if (words[at]?.startsWith(needle)) {
|
|
24
|
+
return at;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return -1;
|
|
28
|
+
}
|
|
29
|
+
reset() {
|
|
30
|
+
this.letters = '';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export const typeaheadBy = (words) => ({
|
|
34
|
+
search: new Typeahead(),
|
|
35
|
+
words,
|
|
36
|
+
});
|
|
37
|
+
export const typeaheadByWords = () => typeaheadBy((item) => item.words ?? '');
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Midnight UTC on a day, with the year as written.
|
|
3
|
+
*
|
|
4
|
+
* `Date.UTC` reads a year from 0 to 99 as 1900 to 1999, so year 1, which a
|
|
5
|
+
* native date input accepts and a year segment reaches with Home, became 1901
|
|
6
|
+
* and the date was refused. `setUTCFullYear` takes the year as given, and
|
|
7
|
+
* rolls a month or a day past its end the way `Date.UTC` does.
|
|
8
|
+
*/
|
|
9
|
+
export declare const utcDate: (year: number, month: number, day: number) => Date;
|
|
1
10
|
/**
|
|
2
11
|
* The date a `YYYY-MM-DD` value names, at midnight UTC, or `null`.
|
|
3
12
|
*
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
2
2
|
const ISO_TIME = /^(\d{2}):(\d{2})(?::(\d{2}(?:\.\d{1,3})?))?$/;
|
|
3
3
|
const DAY = 86_400_000;
|
|
4
|
+
export const utcDate = (year, month, day) => {
|
|
5
|
+
const date = new Date(0);
|
|
6
|
+
date.setUTCFullYear(year, month, day);
|
|
7
|
+
return date;
|
|
8
|
+
};
|
|
4
9
|
export const valueAsDate = (value) => {
|
|
5
10
|
const parts = ISO_DATE.exec(value);
|
|
6
11
|
if (!parts) {
|
|
7
12
|
return null;
|
|
8
13
|
}
|
|
9
14
|
const [year, month, day] = [Number(parts[1]), Number(parts[2]), Number(parts[3])];
|
|
10
|
-
|
|
15
|
+
if (year < 1) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const date = utcDate(year, month - 1, day);
|
|
11
19
|
const rolled = date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day;
|
|
12
20
|
return rolled ? null : date;
|
|
13
21
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a picker field finds wrong: a message, reported as `badInput`, or the
|
|
3
|
+
* validity flag to set and its message. Empty means nothing is wrong.
|
|
4
|
+
*/
|
|
5
|
+
export type PickerVerdict = string | readonly [flag: keyof ValidityStateFlags, message: string];
|
|
6
|
+
/**
|
|
7
|
+
* A verdict as the flags and the message `setValidity` takes, or `null` where
|
|
8
|
+
* there is nothing wrong.
|
|
9
|
+
*
|
|
10
|
+
* Shared by `MixinPickerField` and `MixinSegmentedField`, the two shapes a
|
|
11
|
+
* date, time or color field takes, which report the same verdicts.
|
|
12
|
+
*/
|
|
13
|
+
export declare const verdictFlags: (verdict: PickerVerdict) => [flags: ValidityStateFlags, message: string] | null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calls `heard` whenever a `<label>` changes in the tree `host` is in: one is
|
|
3
|
+
* added or taken out, its `for` changes, or its words do.
|
|
4
|
+
*
|
|
5
|
+
* **For a label that arrives after the control renders**, which a framework
|
|
6
|
+
* does freely and which no attribute on the host reports: the platform adds the
|
|
7
|
+
* label to the host's `labels` and says nothing. `HelixFormElement` updates
|
|
8
|
+
* when this calls, so `AriaForwardController` names the control inside as the
|
|
9
|
+
* label appears, where it used to wait for focus.
|
|
10
|
+
*
|
|
11
|
+
* **One observer per tree, whatever the number of controls in it**, so a form
|
|
12
|
+
* of forty fields watches the page once, and a change that touches no label
|
|
13
|
+
* costs one check per record. The returned function stops the watch, and an
|
|
14
|
+
* observer whose controls have all gone stops at the next change it sees.
|
|
15
|
+
*/
|
|
16
|
+
export declare const watchLabels: (host: Node, heard: () => void) => (() => void);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const trees = new Map();
|
|
2
|
+
const touchesLabel = ({ addedNodes, removedNodes, target }) => [target, ...addedNodes, ...removedNodes].some((node) => (node instanceof Element ? node : node.parentElement)?.closest('label') != null ||
|
|
3
|
+
(node instanceof Element && node.querySelector('label') !== null));
|
|
4
|
+
export const watchLabels = (host, heard) => {
|
|
5
|
+
const root = host.getRootNode();
|
|
6
|
+
let callbacks = trees.get(root);
|
|
7
|
+
if (!callbacks) {
|
|
8
|
+
const made = new Set();
|
|
9
|
+
const observer = new MutationObserver((records) => {
|
|
10
|
+
if (made.size === 0) {
|
|
11
|
+
observer.disconnect();
|
|
12
|
+
trees.delete(root);
|
|
13
|
+
}
|
|
14
|
+
else if (records.some(touchesLabel)) {
|
|
15
|
+
for (const callback of [...made]) {
|
|
16
|
+
callback();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
observer.observe(root, {
|
|
21
|
+
attributeFilter: ['for'],
|
|
22
|
+
characterData: true,
|
|
23
|
+
childList: true,
|
|
24
|
+
subtree: true,
|
|
25
|
+
});
|
|
26
|
+
callbacks = made;
|
|
27
|
+
trees.set(root, made);
|
|
28
|
+
}
|
|
29
|
+
callbacks.add(heard);
|
|
30
|
+
const watched = callbacks;
|
|
31
|
+
return () => {
|
|
32
|
+
watched.delete(heard);
|
|
33
|
+
};
|
|
34
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads an attribute holding words keyed by name, written as a JSON object,
|
|
3
|
+
* such as Orb's `state-labels`, Sandbox Terminal's `level-words` or Color
|
|
4
|
+
* Picker's `color-words`. A property declares it as its `converter`.
|
|
5
|
+
*
|
|
6
|
+
* **Anything else is no words, never `null`.** JSON that does not parse, an
|
|
7
|
+
* array, a string or `null` gives an empty object, and a key whose value is
|
|
8
|
+
* not a string is dropped, so every key keeps its default words. Lit's own
|
|
9
|
+
* `Object` converter answers `null` for JSON that does not parse, and a render
|
|
10
|
+
* that reads a key off it throws and draws nothing, over a typo in markup.
|
|
11
|
+
*/
|
|
12
|
+
export declare const wordsConverter: {
|
|
13
|
+
fromAttribute: (value: string | null) => Record<string, string>;
|
|
14
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const wordsConverter = {
|
|
2
|
+
fromAttribute: (value) => {
|
|
3
|
+
let parsed;
|
|
4
|
+
try {
|
|
5
|
+
parsed = JSON.parse(value ?? '');
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
10
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
return Object.fromEntries(Object.entries(parsed).filter((entry) => typeof entry[1] === 'string'));
|
|
14
|
+
},
|
|
15
|
+
};
|