@fusion.dev/helix-core 0.145.0 → 0.146.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 +5203 -1777
- package/dist/controllers/aria-forward-controller/aria-forward-controller.d.ts +3 -2
- package/dist/controllers/aria-forward-controller/aria-forward-controller.js +3 -14
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +65 -3
- package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +37 -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/keyboard-navigation-controller/keyboard-navigation-controller.d.ts +9 -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 +7 -4
- package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +48 -1
- package/dist/controllers/virtualize-controller/virtualize-controller.js +50 -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/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 +454 -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 +111 -1478
- package/dist/events/events.js +91 -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 +15 -0
- package/dist/events/names.js +88 -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/react-bridge.d.ts +10 -0
- package/dist/helix-element/react-bridge.js +1 -1
- 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 +15 -3
- package/dist/index.js +15 -3
- package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +17 -2
- package/dist/mixins/mixin-chart-source/mixin-chart-source.js +22 -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 +20 -4
- package/dist/mixins/mixin-field/mixin-field.js +2 -2
- package/dist/mixins/mixin-picker-field/mixin-picker-field.d.ts +15 -3
- package/dist/mixins/mixin-picker-field/mixin-picker-field.js +11 -4
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.d.ts +1 -1
- package/dist/mixins/mixin-segmented-field/mixin-segmented-field.js +10 -4
- 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 +19 -6
- package/dist/utils/audit-name/audit-name.js +10 -27
- package/dist/utils/calendar/calendar.js +7 -7
- 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/heading-level/heading-level.d.ts +8 -0
- package/dist/utils/heading-level/heading-level.js +4 -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/name-text/name-text.d.ts +12 -0
- package/dist/utils/name-text/name-text.js +25 -0
- package/dist/utils/number-format/number-format.d.ts +2 -1
- package/dist/utils/number-format/number-format.js +13 -5
- package/dist/utils/plain-click/plain-click.d.ts +10 -0
- package/dist/utils/plain-click/plain-click.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/report-runtime/report-runtime.d.ts +6 -3
- 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/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 +67 -0
- package/dist/utils/typeahead/typeahead.js +36 -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/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 +29 -0
- package/dist/utils/written-list/written-list.js +30 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +18 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { valueAsDate as asDate, dateAsValue as fromDate } from '../value-as/value-as.js';
|
|
1
|
+
import { valueAsDate as asDate, dateAsValue as fromDate, utcDate } from '../value-as/value-as.js';
|
|
2
2
|
import { writtenList } from '../written-list/written-list.js';
|
|
3
3
|
export const localToday = (now = new Date()) => {
|
|
4
4
|
const month = `${now.getMonth() + 1}`.padStart(2, '0');
|
|
@@ -15,13 +15,13 @@ export const weekStartOf = (locale) => {
|
|
|
15
15
|
return 0;
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
|
-
export const monthOf = (date) => `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
|
|
19
|
-
export const addDays = (date, days) =>
|
|
18
|
+
export const monthOf = (date) => `${String(date.getUTCFullYear()).padStart(4, '0')}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
|
|
19
|
+
export const addDays = (date, days) => utcDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + days);
|
|
20
20
|
export const sameMonth = (left, right) => left.getUTCFullYear() === right.getUTCFullYear() && left.getUTCMonth() === right.getUTCMonth();
|
|
21
21
|
export const addMonths = (date, months) => {
|
|
22
|
-
const target =
|
|
23
|
-
const last =
|
|
24
|
-
return
|
|
22
|
+
const target = utcDate(date.getUTCFullYear(), date.getUTCMonth() + months, 1);
|
|
23
|
+
const last = utcDate(target.getUTCFullYear(), target.getUTCMonth() + 1, 0).getUTCDate();
|
|
24
|
+
return utcDate(target.getUTCFullYear(), target.getUTCMonth(), Math.min(date.getUTCDate(), last));
|
|
25
25
|
};
|
|
26
26
|
export const barredBy = (date, rules) => {
|
|
27
27
|
const iso = fromDate(date);
|
|
@@ -72,7 +72,7 @@ export const periodCells = ({ max, min, page, shown, today, view }) => {
|
|
|
72
72
|
const start = years ? yearPageOf(page) : page;
|
|
73
73
|
return Array.from({ length: 12 }, (_, index) => {
|
|
74
74
|
const at = years ? start + index : index;
|
|
75
|
-
const first =
|
|
75
|
+
const first = utcDate(years ? at : start, years ? 0 : index, 1);
|
|
76
76
|
return {
|
|
77
77
|
at,
|
|
78
78
|
chosen: years ? at === shown.getUTCFullYear() : monthOf(first) === monthOf(shown),
|
|
@@ -46,7 +46,7 @@ export interface TooltipAt {
|
|
|
46
46
|
*
|
|
47
47
|
* The chart's `pointer` is one, and is what every chart passes.
|
|
48
48
|
*/
|
|
49
|
-
export type ReadingCursor = Pick<ChartPointerController, 'column' | 'keyed'
|
|
49
|
+
export type ReadingCursor = Pick<ChartPointerController, 'column' | 'keyed'> & Partial<Pick<ChartPointerController, 'position' | 'shows'>>;
|
|
50
50
|
/**
|
|
51
51
|
* What a pointer resting on a reading shows, as `Tooltip` pointed at a mark.
|
|
52
52
|
*
|
|
@@ -10,7 +10,9 @@ export const drawTooltip = (rows, at, tag, where, slotOf, reading) => {
|
|
|
10
10
|
const row = rows[at];
|
|
11
11
|
const live = reading === undefined || rows.length === 0
|
|
12
12
|
? nothing
|
|
13
|
-
: html `<span aria-live="polite" part="live" role="status">${reading.keyed && row
|
|
13
|
+
: html `<span aria-live="polite" part="live" role="status">${reading.keyed && row
|
|
14
|
+
? spokenReading(row, reading.column, (cell) => reading.shows?.(cell) !== false, reading.position)
|
|
15
|
+
: ''}</span>`;
|
|
14
16
|
if (!row || rows.length === 0) {
|
|
15
17
|
return live;
|
|
16
18
|
}
|
|
@@ -5,6 +5,10 @@ import type { Row } from '../read-series/read-series.js';
|
|
|
5
5
|
*
|
|
6
6
|
* The table's own text, as the bubble shows it, so the words and the picture
|
|
7
7
|
* agree, and nothing in it is English: the names are the table's headings. A
|
|
8
|
-
* blank cell says nothing and is left out
|
|
8
|
+
* blank cell says nothing and is left out, and so is a cell `shows` answers
|
|
9
|
+
* false for, which is a series a legend hid.
|
|
10
|
+
*
|
|
11
|
+
* `position` follows in brackets, such as "2 of 12", where the chart gives
|
|
12
|
+
* one: last, so the reading is heard before where it stands.
|
|
9
13
|
*/
|
|
10
|
-
export declare const spokenReading: (row: Row, column?: number) => string;
|
|
14
|
+
export declare const spokenReading: (row: Row, column?: number, shows?: (cell: number) => boolean, position?: string) => string;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export const spokenReading = (row, column) => {
|
|
1
|
+
export const spokenReading = (row, column, shows = () => true, position = '') => {
|
|
2
|
+
const after = position === '' ? '' : ` (${position})`;
|
|
2
3
|
const cell = column === undefined ? undefined : row.cells[column];
|
|
3
4
|
if (cell) {
|
|
4
|
-
return `${row.label}, ${cell.name} ${cell.text}`.trim()
|
|
5
|
+
return `${`${row.label}, ${cell.name} ${cell.text}`.trim()}${after}`;
|
|
5
6
|
}
|
|
6
7
|
const said = row.cells
|
|
7
|
-
.filter((one) => one.text.trim() !== '')
|
|
8
|
+
.filter((one, index) => one.text.trim() !== '' && shows(index))
|
|
8
9
|
.map((one) => `${one.name} ${one.text}`.trim());
|
|
9
|
-
return said.length === 0 ? row.label : `${row.label}: ${said.join(', ')}`;
|
|
10
|
+
return `${said.length === 0 ? row.label : `${row.label}: ${said.join(', ')}`}${after}`;
|
|
10
11
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A heading level a reader can be told: a whole number from 1 to 6, from
|
|
3
|
+
* the level asked for, or `fallback` where none was.
|
|
4
|
+
*
|
|
5
|
+
* `aria-level` takes a whole number, so a fraction is cut to the level below
|
|
6
|
+
* it, and a level past either end is held at it: `0` is 1 and `9` is 6.
|
|
7
|
+
*/
|
|
8
|
+
export declare const headingLevel: (level: number | undefined, fallback?: number) => number;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Whether `element` is a submit button, native or Helix. */
|
|
2
|
+
export declare const submits: (element: Element) => boolean;
|
|
3
|
+
/** The form's default button, the first submit button in it, native or Helix. */
|
|
4
|
+
export declare const defaultButton: (form: HTMLFormElement) => Element | undefined;
|
|
5
|
+
/** Whether `target` is a native field of `form` that Enter submits it from. */
|
|
6
|
+
export declare const submitsOnEnter: (target: EventTarget | undefined, form: HTMLFormElement) => boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Submits a control's form the way pressing Enter in a native text field does.
|
|
9
|
+
*
|
|
10
|
+
* A Helix control's own input is in a shadow root and has no form owner, so
|
|
11
|
+
* the platform never runs implicit submission for it. This runs it: the form's
|
|
12
|
+
* default button is activated, the first submit button in it, a Helix one
|
|
13
|
+
* included. With none, the form is submitted when it holds no more than one
|
|
14
|
+
* field that counts against implicit submission, and nothing happens
|
|
15
|
+
* otherwise.
|
|
16
|
+
*
|
|
17
|
+
* **A module of its own rather than a method on the base**, so its code ships
|
|
18
|
+
* only with the controls that submit on Enter (Input, Number Input, Pin Input,
|
|
19
|
+
* Combobox, the picker fields and Button) and not with Switch, Checkbox or
|
|
20
|
+
* Slider. `form` is the control's `form`, and a control with none submits
|
|
21
|
+
* nothing.
|
|
22
|
+
*
|
|
23
|
+
* @reference https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#implicit-submission implicit submission
|
|
24
|
+
*/
|
|
25
|
+
export declare const submitImplicitly: (form: HTMLFormElement | null) => void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { HelixFormElement } from '../../helix-form-element/helix-form-element.js';
|
|
2
|
+
const BLOCKING_TYPES = new Set([
|
|
3
|
+
'date',
|
|
4
|
+
'datetime-local',
|
|
5
|
+
'email',
|
|
6
|
+
'month',
|
|
7
|
+
'number',
|
|
8
|
+
'password',
|
|
9
|
+
'search',
|
|
10
|
+
'tel',
|
|
11
|
+
'text',
|
|
12
|
+
'time',
|
|
13
|
+
'url',
|
|
14
|
+
'week',
|
|
15
|
+
]);
|
|
16
|
+
const hooks = (element) => element;
|
|
17
|
+
export const submits = (element) => {
|
|
18
|
+
if (element instanceof HelixFormElement) {
|
|
19
|
+
return hooks(element).submitsForm;
|
|
20
|
+
}
|
|
21
|
+
if (element instanceof HTMLButtonElement) {
|
|
22
|
+
return element.type === 'submit';
|
|
23
|
+
}
|
|
24
|
+
return (element instanceof HTMLInputElement && (element.type === 'submit' || element.type === 'image'));
|
|
25
|
+
};
|
|
26
|
+
export const defaultButton = (form) => [...form.elements].find((element) => submits(element));
|
|
27
|
+
export const submitsOnEnter = (target, form) => target instanceof HTMLInputElement && target.form === form && BLOCKING_TYPES.has(target.type);
|
|
28
|
+
export const submitImplicitly = (form) => {
|
|
29
|
+
if (!form) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const button = defaultButton(form);
|
|
33
|
+
if (button) {
|
|
34
|
+
if (button.matches(':disabled')) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (button instanceof HelixFormElement) {
|
|
38
|
+
hooks(button).activate();
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
;
|
|
42
|
+
button.click();
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const blocking = [...form.elements].filter((element) => element instanceof HelixFormElement
|
|
47
|
+
? hooks(element).blocksImplicitSubmission
|
|
48
|
+
: element instanceof HTMLInputElement && BLOCKING_TYPES.has(element.type));
|
|
49
|
+
if (blocking.length <= 1) {
|
|
50
|
+
form.requestSubmit();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The words a node gives a name computed from its content: its text, read
|
|
3
|
+
* through slots, and the `aria-label` or `alt` of a picture in it, such as an
|
|
4
|
+
* icon named by its own label. A node hidden from the reader gives none, and
|
|
5
|
+
* so does `shown`, the part where a control shows its value.
|
|
6
|
+
*
|
|
7
|
+
* **In every build.** `AriaForwardController` asks it whether a control's own
|
|
8
|
+
* label has words in it, which decides where the host's label goes, so it is
|
|
9
|
+
* kept apart from the missing-name warning, which only a development build
|
|
10
|
+
* carries.
|
|
11
|
+
*/
|
|
12
|
+
export declare const nameText: (node: Node, shown?: Element | null) => string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const nameText = (node, shown) => {
|
|
2
|
+
if (node === shown) {
|
|
3
|
+
return '';
|
|
4
|
+
}
|
|
5
|
+
if (node instanceof HTMLSlotElement) {
|
|
6
|
+
return node
|
|
7
|
+
.assignedNodes({ flatten: true })
|
|
8
|
+
.map((one) => nameText(one, shown))
|
|
9
|
+
.join('');
|
|
10
|
+
}
|
|
11
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
12
|
+
return node.textContent ?? '';
|
|
13
|
+
}
|
|
14
|
+
if (!(node instanceof Element)) {
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
if ((node instanceof HTMLElement && node.hidden) || node.getAttribute('aria-hidden') === 'true') {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
const said = node.getAttribute('aria-label') ?? node.getAttribute('alt');
|
|
21
|
+
if (said?.trim()) {
|
|
22
|
+
return said;
|
|
23
|
+
}
|
|
24
|
+
return [...node.childNodes].map((one) => nameText(one, shown)).join('');
|
|
25
|
+
};
|
|
@@ -15,7 +15,8 @@ export declare const formatNumber: (value: number, locale?: string, options?: In
|
|
|
15
15
|
*
|
|
16
16
|
* The locale's group separators are dropped and its decimal separator read as
|
|
17
17
|
* a point, so `1.234,5` in `de-DE` is 1234.5. A currency sign, a percent sign
|
|
18
|
-
* and a unit the options write are dropped too,
|
|
18
|
+
* and a unit the options write are dropped too, in the plural and the
|
|
19
|
+
* singular, and a percent is read back as
|
|
19
20
|
* the fraction it shows: `25 %` is 0.25. A point is read as a decimal point in
|
|
20
21
|
* a locale that groups with something else, which is what a decimal keypad
|
|
21
22
|
* types.
|
|
@@ -15,13 +15,21 @@ const grouped = (text, separator, decimal) => {
|
|
|
15
15
|
};
|
|
16
16
|
const literal = (text) => text.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
17
17
|
export const parseNumber = (text, locale = '', options) => {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
18
|
+
const format = formatter(locale, options);
|
|
19
|
+
const parts = format.formatToParts(-12345.6);
|
|
20
|
+
const one = format.formatToParts(1);
|
|
21
|
+
const of = (type, from = parts) => from.filter((part) => part.type === type).map((part) => part.value);
|
|
20
22
|
const group = of('group')[0] ?? ',';
|
|
21
23
|
const decimal = of('decimal')[0] ?? '.';
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const named = (from) => [
|
|
25
|
+
...of('currency', from),
|
|
26
|
+
...of('percentSign', from),
|
|
27
|
+
...of('unit', from),
|
|
28
|
+
...of('literal', from),
|
|
29
|
+
];
|
|
30
|
+
const dropped = [...new Set([...named(parts), ...named(one)].map((word) => word.trim()))]
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.sort((a, b) => b.length - a.length);
|
|
25
33
|
const percent = options?.style === 'percent' && /[%\u066a\u2030]/u.test(text);
|
|
26
34
|
let read = text.replace(/[\s\u00a0\u202f]/gu, '');
|
|
27
35
|
for (const one of dropped) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a click is a plain press of the main button, with no Command,
|
|
3
|
+
* Control, Shift or Alt held.
|
|
4
|
+
*
|
|
5
|
+
* A link pressed any other way opens where the browser opens it, a new tab or
|
|
6
|
+
* a new window, so an element that acts on a link's click first (a tab
|
|
7
|
+
* choosing itself, a skip link moving focus, an outline scrolling) leaves
|
|
8
|
+
* every other click to the browser.
|
|
9
|
+
*/
|
|
10
|
+
export declare const plainClick: (event: MouseEvent) => boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const plainClick = (event) => event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PressPointerType } from '../../events/details.js';
|
|
2
|
+
/** What the control saw before the click, which the click does not carry everywhere. */
|
|
3
|
+
export interface RecentPress {
|
|
4
|
+
/** Whether Enter or Space started this press. */
|
|
5
|
+
key?: boolean;
|
|
6
|
+
/** The `pointerType` of the last `pointerdown` on the control. */
|
|
7
|
+
pointer?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* What a `click` came from, for `HelixPressEvent`.
|
|
11
|
+
*
|
|
12
|
+
* **Read in order.** The click's own pointer kind first, which Chromium and
|
|
13
|
+
* Firefox put on it. Then a key, which the caller saw start the press, because
|
|
14
|
+
* a keyboard press and a script's `click()` arrive alike. Then, for a click
|
|
15
|
+
* with a press count (`detail` above zero), the last pointer's kind, or
|
|
16
|
+
* `mouse` where none was seen, which is how Safari's click arrives. A click
|
|
17
|
+
* with none of these is `virtual`, as React Aria reads one: a screen reader's
|
|
18
|
+
* press or a script's `click()`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const pressType: (click: MouseEvent, recent?: RecentPress) => PressPointerType;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const POINTERS = new Set(['mouse', 'pen', 'touch']);
|
|
2
|
+
export const pressType = (click, recent = {}) => {
|
|
3
|
+
const own = click.pointerType ?? '';
|
|
4
|
+
if (POINTERS.has(own)) {
|
|
5
|
+
return own;
|
|
6
|
+
}
|
|
7
|
+
if (recent.key === true) {
|
|
8
|
+
return 'keyboard';
|
|
9
|
+
}
|
|
10
|
+
if (click.detail > 0) {
|
|
11
|
+
const last = recent.pointer ?? '';
|
|
12
|
+
return POINTERS.has(last) ? last : 'mouse';
|
|
13
|
+
}
|
|
14
|
+
return 'virtual';
|
|
15
|
+
};
|
|
@@ -22,8 +22,6 @@ export type RuntimeKind =
|
|
|
22
22
|
'duplicate-registration'
|
|
23
23
|
/** The browser refused fullscreen, usually for a frame without the permission. */
|
|
24
24
|
| 'fullscreen-refused'
|
|
25
|
-
/** An Avatar Group's `max` is past the number the fold is written for. */
|
|
26
|
-
| 'max-past-fold'
|
|
27
25
|
/** A rating's `min` is no score between 0 and its `max`. */
|
|
28
26
|
| 'min-out-of-range'
|
|
29
27
|
/** A Stat was given one of Trend's directions, `up`, `down` or `neutral`, which lay nothing out. */
|
|
@@ -34,7 +32,10 @@ export type RuntimeKind =
|
|
|
34
32
|
| 'missing-alt'
|
|
35
33
|
/** The three.js driver could not load its layout, so the notes stay where they started. */
|
|
36
34
|
| 'missing-layout'
|
|
37
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* A control has no accessible name, so a screen reader announces it by its role alone.
|
|
37
|
+
* Reported in development builds only (the `development` export condition).
|
|
38
|
+
*/
|
|
38
39
|
| 'missing-name'
|
|
39
40
|
/** A selectable Data Table has no `row-key`, so it knows each row by its place. */
|
|
40
41
|
| 'missing-row-key'
|
|
@@ -64,6 +65,8 @@ export type RuntimeKind =
|
|
|
64
65
|
| 'too-many-rows'
|
|
65
66
|
/** A filter field marks more than one operator as its default. */
|
|
66
67
|
| 'two-default-operators'
|
|
68
|
+
/** A Quote's text starts or ends with a quotation mark, so the browser's marks double it. */
|
|
69
|
+
| 'typed-marks'
|
|
67
70
|
/** A React handler prop names an event nothing dispatches. */
|
|
68
71
|
| 'unknown-event'
|
|
69
72
|
/** An Icon's `name` is in no icon set it can read, so it draws nothing. */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value at `path` in a row of data, or `undefined` where the path leads
|
|
3
|
+
* nowhere: the field a Table or Data Table column, a Data Table's `row-key`
|
|
4
|
+
* or a Virtual List's template hole names.
|
|
5
|
+
*
|
|
6
|
+
* A dot separates keys, so `author.name` reads `row.author.name`. A key that
|
|
7
|
+
* holds a dot itself is read whole first, so a flat record with a key such as
|
|
8
|
+
* `"v1.2"` keeps working. An empty path reads nothing, and so does a row that
|
|
9
|
+
* is no record. `undefined` is drawn as an empty cell: a missing key is a hole
|
|
10
|
+
* in the data, and drawing "undefined" tells a reader the software is broken.
|
|
11
|
+
*
|
|
12
|
+
* Written here rather than taken from a library, because the whole of what is
|
|
13
|
+
* needed is "walk the keys and stop at the first thing that is not an object".
|
|
14
|
+
*/
|
|
15
|
+
export declare const valueAt: (row: unknown, path: string) => unknown;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const valueAt = (row, path) => {
|
|
2
|
+
if (path === '' || row === null || typeof row !== 'object') {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
const record = row;
|
|
6
|
+
if (path in record) {
|
|
7
|
+
return record[path];
|
|
8
|
+
}
|
|
9
|
+
let value = record;
|
|
10
|
+
for (const key of path.split('.')) {
|
|
11
|
+
if (value === null || typeof value !== 'object') {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
value = value[key];
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -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;
|