@heartlandone/vega-react 2.91.0 → 2.93.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.
@@ -80,6 +80,7 @@ export declare const VegaRadioGroup: import("react").ForwardRefExoticComponent<V
80
80
  export declare const VegaRichTextContent: import("react").ForwardRefExoticComponent<JSX.VegaRichTextContent & Omit<import("react").HTMLAttributes<HTMLVegaRichTextContentElement>, "style"> & import("./react-component-lib/interfaces").StyleReactProps & import("react").RefAttributes<HTMLVegaRichTextContentElement>>;
81
81
  export declare const VegaRichTextEditor: import("react").ForwardRefExoticComponent<VegaReactComponentWithNativeEvent<JSX.VegaRichTextEditor, Omit<import("react").HTMLAttributes<HTMLVegaRichTextEditorElement>, "style">, "onChange"> & import("./react-component-lib/interfaces").StyleReactProps & import("react").RefAttributes<HTMLVegaRichTextEditorElement>>;
82
82
  export declare const VegaRichTextEditorToolbarButton: import("react").ForwardRefExoticComponent<VegaReactComponentWithNativeEvent<JSX.VegaRichTextEditorToolbarButton, Omit<import("react").HTMLAttributes<HTMLVegaRichTextEditorToolbarButtonElement>, "style">, "onClick"> & import("./react-component-lib/interfaces").StyleReactProps & import("react").RefAttributes<HTMLVegaRichTextEditorToolbarButtonElement>>;
83
+ export declare const VegaRichTextFindReplacePanel: import("react").ForwardRefExoticComponent<JSX.VegaRichTextFindReplacePanel & Omit<import("react").HTMLAttributes<HTMLVegaRichTextFindReplacePanelElement>, "style"> & import("./react-component-lib/interfaces").StyleReactProps & import("react").RefAttributes<HTMLVegaRichTextFindReplacePanelElement>>;
83
84
  export declare const VegaRichTextImageEditor: import("react").ForwardRefExoticComponent<VegaReactComponentWithNativeEvent<JSX.VegaRichTextImageEditor, Omit<import("react").HTMLAttributes<HTMLVegaRichTextImageEditorElement>, "style">, "onChange"> & import("./react-component-lib/interfaces").StyleReactProps & import("react").RefAttributes<HTMLVegaRichTextImageEditorElement>>;
84
85
  export declare const VegaRichTextLinkEditor: import("react").ForwardRefExoticComponent<VegaReactComponentWithNativeEvent<JSX.VegaRichTextLinkEditor, Omit<import("react").HTMLAttributes<HTMLVegaRichTextLinkEditorElement>, "style">, "onChange"> & import("./react-component-lib/interfaces").StyleReactProps & import("react").RefAttributes<HTMLVegaRichTextLinkEditorElement>>;
85
86
  export declare const VegaRichTextSpecialCharactersPanel: import("react").ForwardRefExoticComponent<JSX.VegaRichTextSpecialCharactersPanel & Omit<import("react").HTMLAttributes<HTMLVegaRichTextSpecialCharactersPanelElement>, "style"> & import("./react-component-lib/interfaces").StyleReactProps & import("react").RefAttributes<HTMLVegaRichTextSpecialCharactersPanelElement>>;
