flet-box 1.0.4 → 1.0.6
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 +10 -0
- package/bin/cli.js +23 -7
- package/bin/commands/checkApi.js +761 -0
- package/bin/commands/create.js +77 -5
- package/bin/utils/dtsProps.js +191 -0
- package/bin/utils/moduleExports.js +165 -0
- package/bin/utils/neutralise.js +64 -0
- package/bin/utils/templates.js +366 -27
- package/docs/CONTRIBUTING.md +156 -0
- package/docs/README.md +68 -0
- package/docs/arquitectura.md +164 -0
- package/docs/cli/README.md +542 -0
- package/docs/guides/README.md +33 -0
- package/docs/guides/app-templates.md +197 -0
- package/docs/guides/compatibility.md +95 -0
- package/docs/guides/embedding.md +284 -0
- package/docs/guides/first-app.md +208 -0
- package/docs/guides/frontend-services.md +171 -0
- package/docs/guides/minification-and-protection.md +260 -0
- package/docs/guides/mobile-and-platforms.md +439 -0
- package/docs/guides/online-updates.md +196 -0
- package/docs/guides/router.md +282 -0
- package/docs/guides/state.md +283 -0
- package/docs/guides/syntax-philosophy.md +150 -0
- package/docs/guides/utilities.md +241 -0
- package/docs/guides/widget-structure.md +163 -0
- package/docs/server/README.md +68 -0
- package/docs/server/START_HERE.md +121 -0
- package/docs/server/api.md +225 -0
- package/docs/server/data-services.md +300 -0
- package/docs/server/deployment.md +113 -0
- package/docs/server/security.md +196 -0
- package/docs/services/README.md +79 -0
- package/docs/services/RamStore.md +84 -0
- package/docs/services/Session.md +84 -0
- package/docs/services/Storage.md +88 -0
- package/docs/services/http.md +86 -0
- package/docs/tools/README.md +138 -0
- package/docs/tools/animation.md +87 -0
- package/docs/tools/async-ids-log.md +80 -0
- package/docs/tools/device.md +132 -0
- package/docs/tools/introspection.md +67 -0
- package/docs/tools/lists.md +96 -0
- package/docs/tools/markdown.md +79 -0
- package/docs/tools/reactivity.md +288 -0
- package/docs/tools/state.md +103 -0
- package/docs/tools/styling.md +92 -0
- package/docs/tools/text-and-time.md +89 -0
- package/docs/tools/theme.md +102 -0
- package/docs/tools/validation.md +75 -0
- package/docs/tools/visual-effects.md +84 -0
- package/docs/widget/Accordion.md +144 -0
- package/docs/widget/AdMob.md +144 -0
- package/docs/widget/AdSense.md +133 -0
- package/docs/widget/AdaptiveScaffold.md +105 -0
- package/docs/widget/AlertDialog.md +103 -0
- package/docs/widget/AnimatedBox.md +130 -0
- package/docs/widget/AnimatedText.md +123 -0
- package/docs/widget/AppBar.md +118 -0
- package/docs/widget/Audio.md +135 -0
- package/docs/widget/Avatar.md +119 -0
- package/docs/widget/Badge.md +128 -0
- package/docs/widget/BottomNavigation.md +116 -0
- package/docs/widget/BottomSheet.md +125 -0
- package/docs/widget/Button.md +137 -0
- package/docs/widget/COMMON_PROPS.md +150 -0
- package/docs/widget/Card.md +125 -0
- package/docs/widget/Carousel.md +151 -0
- package/docs/widget/Chart.md +151 -0
- package/docs/widget/Checkbox.md +118 -0
- package/docs/widget/Chip.md +126 -0
- package/docs/widget/CircularBar.md +168 -0
- package/docs/widget/CircularChart.md +164 -0
- package/docs/widget/CodeViewer.md +130 -0
- package/docs/widget/CollapsibleSideBar.md +112 -0
- package/docs/widget/Column.md +129 -0
- package/docs/widget/Container.md +136 -0
- package/docs/widget/DataTable.md +150 -0
- package/docs/widget/Divider.md +109 -0
- package/docs/widget/DraggBox.md +129 -0
- package/docs/widget/Drawer.md +135 -0
- package/docs/widget/DrawerItem.md +98 -0
- package/docs/widget/Dropdown.md +139 -0
- package/docs/widget/DroppBox.md +152 -0
- package/docs/widget/FloatingActionButton.md +120 -0
- package/docs/widget/GridView.md +140 -0
- package/docs/widget/Icon.md +117 -0
- package/docs/widget/Image.md +125 -0
- package/docs/widget/Input.md +151 -0
- package/docs/widget/Inspector.md +92 -0
- package/docs/widget/InstallButton.md +128 -0
- package/docs/widget/ListTile.md +132 -0
- package/docs/widget/ListView.md +138 -0
- package/docs/widget/Markdown.md +175 -0
- package/docs/widget/MatrixRain.md +122 -0
- package/docs/widget/Modal.md +129 -0
- package/docs/widget/Pagination.md +135 -0
- package/docs/widget/ParallaxBox.md +128 -0
- package/docs/widget/ProgressBar.md +130 -0
- package/docs/widget/QRCode.md +117 -0
- package/docs/widget/README.md +188 -0
- package/docs/widget/Radio.md +123 -0
- package/docs/widget/Rating.md +131 -0
- package/docs/widget/Row.md +131 -0
- package/docs/widget/START_HERE.md +146 -0
- package/docs/widget/Scaffold.md +152 -0
- package/docs/widget/Skeleton.md +130 -0
- package/docs/widget/Slider.md +146 -0
- package/docs/widget/SnackBar.md +102 -0
- package/docs/widget/Stack.md +128 -0
- package/docs/widget/Stepper.md +145 -0
- package/docs/widget/Switch.md +118 -0
- package/docs/widget/Tabs.md +124 -0
- package/docs/widget/Text.md +133 -0
- package/docs/widget/Tooltip.md +145 -0
- package/docs/widget/TreeView.md +176 -0
- package/docs/widget/Video.md +127 -0
- package/docs/widget/_template.md +61 -0
- package/package.json +3 -2
- package/src/animations/animate.js +180 -70
- package/src/animations/index.js +1 -0
- package/src/create-index.sh +1 -1
- package/src/index.d.ts +45 -22
- package/src/index.js +1 -0
- package/src/navigations/AdaptiveScaffold.js +11 -2
- package/src/navigations/BottomNavigation.js +10 -3
- package/src/navigations/Tabs.js +6 -2
- package/src/tools/useState.js +54 -63
- package/src/widget-factory/assignProps.js +23 -1
- package/src/widget-factory/processProps.js +10 -3
- package/src/widget-factory/reactiveValue.js +43 -18
- package/src/widget-factory/translateProps.js +777 -901
- package/src/widgets/AlertDialog.js +7 -5
- package/src/widgets/Checkbox.js +9 -7
- package/src/widgets/CircularBar.js +12 -1
- package/src/widgets/FloatingActionButton.js +4 -7
- package/src/widgets/Image.js +5 -7
- package/src/widgets/Input.js +92 -0
- package/src/widgets/ListView.js +73 -29
- package/src/widgets/Pagination.js +6 -3
- package/src/widgets/ProgressBar.js +16 -13
- package/src/widgets/Radio.js +6 -7
- package/src/widgets/Slider.js +23 -4
- package/src/widgets/Stack.js +36 -3
- package/src/widgets/Stepper.js +4 -3
- package/src/widgets/Switch.js +3 -4
- package/src/widgets/TreeView.js +0 -1
package/src/tools/useState.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// src/tools/useState.js
|
|
2
2
|
import { saveRam, getRam, subscribeRam } from "../services/RamStore.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createReactiveValue,
|
|
5
|
+
isStructuralRead,
|
|
6
|
+
resetStructuralReads,
|
|
7
|
+
} from "../widget-factory/reactiveValue.js";
|
|
4
8
|
|
|
5
9
|
// Store callbacks for each state key
|
|
6
10
|
const subscribers = new Map();
|
|
@@ -8,13 +12,6 @@ const subscribers = new Map();
|
|
|
8
12
|
// 🔥 Store widgets by state key for automatic updates
|
|
9
13
|
const widgetsByKey = new Map();
|
|
10
14
|
|
|
11
|
-
// Keys the component reads *structurally* — inside a template literal, a
|
|
12
|
-
// conditional, a list. Those need the app render even when a widget is bound to
|
|
13
|
-
// the key, because a surgical prop patch cannot change what the component
|
|
14
|
-
// computes. A key passed straight into a prop is never listed here.
|
|
15
|
-
const imperativeKeys = new Set();
|
|
16
|
-
setImperativeReadListener((key) => imperativeKeys.add(key));
|
|
17
|
-
|
|
18
15
|
// Global render function (set by runApp)
|
|
19
16
|
let globalRender = null;
|
|
20
17
|
let renderTimeout = null;
|
|
@@ -81,10 +78,10 @@ const ensureRamDispatcher = () => {
|
|
|
81
78
|
// 2. Notify the manual subscribers.
|
|
82
79
|
callbacks.forEach((callback) => callback(newValue, oldValue));
|
|
83
80
|
|
|
84
|
-
// 3. Re-render the app when nothing was bound, or when the
|
|
85
|
-
//
|
|
86
|
-
// the surgical patch above cannot change what the component computes.
|
|
87
|
-
if (handled === 0 ||
|
|
81
|
+
// 3. Re-render the app when nothing was bound, or when the last render pass
|
|
82
|
+
// also read this key structurally (a template literal, a conditional, a
|
|
83
|
+
// list): the surgical patch above cannot change what the component computes.
|
|
84
|
+
if (handled === 0 || isStructuralRead(changedKey)) triggerRender();
|
|
88
85
|
});
|
|
89
86
|
|
|
90
87
|
if (typeof window !== "undefined") {
|
|
@@ -131,38 +128,21 @@ export const registerWidget = (key, widget, propName) => {
|
|
|
131
128
|
if (already) return;
|
|
132
129
|
|
|
133
130
|
// Release the binding through `onUnmount`, which the framework fires on BOTH
|
|
134
|
-
// teardown paths. Patching `_cleanup`
|
|
131
|
+
// teardown paths. Patching `_cleanup` (the previous approach) leaked: the
|
|
135
132
|
// lifecycle observer never calls `_cleanup`, so a widget removed without a full
|
|
136
133
|
// app rebuild stayed registered forever and kept receiving updates.
|
|
137
134
|
const release = () => {
|
|
138
|
-
const
|
|
139
|
-
const props =
|
|
135
|
+
const perWidget = widgetsByKey.get(key);
|
|
136
|
+
const props = perWidget && perWidget.get(widget);
|
|
140
137
|
if (!props) return;
|
|
141
138
|
props.delete(propName);
|
|
142
|
-
if (props.size === 0)
|
|
139
|
+
if (props.size === 0) perWidget.delete(widget);
|
|
140
|
+
if (perWidget.size === 0) widgetsByKey.delete(key);
|
|
143
141
|
};
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const originalCleanup = widget._cleanup;
|
|
149
|
-
widget._cleanup = () => {
|
|
150
|
-
release();
|
|
151
|
-
if (originalCleanup) originalCleanup();
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Removes a specific widget from the automatic-update registry for a given key.
|
|
158
|
-
*
|
|
159
|
-
* @param {string} key - The state key the widget was registered under.
|
|
160
|
-
* @param {Object} widget - The widget instance to unregister.
|
|
161
|
-
* @returns {void}
|
|
162
|
-
*/
|
|
163
|
-
export const unregisterWidget = (key, widget) => {
|
|
164
|
-
const perWidget = widgetsByKey.get(key);
|
|
165
|
-
if (perWidget) perWidget.delete(widget);
|
|
143
|
+
// The factory installs lifecycle hooks before it binds reactive props, so a
|
|
144
|
+
// widget reaching this point always owns `onUnmount`.
|
|
145
|
+
widget.onUnmount(release);
|
|
166
146
|
};
|
|
167
147
|
|
|
168
148
|
/**
|
|
@@ -184,11 +164,12 @@ export const unregisterWidget = (key, widget) => {
|
|
|
184
164
|
*/
|
|
185
165
|
export const useState = (key, initialState, widget = null, propName = null) => {
|
|
186
166
|
// Initialize the state in RamStore if it does not exist yet
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
167
|
+
const existing = getRam(key);
|
|
168
|
+
if (existing === undefined || existing === null) {
|
|
169
|
+
saveRam(
|
|
170
|
+
key,
|
|
171
|
+
typeof initialState === "function" ? initialState() : initialState,
|
|
172
|
+
);
|
|
192
173
|
}
|
|
193
174
|
|
|
194
175
|
// Register the key so the shared dispatcher knows it has listeners. Previously
|
|
@@ -212,19 +193,18 @@ export const useState = (key, initialState, widget = null, propName = null) => {
|
|
|
212
193
|
* @returns {void}
|
|
213
194
|
*/
|
|
214
195
|
const setState = (newValueOrUpdater) => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
196
|
+
// Read the store rather than a value captured when the hook ran: a setter
|
|
197
|
+
// kept across renders would otherwise compare against a stale `oldValue` and
|
|
198
|
+
// skip the dispatch when an external `saveState` had already moved the key to
|
|
199
|
+
// the value being set — leaving the store, the widgets and the callbacks
|
|
200
|
+
// disagreeing about what the state is.
|
|
201
|
+
const oldValue = getRam(key);
|
|
202
|
+
const newValue =
|
|
203
|
+
typeof newValueOrUpdater === "function"
|
|
204
|
+
? newValueOrUpdater(oldValue)
|
|
205
|
+
: newValueOrUpdater;
|
|
206
|
+
|
|
207
|
+
if (oldValue !== newValue) saveRam(key, newValue);
|
|
228
208
|
};
|
|
229
209
|
|
|
230
210
|
/**
|
|
@@ -239,9 +219,7 @@ export const useState = (key, initialState, widget = null, propName = null) => {
|
|
|
239
219
|
}
|
|
240
220
|
subscribers.get(key).add(callback);
|
|
241
221
|
return () => {
|
|
242
|
-
|
|
243
|
-
subscribers.get(key).delete(callback);
|
|
244
|
-
}
|
|
222
|
+
subscribers.get(key)?.delete(callback);
|
|
245
223
|
};
|
|
246
224
|
};
|
|
247
225
|
|
|
@@ -268,7 +246,9 @@ export const useWatchState = (key, callback) => {
|
|
|
268
246
|
subscribers.set(key, new Set());
|
|
269
247
|
}
|
|
270
248
|
subscribers.get(key).add(callback);
|
|
271
|
-
return () =>
|
|
249
|
+
return () => {
|
|
250
|
+
subscribers.get(key)?.delete(callback);
|
|
251
|
+
};
|
|
272
252
|
};
|
|
273
253
|
|
|
274
254
|
/**
|
|
@@ -286,7 +266,7 @@ export const cleanupUseState = () => {
|
|
|
286
266
|
}
|
|
287
267
|
subscribers.clear();
|
|
288
268
|
widgetsByKey.clear();
|
|
289
|
-
|
|
269
|
+
resetStructuralReads();
|
|
290
270
|
// The dispatcher was unsubscribed above, so a later call must reinstall it.
|
|
291
271
|
ramDispatcherReady = false;
|
|
292
272
|
globalRender = null;
|
|
@@ -297,12 +277,23 @@ export const cleanupUseState = () => {
|
|
|
297
277
|
* Returns diagnostic statistics about the current state of the useState module.
|
|
298
278
|
* Useful for debugging subscription leaks or widget registration issues.
|
|
299
279
|
*
|
|
300
|
-
* @returns {{
|
|
280
|
+
* @returns {{ stateKeys: number, boundWidgets: number, watchers: number, globalRender: boolean }}
|
|
301
281
|
*/
|
|
302
282
|
export const getUseStateStats = () => {
|
|
283
|
+
// `widgetsByKey` maps key -> widget -> prop names, so its size is the number of
|
|
284
|
+
// watched *keys*, not of widgets. Count one per widget.
|
|
285
|
+
let boundWidgets = 0;
|
|
286
|
+
let watchers = 0;
|
|
287
|
+
widgetsByKey.forEach((perWidget) => {
|
|
288
|
+
boundWidgets += perWidget.size;
|
|
289
|
+
});
|
|
290
|
+
subscribers.forEach((callbacks) => {
|
|
291
|
+
watchers += callbacks.size;
|
|
292
|
+
});
|
|
303
293
|
return {
|
|
304
|
-
|
|
305
|
-
|
|
294
|
+
stateKeys: subscribers.size,
|
|
295
|
+
boundWidgets,
|
|
296
|
+
watchers,
|
|
306
297
|
globalRender: !!globalRender,
|
|
307
298
|
};
|
|
308
299
|
};
|
|
@@ -59,6 +59,28 @@ const applySpecial = (widget, key, value) => {
|
|
|
59
59
|
case "variant":
|
|
60
60
|
widget.dataset.variant = val;
|
|
61
61
|
break;
|
|
62
|
+
case "visible": {
|
|
63
|
+
// Hiding must not destroy the `display` the widget's own styles chose
|
|
64
|
+
// (`flex` for a Row, `inline-flex` for an Input…), so the previous inline
|
|
65
|
+
// value is stashed and handed back on show. The stash is only reused while
|
|
66
|
+
// we are still the reason the element is hidden: if an update set a new
|
|
67
|
+
// `display` in the meantime, that value is the truth and is left alone.
|
|
68
|
+
if (val) {
|
|
69
|
+
if (
|
|
70
|
+
widget._hiddenByVisibleProp !== undefined &&
|
|
71
|
+
widget.style.display === "none"
|
|
72
|
+
) {
|
|
73
|
+
widget.style.display = widget._hiddenByVisibleProp;
|
|
74
|
+
}
|
|
75
|
+
delete widget._hiddenByVisibleProp;
|
|
76
|
+
} else if (widget.style.display !== "none") {
|
|
77
|
+
if (widget._hiddenByVisibleProp === undefined) {
|
|
78
|
+
widget._hiddenByVisibleProp = widget.style.display;
|
|
79
|
+
}
|
|
80
|
+
widget.style.display = "none";
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
62
84
|
}
|
|
63
85
|
};
|
|
64
86
|
|
|
@@ -72,7 +94,7 @@ const applySpecial = (widget, key, value) => {
|
|
|
72
94
|
* `click` is assigned via `onclick` for reliable override behaviour.
|
|
73
95
|
* 4. **attributes** – applied with `setAttribute`, with special handling for
|
|
74
96
|
* `className` and boolean attributes (`disabled`, `readOnly`, `hidden`, …).
|
|
75
|
-
* 5. **special** – delegated to `applySpecial` (
|
|
97
|
+
* 5. **special** – delegated to `applySpecial` (`value`, `variant`, `visible`).
|
|
76
98
|
*
|
|
77
99
|
* Any keys not in the known set are handled as a flat-prop fallback: `on*`
|
|
78
100
|
* functions become event listeners, common HTML attributes are set directly, and
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// core/processProps.js
|
|
2
|
-
import { getPropDefinition, ATTRIBUTE_DOM_SET } from "./translateProps.js";
|
|
2
|
+
import { getPropDefinition, ATTRIBUTE_DOM_SET, PASSTHROUGH_PREFIXES } from "./translateProps.js";
|
|
3
3
|
import { toREM } from "./remTool.js"; // Centralized REM conversion utility
|
|
4
4
|
import { resolveReactive } from "./reactiveValue.js";
|
|
5
5
|
|
|
@@ -53,6 +53,7 @@ export const processProps = (props, tag = "div", widgetName = null) => {
|
|
|
53
53
|
// Shorthands may fan out to several CSS props (e.g. `px` →
|
|
54
54
|
// paddingLeft + paddingRight). Everything else writes to its domProp.
|
|
55
55
|
const cssValue = applyUnit(def, value);
|
|
56
|
+
if (cssValue === undefined) break;
|
|
56
57
|
const targets = def.targets || [def.domProp];
|
|
57
58
|
for (const target of targets) style[target] = cssValue;
|
|
58
59
|
break;
|
|
@@ -84,7 +85,7 @@ export const processProps = (props, tag = "div", widgetName = null) => {
|
|
|
84
85
|
}
|
|
85
86
|
} else {
|
|
86
87
|
// Unknown prop → intelligent fallback
|
|
87
|
-
if (
|
|
88
|
+
if (PASSTHROUGH_PREFIXES.some((prefix) => key.startsWith(prefix))) {
|
|
88
89
|
attributes[key] = value;
|
|
89
90
|
} else if (key.startsWith("on") && typeof value === "function") {
|
|
90
91
|
const eventName = key.slice(2).toLowerCase();
|
|
@@ -103,6 +104,9 @@ export const processProps = (props, tag = "div", widgetName = null) => {
|
|
|
103
104
|
/**
|
|
104
105
|
* Applies the correct CSS unit to a numeric value based on the prop definition.
|
|
105
106
|
*
|
|
107
|
+
* - A prop with `boolValues` maps `true`/`false` to the CSS the property
|
|
108
|
+
* actually accepts (e.g. `wrap: true` → `flex-wrap: wrap`); an `undefined`
|
|
109
|
+
* mapping skips the assignment.
|
|
106
110
|
* - `true` / `false` are coerced to `1` / `0` (unitless).
|
|
107
111
|
* - Non-numeric values pass through unchanged.
|
|
108
112
|
* - `unit: "none"` returns the raw number without a suffix.
|
|
@@ -111,9 +115,12 @@ export const processProps = (props, tag = "div", widgetName = null) => {
|
|
|
111
115
|
*
|
|
112
116
|
* @param {Object} def - Prop definition from the prop database (see `translateProps.js`).
|
|
113
117
|
* @param {*} value - The value to convert.
|
|
114
|
-
* @returns {string|number} Value with the correct CSS unit applied.
|
|
118
|
+
* @returns {string|number|undefined} Value with the correct CSS unit applied.
|
|
115
119
|
*/
|
|
116
120
|
function applyUnit(def, value) {
|
|
121
|
+
if (def.boolValues && typeof value === "boolean") {
|
|
122
|
+
return def.boolValues[String(value)];
|
|
123
|
+
}
|
|
117
124
|
if (value === true) return 1;
|
|
118
125
|
if (value === false) return 0;
|
|
119
126
|
if (typeof value !== "number") return value;
|
|
@@ -38,28 +38,48 @@ let resolving = false;
|
|
|
38
38
|
*/
|
|
39
39
|
let renderPassDepth = 0;
|
|
40
40
|
|
|
41
|
-
/** Called with the state key when the component reads a reactive value. */
|
|
42
|
-
let onImperativeRead = null;
|
|
43
|
-
|
|
44
41
|
/**
|
|
45
|
-
*
|
|
46
|
-
* know which keys still need the app render in addition to the surgical patch.
|
|
42
|
+
* Keys the component read structurally during the pass that just finished.
|
|
47
43
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
44
|
+
* Rebuilt by every render pass: a component re-reads everything it consumes, so
|
|
45
|
+
* what the last pass saw is the complete picture. Keeping the keys of *earlier*
|
|
46
|
+
* passes instead would pin the app to full re-renders forever after one
|
|
47
|
+
* conditional read, which is exactly what the surgical binding exists to avoid.
|
|
50
48
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
let structuralKeys = new Set();
|
|
50
|
+
|
|
51
|
+
/** The keys collected by the pass currently running, or `null` between passes. */
|
|
52
|
+
let passKeys = null;
|
|
54
53
|
|
|
55
54
|
/** Marks the start of a component-render pass. */
|
|
56
55
|
export const beginRenderPass = () => {
|
|
56
|
+
if (renderPassDepth === 0) passKeys = new Set();
|
|
57
57
|
renderPassDepth++;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
/** Marks the end of a component-render pass. */
|
|
60
|
+
/** Marks the end of a component-render pass and publishes what it observed. */
|
|
61
61
|
export const endRenderPass = () => {
|
|
62
|
-
if (renderPassDepth
|
|
62
|
+
if (renderPassDepth === 0) return;
|
|
63
|
+
renderPassDepth--;
|
|
64
|
+
if (renderPassDepth === 0) {
|
|
65
|
+
if (passKeys) structuralKeys = passKeys;
|
|
66
|
+
passKeys = null;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* True when the last render pass consumed `key` structurally, so a change to it
|
|
72
|
+
* needs an app render on top of the surgical prop patch.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} key
|
|
75
|
+
* @returns {boolean}
|
|
76
|
+
*/
|
|
77
|
+
export const isStructuralRead = (key) => structuralKeys.has(key);
|
|
78
|
+
|
|
79
|
+
/** Forgets every observed key. Used by hot-reload teardown. */
|
|
80
|
+
export const resetStructuralReads = () => {
|
|
81
|
+
structuralKeys = new Set();
|
|
82
|
+
passKeys = renderPassDepth > 0 ? new Set() : null;
|
|
63
83
|
};
|
|
64
84
|
|
|
65
85
|
/**
|
|
@@ -90,11 +110,15 @@ export const reactiveKeyOf = (val) => (isReactive(val) ? val._stateKey ?? null :
|
|
|
90
110
|
*/
|
|
91
111
|
export const resolveReactive = (val) => {
|
|
92
112
|
if (typeof val === "function" && val._isReactive) {
|
|
113
|
+
// Save and restore instead of clearing: a reactive whose `valueOf` reads
|
|
114
|
+
// another one would otherwise leave the inner read flagged as the factory's
|
|
115
|
+
// and the outer one reported as a structural read.
|
|
116
|
+
const wasResolving = resolving;
|
|
93
117
|
resolving = true;
|
|
94
118
|
try {
|
|
95
119
|
return val();
|
|
96
120
|
} finally {
|
|
97
|
-
resolving =
|
|
121
|
+
resolving = wasResolving;
|
|
98
122
|
}
|
|
99
123
|
}
|
|
100
124
|
if (val && typeof val === "object" && val._isReactive === true) {
|
|
@@ -111,9 +135,9 @@ export const resolveReactive = (val) => {
|
|
|
111
135
|
* arithmetic work) and a `_value` getter (so the legacy `state._value` read
|
|
112
136
|
* keeps working — now live instead of frozen).
|
|
113
137
|
*
|
|
114
|
-
* Every read that the component itself performs during a render pass is
|
|
115
|
-
*
|
|
116
|
-
* structurally and the app must re-render too.
|
|
138
|
+
* Every read that the component itself performs during a render pass is recorded
|
|
139
|
+
* in the pass's structural-key set, because such a read means the value is
|
|
140
|
+
* consumed structurally and the app must re-render too.
|
|
117
141
|
*
|
|
118
142
|
* @param {() => *} read - Reads the current value from the store.
|
|
119
143
|
* @param {string} key - The state key this value is bound to.
|
|
@@ -122,7 +146,7 @@ export const resolveReactive = (val) => {
|
|
|
122
146
|
export const createReactiveValue = (read, key) => {
|
|
123
147
|
const live = () => {
|
|
124
148
|
const value = read();
|
|
125
|
-
if (!resolving &&
|
|
149
|
+
if (!resolving && passKeys) passKeys.add(key);
|
|
126
150
|
return value;
|
|
127
151
|
};
|
|
128
152
|
live._isReactive = true;
|
|
@@ -141,7 +165,8 @@ export default {
|
|
|
141
165
|
reactiveKeyOf,
|
|
142
166
|
resolveReactive,
|
|
143
167
|
createReactiveValue,
|
|
144
|
-
|
|
168
|
+
isStructuralRead,
|
|
169
|
+
resetStructuralReads,
|
|
145
170
|
beginRenderPass,
|
|
146
171
|
endRenderPass,
|
|
147
172
|
};
|