@jielga/tmdatagrid 2.5.0 → 2.6.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.
@@ -1,4 +1,4 @@
1
- import { MultiSelect } from "@mantine/core";
1
+ import { MultiSelect, type MultiSelectProps } from "@mantine/core";
2
2
  import type { TMDataGridEditorArgs } from "../../core/editEngine";
3
3
  import {
4
4
  optionsToComboboxData,
@@ -6,7 +6,35 @@ import {
6
6
  } from "../../core/columnOptions";
7
7
  import { getColumnLabel } from "../../core/columnUtils";
8
8
  import { useTMDataGridContext } from "../../TMDataGridContext";
9
- import { useFieldInvalid, useFieldValue } from "./editorShared";
9
+ import {
10
+ useFieldInvalid,
11
+ useFieldValue,
12
+ type EditorDataAttributes,
13
+ } from "./editorShared";
14
+
15
+ export type TMDataGridMultiSelectEditorProps = TMDataGridEditorArgs & {
16
+ /**
17
+ * Props for the Mantine `MultiSelect`. The grid keeps `data`, the value,
18
+ * `onChange`, `error`, `size`, the accessible name and
19
+ * `comboboxProps.withinPortal`; `onBlur` runs after the grid's own.
20
+ */
21
+ inputProps?: Omit<
22
+ MultiSelectProps,
23
+ | "data"
24
+ | "value"
25
+ | "defaultValue"
26
+ | "onChange"
27
+ | "error"
28
+ | "size"
29
+ | "aria-label"
30
+ | "comboboxProps"
31
+ > & {
32
+ comboboxProps?: Omit<
33
+ NonNullable<MultiSelectProps["comboboxProps"]>,
34
+ "withinPortal"
35
+ >;
36
+ } & EditorDataAttributes;
37
+ };
10
38
 
11
39
  /**
12
40
  * The built-in editor for `meta.type: "multiSelect"` - cells holding string
@@ -19,16 +47,18 @@ export function TMDataGridMultiSelectEditor({
19
47
  row,
20
48
  table,
21
49
  size,
22
- }: TMDataGridEditorArgs) {
50
+ inputProps,
51
+ }: TMDataGridMultiSelectEditorProps) {
23
52
  const { labels } = useTMDataGridContext();
24
53
  const value = useFieldValue(field);
25
54
  const invalid = useFieldInvalid(field);
26
55
  return (
27
56
  <MultiSelect
28
- size={size}
29
57
  w="100%"
30
58
  searchable
31
- comboboxProps={{ withinPortal: false }}
59
+ {...inputProps}
60
+ size={size}
61
+ comboboxProps={{ ...inputProps?.comboboxProps, withinPortal: false }}
32
62
  aria-label={labels.editCell(getColumnLabel(column))}
33
63
  data-dg-part="editor-input"
34
64
  data={optionsToComboboxData(
@@ -36,7 +66,10 @@ export function TMDataGridMultiSelectEditor({
36
66
  )}
37
67
  value={Array.isArray(value) ? value.map(String) : []}
38
68
  onChange={(next) => field.handleChange(next)}
39
- onBlur={field.handleBlur}
69
+ onBlur={(event) => {
70
+ field.handleBlur();
71
+ inputProps?.onBlur?.(event);
72
+ }}
40
73
  error={invalid}
41
74
  />
42
75
  );
@@ -1,17 +1,37 @@
1
- import { NumberInput } from "@mantine/core";
1
+ import { NumberInput, type NumberInputProps } from "@mantine/core";
2
2
  import { useState } from "react";
3
3
  import type { TMDataGridEditorArgs } from "../../core/editEngine";
4
4
  import { getColumnLabel } from "../../core/columnUtils";
5
5
  import { useTMDataGridContext } from "../../TMDataGridContext";
6
6
  import {
7
+ mergeRefs,
7
8
  selectAllOnFocus,
8
9
  useCaretKeeper,
9
10
  useFieldInvalid,
10
11
  useFieldValue,
12
+ type EditorDataAttributes,
11
13
  } from "./editorShared";
12
14
 
15
+ export type TMDataGridNumberEditorProps = TMDataGridEditorArgs & {
16
+ /**
17
+ * Props for the Mantine `NumberInput`. The grid keeps the value, `onChange`,
18
+ * `error`, `size` and the accessible name; `onFocus` and `onBlur` run after
19
+ * the grid's own, and `ref` receives the input beside the grid's ref.
20
+ */
21
+ inputProps?: Omit<
22
+ NumberInputProps,
23
+ "value" | "defaultValue" | "onChange" | "error" | "size" | "aria-label"
24
+ > &
25
+ EditorDataAttributes;
26
+ };
27
+
13
28
  /** The built-in editor for `meta.type: "number"`. */
