@selvajs/ui 4.12.3 → 4.12.5
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.
|
@@ -110,20 +110,21 @@
|
|
|
110
110
|
);
|
|
111
111
|
|
|
112
112
|
// When a dynamic value list recomputes, a previously-selected value may no longer be an
|
|
113
|
-
// available option. Prune the stale selection so the control shows a valid option
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
// DEPEND on the selection can oscillate
|
|
125
|
-
// exclude A → auto-pick B → solve → … Each cycle force-
|
|
126
|
-
// the
|
|
113
|
+
// available option. Prune the stale selection so the control shows a valid option instead
|
|
114
|
+
// of rendering the orphaned raw value as its own label. This is a system-initiated change
|
|
115
|
+
// (the user didn't pick the new option), so force a solve — otherwise manual-solve schemas
|
|
116
|
+
// would keep the prior output on screen, making it look like the auto-picked option produced it.
|
|
117
|
+
//
|
|
118
|
+
// INVARIANT: a dynamic value list must never dispatch an empty/null value to solve. There
|
|
119
|
+
// is always at least one option, and an empty selection reaches the definition as null/""
|
|
120
|
+
// — which throws NREs in downstream geometry components (e.g. Bounding Rectangle) and nulls
|
|
121
|
+
// every output beyond them. So every terminal state below resolves to a valid option; there
|
|
122
|
+
// is deliberately no "user cleared it, stay empty" path.
|
|
123
|
+
//
|
|
124
|
+
// LOOP BREAKER: a definition whose computed options DEPEND on the selection can oscillate —
|
|
125
|
+
// auto-pick A → solve → new options exclude A → auto-pick B → solve → … Each cycle force-
|
|
126
|
+
// solves and re-parses/re-renders the (possibly multi-MB) options, which FREEZES the tab
|
|
127
|
+
// (each solve result blocks the main thread; the loop never yields). Bound consecutive
|
|
127
128
|
// system-initiated picks; any real user commit resets the budget.
|
|
128
129
|
const MAX_CONSECUTIVE_AUTO_PICKS = 3;
|
|
129
130
|
let autoPickCount = 0;
|
|
@@ -132,7 +133,7 @@
|
|
|
132
133
|
if (!isDynamicValueListWidget(item) || !dynamicListHasOptions) return;
|
|
133
134
|
const validValues = new Set(Object.values(dynamicListOptions));
|
|
134
135
|
const firstOption = Object.values(dynamicListOptions)[0];
|
|
135
|
-
//
|
|
136
|
+
// Already valid → stable; refill the auto-pick budget and stop.
|
|
136
137
|
const isValid = Array.isArray(value)
|
|
137
138
|
? value.length > 0 && value.every((v) => typeof v === 'string' && validValues.has(v))
|
|
138
139
|
: typeof value === 'string' && value !== '' && validValues.has(value);
|
|
@@ -148,42 +149,22 @@
|
|
|
148
149
|
);
|
|
149
150
|
return;
|
|
150
151
|
}
|
|
151
|
-
// Route through onChange (not commit) — value is a one-way prop here, so writing it
|
|
152
|
-
// directly from an effect trips Svelte's binding-ownership check.
|
|
153
|
-
// A NEVER-made selection (empty string/array, e.g. no default on a fresh page)
|
|
154
|
-
// gets the same first-option fallback as a stale one: an empty selection solves
|
|
155
|
-
// as an empty string, which cascades through the definition as null-data errors
|
|
156
|
-
// ("File not found: .dmf", Text→Number conversion failures, …) and the empty
|
|
157
|
-
// result then gets replayed by the solve caches.
|
|
158
|
-
// Diagnostic: each system pick logs itself — a runaway sequence of these lines
|
|
159
|
-
// (pick #1, #2, #3 + the loop-breaker warning) is the solve-loop signature.
|
|
160
|
-
const logAutoPick = (picked: string) =>
|
|
161
|
-
console.info(
|
|
162
|
-
`[DVL] auto-pick #${autoPickCount} on "${item.paramId}": "${picked.slice(0, 60)}" ` +
|
|
163
|
-
`(was ${JSON.stringify(Array.isArray(value) ? value.slice(0, 3) : value)?.slice(0, 80)})`
|
|
164
|
-
);
|
|
165
152
|
if (Array.isArray(value)) {
|
|
166
153
|
const pruned = value.filter((v) => typeof v === 'string' && validValues.has(v));
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
154
|
+
// Empty (never selected or fully pruned) always falls back to the first option —
|
|
155
|
+
// a checklist that solves empty produces the same null cascade as a single value.
|
|
156
|
+
if (pruned.length !== value.length || value.length === 0) {
|
|
170
157
|
autoPickCount++;
|
|
171
|
-
logAutoPick(pruned.length > 0 ? String(pruned[0]) : firstOption);
|
|
172
158
|
onChange(item.paramId, pruned.length > 0 ? pruned : [firstOption], true);
|
|
173
159
|
}
|
|
174
|
-
} else if (
|
|
175
|
-
|
|
176
|
-
((value == null || value === '') && !userTouched)
|
|
177
|
-
) {
|
|
178
|
-
// Stale single value, or never-selected — fall back to the first option.
|
|
160
|
+
} else if (typeof value !== 'string' || value === '' || !validValues.has(value)) {
|
|
161
|
+
// Never-selected or stale single value — fall back to the first option.
|
|
179
162
|
autoPickCount++;
|
|
180
|
-
logAutoPick(firstOption);
|
|
181
163
|
onChange(item.paramId, firstOption, true);
|
|
182
164
|
}
|
|
183
165
|
});
|
|
184
166
|
|
|
185
167
|
function commit(newValue: SupportedTypes) {
|
|
186
|
-
userTouched = true;
|
|
187
168
|
// A real user pick re-arms the system fallback (see autoPickCount above).
|
|
188
169
|
autoPickCount = 0;
|
|
189
170
|
value = newValue;
|
package/package.json
CHANGED
|
@@ -110,20 +110,21 @@
|
|
|
110
110
|
);
|
|
111
111
|
|
|
112
112
|
// When a dynamic value list recomputes, a previously-selected value may no longer be an
|
|
113
|
-
// available option. Prune the stale selection so the control shows a valid option
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
// DEPEND on the selection can oscillate
|
|
125
|
-
// exclude A → auto-pick B → solve → … Each cycle force-
|
|
126
|
-
// the
|
|
113
|
+
// available option. Prune the stale selection so the control shows a valid option instead
|
|
114
|
+
// of rendering the orphaned raw value as its own label. This is a system-initiated change
|
|
115
|
+
// (the user didn't pick the new option), so force a solve — otherwise manual-solve schemas
|
|
116
|
+
// would keep the prior output on screen, making it look like the auto-picked option produced it.
|
|
117
|
+
//
|
|
118
|
+
// INVARIANT: a dynamic value list must never dispatch an empty/null value to solve. There
|
|
119
|
+
// is always at least one option, and an empty selection reaches the definition as null/""
|
|
120
|
+
// — which throws NREs in downstream geometry components (e.g. Bounding Rectangle) and nulls
|
|
121
|
+
// every output beyond them. So every terminal state below resolves to a valid option; there
|
|
122
|
+
// is deliberately no "user cleared it, stay empty" path.
|
|
123
|
+
//
|
|
124
|
+
// LOOP BREAKER: a definition whose computed options DEPEND on the selection can oscillate —
|
|
125
|
+
// auto-pick A → solve → new options exclude A → auto-pick B → solve → … Each cycle force-
|
|
126
|
+
// solves and re-parses/re-renders the (possibly multi-MB) options, which FREEZES the tab
|
|
127
|
+
// (each solve result blocks the main thread; the loop never yields). Bound consecutive
|
|
127
128
|
// system-initiated picks; any real user commit resets the budget.
|
|
128
129
|
const MAX_CONSECUTIVE_AUTO_PICKS = 3;
|
|
129
130
|
let autoPickCount = 0;
|
|
@@ -132,7 +133,7 @@
|
|
|
132
133
|
if (!isDynamicValueListWidget(item) || !dynamicListHasOptions) return;
|
|
133
134
|
const validValues = new Set(Object.values(dynamicListOptions));
|
|
134
135
|
const firstOption = Object.values(dynamicListOptions)[0];
|
|
135
|
-
//
|
|
136
|
+
// Already valid → stable; refill the auto-pick budget and stop.
|
|
136
137
|
const isValid = Array.isArray(value)
|
|
137
138
|
? value.length > 0 && value.every((v) => typeof v === 'string' && validValues.has(v))
|
|
138
139
|
: typeof value === 'string' && value !== '' && validValues.has(value);
|
|
@@ -148,42 +149,22 @@
|
|
|
148
149
|
);
|
|
149
150
|
return;
|
|
150
151
|
}
|
|
151
|
-
// Route through onChange (not commit) — value is a one-way prop here, so writing it
|
|
152
|
-
// directly from an effect trips Svelte's binding-ownership check.
|
|
153
|
-
// A NEVER-made selection (empty string/array, e.g. no default on a fresh page)
|
|
154
|
-
// gets the same first-option fallback as a stale one: an empty selection solves
|
|
155
|
-
// as an empty string, which cascades through the definition as null-data errors
|
|
156
|
-
// ("File not found: .dmf", Text→Number conversion failures, …) and the empty
|
|
157
|
-
// result then gets replayed by the solve caches.
|
|
158
|
-
// Diagnostic: each system pick logs itself — a runaway sequence of these lines
|
|
159
|
-
// (pick #1, #2, #3 + the loop-breaker warning) is the solve-loop signature.
|
|
160
|
-
const logAutoPick = (picked: string) =>
|
|
161
|
-
console.info(
|
|
162
|
-
`[DVL] auto-pick #${autoPickCount} on "${item.paramId}": "${picked.slice(0, 60)}" ` +
|
|
163
|
-
`(was ${JSON.stringify(Array.isArray(value) ? value.slice(0, 3) : value)?.slice(0, 80)})`
|
|
164
|
-
);
|
|
165
152
|
if (Array.isArray(value)) {
|
|
166
153
|
const pruned = value.filter((v) => typeof v === 'string' && validValues.has(v));
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
154
|
+
// Empty (never selected or fully pruned) always falls back to the first option —
|
|
155
|
+
// a checklist that solves empty produces the same null cascade as a single value.
|
|
156
|
+
if (pruned.length !== value.length || value.length === 0) {
|
|
170
157
|
autoPickCount++;
|
|
171
|
-
logAutoPick(pruned.length > 0 ? String(pruned[0]) : firstOption);
|
|
172
158
|
onChange(item.paramId, pruned.length > 0 ? pruned : [firstOption], true);
|
|
173
159
|
}
|
|
174
|
-
} else if (
|
|
175
|
-
|
|
176
|
-
((value == null || value === '') && !userTouched)
|
|
177
|
-
) {
|
|
178
|
-
// Stale single value, or never-selected — fall back to the first option.
|
|
160
|
+
} else if (typeof value !== 'string' || value === '' || !validValues.has(value)) {
|
|
161
|
+
// Never-selected or stale single value — fall back to the first option.
|
|
179
162
|
autoPickCount++;
|
|
180
|
-
logAutoPick(firstOption);
|
|
181
163
|
onChange(item.paramId, firstOption, true);
|
|
182
164
|
}
|
|
183
165
|
});
|
|
184
166
|
|
|
185
167
|
function commit(newValue: SupportedTypes) {
|
|
186
|
-
userTouched = true;
|
|
187
168
|
// A real user pick re-arms the system fallback (see autoPickCount above).
|
|
188
169
|
autoPickCount = 0;
|
|
189
170
|
value = newValue;
|