@jielga/tmdatagrid 2.0.0-beta.9 → 2.0.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 +5 -212
- package/dist/index.d.ts +1281 -768
- package/dist/index.js +4607 -3250
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/docs/adding-rows.md +132 -0
- package/docs/anatomy.md +119 -0
- package/docs/card-view.md +108 -0
- package/docs/cell-selection.md +194 -0
- package/docs/column-layout.md +182 -0
- package/docs/column-menu.md +66 -0
- package/docs/columns.md +268 -0
- package/docs/components.md +311 -0
- package/docs/draft-store.md +242 -0
- package/docs/editing.md +303 -0
- package/docs/editors.md +250 -0
- package/docs/export.md +319 -0
- package/docs/filtering.md +362 -0
- package/docs/getting-started.md +123 -0
- package/docs/grouping.md +165 -0
- package/docs/loading-and-empty.md +92 -0
- package/docs/localization.md +79 -0
- package/docs/menu.md +143 -0
- package/docs/migrating-to-2.md +163 -0
- package/docs/pagination.md +144 -0
- package/docs/persistence.md +114 -0
- package/docs/portfolio-rebalancer.md +94 -0
- package/docs/query-builder.md +179 -0
- package/docs/quick-search.md +84 -0
- package/docs/row-details.md +115 -0
- package/docs/row-interaction.md +149 -0
- package/docs/row-pinning.md +132 -0
- package/docs/row-selection.md +136 -0
- package/docs/row-styling.md +133 -0
- package/docs/scrolling.md +112 -0
- package/docs/server-query.md +246 -0
- package/docs/server-side.md +206 -0
- package/docs/sorting.md +101 -0
- package/docs/styling.md +126 -0
- package/docs/summary-row.md +76 -0
- package/docs/testing.md +744 -0
- package/docs/toolbar.md +161 -0
- package/docs/use-tm-data-grid.md +361 -0
- package/package.json +22 -46
- package/skills/appearance/SKILL.md +72 -19
- package/skills/cell-selection/SKILL.md +46 -47
- package/skills/columns/SKILL.md +90 -34
- package/skills/data/SKILL.md +86 -16
- package/skills/editing/SKILL.md +67 -40
- package/skills/editing/references/common-mistakes.md +77 -69
- package/skills/editing/references/editing-api.md +22 -19
- package/skills/editing/references/editors-and-validation.md +24 -17
- package/skills/filtering/SKILL.md +148 -40
- package/skills/getting-started/SKILL.md +17 -15
- package/skills/grouping/SKILL.md +31 -16
- package/skills/options/SKILL.md +7 -7
- package/skills/rows/SKILL.md +22 -18
- package/skills/server-side/SKILL.md +170 -17
- package/skills/testing/SKILL.md +150 -32
- package/skills/testing-components/SKILL.md +230 -0
- package/skills/testing-editing/SKILL.md +240 -0
- package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +38 -21
- package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +54 -8
- package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
- package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
- package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +5 -51
- package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
- package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +12 -59
- package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +164 -92
- package/src/components/TMDataGridExportPicker.module.css +77 -0
- package/src/components/TMDataGridExportPicker.tsx +234 -0
- package/src/components/TMDataGridFilterPanel.module.css +54 -0
- package/src/components/TMDataGridFilterPanel.tsx +348 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +15 -8
- package/src/components/TMDataGridFilterSurface.module.css +54 -0
- package/src/components/TMDataGridFilterSurface.tsx +167 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -90
- package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +5 -69
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +12 -2
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +58 -21
- package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
- package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
- package/src/components/TMDataGridMenu.tsx +357 -0
- package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +11 -48
- package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +69 -56
- package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +240 -138
- package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -0
- package/src/components/TMDataGridToolbar.tsx +181 -0
- package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/editorShared.ts +16 -2
- package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
- package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
- package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
- package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
- package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
- package/src/components/filters/controlLayout.ts +32 -0
- package/src/components/filters/filterControlFor.ts +65 -0
- package/src/components/generatedColumns.tsx +187 -0
- package/src/{tmdatagrid/components → components}/icons.ts +1 -0
- package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
- package/src/components/useHideableColumns.ts +52 -0
- package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
- package/src/{tmdatagrid/core → core}/columnOptions.ts +60 -8
- package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
- package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
- package/src/core/controlledStateSync.ts +108 -0
- package/src/core/deletedRows.ts +34 -0
- package/src/core/dom.ts +74 -0
- package/src/{tmdatagrid/core → core}/editEngine.ts +1107 -460
- package/src/core/export.ts +704 -0
- package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
- package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
- package/src/core/filterSurface.ts +99 -0
- package/src/{tmdatagrid/core → core}/grouping.ts +21 -0
- package/src/{tmdatagrid/core → core}/labels.ts +51 -6
- package/src/{tmdatagrid/core → core}/labelsSv.ts +27 -6
- package/src/core/pageReset.ts +120 -0
- package/src/core/pagination.ts +81 -0
- package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
- package/src/{tmdatagrid/core → core}/summary.ts +20 -4
- package/src/{tmdatagrid/index.ts → index.ts} +69 -35
- package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +428 -109
- package/src/useTMDataGridExport.ts +78 -0
- package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -161
- package/src/tmdatagrid/core/cellExport.ts +0 -320
- /package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
- /package/src/{tmdatagrid/core → core}/capabilities.ts +0 -0
- /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
- /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
- /package/src/{tmdatagrid/core → core}/controlledState.ts +0 -0
- /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
- /package/src/{tmdatagrid/core → core}/editorFocus.ts +0 -0
- /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
- /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
- /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
- /package/src/{tmdatagrid/core → core}/resizePreview.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
- /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
- /package/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Row } from "@tanstack/react-table";
|
|
2
2
|
import {
|
|
3
3
|
getDefaultOperator,
|
|
4
|
+
getOperatorsForType,
|
|
4
5
|
type TMDataGridColumnType,
|
|
5
6
|
type TMDataGridFilterOperator,
|
|
6
7
|
} from "./filterOperators";
|
|
@@ -37,17 +38,36 @@ export function getColumnType(column: ColumnLike): TMDataGridColumnType {
|
|
|
37
38
|
return column.columnDef.meta?.type ?? "string";
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/**
|
|
42
|
+
* The operators this column offers: the type's list, narrowed to
|
|
43
|
+
* `meta.filter.operators` when the column declares one. The type's order is
|
|
44
|
+
* kept so the menu reads the same on every column; an operator the type does
|
|
45
|
+
* not offer is dropped, and an allowlist that leaves nothing falls back to the
|
|
46
|
+
* type's full list rather than an empty menu.
|
|
47
|
+
*/
|
|
48
|
+
export function getColumnOperators(
|
|
49
|
+
column: ColumnLike,
|
|
50
|
+
): readonly TMDataGridFilterOperator[] {
|
|
51
|
+
const offered = getOperatorsForType(getColumnType(column));
|
|
52
|
+
const allowed = column.columnDef.meta?.filter?.operators;
|
|
53
|
+
if (!allowed) return offered;
|
|
54
|
+
const narrowed = offered.filter((operator) => allowed.includes(operator));
|
|
55
|
+
return narrowed.length > 0 ? narrowed : offered;
|
|
56
|
+
}
|
|
57
|
+
|
|
40
58
|
/**
|
|
41
59
|
* The operator a fresh filter on this column starts with -
|
|
42
|
-
* `meta.filter.defaultOperator`, else the type's default
|
|
60
|
+
* `meta.filter.defaultOperator`, else the type's default where the column
|
|
61
|
+
* offers it, else the first operator it does offer.
|
|
43
62
|
*/
|
|
44
63
|
export function getColumnDefaultOperator(
|
|
45
64
|
column: ColumnLike,
|
|
46
65
|
): TMDataGridFilterOperator {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
);
|
|
66
|
+
const declared = column.columnDef.meta?.filter?.defaultOperator;
|
|
67
|
+
if (declared) return declared;
|
|
68
|
+
const offered = getColumnOperators(column);
|
|
69
|
+
const typeDefault = getDefaultOperator(getColumnType(column));
|
|
70
|
+
return offered.includes(typeDefault) ? typeDefault : offered[0];
|
|
51
71
|
}
|
|
52
72
|
|
|
53
73
|
/**
|
|
@@ -139,3 +159,22 @@ export function isColumnReorderable(column: ColumnLike): boolean {
|
|
|
139
159
|
if (column.columnDef.meta?.enableOrdering === false) return false;
|
|
140
160
|
return column.parent === undefined;
|
|
141
161
|
}
|
|
162
|
+
|
|
163
|
+
/** The list length from which a column chooser shows its search box. */
|
|
164
|
+
export const COLUMN_SEARCH_MIN = 6;
|
|
165
|
+
|
|
166
|
+
/** `"auto"` shows the search box from `COLUMN_SEARCH_MIN` columns. */
|
|
167
|
+
export type TMDataGridColumnSearchable = boolean | "auto";
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Whether a column list shows a search box. A handful of columns is read at
|
|
171
|
+
* a glance, so `"auto"` keeps the input out of the way until the list is long
|
|
172
|
+
* enough to need one.
|
|
173
|
+
*/
|
|
174
|
+
export function showColumnSearch(
|
|
175
|
+
searchable: TMDataGridColumnSearchable,
|
|
176
|
+
count: number,
|
|
177
|
+
): boolean {
|
|
178
|
+
if (searchable === "auto") return count >= COLUMN_SEARCH_MIN;
|
|
179
|
+
return searchable;
|
|
180
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { ReadonlyStore, Store } from "@tanstack/store";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Timing repair for TanStack's controlled-state sync.
|
|
5
|
+
*
|
|
6
|
+
* `useTable` calls `table.setOptions` from its render body, and that syncs
|
|
7
|
+
* `options.state` into the table's atoms. An atom write publishes the store
|
|
8
|
+
* synchronously, so every component subscribed to `table.store` schedules a
|
|
9
|
+
* setState while the consumer's component is still rendering - which React
|
|
10
|
+
* reports as "Cannot update a component (X) while rendering a different
|
|
11
|
+
* component (Y)", pointing at the consumer's component rather than at the
|
|
12
|
+
* grid.
|
|
13
|
+
*
|
|
14
|
+
* The write has to stay where it is: the atoms must hold the controlled value
|
|
15
|
+
* before the table builds its row models for that render. Only the
|
|
16
|
+
* notification is early, so it moves to a microtask. `getSnapshot` already
|
|
17
|
+
* returns the new value, so a component reads the same state either way; the
|
|
18
|
+
* subscription is only what schedules a re-render for the components the
|
|
19
|
+
* consumer's own render does not reach.
|
|
20
|
+
*
|
|
21
|
+
* Scope and assumptions. Only `table.store` is patched: a subscription made
|
|
22
|
+
* on `table.atoms.*` or `table.baseAtoms.*` keeps the synchronous timing.
|
|
23
|
+
* The patch leans on three table-core 9.0.0-beta.21 internals - the store is
|
|
24
|
+
* created once in `constructTable`, `useTable` returns a shallow copy that
|
|
25
|
+
* shares it, and the sync runs inside `setOptions` during render. The
|
|
26
|
+
* publish test in controlledState.test.tsx fails loudly if any of them
|
|
27
|
+
* moves; it is the guard for this module, not a redundant check. The
|
|
28
|
+
* intended end state - owning the controlled slices through `options.atoms`,
|
|
29
|
+
* which removes the render-time sync entirely - is in the backlog.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
type Observer = ((value: unknown) => void) | { next?: (value: unknown) => void };
|
|
33
|
+
|
|
34
|
+
type Subscribe = (observer: Observer) => { unsubscribe: () => void };
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* True while `useTable` is syncing controlled state inside a render pass.
|
|
38
|
+
* Process-global, so while one grid is inside its `useTable` call a publish
|
|
39
|
+
* on any patched store is deferred too - harmless, since nothing else runs
|
|
40
|
+
* during a synchronous render pass.
|
|
41
|
+
*/
|
|
42
|
+
let syncing = false;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Called immediately before the `useTable` call that performs the sync.
|
|
46
|
+
*
|
|
47
|
+
* The microtask is the backstop that bounds the flag to the current task: if
|
|
48
|
+
* `useTable` throws and an error boundary unmounts the grid, nothing after
|
|
49
|
+
* the call runs, and without it every patched store would keep deferring its
|
|
50
|
+
* notifications for the life of the page.
|
|
51
|
+
*/
|
|
52
|
+
export function beginControlledStateSync(): void {
|
|
53
|
+
syncing = true;
|
|
54
|
+
queueMicrotask(() => {
|
|
55
|
+
syncing = false;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Called immediately after it - the prompt clear; correctness rests on the
|
|
61
|
+
* backstop above. Not a `finally` around the call, because hooks inside
|
|
62
|
+
* `try` opt the whole hook out of the React Compiler.
|
|
63
|
+
*/
|
|
64
|
+
export function endControlledStateSync(): void {
|
|
65
|
+
syncing = false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const patched = new WeakSet<object>();
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Wraps a table store's `subscribe` so notifications raised during the sync
|
|
72
|
+
* are delivered in a microtask. Idempotent, and installed on the store object
|
|
73
|
+
* itself rather than on a copy of the table - rows hold the original, so
|
|
74
|
+
* `row.table.store` has to be the patched one.
|
|
75
|
+
*/
|
|
76
|
+
export function deferControlledStateSyncPublishes<TState>(
|
|
77
|
+
store: Store<TState> | ReadonlyStore<TState>,
|
|
78
|
+
): void {
|
|
79
|
+
if (patched.has(store)) return;
|
|
80
|
+
patched.add(store);
|
|
81
|
+
|
|
82
|
+
const subscribe = store.subscribe.bind(store) as Subscribe;
|
|
83
|
+
|
|
84
|
+
(store as unknown as { subscribe: Subscribe }).subscribe = (observer) => {
|
|
85
|
+
const next =
|
|
86
|
+
typeof observer === "function" ? observer : observer.next?.bind(observer);
|
|
87
|
+
if (next === undefined) return subscribe(observer);
|
|
88
|
+
|
|
89
|
+
let live = true;
|
|
90
|
+
const subscription = subscribe((value) => {
|
|
91
|
+
if (!syncing) {
|
|
92
|
+
next(value);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
queueMicrotask(() => {
|
|
96
|
+
// The subscriber may have unmounted between the write and the flush.
|
|
97
|
+
if (live) next(value);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
unsubscribe: () => {
|
|
103
|
+
live = false;
|
|
104
|
+
subscription.unsubscribe();
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { RowData } from "@tanstack/react-table";
|
|
2
|
+
import type { TMDataGridTable } from "../useTMDataGrid";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Which rows of a table carry a deletion mark, for readers that hold the
|
|
6
|
+
* table and nothing else - `exportGrid` and the export data it builds on.
|
|
7
|
+
* The marks live in the edit engine, which those readers cannot reach; the
|
|
8
|
+
* hook registers the engine's answer per table instead. A table built without
|
|
9
|
+
* the grid has no marks.
|
|
10
|
+
*/
|
|
11
|
+
// Keyed on the store, not the table: `useTable` hands out a fresh shallow
|
|
12
|
+
// copy of the table on every render, and every copy shares the one store.
|
|
13
|
+
const sources = new WeakMap<object, (rowId: string) => boolean>();
|
|
14
|
+
|
|
15
|
+
/** Registers the answer for one table; returns the unregister. */
|
|
16
|
+
export function registerDeletedRows<TData extends RowData>(
|
|
17
|
+
table: TMDataGridTable<TData>,
|
|
18
|
+
isRowDeleted: (rowId: string) => boolean,
|
|
19
|
+
): () => void {
|
|
20
|
+
sources.set(table.store, isRowDeleted);
|
|
21
|
+
return () => {
|
|
22
|
+
if (sources.get(table.store) === isRowDeleted) {
|
|
23
|
+
sources.delete(table.store);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Whether the row is marked for deletion under `editing.draft`. */
|
|
29
|
+
export function isRowMarkedDeleted<TData extends RowData>(
|
|
30
|
+
table: TMDataGridTable<TData>,
|
|
31
|
+
rowId: string,
|
|
32
|
+
): boolean {
|
|
33
|
+
return sources.get(table.store)?.(rowId) ?? false;
|
|
34
|
+
}
|
package/src/core/dom.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM checks that survive a second realm.
|
|
3
|
+
*
|
|
4
|
+
* The grid can be rendered through a portal into a window opened with
|
|
5
|
+
* `window.open`. That window is a separate JavaScript realm: its nodes and
|
|
6
|
+
* events are instances of *its* `Node`, `Element`, `HTMLInputElement` and
|
|
7
|
+
* `MouseEvent`, not the opener's. A plain `x instanceof HTMLInputElement`
|
|
8
|
+
* is therefore false for an input sitting in the grid's own window, and the
|
|
9
|
+
* check silently takes the wrong branch.
|
|
10
|
+
*
|
|
11
|
+
* Every helper here answers the same question either by duck-typing, or by
|
|
12
|
+
* taking the constructor from the realm the value itself came from.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The window a node lives in - the realm to take its constructors from.
|
|
17
|
+
*
|
|
18
|
+
* A document made by `createHTMLDocument` has no window of its own, so the
|
|
19
|
+
* global one is the fallback.
|
|
20
|
+
*/
|
|
21
|
+
export function windowOf(node: Node): Window & typeof globalThis {
|
|
22
|
+
return node.ownerDocument?.defaultView ?? window;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether a value is a DOM node.
|
|
27
|
+
*
|
|
28
|
+
* Duck-typed on `nodeType`, because `instanceof Node` is false for a node
|
|
29
|
+
* from another realm.
|
|
30
|
+
*/
|
|
31
|
+
export function isNode(value: unknown): value is Node {
|
|
32
|
+
return (
|
|
33
|
+
typeof value === "object" &&
|
|
34
|
+
value !== null &&
|
|
35
|
+
typeof (value as Node).nodeType === "number"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether a value is an element - a node whose `nodeType` is 1
|
|
41
|
+
* (`Node.ELEMENT_NODE`), read as a number so no realm's constant is needed.
|
|
42
|
+
*/
|
|
43
|
+
export function isElement(value: unknown): value is Element {
|
|
44
|
+
return isNode(value) && value.nodeType === 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Whether a value is an HTML element, asked of its own realm's constructor. */
|
|
48
|
+
export function isHTMLElement(value: unknown): value is HTMLElement {
|
|
49
|
+
return isElement(value) && value instanceof windowOf(value).HTMLElement;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Whether a value is an `<input>`, asked of its own realm's constructor. */
|
|
53
|
+
export function isInputElement(value: unknown): value is HTMLInputElement {
|
|
54
|
+
return isElement(value) && value instanceof windowOf(value).HTMLInputElement;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Whether a value is a `<button>`, asked of its own realm's constructor. */
|
|
58
|
+
export function isButtonElement(value: unknown): value is HTMLButtonElement {
|
|
59
|
+
return isElement(value) && value instanceof windowOf(value).HTMLButtonElement;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether an event is a mouse event, asked of the constructor in the event's
|
|
64
|
+
* own window.
|
|
65
|
+
*
|
|
66
|
+
* A browser-dispatched event carries that window in `view`; one built by hand
|
|
67
|
+
* may not, and its target still knows the window.
|
|
68
|
+
*/
|
|
69
|
+
export function isMouseEvent(event: Event): event is MouseEvent {
|
|
70
|
+
const view =
|
|
71
|
+
(event as UIEvent).view ??
|
|
72
|
+
(isNode(event.target) ? windowOf(event.target) : window);
|
|
73
|
+
return event instanceof (view as Window & typeof globalThis).MouseEvent;
|
|
74
|
+
}
|