14
- export function TMDataGridNumberEditor({ field, column, size }: TMDataGridEditorArgs) {
29
+ export function TMDataGridNumberEditor({
30
+ field,
31
+ column,
32
+ size,
33
+ inputProps,
34
+ }: TMDataGridNumberEditorProps) {
15
35
  const { labels } = useTMDataGridContext();
16
36
  const value = useFieldValue(field);
17
37
  const invalid = useFieldInvalid(field);
@@ -31,11 +51,15 @@ export function TMDataGridNumberEditor({ field, column, size }: TMDataGridEditor
31
51
  : value === null;
32
52
  return (
33
53
  <NumberInput
34
- ref={inputRef}
35
- size={size}
36
54
  w="100%"
37
- onFocus={selectAllOnFocus}
38
55
  hideControls
56
+ {...inputProps}
57
+ ref={mergeRefs(inputRef, inputProps?.ref)}
58
+ size={size}
59
+ onFocus={(event) => {
60
+ selectAllOnFocus(event);
61
+ inputProps?.onFocus?.(event);
62
+ }}
39
63
  aria-label={labels.editCell(getColumnLabel(column))}
40
64
  data-dg-part="editor-input"
41
65
  value={
@@ -63,7 +87,10 @@ export function TMDataGridNumberEditor({ field, column, size }: TMDataGridEditor
63
87
  next !== "" && Number.isFinite(parsed) ? parsed : null,
64
88
  );
65
89
  }}
66
- onBlur={field.handleBlur}
90
+ onBlur={(event) => {
91
+ field.handleBlur();
92
+ inputProps?.onBlur?.(event);
93
+ }}
67
94
  error={invalid}
68
95
  />
69
96
  );
@@ -1,4 +1,4 @@
1
- import { Select } from "@mantine/core";
1
+ import { Select, type SelectProps } from "@mantine/core";
2
2
  import type { TMDataGridEditorArgs } from "../../core/editEngine";
3
3
  import {
4
4
  optionsToComboboxData,
@@ -6,7 +6,35 @@ import {
6
6
  } from "../../core/columnOptions";
7
7
  import { getColumnLabel } from "../../core/columnUtils";
8
8
  import { useTMDataGridContext } from "../../TMDataGridContext";
9
- import { useFieldInvalid, useFieldValue } from "./editorShared";
9
+ import {
10
+ useFieldInvalid,
11
+ useFieldValue,
12
+ type EditorDataAttributes,
13
+ } from "./editorShared";
14
+
15
+ export type TMDataGridSelectEditorProps = TMDataGridEditorArgs & {
16
+ /**
17
+ * Props for the Mantine `Select`. The grid keeps `data`, the value,
18
+ * `onChange`, `error`, `size`, the accessible name and
19
+ * `comboboxProps.withinPortal`; `onBlur` runs after the grid's own.
20
+ */
21
+ inputProps?: Omit<
22
+ SelectProps,
23
+ | "data"
24
+ | "value"
25
+ | "defaultValue"
26
+ | "onChange"
27
+ | "error"
28
+ | "size"
29
+ | "aria-label"
30
+ | "comboboxProps"
31
+ > & {
32
+ comboboxProps?: Omit<
33
+ NonNullable<SelectProps["comboboxProps"]>,
34
+ "withinPortal"
35
+ >;
36
+ } & EditorDataAttributes;
37
+ };
10
38
 
11
39
  /**
12
40
  * The built-in editor for `meta.type: "select"`. Options come from the same
@@ -20,18 +48,20 @@ export function TMDataGridSelectEditor({
20
48
  table,
21
49
  size,
22
50
  commit,
23
- }: TMDataGridEditorArgs) {
51
+ inputProps,
52
+ }: TMDataGridSelectEditorProps) {
24
53
  const { labels, features } = useTMDataGridContext();
25
54
  const value = useFieldValue(field);
26
55
  const invalid = useFieldInvalid(field);
27
56
  return (
28
57
  <Select
29
- size={size}
30
58
  w="100%"
31
59
  searchable
60
+ {...inputProps}
61
+ size={size}
32
62
  // Inside the cell, so picking an option is not a click-away - the same
33
63
  // reason the filter panel keeps its dropdowns unportalled.
34
- comboboxProps={{ withinPortal: false }}
64
+ comboboxProps={{ ...inputProps?.comboboxProps, withinPortal: false }}
35
65
  aria-label={labels.editCell(getColumnLabel(column))}
36
66
  data-dg-part="editor-input"
37
67
  data={optionsToComboboxData(
@@ -44,7 +74,10 @@ export function TMDataGridSelectEditor({
44
74
  // The confirming modes leave the draft for their explicit commit.
45
75
  if (next !== null && features.editMode === "cell") void commit();
46
76
  }}
47
- onBlur={field.handleBlur}
77
+ onBlur={(event) => {
78
+ field.handleBlur();
79
+ inputProps?.onBlur?.(event);
80
+ }}
48
81
  error={invalid}
49
82
  />
50
83
  );
@@ -1,26 +1,50 @@
1
- import { TextInput } from "@mantine/core";
1
+ import { TextInput, type TextInputProps } from "@mantine/core";
2
2
  import type { TMDataGridEditorArgs } from "../../core/editEngine";
3
3
  import { getColumnLabel } from "../../core/columnUtils";
4
4
  import { useTMDataGridContext } from "../../TMDataGridContext";
5
5
  import {
6
+ mergeRefs,
6
7
  selectAllOnFocus,
7
8
  useCaretKeeper,
8
9
  useFieldInvalid,
9
10
  useFieldValue,
11
+ type EditorDataAttributes,
10
12
  } from "./editorShared";
11
13
 
14
+ export type TMDataGridStringEditorProps = TMDataGridEditorArgs & {
15
+ /**
16
+ * Props for the Mantine `TextInput`. The grid keeps the value, `onChange`,
17
+ * `error`, `size` and the accessible name; `onFocus` and `onBlur` run after
18
+ * the grid's own, and `ref` receives the input beside the grid's ref.
19
+ */
20
+ inputProps?: Omit<
21
+ TextInputProps,
22
+ "value" | "defaultValue" | "onChange" | "error" | "size" | "aria-label"
23
+ > &
24
+ EditorDataAttributes;
25
+ };
26
+
12
27
  /** The built-in editor for `meta.type: "string"` (and the default). */
13
- export function TMDataGridStringEditor({ field, column, size }: TMDataGridEditorArgs) {
28
+ export function TMDataGridStringEditor({
29
+ field,
30
+ column,
31
+ size,
32
+ inputProps,
33
+ }: TMDataGridStringEditorProps) {
14
34
  const { labels } = useTMDataGridContext();
15
35
  const value = useFieldValue(field);
16
36
  const invalid = useFieldInvalid(field);
17
37
  const { inputRef, remember } = useCaretKeeper();
18
38
  return (
19
39
  <TextInput
20
- ref={inputRef}
21
- size={size}
22
40
  w="100%"
23
- onFocus={selectAllOnFocus}
41
+ {...inputProps}
42
+ ref={mergeRefs(inputRef, inputProps?.ref)}
43
+ size={size}
44
+ onFocus={(event) => {
45
+ selectAllOnFocus(event);
46
+ inputProps?.onFocus?.(event);
47
+ }}
24
48
  aria-label={labels.editCell(getColumnLabel(column))}
25
49
  data-dg-part="editor-input"
26
50
  value={typeof value === "string" ? value : ""}
@@ -28,7 +52,10 @@ export function TMDataGridStringEditor({ field, column, size }: TMDataGridEditor
28
52
  remember(event.currentTarget);
29
53
  field.handleChange(event.currentTarget.value);
30
54
  }}
31
- onBlur={field.handleBlur}
55
+ onBlur={(event) => {
56
+ field.handleBlur();
57
+ inputProps?.onBlur?.(event);
58
+ }}
32
59
  error={invalid}
33
60
  />
34
61
  );
@@ -1,8 +1,34 @@
1
1
  import { useStore } from "@tanstack/react-form";
2
- import { useLayoutEffect, useRef, type RefObject } from "react";
2
+ import {
3
+ useLayoutEffect,
4
+ useRef,
5
+ type Ref,
6
+ type RefCallback,
7
+ type RefObject,
8
+ } from "react";
3
9
  import { isInputElement } from "../../core/dom";
4
10
  import { firstErrorText, type TMDataGridEditField } from "../../core/editEngine";
5
11
 
12
+ /**
13
+ * `data-*` attributes for a built-in editor's `inputProps`. JSX accepts them
14
+ * on any element, but a props object literal only takes declared keys.
15
+ */
16
+ export type EditorDataAttributes = {
17
+ [attribute: `data-${string}`]: string | number | boolean | undefined;
18
+ };
19
+
20
+ /** One ref callback feeding the grid's own ref and the consumer's. */
21
+ export function mergeRefs<T>(
22
+ ...refs: Array<Ref<T> | undefined>
23
+ ): RefCallback<T> {
24
+ return (node) => {
25
+ for (const ref of refs) {
26
+ if (typeof ref === "function") ref(node);
27
+ else if (ref !== null && ref !== undefined) ref.current = node;
28
+ }
29
+ };
30
+ }
31
+
6
32
  /** The field's live value - one subscription, on the field's own store. */
7
33
  export function useFieldValue(field: TMDataGridEditField): unknown {
8
34
  return useStore(field.store, (state) => state.value as unknown);
package/src/index.ts CHANGED
@@ -143,12 +143,30 @@ export { DgRangeSliderFilter } from "./components/filters/DgRangeSliderFilter";
143
143
  export { DgDateRangeFilter } from "./components/filters/DgDateRangeFilter";
144
144
  export { DgAutocompleteFilter } from "./components/filters/DgAutocompleteFilter";
145
145
  export { DgTriStateFilter } from "./components/filters/DgTriStateFilter";
146
- export { TMDataGridStringEditor } from "./components/editors/TMDataGridStringEditor";
147
- export { TMDataGridNumberEditor } from "./components/editors/TMDataGridNumberEditor";
148
- export { TMDataGridBooleanEditor } from "./components/editors/TMDataGridBooleanEditor";
149
- export { TMDataGridDateEditor } from "./components/editors/TMDataGridDateEditor";
150
- export { TMDataGridSelectEditor } from "./components/editors/TMDataGridSelectEditor";
151
- export { TMDataGridMultiSelectEditor } from "./components/editors/TMDataGridMultiSelectEditor";
146
+ export {
147
+ TMDataGridStringEditor,
148
+ type TMDataGridStringEditorProps,
149
+ } from "./components/editors/TMDataGridStringEditor";
150
+ export {
151
+ TMDataGridNumberEditor,
152
+ type TMDataGridNumberEditorProps,
153
+ } from "./components/editors/TMDataGridNumberEditor";
154
+ export {
155
+ TMDataGridBooleanEditor,
156
+ type TMDataGridBooleanEditorProps,
157
+ } from "./components/editors/TMDataGridBooleanEditor";
158
+ export {
159
+ TMDataGridDateEditor,
160
+ type TMDataGridDateEditorProps,
161
+ } from "./components/editors/TMDataGridDateEditor";
162
+ export {
163
+ TMDataGridSelectEditor,
164
+ type TMDataGridSelectEditorProps,
165
+ } from "./components/editors/TMDataGridSelectEditor";
166
+ export {
167
+ TMDataGridMultiSelectEditor,
168
+ type TMDataGridMultiSelectEditorProps,
169
+ } from "./components/editors/TMDataGridMultiSelectEditor";
152
170
  export {
153
171
  mergeLabels,
154
172
  TMDATAGRID_LABELS_EN,