@@ -87,6 +87,7 @@ export const VegaRadioGroup = /*@__PURE__*/ createReactComponent('vega-radio-gro
87
87
  export const VegaRichTextContent = /*@__PURE__*/ createReactComponent('vega-rich-text-content');
88
88
  export const VegaRichTextEditor = /*@__PURE__*/ createReactComponent('vega-rich-text-editor');
89
89
  export const VegaRichTextEditorToolbarButton = /*@__PURE__*/ createReactComponent('vega-rich-text-editor-toolbar-button');
90
+ export const VegaRichTextFindReplacePanel = /*@__PURE__*/ createReactComponent('vega-rich-text-find-replace-panel');
90
91
  export const VegaRichTextImageEditor = /*@__PURE__*/ createReactComponent('vega-rich-text-image-editor');
91
92
  export const VegaRichTextLinkEditor = /*@__PURE__*/ createReactComponent('vega-rich-text-link-editor');
92
93
  export const VegaRichTextSpecialCharactersPanel = /*@__PURE__*/ createReactComponent('vega-rich-text-special-characters-panel');
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heartlandone/vega-react",
3
- "version": "2.90.0",
3
+ "version": "2.92.0",
4
4
  "description": "React specific wrapper for @heartlandone/vega",
5
5
  "license": "UNLICENSED",
6
6
  "sideEffects": false,
@@ -8,7 +8,7 @@
8
8
  "module": "./index.js",
9
9
  "types": "./index.d.ts",
10
10
  "dependencies": {
11
- "@heartlandone/vega": "2.90.0"
11
+ "@heartlandone/vega": "2.92.0"
12
12
  },
13
13
  "peerDependencies": {
14
14
  "react": ">=16.7.0",
@@ -24,6 +24,16 @@ export const attachProps = (node, newProps, oldProps = {}) => {
24
24
  syncEvent(node, eventNameLc, newProps[name]);
25
25
  }
26
26
  }
27
+ else if (
28
+ // Skip re-applying when the JSX value is referentially unchanged.
29
+ // This prevents attachProps from clobbering host-element properties
30
+ // mutated outside React (e.g. devtools, imperative refs) on every
31
+ // parent re-render. See VD-9458.
32
+ oldProps !== newProps &&
33
+ Object.prototype.hasOwnProperty.call(oldProps, name) &&
34
+ oldProps[name] === newProps[name]) {
35
+ return;
36
+ }
27
37
  else if (FeatureFlag.isEnabled('VEGA_REACT.PREVENT_CALL_WATCH_METHOD_BEFORE_COMPONENT_LOAD')) {
28
38
  attachProp(node, newProps, name);
29
39
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tests for attachProps reference-equality guard (VD-9458).
3
+ *
4
+ * Regression: when a parent React component re-renders, attachProps used to
5
+ * re-assign every prop on the host element unconditionally. On Next.js the
6
+ * assignment is deferred via `componentOnReady().then(...)`, which means any
7
+ * imperative mutation a developer makes on the host element (e.g. from
8
+ * devtools, refs, or web-component watchers) gets clobbered by the next
9
+ * React render even when the JSX value did not change.
10
+ *
11
+ * The fix is a per-prop `oldProps[name] === newProps[name]` early-return.
12
+ */
13
+ import "jest";
@@ -0,0 +1,136 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ /**
11
+ * Tests for attachProps reference-equality guard (VD-9458).
12
+ *
13
+ * Regression: when a parent React component re-renders, attachProps used to
14
+ * re-assign every prop on the host element unconditionally. On Next.js the
15
+ * assignment is deferred via `componentOnReady().then(...)`, which means any
16
+ * imperative mutation a developer makes on the host element (e.g. from
17
+ * devtools, refs, or web-component watchers) gets clobbered by the next
18
+ * React render even when the JSX value did not change.
19
+ *
20
+ * The fix is a per-prop `oldProps[name] === newProps[name]` early-return.
21
+ */
22
+ import "jest";
23
+ jest.mock("@heartlandone/vega", () => ({
24
+ FeatureFlag: {
25
+ // Use the synchronous attach path so assertions are deterministic.
26
+ isEnabled: (flag) => flag === "VEGA_REACT.PREVENT_CALL_WATCH_METHOD_BEFORE_COMPONENT_LOAD",
27
+ },
28
+ }), { virtual: true });
29
+ import { attachProps } from "./attachProps";
30
+ describe("attachProps reference-equality guard (VD-9458)", () => {
31
+ let node;
32
+ const setSpy = jest.fn();
33
+ beforeEach(() => {
34
+ setSpy.mockClear();
35
+ node = document.createElement("div");
36
+ // Intercept every property set so we can assert which props were
37
+ // (re-)assigned without relying on Stencil internals.
38
+ Object.defineProperty(node, "vegaDropdownProps", {
39
+ configurable: true,
40
+ set: setSpy,
41
+ get: () => undefined,
42
+ });
43
+ });
44
+ it("does not re-assign a prop whose value is referentially equal across renders", () => {
45
+ const stableProps = { allowSelectAll: false };
46
+ attachProps(node, { vegaDropdownProps: stableProps }, {});
47
+ expect(setSpy).toHaveBeenCalledTimes(1);
48
+ // Simulate a parent re-render that passes the same object reference.
49
+ attachProps(node, { vegaDropdownProps: stableProps }, { vegaDropdownProps: stableProps });
50
+ // The guard should short-circuit — no second assignment, so any value
51
+ // mutated on the host element between renders is preserved.
52
+ expect(setSpy).toHaveBeenCalledTimes(1);
53
+ });
54
+ it("still assigns when the prop reference changes between renders", () => {
55
+ const firstProps = { allowSelectAll: false };
56
+ const nextProps = { allowSelectAll: true };
57
+ attachProps(node, { vegaDropdownProps: firstProps }, {});
58
+ attachProps(node, { vegaDropdownProps: nextProps }, { vegaDropdownProps: firstProps });
59
+ expect(setSpy).toHaveBeenCalledTimes(2);
60
+ expect(setSpy).toHaveBeenLastCalledWith(nextProps);
61
+ });
62
+ it("assigns on the first render even when oldProps is empty", () => {
63
+ attachProps(node, { vegaDropdownProps: { allowSelectAll: true } }, {});
64
+ expect(setSpy).toHaveBeenCalledTimes(1);
65
+ });
66
+ it("assigns on mount when oldProps and newProps are the same object reference", () => {
67
+ const mountProps = { vegaDropdownProps: { allowSelectAll: true } };
68
+ // createReactComponent calls attachProps(this.props, this.props) on mount.
69
+ attachProps(node, mountProps, mountProps);
70
+ // Mount should still attach properties to host element.
71
+ expect(setSpy).toHaveBeenCalledTimes(1);
72
+ });
73
+ });
74
+ /**
75
+ * VD-9458 async path coverage.
76
+ *
77
+ * When the `VEGA_REACT.PREVENT_CALL_WATCH_METHOD_BEFORE_COMPONENT_LOAD` flag
78
+ * is OFF and the host element exposes `componentOnReady()`, attachProps
79
+ * defers the assignment via `node.componentOnReady().then(...)`. This is the
80
+ * exact Next.js code path where the regression was observed — React's deferred
81
+ * write landed in a microtask AFTER an imperative mutation, clobbering it.
82
+ *
83
+ * Re-import attachProps in isolation with the flag mocked to `false` so this
84
+ * suite exercises the async branch independently of the sync-path suite above.
85
+ */
86
+ describe("attachProps reference-equality guard — async componentOnReady path (VD-9458)", () => {
87
+ let asyncAttachProps;
88
+ beforeAll(() => {
89
+ jest.resetModules();
90
+ jest.doMock("@heartlandone/vega", () => ({
91
+ FeatureFlag: {
92
+ // Force the async componentOnReady().then(...) path.
93
+ isEnabled: () => false,
94
+ },
95
+ }), { virtual: true });
96
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
97
+ asyncAttachProps = require("./attachProps").attachProps;
98
+ });
99
+ function createNodeWithOnReady(setSpy) {
100
+ const el = document.createElement("div");
101
+ Object.defineProperty(el, "vegaDropdownProps", {
102
+ configurable: true,
103
+ set: setSpy,
104
+ get: () => undefined,
105
+ });
106
+ el.componentOnReady =
107
+ () => Promise.resolve(el);
108
+ return el;
109
+ }
110
+ it("does not schedule a deferred re-assignment when the prop reference is stable across renders", () => __awaiter(void 0, void 0, void 0, function* () {
111
+ const setSpy = jest.fn();
112
+ const node = createNodeWithOnReady(setSpy);
113
+ const stableProps = { allowSelectAll: false };
114
+ asyncAttachProps(node, { vegaDropdownProps: stableProps }, {});
115
+ asyncAttachProps(node, { vegaDropdownProps: stableProps }, { vegaDropdownProps: stableProps });
116
+ // Flush any pending componentOnReady().then(...) microtasks.
117
+ yield Promise.resolve();
118
+ yield Promise.resolve();
119
+ // First call must still attach (mount), second call must be skipped by
120
+ // the guard — otherwise the deferred write would clobber any imperative
121
+ // mutation made between renders (the original VD-9458 Next.js bug).
122
+ expect(setSpy).toHaveBeenCalledTimes(1);
123
+ }));
124
+ it("still schedules a deferred re-assignment when the prop reference changes between renders", () => __awaiter(void 0, void 0, void 0, function* () {
125
+ const setSpy = jest.fn();
126
+ const node = createNodeWithOnReady(setSpy);
127
+ const firstProps = { allowSelectAll: false };
128
+ const nextProps = { allowSelectAll: true };
129
+ asyncAttachProps(node, { vegaDropdownProps: firstProps }, {});
130
+ asyncAttachProps(node, { vegaDropdownProps: nextProps }, { vegaDropdownProps: firstProps });
131
+ yield Promise.resolve();
132
+ yield Promise.resolve();
133
+ expect(setSpy).toHaveBeenCalledTimes(2);
134
+ expect(setSpy).toHaveBeenLastCalledWith(nextProps);
135
+ }));
136
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@heartlandone/vega-react",
3
3
  "sideEffects": false,
4
- "version": "2.91.0",
4
+ "version": "2.93.0",
5
5
  "description": "React specific wrapper for @heartlandone/vega",
6
6
  "license": "UNLICENSED",
7
7
  "scripts": {
@@ -44,7 +44,7 @@
44
44
  "typescript": "^4.0.0"
45
45
  },
46
46
  "dependencies": {
47
- "@heartlandone/vega": "2.91.0"
47
+ "@heartlandone/vega": "2.93.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "react": ">=16.7.0",