xmlui 0.10.14 → 0.10.16
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/dist/lib/{index-779mp2Bm.mjs → index-D4RYJasT.mjs} +2952 -937
- package/dist/lib/index.css +1 -1
- package/dist/lib/{initMock-CAXdczCj.mjs → initMock-qzTZlH-6.mjs} +1 -1
- package/dist/lib/language-server-web-worker.mjs +1 -1
- package/dist/lib/language-server.mjs +1 -1
- package/dist/lib/{metadata-utils-D90qqMGc.mjs → metadata-utils-CtY0QcvH.mjs} +2 -1
- package/dist/lib/{server-common-lmBDLpUh.mjs → server-common-Cine5nRR.mjs} +1 -1
- package/dist/lib/xmlui-parser.d.ts +1 -1
- package/dist/lib/xmlui.d.ts +87 -11
- package/dist/lib/xmlui.mjs +72 -37
- package/dist/metadata/{collectedComponentMetadata-7DFXlw-J.mjs → collectedComponentMetadata-BQaefK3f.mjs} +3189 -1242
- package/dist/metadata/{initMock-AFWEftc6.mjs → initMock-Cz6QssI3.mjs} +1 -1
- package/dist/metadata/style.css +1 -1
- package/dist/metadata/xmlui-metadata.mjs +1 -1
- package/dist/metadata/xmlui-metadata.umd.js +3 -3
- package/dist/scripts/package.json +2 -3
- package/dist/scripts/src/components/Animation/AnimationNative.js +5 -1
- package/dist/scripts/src/components/AppState/AppState.js +32 -2
- package/dist/scripts/src/components/AppState/AppStateNative.js +27 -3
- package/dist/scripts/src/components/AutoComplete/AutoComplete.js +1 -5
- package/dist/scripts/src/components/AutoComplete/AutoCompleteContext.js +2 -0
- package/dist/scripts/src/components/AutoComplete/AutoCompleteNative.js +263 -82
- package/dist/scripts/src/components/Button/Button.js +5 -1
- package/dist/scripts/src/components/Charts/PieChart/PieChartNative.js +41 -2
- package/dist/scripts/src/components/DropdownMenu/DropdownMenuNative.js +7 -9
- package/dist/scripts/src/components/Form/FormNative.js +33 -25
- package/dist/scripts/src/components/FormItem/ItemWithLabel.js +3 -3
- package/dist/scripts/src/components/Icon/IconNative.js +18 -15
- package/dist/scripts/src/components/NavGroup/NavGroup.spec.js +182 -123
- package/dist/scripts/src/components/NavGroup/NavGroupNative.js +14 -6
- package/dist/scripts/src/components/NestedApp/AppWithCodeViewNative.js +1 -1
- package/dist/scripts/src/components/NumberBox/NumberBox.js +4 -4
- package/dist/scripts/src/components/NumberBox/NumberBox.spec.js +112 -423
- package/dist/scripts/src/components/NumberBox/NumberBoxNative.js +18 -4
- package/dist/scripts/src/components/Option/Option.js +3 -1
- package/dist/scripts/src/components/Select/HiddenOption.js +1 -1
- package/dist/scripts/src/components/SelectionStore/SelectionStoreNative.js +3 -1
- package/dist/scripts/src/components/Slider/Slider.spec.js +46 -13
- package/dist/scripts/src/components/Slider/SliderNative.js +19 -9
- package/dist/scripts/src/components/Table/Table.js +7 -1
- package/dist/scripts/src/components/Table/TableNative.js +4 -1
- package/dist/scripts/src/components/Table/useRowSelection.js +215 -1
- package/dist/scripts/src/components/TextBox/TextBox.js +1 -5
- package/dist/scripts/src/components/TextBox/TextBox.spec.js +368 -324
- package/dist/scripts/src/components/TextBox/TextBoxNative.js +10 -15
- package/dist/scripts/src/components/Theme/ThemeNative.js +2 -6
- package/dist/scripts/src/components/TimeInput/TimeInput.js +1 -5
- package/dist/scripts/src/components/TimeInput/TimeInputNative.js +2 -9
- package/dist/scripts/src/components/Tree/Tree-dynamic.spec.js +2894 -0
- package/dist/scripts/src/components/Tree/Tree.spec.js +2932 -0
- package/dist/scripts/src/components/Tree/TreeComponent.js +266 -10
- package/dist/scripts/src/components/Tree/TreeNative.js +1048 -23
- package/dist/scripts/src/components/Tree/testData.js +272 -0
- package/dist/scripts/src/components-core/ApiBoundComponent.js +38 -24
- package/dist/scripts/src/components-core/RestApiProxy.js +0 -1
- package/dist/scripts/src/components-core/behaviors/BehaviorContext.js +54 -0
- package/dist/scripts/src/components-core/behaviors/CoreBehaviors.js +81 -0
- package/dist/scripts/src/components-core/descriptorHelper.js +1 -0
- package/dist/scripts/src/components-core/parts.js +4 -1
- package/dist/scripts/src/components-core/rendering/AppRoot.js +2 -1
- package/dist/scripts/src/components-core/rendering/ComponentAdapter.js +32 -48
- package/dist/scripts/src/components-core/rendering/nodeUtils.js +6 -0
- package/dist/scripts/src/components-core/theming/layout-resolver.js +2 -0
- package/dist/scripts/src/components-core/utils/treeUtils.js +187 -12
- package/dist/scripts/src/index.js +38 -1
- package/dist/scripts/src/testing/ComponentDrivers.js +77 -31
- package/dist/scripts/src/testing/drivers/NumberBoxDriver.js +44 -0
- package/dist/scripts/src/testing/drivers/TextBoxDriver.js +20 -0
- package/dist/scripts/src/testing/drivers/TreeDriver.js +13 -0
- package/dist/scripts/src/testing/fixtures.js +40 -9
- package/dist/standalone/xmlui-standalone.es.d.ts +158 -15
- package/dist/standalone/xmlui-standalone.umd.js +36 -36
- package/package.json +2 -3
- package/dist/scripts/src/components/Animation/Animation.js +0 -50
|
@@ -86,6 +86,8 @@ exports.NumberBox = (0, react_1.forwardRef)(function NumberBox(_a, forwardedRef)
|
|
|
86
86
|
// Ensure the provided minimum is not smaller than the 0 if zeroOrPositive is set to true
|
|
87
87
|
min = Math.max(zeroOrPositive ? 0 : -numberbox_abstractions_1.NUMBERBOX_MAX_VALUE, min);
|
|
88
88
|
// Step must be an integer since floating point arithmetic needs a deeper dive.
|
|
89
|
+
// probably some way to integrate with https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
|
|
90
|
+
// since there are footguns, like 0.1 + 0.2 = 0.0000...04
|
|
89
91
|
const _step = (_b = (0, numberbox_abstractions_1.toUsableNumber)(step, true)) !== null && _b !== void 0 ? _b : numberbox_abstractions_1.DEFAULT_STEP;
|
|
90
92
|
const inputRef = (0, react_1.useRef)(null);
|
|
91
93
|
const upButton = (0, react_1.useRef)(null);
|
|
@@ -414,10 +416,22 @@ exports.NumberBox = (0, react_1.forwardRef)(function NumberBox(_a, forwardedRef)
|
|
|
414
416
|
if (!integersOnly && currentInputValue.endsWith(numberbox_abstractions_1.DECIMAL_SEPARATOR)) {
|
|
415
417
|
// --- Add trailing zero if the value ends with decimal separator
|
|
416
418
|
finalValue = currentInputValue + "0";
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
419
|
+
}
|
|
420
|
+
// --- Convert to number and clamp to min/max bounds
|
|
421
|
+
const numericValue = (0, numberbox_abstractions_1.toUsableNumber)(finalValue, integersOnly);
|
|
422
|
+
if (!(0, numberbox_abstractions_1.isEmptyLike)(numericValue)) {
|
|
423
|
+
const clampedValue = (0, numberbox_abstractions_1.clamp)(numericValue, min, max);
|
|
424
|
+
if (clampedValue !== numericValue) {
|
|
425
|
+
const clampedString = clampedValue.toString();
|
|
426
|
+
finalValue = clampedString;
|
|
427
|
+
// --- Update the input field immediately
|
|
428
|
+
if (inputRef.current) {
|
|
429
|
+
inputRef.current.value = clampedString;
|
|
430
|
+
}
|
|
420
431
|
}
|
|
432
|
+
}
|
|
433
|
+
// --- Update the state if the final value is different from current input
|
|
434
|
+
if (finalValue !== currentInputValue) {
|
|
421
435
|
updateValue(finalValue, finalValue);
|
|
422
436
|
}
|
|
423
437
|
else {
|
|
@@ -425,7 +439,7 @@ exports.NumberBox = (0, react_1.forwardRef)(function NumberBox(_a, forwardedRef)
|
|
|
425
439
|
setValueStrRep((0, numberbox_abstractions_1.mapToRepresentation)(value));
|
|
426
440
|
}
|
|
427
441
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur();
|
|
428
|
-
}, [value, onBlur, integersOnly, updateValue]);
|
|
442
|
+
}, [value, onBlur, integersOnly, updateValue, min, max]);
|
|
429
443
|
const focus = (0, react_1.useCallback)(() => {
|
|
430
444
|
var _a;
|
|
431
445
|
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
@@ -25,6 +25,8 @@ exports.OptionMd = (0, metadata_helpers_1.createMetadata)({
|
|
|
25
25
|
valueType: "boolean",
|
|
26
26
|
defaultValue: OptionNative_1.defaultProps.enabled,
|
|
27
27
|
},
|
|
28
|
+
keywords: (0, metadata_helpers_1.d)("An array of keywords that can be used for searching and filtering the option. " +
|
|
29
|
+
"These keywords are not displayed but help users find the option through search."),
|
|
28
30
|
},
|
|
29
31
|
});
|
|
30
32
|
exports.optionComponentRenderer = (0, renderers_1.createComponentRenderer)(COMP, exports.OptionMd, ({ node, extractValue, className, renderChild, layoutContext }) => {
|
|
@@ -36,7 +38,7 @@ exports.optionComponentRenderer = (0, renderers_1.createComponentRenderer)(COMP,
|
|
|
36
38
|
}
|
|
37
39
|
const hasTextNodeChild = ((_a = node.children) === null || _a === void 0 ? void 0 : _a.length) === 1 && (node.children[0].type === "TextNode" || node.children[0].type === "TextNodeCData");
|
|
38
40
|
const textNodeChild = hasTextNodeChild ? renderChild(node.children) : undefined;
|
|
39
|
-
return ((0, jsx_runtime_1.jsx)(OptionNative_1.OptionNative, { label: label || textNodeChild, value: value !== undefined && value !== "" ? value : label, enabled: extractValue.asOptionalBoolean(node.props.enabled), className: className, optionRenderer: ((_b = node.children) === null || _b === void 0 ? void 0 : _b.length) > 0
|
|
41
|
+
return ((0, jsx_runtime_1.jsx)(OptionNative_1.OptionNative, { label: label || textNodeChild, value: value !== undefined && value !== "" ? value : label, enabled: extractValue.asOptionalBoolean(node.props.enabled), keywords: extractValue.asOptionalStringArray(node.props.keywords), className: className, optionRenderer: ((_b = node.children) === null || _b === void 0 ? void 0 : _b.length) > 0
|
|
40
42
|
? !hasTextNodeChild ? (contextVars) => ((0, jsx_runtime_1.jsx)(container_helpers_1.MemoizedItem, { node: node.children, renderChild: renderChild, contextVars: contextVars, layoutContext: layoutContext })) : undefined
|
|
41
43
|
: undefined, children: !hasTextNodeChild && renderChild(node.children) }));
|
|
42
44
|
});
|
|
@@ -10,7 +10,7 @@ function HiddenOption(option) {
|
|
|
10
10
|
const [node, setNode] = (0, react_1.useState)(null);
|
|
11
11
|
const opt = (0, react_1.useMemo)(() => {
|
|
12
12
|
var _a, _b;
|
|
13
|
-
return Object.assign(Object.assign({}, option), { label: (_a = label !== null && label !== void 0 ? label : node === null || node === void 0 ? void 0 : node.textContent) !== null && _a !== void 0 ? _a : "", keywords: [(_b = label !== null && label !== void 0 ? label : node === null || node === void 0 ? void 0 : node.textContent) !== null && _b !== void 0 ? _b : ""] });
|
|
13
|
+
return Object.assign(Object.assign({}, option), { label: (_a = label !== null && label !== void 0 ? label : node === null || node === void 0 ? void 0 : node.textContent) !== null && _a !== void 0 ? _a : "", keywords: option.keywords || [(_b = label !== null && label !== void 0 ? label : node === null || node === void 0 ? void 0 : node.textContent) !== null && _b !== void 0 ? _b : ""] });
|
|
14
14
|
}, [option, node, label]);
|
|
15
15
|
(0, react_1.useEffect)(() => {
|
|
16
16
|
onOptionAdd(opt);
|
|
@@ -55,10 +55,12 @@ exports.StandaloneSelectionStore = StandaloneSelectionStore;
|
|
|
55
55
|
const SelectionStore = ({ updateState = lodash_es_1.noop, idKey = exports.defaultProps.idKey, children, selectedItems = exports.defaultProps.selectedItems, registerComponentApi = lodash_es_1.noop, }) => {
|
|
56
56
|
const [items, setItems] = (0, react_1.useState)(selectedItems);
|
|
57
57
|
const valueInitializedRef = (0, react_1.useRef)(false);
|
|
58
|
+
const currentItemsRef = (0, react_1.useRef)(selectedItems);
|
|
58
59
|
const refreshSelection = (0, misc_1.useEvent)((allItems = constants_1.EMPTY_ARRAY) => {
|
|
59
60
|
const safeAllItems = allItems || constants_1.EMPTY_ARRAY;
|
|
60
61
|
const safeSelectedItems = selectedItems || constants_1.EMPTY_ARRAY;
|
|
61
62
|
setItems(safeAllItems);
|
|
63
|
+
currentItemsRef.current = safeAllItems; // Update the ref synchronously
|
|
62
64
|
let value = safeAllItems.filter((item) => !!safeSelectedItems.find((si) => si && item && si[idKey] === item[idKey]));
|
|
63
65
|
if (!(0, lodash_es_1.isEqual)(safeSelectedItems, value) || !valueInitializedRef.current) {
|
|
64
66
|
valueInitializedRef.current = true;
|
|
@@ -68,7 +70,7 @@ const SelectionStore = ({ updateState = lodash_es_1.noop, idKey = exports.defaul
|
|
|
68
70
|
}
|
|
69
71
|
});
|
|
70
72
|
const setSelectedRowIds = (0, misc_1.useEvent)((rowIds) => {
|
|
71
|
-
const safeItems =
|
|
73
|
+
const safeItems = currentItemsRef.current || constants_1.EMPTY_ARRAY; // Use ref instead of state
|
|
72
74
|
updateState({ value: safeItems.filter((item) => rowIds.includes(item[idKey])) });
|
|
73
75
|
});
|
|
74
76
|
const clearSelection = (0, misc_1.useEvent)(() => {
|
|
@@ -141,29 +141,62 @@ fixtures_1.test.describe("Basic Functionality", () => {
|
|
|
141
141
|
yield slider.press("ArrowRight");
|
|
142
142
|
yield (0, fixtures_1.expect)(page.getByTestId("slider-value")).toHaveText("0.1");
|
|
143
143
|
}));
|
|
144
|
-
|
|
145
|
-
yield initTestBed(`<Slider initialValue="{[2,
|
|
146
|
-
|
|
147
|
-
yield (0, fixtures_1.expect)(
|
|
144
|
+
(0, fixtures_1.test)("component handles multiple thumbs", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
|
|
145
|
+
yield initTestBed(`<Slider initialValue="{[2, 4]}" />`);
|
|
146
|
+
const thumbs = page.getByRole("slider");
|
|
147
|
+
yield (0, fixtures_1.expect)(thumbs).toHaveCount(2);
|
|
148
|
+
}));
|
|
149
|
+
(0, fixtures_1.test)("all thumbs are interactable via mouse", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, createSliderDriver, page, }) {
|
|
150
|
+
yield initTestBed(`
|
|
151
|
+
<Fragment>
|
|
152
|
+
<Slider id="slider" initialValue="{[2, 4]}" minValue="0" maxValue="10" />
|
|
153
|
+
<Text testId="sliderValue0">{slider.value[0]}</Text>
|
|
154
|
+
<Text testId="sliderValue1">{slider.value[1]}</Text>
|
|
155
|
+
</Fragment>
|
|
156
|
+
`);
|
|
157
|
+
const driver = yield createSliderDriver("slider");
|
|
158
|
+
yield driver.dragThumbByMouse("start", 0);
|
|
159
|
+
yield driver.dragThumbByMouse("end", 1);
|
|
160
|
+
yield (0, fixtures_1.expect)(page.getByTestId("sliderValue0")).toHaveText("0");
|
|
161
|
+
yield (0, fixtures_1.expect)(page.getByTestId("sliderValue1")).toHaveText("10");
|
|
162
|
+
}));
|
|
163
|
+
(0, fixtures_1.test)("all thumbs are interactable via keyboard", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, createSliderDriver, page }) {
|
|
164
|
+
yield initTestBed(`
|
|
165
|
+
<Fragment>
|
|
166
|
+
<Slider id="slider" initialValue="{[2, 4]}" minValue="0" maxValue="10" />
|
|
167
|
+
<Text testId="sliderValue0">{slider.value[0]}</Text>
|
|
168
|
+
<Text testId="sliderValue1">{slider.value[1]}</Text>
|
|
169
|
+
</Fragment>
|
|
170
|
+
`);
|
|
171
|
+
const driver = yield createSliderDriver("slider");
|
|
172
|
+
yield driver.stepThumbByKeyboard("ArrowLeft", 0);
|
|
173
|
+
yield driver.stepThumbByKeyboard("ArrowRight", 1);
|
|
174
|
+
yield (0, fixtures_1.expect)(page.getByTestId("sliderValue0")).toHaveText("1");
|
|
175
|
+
yield (0, fixtures_1.expect)(page.getByTestId("sliderValue1")).toHaveText("5");
|
|
176
|
+
}));
|
|
177
|
+
(0, fixtures_1.test)("minStepsBetweenThumbs maintains thumb separation", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, createSliderDriver, page }) {
|
|
178
|
+
yield initTestBed(`
|
|
179
|
+
<Fragment>
|
|
180
|
+
<Slider id="slider" initialValue="{[0, 5]}" minStepsBetweenThumbs="3" minValue="0" maxValue="10" />
|
|
181
|
+
<Text testId="sliderValue1">{slider.value[1]}</Text>
|
|
182
|
+
</Fragment>
|
|
183
|
+
`);
|
|
184
|
+
const driver = yield createSliderDriver("slider");
|
|
185
|
+
yield driver.stepThumbByKeyboard("ArrowLeft", 1, 3); // Try to move left by 3 steps
|
|
186
|
+
yield (0, fixtures_1.expect)(page.getByTestId("sliderValue1")).toHaveText("3");
|
|
148
187
|
}));
|
|
149
188
|
(0, fixtures_1.test)("enabled=false disables control", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
|
|
150
189
|
yield initTestBed(`<Slider enabled="false" />`);
|
|
151
190
|
yield (0, fixtures_1.expect)(page.getByRole("slider")).toBeDisabled();
|
|
152
191
|
}));
|
|
153
|
-
(0, fixtures_1.test)("readOnly prevents interaction", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
|
|
192
|
+
(0, fixtures_1.test)("readOnly prevents interaction", (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page, createSliderDriver }) {
|
|
154
193
|
yield initTestBed(`
|
|
155
194
|
<Fragment>
|
|
156
195
|
<Slider id="mySlider" readOnly="true" />
|
|
157
196
|
<Text testId="slider-value" value="{mySlider.value}" />
|
|
158
197
|
</Fragment>`);
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
return el.getBoundingClientRect().width;
|
|
162
|
-
});
|
|
163
|
-
yield slider.hover();
|
|
164
|
-
yield page.mouse.down({ button: "left" });
|
|
165
|
-
yield page.mouse.move(sliderOffsetWidth, 0); // Attempt to drag to end
|
|
166
|
-
yield page.mouse.up();
|
|
198
|
+
const driver = yield createSliderDriver("mySlider");
|
|
199
|
+
yield driver.dragThumbByMouse("end");
|
|
167
200
|
yield (0, fixtures_1.expect)(page.getByTestId("slider-value")).toHaveText("0"); // Value should remain unchanged
|
|
168
201
|
}));
|
|
169
202
|
fixtures_1.test.fixme("autoFocus focuses slider on mount", component_test_helpers_1.SKIP_REASON.XMLUI_BUG("autoFocus does not seem to work with radix-ui, need to double-check"), (_a) => __awaiter(void 0, [_a], void 0, function* ({ initTestBed, page }) {
|
|
@@ -108,7 +108,7 @@ exports.Slider = (0, react_2.forwardRef)((_a, forwardedRef) => {
|
|
|
108
108
|
id = id || _id;
|
|
109
109
|
const inputRef = (0, react_1.useRef)(null);
|
|
110
110
|
const tooltipRef = (0, react_1.useRef)(null);
|
|
111
|
-
const
|
|
111
|
+
const thumbsRef = (0, react_1.useRef)([]);
|
|
112
112
|
min = parseValue(min, exports.defaultProps.min);
|
|
113
113
|
max = parseValue(max, exports.defaultProps.max);
|
|
114
114
|
// Initialize localValue properly
|
|
@@ -181,18 +181,22 @@ exports.Slider = (0, react_2.forwardRef)((_a, forwardedRef) => {
|
|
|
181
181
|
}, [updateValue, readOnly]);
|
|
182
182
|
// Component APIs
|
|
183
183
|
const handleOnFocus = (0, react_1.useCallback)((ev) => {
|
|
184
|
-
|
|
185
|
-
(_a = thumbRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
184
|
+
onShowTooltip();
|
|
186
185
|
onFocus === null || onFocus === void 0 ? void 0 : onFocus(ev);
|
|
187
|
-
}, [onFocus]);
|
|
186
|
+
}, [onFocus, onShowTooltip]);
|
|
188
187
|
const handleOnBlur = (0, react_1.useCallback)((ev) => {
|
|
189
|
-
var _a;
|
|
190
|
-
(_a = thumbRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
191
188
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(ev);
|
|
192
189
|
}, [onBlur]);
|
|
193
190
|
const focus = (0, react_1.useCallback)(() => {
|
|
194
191
|
var _a;
|
|
195
|
-
|
|
192
|
+
// Focus the first available thumb
|
|
193
|
+
const firstThumb = thumbsRef.current.find(thumb => thumb !== null);
|
|
194
|
+
if (firstThumb) {
|
|
195
|
+
firstThumb.focus();
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
199
|
+
}
|
|
196
200
|
}, []);
|
|
197
201
|
const setValue = (0, misc_1.useEvent)((newValue) => {
|
|
198
202
|
if (readOnly || !enabled) {
|
|
@@ -210,6 +214,10 @@ exports.Slider = (0, react_2.forwardRef)((_a, forwardedRef) => {
|
|
|
210
214
|
}, [focus, registerComponentApi, setValue]);
|
|
211
215
|
// Ensure we always have at least one thumb
|
|
212
216
|
const displayValue = localValue.length > 0 ? localValue : formatValue(undefined, min, min, max);
|
|
217
|
+
// Clean up thumbs ref array when number of thumbs changes
|
|
218
|
+
(0, react_1.useEffect)(() => {
|
|
219
|
+
thumbsRef.current = thumbsRef.current.slice(0, displayValue.length);
|
|
220
|
+
}, [displayValue.length]);
|
|
213
221
|
return ((0, jsx_runtime_1.jsx)(ItemWithLabel_1.ItemWithLabel, Object.assign({}, rest, { labelPosition: labelPosition, label: label, labelWidth: labelWidth, labelBreak: labelBreak, required: required, enabled: enabled, onFocus: onFocus, onBlur: onBlur, style: style, className: className, ref: forwardedRef, id: id, isInputTemplateUsed: true, children: (0, jsx_runtime_1.jsx)("div", { className: Slider_module_scss_1.default.sliderContainer, "data-slider-container": true, children: (0, jsx_runtime_1.jsxs)(react_slider_1.Root, { minStepsBetweenThumbs: minStepsBetweenThumbs, ref: inputRef, tabIndex: tabIndex, "aria-readonly": readOnly, className: (0, classnames_1.default)(className, Slider_module_scss_1.default.sliderRoot, {
|
|
214
222
|
[Slider_module_scss_1.default.disabled]: !enabled,
|
|
215
223
|
[Slider_module_scss_1.default.readOnly]: readOnly,
|
|
@@ -221,8 +229,10 @@ exports.Slider = (0, react_2.forwardRef)((_a, forwardedRef) => {
|
|
|
221
229
|
[Slider_module_scss_1.default.valid]: validationStatus === "valid",
|
|
222
230
|
}), style: rangeStyle ? Object.assign({}, rangeStyle) : undefined, children: (0, jsx_runtime_1.jsx)(react_slider_1.Range, { "data-range": true, className: (0, classnames_1.default)(Slider_module_scss_1.default.sliderRange, {
|
|
223
231
|
[Slider_module_scss_1.default.disabled]: !enabled,
|
|
224
|
-
}) }) }), displayValue.map((_, index) => ((0, jsx_runtime_1.jsx)(TooltipNative_1.Tooltip, { ref: tooltipRef, text: valueFormat(displayValue[index]), delayDuration: 100, open: showValues && showTooltip, children: (0, jsx_runtime_1.jsx)(react_slider_1.Thumb, {
|
|
232
|
+
}) }) }), displayValue.map((_, index) => ((0, jsx_runtime_1.jsx)(TooltipNative_1.Tooltip, { ref: tooltipRef, text: valueFormat(displayValue[index]), delayDuration: 100, open: showValues && showTooltip, children: (0, jsx_runtime_1.jsx)(react_slider_1.Thumb, { id: id, "aria-required": required, ref: (el) => {
|
|
233
|
+
thumbsRef.current[index] = el;
|
|
234
|
+
}, className: (0, classnames_1.default)(Slider_module_scss_1.default.sliderThumb, {
|
|
225
235
|
[Slider_module_scss_1.default.disabled]: !enabled,
|
|
226
|
-
}), style: thumbStyle ? Object.assign({}, thumbStyle) : undefined,
|
|
236
|
+
}), style: thumbStyle ? Object.assign({}, thumbStyle) : undefined, "data-thumb-index": index, autoFocus: autoFocus && index === 0 }) }, index)))] }) }) })));
|
|
227
237
|
});
|
|
228
238
|
exports.Slider.displayName = "Slider";
|
|
@@ -37,6 +37,12 @@ exports.TableMd = (0, metadata_helpers_1.createMetadata)({
|
|
|
37
37
|
`property is useful when data is loaded conditionally or receiving it takes some time.`),
|
|
38
38
|
headerHeight: (0, metadata_helpers_1.d)(`This optional property is used to specify the height of the table header.`),
|
|
39
39
|
rowsSelectable: (0, metadata_helpers_1.d)(`Indicates whether the rows are selectable (\`true\`) or not (\`false\`).`),
|
|
40
|
+
initiallySelected: (0, metadata_helpers_1.d)(`An array of IDs that should be initially selected when the table is rendered. ` +
|
|
41
|
+
`This property only has an effect when the rowsSelectable property is set to \`true\`.`),
|
|
42
|
+
syncWithAppState: (0, metadata_helpers_1.d)(`An AppState instance to synchronize the table's selection state with. The table will ` +
|
|
43
|
+
`read from and write to the 'selectedIds' property of the AppState object. When provided, ` +
|
|
44
|
+
`this takes precedence over the initiallySelected property for initial selection. ` +
|
|
45
|
+
`You can use the AppState's didUpdate event to receive notifications when the selection changes.`),
|
|
40
46
|
pageSize: (0, metadata_helpers_1.d)(`This property defines the number of rows to display per page when pagination is enabled.`),
|
|
41
47
|
pageSizeOptions: {
|
|
42
48
|
description: "This property holds an array of page sizes (numbers) the user can select for " +
|
|
@@ -240,7 +246,7 @@ const TableWithColumns = (0, react_1.memo)((0, react_1.forwardRef)(({ extractVal
|
|
|
240
246
|
const tableContent = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(TableContext_1.TableContext.Provider, { value: tableContextValue, children: renderChild(node.children) }, tableKey), (0, jsx_runtime_1.jsx)(TableContext_1.TableContext.Provider, { value: columnRefresherContextValue, children: renderChild(node.children) }), (0, jsx_runtime_1.jsx)(TableNative_1.Table, { className: className, ref: ref, data: data, columns: columns, pageSizeOptions: extractValue(node.props.pageSizeOptions), pageSize: extractValue.asOptionalNumber(node.props.pageSize), rowsSelectable: extractValue.asOptionalBoolean(node.props.rowsSelectable), registerComponentApi: registerComponentApi, noDataRenderer: node.props.noDataTemplate &&
|
|
241
247
|
(() => {
|
|
242
248
|
return renderChild(node.props.noDataTemplate);
|
|
243
|
-
}), hideNoDataView: node.props.noDataTemplate === null || node.props.noDataTemplate === "", loading: extractValue.asOptionalBoolean(node.props.loading), isPaginated: extractValue.asOptionalBoolean((_a = node.props) === null || _a === void 0 ? void 0 : _a.isPaginated), headerHeight: extractValue.asSize(node.props.headerHeight), rowDisabledPredicate: lookupSyncCallback(node.props.rowDisabledPredicate), sortBy: extractValue((_b = node.props) === null || _b === void 0 ? void 0 : _b.sortBy), sortingDirection: extractValue((_c = node.props) === null || _c === void 0 ? void 0 : _c.sortDirection), iconSortAsc: extractValue.asOptionalString((_d = node.props) === null || _d === void 0 ? void 0 : _d.iconSortAsc), iconSortDesc: extractValue.asOptionalString((_e = node.props) === null || _e === void 0 ? void 0 : _e.iconSortDesc), iconNoSort: extractValue.asOptionalString((_f = node.props) === null || _f === void 0 ? void 0 : _f.iconNoSort), sortingDidChange: lookupEventHandler("sortingDidChange"), onSelectionDidChange: lookupEventHandler("selectionDidChange"), willSort: lookupEventHandler("willSort"), uid: node.uid, autoFocus: extractValue.asOptionalBoolean(node.props.autoFocus), hideHeader: extractValue.asOptionalBoolean(node.props.hideHeader), enableMultiRowSelection: extractValue.asOptionalBoolean(node.props.enableMultiRowSelection), alwaysShowSelectionHeader: extractValue.asOptionalBoolean(node.props.alwaysShowSelectionHeader), noBottomBorder: extractValue.asOptionalBoolean(node.props.noBottomBorder), paginationControlsLocation: extractValue.asOptionalString(node.props.paginationControlsLocation), cellVerticalAlign: extractValue.asOptionalString(node.props.cellVerticalAlign), buttonRowPosition: extractValue.asOptionalString(node.props.buttonRowPosition), pageSizeSelectorPosition: extractValue.asOptionalString(node.props.pageSizeSelectorPosition), pageInfoPosition: extractValue.asOptionalString(node.props.pageInfoPosition), showCurrentPage: extractValue.asOptionalBoolean(node.props.showCurrentPage), showPageInfo: extractValue.asOptionalBoolean(node.props.showPageInfo), showPageSizeSelector: extractValue.asOptionalBoolean(node.props.showPageSizeSelector) })] }));
|
|
249
|
+
}), hideNoDataView: node.props.noDataTemplate === null || node.props.noDataTemplate === "", loading: extractValue.asOptionalBoolean(node.props.loading), isPaginated: extractValue.asOptionalBoolean((_a = node.props) === null || _a === void 0 ? void 0 : _a.isPaginated), headerHeight: extractValue.asSize(node.props.headerHeight), rowDisabledPredicate: lookupSyncCallback(node.props.rowDisabledPredicate), sortBy: extractValue((_b = node.props) === null || _b === void 0 ? void 0 : _b.sortBy), sortingDirection: extractValue((_c = node.props) === null || _c === void 0 ? void 0 : _c.sortDirection), iconSortAsc: extractValue.asOptionalString((_d = node.props) === null || _d === void 0 ? void 0 : _d.iconSortAsc), iconSortDesc: extractValue.asOptionalString((_e = node.props) === null || _e === void 0 ? void 0 : _e.iconSortDesc), iconNoSort: extractValue.asOptionalString((_f = node.props) === null || _f === void 0 ? void 0 : _f.iconNoSort), sortingDidChange: lookupEventHandler("sortingDidChange"), onSelectionDidChange: lookupEventHandler("selectionDidChange"), willSort: lookupEventHandler("willSort"), uid: node.uid, autoFocus: extractValue.asOptionalBoolean(node.props.autoFocus), hideHeader: extractValue.asOptionalBoolean(node.props.hideHeader), enableMultiRowSelection: extractValue.asOptionalBoolean(node.props.enableMultiRowSelection), alwaysShowSelectionHeader: extractValue.asOptionalBoolean(node.props.alwaysShowSelectionHeader), noBottomBorder: extractValue.asOptionalBoolean(node.props.noBottomBorder), paginationControlsLocation: extractValue.asOptionalString(node.props.paginationControlsLocation), cellVerticalAlign: extractValue.asOptionalString(node.props.cellVerticalAlign), buttonRowPosition: extractValue.asOptionalString(node.props.buttonRowPosition), pageSizeSelectorPosition: extractValue.asOptionalString(node.props.pageSizeSelectorPosition), pageInfoPosition: extractValue.asOptionalString(node.props.pageInfoPosition), showCurrentPage: extractValue.asOptionalBoolean(node.props.showCurrentPage), showPageInfo: extractValue.asOptionalBoolean(node.props.showPageInfo), showPageSizeSelector: extractValue.asOptionalBoolean(node.props.showPageSizeSelector), initiallySelected: extractValue(node.props.initiallySelected), syncWithAppState: extractValue(node.props.syncWithAppState) })] }));
|
|
244
250
|
if (selectionContext === null) {
|
|
245
251
|
return (0, jsx_runtime_1.jsx)(SelectionStoreNative_1.StandaloneSelectionStore, { children: tableContent });
|
|
246
252
|
}
|
|
@@ -74,7 +74,7 @@ const getCommonPinningStyles = (column) => {
|
|
|
74
74
|
// eslint-disable-next-line react/display-name
|
|
75
75
|
exports.Table = (0, react_1.forwardRef)((_a, forwardedRef) => {
|
|
76
76
|
var _b, _c, _d, _e, _f;
|
|
77
|
-
var { data = exports.defaultProps.data, columns = exports.defaultProps.columns, isPaginated = exports.defaultProps.isPaginated, loading = exports.defaultProps.loading, headerHeight, rowsSelectable = exports.defaultProps.rowsSelectable, enableMultiRowSelection = exports.defaultProps.enableMultiRowSelection, pageSizeOptions = exports.defaultProps.pageSizeOptions, pageSize = (pageSizeOptions === null || pageSizeOptions === void 0 ? void 0 : pageSizeOptions[0]) || DEFAULT_PAGE_SIZES[0], currentPageIndex = 0, rowDisabledPredicate = defaultIsRowDisabled, sortBy, sortingDirection = exports.defaultProps.sortingDirection, iconSortAsc, iconSortDesc, iconNoSort, sortingDidChange, willSort, style, className, noDataRenderer, autoFocus = exports.defaultProps.autoFocus, hideHeader = exports.defaultProps.hideHeader, hideNoDataView = exports.defaultProps.hideNoDataView, alwaysShowSelectionHeader = exports.defaultProps.alwaysShowSelectionHeader, registerComponentApi, onSelectionDidChange, noBottomBorder = exports.defaultProps.noBottomBorder, paginationControlsLocation = exports.defaultProps.paginationControlsLocation, cellVerticalAlign = exports.defaultProps.cellVerticalAlign, buttonRowPosition = exports.defaultProps.buttonRowPosition, pageSizeSelectorPosition = exports.defaultProps.pageSizeSelectorPosition, pageInfoPosition = exports.defaultProps.pageInfoPosition, showCurrentPage = exports.defaultProps.showCurrentPage, showPageInfo = exports.defaultProps.showPageInfo, showPageSizeSelector = exports.defaultProps.showPageSizeSelector } = _a, rest = __rest(_a, ["data", "columns", "isPaginated", "loading", "headerHeight", "rowsSelectable", "enableMultiRowSelection", "pageSizeOptions", "pageSize", "currentPageIndex", "rowDisabledPredicate", "sortBy", "sortingDirection", "iconSortAsc", "iconSortDesc", "iconNoSort", "sortingDidChange", "willSort", "style", "className", "noDataRenderer", "autoFocus", "hideHeader", "hideNoDataView", "alwaysShowSelectionHeader", "registerComponentApi", "onSelectionDidChange", "noBottomBorder", "paginationControlsLocation", "cellVerticalAlign", "buttonRowPosition", "pageSizeSelectorPosition", "pageInfoPosition", "showCurrentPage", "showPageInfo", "showPageSizeSelector"])
|
|
77
|
+
var { data = exports.defaultProps.data, columns = exports.defaultProps.columns, isPaginated = exports.defaultProps.isPaginated, loading = exports.defaultProps.loading, headerHeight, rowsSelectable = exports.defaultProps.rowsSelectable, enableMultiRowSelection = exports.defaultProps.enableMultiRowSelection, initiallySelected = exports.defaultProps.initiallySelected, syncWithAppState, pageSizeOptions = exports.defaultProps.pageSizeOptions, pageSize = (pageSizeOptions === null || pageSizeOptions === void 0 ? void 0 : pageSizeOptions[0]) || DEFAULT_PAGE_SIZES[0], currentPageIndex = 0, rowDisabledPredicate = defaultIsRowDisabled, sortBy, sortingDirection = exports.defaultProps.sortingDirection, iconSortAsc, iconSortDesc, iconNoSort, sortingDidChange, willSort, style, className, noDataRenderer, autoFocus = exports.defaultProps.autoFocus, hideHeader = exports.defaultProps.hideHeader, hideNoDataView = exports.defaultProps.hideNoDataView, alwaysShowSelectionHeader = exports.defaultProps.alwaysShowSelectionHeader, registerComponentApi, onSelectionDidChange, noBottomBorder = exports.defaultProps.noBottomBorder, paginationControlsLocation = exports.defaultProps.paginationControlsLocation, cellVerticalAlign = exports.defaultProps.cellVerticalAlign, buttonRowPosition = exports.defaultProps.buttonRowPosition, pageSizeSelectorPosition = exports.defaultProps.pageSizeSelectorPosition, pageInfoPosition = exports.defaultProps.pageInfoPosition, showCurrentPage = exports.defaultProps.showCurrentPage, showPageInfo = exports.defaultProps.showPageInfo, showPageSizeSelector = exports.defaultProps.showPageSizeSelector } = _a, rest = __rest(_a, ["data", "columns", "isPaginated", "loading", "headerHeight", "rowsSelectable", "enableMultiRowSelection", "initiallySelected", "syncWithAppState", "pageSizeOptions", "pageSize", "currentPageIndex", "rowDisabledPredicate", "sortBy", "sortingDirection", "iconSortAsc", "iconSortDesc", "iconNoSort", "sortingDidChange", "willSort", "style", "className", "noDataRenderer", "autoFocus", "hideHeader", "hideNoDataView", "alwaysShowSelectionHeader", "registerComponentApi", "onSelectionDidChange", "noBottomBorder", "paginationControlsLocation", "cellVerticalAlign", "buttonRowPosition", "pageSizeSelectorPosition", "pageInfoPosition", "showCurrentPage", "showPageInfo", "showPageSizeSelector"])
|
|
78
78
|
// cols
|
|
79
79
|
;
|
|
80
80
|
const { getThemeVar } = (0, ThemeContext_1.useTheme)();
|
|
@@ -107,6 +107,8 @@ exports.Table = (0, react_1.forwardRef)((_a, forwardedRef) => {
|
|
|
107
107
|
enableMultiRowSelection,
|
|
108
108
|
rowDisabledPredicate,
|
|
109
109
|
onSelectionDidChange,
|
|
110
|
+
initiallySelected,
|
|
111
|
+
syncWithAppState,
|
|
110
112
|
});
|
|
111
113
|
// --- Create data with order information whenever the items in the table change
|
|
112
114
|
const dataWithOrder = (0, react_1.useMemo)(() => {
|
|
@@ -492,6 +494,7 @@ exports.defaultProps = {
|
|
|
492
494
|
loading: false,
|
|
493
495
|
rowsSelectable: false,
|
|
494
496
|
enableMultiRowSelection: true,
|
|
497
|
+
initiallySelected: constants_1.EMPTY_ARRAY,
|
|
495
498
|
pageSizeOptions: [5, 10, 15],
|
|
496
499
|
sortingDirection: "ascending",
|
|
497
500
|
autoFocus: false,
|
|
@@ -7,7 +7,76 @@ const misc_1 = require("../../components-core/utils/misc");
|
|
|
7
7
|
const constants_1 = require("../../components-core/constants");
|
|
8
8
|
const hooks_1 = require("../../components-core/utils/hooks");
|
|
9
9
|
const SelectionStoreNative_1 = require("../SelectionStore/SelectionStoreNative");
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Hook for managing table row selection with optional bidirectional AppState synchronization.
|
|
12
|
+
*
|
|
13
|
+
* ## AppState Synchronization Mechanism
|
|
14
|
+
*
|
|
15
|
+
* When `syncWithAppState` is provided, this hook implements a robust bidirectional synchronization
|
|
16
|
+
* between the table's selection state and an AppState instance. The synchronization prevents
|
|
17
|
+
* infinite loops using a state machine approach.
|
|
18
|
+
*
|
|
19
|
+
* ### State Machine Design
|
|
20
|
+
*
|
|
21
|
+
* The sync operates through three states:
|
|
22
|
+
* - `idle`: Normal state, ready to respond to changes from either side
|
|
23
|
+
* - `updating_to_appstate`: Currently propagating table selection → AppState (blocks AppState → table)
|
|
24
|
+
* - `updating_from_appstate`: Currently propagating AppState → table selection (blocks table → AppState)
|
|
25
|
+
*
|
|
26
|
+
* ### Synchronization Flow
|
|
27
|
+
*
|
|
28
|
+
* **AppState → Table (External Updates)**:
|
|
29
|
+
* 1. AppState.value.selectedIds changes externally (e.g., from another component)
|
|
30
|
+
* 2. Effect detects change and validates it's not from our own update (using source tracking)
|
|
31
|
+
* 3. Sets state to `updating_from_appstate` to block reverse sync
|
|
32
|
+
* 4. Updates table selection via setSelectedRowIds()
|
|
33
|
+
* 5. Uses requestAnimationFrame to reset state to `idle` after update completes
|
|
34
|
+
*
|
|
35
|
+
* **Table → AppState (User Interaction)**:
|
|
36
|
+
* 1. User interacts with table (clicks, keyboard navigation)
|
|
37
|
+
* 2. selectedItems changes through normal table selection logic
|
|
38
|
+
* 3. Effect detects change and validates it's different from AppState
|
|
39
|
+
* 4. Sets state to `updating_to_appstate` to block reverse sync
|
|
40
|
+
* 5. Calls syncWithAppState.update({ selectedIds: [...] })
|
|
41
|
+
* 6. Uses requestAnimationFrame to reset state to `idle` after update completes
|
|
42
|
+
*
|
|
43
|
+
* ### Loop Prevention Strategy
|
|
44
|
+
*
|
|
45
|
+
* Multiple mechanisms prevent infinite loops:
|
|
46
|
+
* - **State Machine**: Directional blocking prevents simultaneous updates
|
|
47
|
+
* - **Source Tracking**: lastUpdateSourceRef tracks whether the last change came from 'table' or 'appstate'
|
|
48
|
+
* - **Value Tracking**: lastAppStateSelectionRef and lastTableSelectionRef track last known values
|
|
49
|
+
* - **Change Detection**: Only triggers updates when values actually differ using JSON.stringify comparison
|
|
50
|
+
* - **Frame-Based Reset**: Uses requestAnimationFrame instead of setTimeout for deterministic timing
|
|
51
|
+
*
|
|
52
|
+
* ### Usage with AppState
|
|
53
|
+
*
|
|
54
|
+
* ```typescript
|
|
55
|
+
* // In your component
|
|
56
|
+
* const appState = useAppState('myBucket');
|
|
57
|
+
*
|
|
58
|
+
* // Pass to Table
|
|
59
|
+
* <Table
|
|
60
|
+
* items={data}
|
|
61
|
+
* syncWithAppState={appState}
|
|
62
|
+
* // ... other props
|
|
63
|
+
* />
|
|
64
|
+
*
|
|
65
|
+
* // AppState will contain: { selectedIds: ['id1', 'id2', ...] }
|
|
66
|
+
* // Changes from either side are automatically synchronized
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* ### Precedence Rules
|
|
70
|
+
*
|
|
71
|
+
* - When both `initiallySelected` and `syncWithAppState` are provided, `syncWithAppState` takes precedence
|
|
72
|
+
* - Multi-row selection limits are respected (single selection truncates to first ID)
|
|
73
|
+
* - Only valid item IDs (present in current `items` array) are synchronized
|
|
74
|
+
*
|
|
75
|
+
* @param options Configuration object for row selection behavior
|
|
76
|
+
* @returns Row selection operations and state management interface
|
|
77
|
+
*/
|
|
78
|
+
function useRowSelection({ items = constants_1.EMPTY_ARRAY, visibleItems = items, rowsSelectable, enableMultiRowSelection, rowDisabledPredicate, onSelectionDidChange, initiallySelected = constants_1.EMPTY_ARRAY, syncWithAppState, }) {
|
|
79
|
+
var _a;
|
|
11
80
|
// --- The focused index in the row source (if there is any)
|
|
12
81
|
const [focusedIndex, setFocusedIndex] = (0, react_1.useState)(-1);
|
|
13
82
|
// --- The current selection interval
|
|
@@ -18,10 +87,155 @@ function useRowSelection({ items = constants_1.EMPTY_ARRAY, visibleItems = items
|
|
|
18
87
|
const walkableList = (0, react_1.useMemo)(() => {
|
|
19
88
|
return visibleItems.map((item) => item[idKey]);
|
|
20
89
|
}, [idKey, visibleItems]);
|
|
90
|
+
// --- Track if initial selection has been applied
|
|
91
|
+
const [initialSelectionApplied, setInitialSelectionApplied] = (0, react_1.useState)(false);
|
|
21
92
|
// --- If the items change, refresh the selectable items (if the rows are selectable)
|
|
22
93
|
(0, react_1.useEffect)(() => {
|
|
23
94
|
refreshSelection(rowsSelectable ? items : constants_1.EMPTY_ARRAY);
|
|
24
95
|
}, [refreshSelection, items, rowsSelectable]);
|
|
96
|
+
// --- Handle AppState synchronization
|
|
97
|
+
// This implements bidirectional sync between Table selection and AppState.
|
|
98
|
+
// The approach uses React's useEffect pattern which is appropriate for React-to-React communication.
|
|
99
|
+
// The new AppState didUpdate event is more useful for non-React integrations.
|
|
100
|
+
const appStateSelection = (_a = syncWithAppState === null || syncWithAppState === void 0 ? void 0 : syncWithAppState.value) === null || _a === void 0 ? void 0 : _a.selectedIds;
|
|
101
|
+
const prevAppStateSelection = (0, hooks_1.usePrevious)(appStateSelection);
|
|
102
|
+
// --- State machine for sync direction control
|
|
103
|
+
const [syncState, setSyncState] = (0, react_1.useState)('idle');
|
|
104
|
+
// --- Use refs to track the last known selections to prevent update loops
|
|
105
|
+
const lastAppStateSelectionRef = (0, react_1.useRef)();
|
|
106
|
+
const lastTableSelectionRef = (0, react_1.useRef)();
|
|
107
|
+
// --- Track the source of the last update to prevent echoing
|
|
108
|
+
const lastUpdateSourceRef = (0, react_1.useRef)(null);
|
|
109
|
+
// --- Sync from AppState to table selection (when AppState changes externally)
|
|
110
|
+
(0, react_1.useEffect)(() => {
|
|
111
|
+
// Skip if not selectable, no sync, no selection, or we're currently updating to AppState
|
|
112
|
+
if (!rowsSelectable ||
|
|
113
|
+
!syncWithAppState ||
|
|
114
|
+
!appStateSelection ||
|
|
115
|
+
syncState === 'updating_to_appstate') {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// Only update if AppState selection actually changed and this wasn't caused by our own table update
|
|
119
|
+
const appStateChanged = appStateSelection !== prevAppStateSelection;
|
|
120
|
+
const isDifferentFromLastKnown = JSON.stringify([...(appStateSelection || [])].sort()) !==
|
|
121
|
+
JSON.stringify([...(lastAppStateSelectionRef.current || [])].sort());
|
|
122
|
+
const wasNotOurUpdate = lastUpdateSourceRef.current !== 'table';
|
|
123
|
+
if (appStateChanged && isDifferentFromLastKnown && wasNotOurUpdate && items.length > 0) {
|
|
124
|
+
// Set state machine to indicate we're updating from AppState
|
|
125
|
+
setSyncState('updating_from_appstate');
|
|
126
|
+
const validIds = appStateSelection.filter((id) => items.some((item) => item[idKey] === id));
|
|
127
|
+
const idsToSelect = enableMultiRowSelection ? validIds : validIds.slice(0, 1);
|
|
128
|
+
// Track what we're setting to prevent loop
|
|
129
|
+
lastAppStateSelectionRef.current = [...appStateSelection];
|
|
130
|
+
lastTableSelectionRef.current = [...idsToSelect];
|
|
131
|
+
lastUpdateSourceRef.current = 'appstate';
|
|
132
|
+
setSelectedRowIds(idsToSelect);
|
|
133
|
+
setInitialSelectionApplied(true);
|
|
134
|
+
}
|
|
135
|
+
}, [
|
|
136
|
+
appStateSelection,
|
|
137
|
+
prevAppStateSelection,
|
|
138
|
+
items,
|
|
139
|
+
rowsSelectable,
|
|
140
|
+
syncWithAppState,
|
|
141
|
+
idKey,
|
|
142
|
+
enableMultiRowSelection,
|
|
143
|
+
setSelectedRowIds,
|
|
144
|
+
syncState,
|
|
145
|
+
]);
|
|
146
|
+
// --- Sync from table selection to AppState (when user interacts with table)
|
|
147
|
+
(0, react_1.useEffect)(() => {
|
|
148
|
+
var _a;
|
|
149
|
+
// Skip if not selectable, no sync, or currently updating from AppState
|
|
150
|
+
if (!rowsSelectable ||
|
|
151
|
+
!syncWithAppState ||
|
|
152
|
+
syncState === 'updating_from_appstate') {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const currentSelectionIds = selectedItems.map((item) => item[idKey]);
|
|
156
|
+
const appStateSelectionIds = appStateSelection || [];
|
|
157
|
+
// Only update if table selection is different from AppState, different from our last update, and wasn't caused by AppState
|
|
158
|
+
const tableChanged = JSON.stringify([...currentSelectionIds].sort()) !==
|
|
159
|
+
JSON.stringify([...(lastTableSelectionRef.current || [])].sort());
|
|
160
|
+
const isDifferentFromAppState = JSON.stringify([...currentSelectionIds].sort()) !==
|
|
161
|
+
JSON.stringify([...appStateSelectionIds].sort());
|
|
162
|
+
const wasNotAppStateUpdate = lastUpdateSourceRef.current !== 'appstate';
|
|
163
|
+
if (tableChanged && isDifferentFromAppState && wasNotAppStateUpdate) {
|
|
164
|
+
// Set state machine to indicate we're updating to AppState
|
|
165
|
+
setSyncState('updating_to_appstate');
|
|
166
|
+
// Track what we're updating to prevent loop
|
|
167
|
+
lastTableSelectionRef.current = [...currentSelectionIds];
|
|
168
|
+
lastAppStateSelectionRef.current = [...currentSelectionIds];
|
|
169
|
+
lastUpdateSourceRef.current = 'table';
|
|
170
|
+
(_a = syncWithAppState.update) === null || _a === void 0 ? void 0 : _a.call(syncWithAppState, { selectedIds: currentSelectionIds });
|
|
171
|
+
}
|
|
172
|
+
}, [
|
|
173
|
+
selectedItems,
|
|
174
|
+
syncWithAppState,
|
|
175
|
+
appStateSelection,
|
|
176
|
+
idKey,
|
|
177
|
+
rowsSelectable,
|
|
178
|
+
syncState,
|
|
179
|
+
]);
|
|
180
|
+
// --- Reset sync state machine to idle when updates are complete
|
|
181
|
+
(0, react_1.useEffect)(() => {
|
|
182
|
+
if (syncState !== 'idle') {
|
|
183
|
+
// Reset to idle state in the next tick to allow the current update to complete
|
|
184
|
+
const resetTimer = requestAnimationFrame(() => {
|
|
185
|
+
setSyncState('idle');
|
|
186
|
+
});
|
|
187
|
+
return () => cancelAnimationFrame(resetTimer);
|
|
188
|
+
}
|
|
189
|
+
}, [syncState, appStateSelection, selectedItems]);
|
|
190
|
+
// --- Clear update source when sync state becomes idle
|
|
191
|
+
(0, react_1.useEffect)(() => {
|
|
192
|
+
if (syncState === 'idle') {
|
|
193
|
+
// Use a separate frame to clear the source after the sync state is reset
|
|
194
|
+
const clearTimer = requestAnimationFrame(() => {
|
|
195
|
+
lastUpdateSourceRef.current = null;
|
|
196
|
+
});
|
|
197
|
+
return () => cancelAnimationFrame(clearTimer);
|
|
198
|
+
}
|
|
199
|
+
}, [syncState]);
|
|
200
|
+
// --- Set initial selection when component mounts and items are available
|
|
201
|
+
// Use a separate effect that runs after the refresh to ensure timing is correct
|
|
202
|
+
(0, react_1.useEffect)(() => {
|
|
203
|
+
// If we have AppState sync, don't use initiallySelected
|
|
204
|
+
if (syncWithAppState) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!rowsSelectable ||
|
|
208
|
+
!initiallySelected ||
|
|
209
|
+
initiallySelected.length === 0 ||
|
|
210
|
+
initialSelectionApplied) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
// Only set initial selection when items are available and we haven't applied it yet
|
|
214
|
+
if (items.length > 0) {
|
|
215
|
+
// Use requestAnimationFrame to ensure this runs after the refreshSelection effect
|
|
216
|
+
const frameId = requestAnimationFrame(() => {
|
|
217
|
+
// Filter initiallySelected to only include IDs that exist in current items
|
|
218
|
+
const validIds = initiallySelected.filter((id) => items.some((item) => item[idKey] === id));
|
|
219
|
+
if (validIds.length > 0) {
|
|
220
|
+
// If multi-row selection is disabled, only select the first valid ID
|
|
221
|
+
const idsToSelect = enableMultiRowSelection ? validIds : [validIds[0]];
|
|
222
|
+
setSelectedRowIds(idsToSelect);
|
|
223
|
+
setInitialSelectionApplied(true);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
return () => cancelAnimationFrame(frameId);
|
|
227
|
+
}
|
|
228
|
+
}, [
|
|
229
|
+
items,
|
|
230
|
+
initiallySelected,
|
|
231
|
+
rowsSelectable,
|
|
232
|
+
idKey,
|
|
233
|
+
enableMultiRowSelection,
|
|
234
|
+
setSelectedRowIds,
|
|
235
|
+
initialSelectionApplied,
|
|
236
|
+
selectedItems,
|
|
237
|
+
syncWithAppState,
|
|
238
|
+
]);
|
|
25
239
|
// --- If the multi-row selection switches to disabled, keep only the first selected item
|
|
26
240
|
const prevEnableMultiRowSelection = (0, hooks_1.usePrevious)(enableMultiRowSelection);
|
|
27
241
|
(0, react_1.useEffect)(() => {
|
|
@@ -33,10 +33,6 @@ exports.TextBoxMd = (0, metadata_helpers_1.createMetadata)({
|
|
|
33
33
|
props: {
|
|
34
34
|
placeholder: (0, metadata_helpers_1.dPlaceholder)(),
|
|
35
35
|
initialValue: Object.assign(Object.assign({}, (0, metadata_helpers_1.dInitialValue)()), { defaultValue: TextBoxNative_1.defaultProps.initialValue }),
|
|
36
|
-
label: (0, metadata_helpers_1.dLabel)(),
|
|
37
|
-
labelPosition: (0, metadata_helpers_1.dLabelPosition)("top"),
|
|
38
|
-
labelWidth: (0, metadata_helpers_1.dLabelWidth)(COMP),
|
|
39
|
-
labelBreak: (0, metadata_helpers_1.dLabelBreak)(COMP),
|
|
40
36
|
maxLength: (0, metadata_helpers_1.dMaxLength)(),
|
|
41
37
|
autoFocus: (0, metadata_helpers_1.dAutoFocus)(),
|
|
42
38
|
required: (0, metadata_helpers_1.dRequired)(),
|
|
@@ -123,7 +119,7 @@ exports.TextBoxMd = (0, metadata_helpers_1.createMetadata)({
|
|
|
123
119
|
function renderTextBox(className, state, updateState, extractValue, node, lookupEventHandler, registerComponentApi, type = "text") {
|
|
124
120
|
// TODO: How can we use the gap from the className?
|
|
125
121
|
//delete layoutCss.gap;
|
|
126
|
-
return ((0, jsx_runtime_1.jsx)(TextBoxNative_1.TextBox, { type: type, className: className, value: state.value, updateState: updateState, initialValue: extractValue(node.props.initialValue), maxLength: extractValue(node.props.maxLength), enabled: extractValue.asOptionalBoolean(node.props.enabled), placeholder: extractValue.asOptionalString(node.props.placeholder), validationStatus: extractValue(node.props.validationStatus), onDidChange: lookupEventHandler("didChange"), onFocus: lookupEventHandler("gotFocus"), onBlur: lookupEventHandler("lostFocus"), registerComponentApi: registerComponentApi, startText: extractValue.asOptionalString(node.props.startText), startIcon: extractValue.asOptionalString(node.props.startIcon), endText: extractValue.asOptionalString(node.props.endText), endIcon: extractValue.asOptionalString(node.props.endIcon), gap: extractValue.asOptionalString(node.props.gap), autoFocus: extractValue.asOptionalBoolean(node.props.autoFocus), readOnly: extractValue.asOptionalBoolean(node.props.readOnly),
|
|
122
|
+
return ((0, jsx_runtime_1.jsx)(TextBoxNative_1.TextBox, { type: type, className: className, value: state.value, updateState: updateState, initialValue: extractValue(node.props.initialValue), maxLength: extractValue(node.props.maxLength), enabled: extractValue.asOptionalBoolean(node.props.enabled), placeholder: extractValue.asOptionalString(node.props.placeholder), validationStatus: extractValue(node.props.validationStatus), onDidChange: lookupEventHandler("didChange"), onFocus: lookupEventHandler("gotFocus"), onBlur: lookupEventHandler("lostFocus"), registerComponentApi: registerComponentApi, startText: extractValue.asOptionalString(node.props.startText), startIcon: extractValue.asOptionalString(node.props.startIcon), endText: extractValue.asOptionalString(node.props.endText), endIcon: extractValue.asOptionalString(node.props.endIcon), gap: extractValue.asOptionalString(node.props.gap), autoFocus: extractValue.asOptionalBoolean(node.props.autoFocus), readOnly: extractValue.asOptionalBoolean(node.props.readOnly), required: extractValue.asOptionalBoolean(node.props.required), showPasswordToggle: extractValue.asOptionalBoolean(node.props.showPasswordToggle, false), passwordVisibleIcon: extractValue.asOptionalString(node.props.passwordVisibleIcon), passwordHiddenIcon: extractValue.asOptionalString(node.props.passwordHiddenIcon) }));
|
|
127
123
|
}
|
|
128
124
|
exports.textBoxComponentRenderer = (0, renderers_1.createComponentRenderer)(COMP, exports.TextBoxMd, ({ node, state, updateState, lookupEventHandler, extractValue, className, registerComponentApi, }) => {
|
|
129
125
|
return renderTextBox(className, state, updateState, extractValue, node, lookupEventHandler, registerComponentApi);
|