@selvajs/ui 4.12.4 → 5.0.0-beta.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.
|
@@ -120,24 +120,53 @@
|
|
|
120
120
|
// — which throws NREs in downstream geometry components (e.g. Bounding Rectangle) and nulls
|
|
121
121
|
// every output beyond them. So every terminal state below resolves to a valid option; there
|
|
122
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
|
|
128
|
+
// system-initiated picks; any real user commit resets the budget.
|
|
129
|
+
const MAX_CONSECUTIVE_AUTO_PICKS = 3;
|
|
130
|
+
let autoPickCount = 0;
|
|
131
|
+
|
|
123
132
|
$effect(() => {
|
|
124
133
|
if (!isDynamicValueListWidget(item) || !dynamicListHasOptions) return;
|
|
125
134
|
const validValues = new Set(Object.values(dynamicListOptions));
|
|
126
135
|
const firstOption = Object.values(dynamicListOptions)[0];
|
|
136
|
+
// Already valid → stable; refill the auto-pick budget and stop.
|
|
137
|
+
const isValid = Array.isArray(value)
|
|
138
|
+
? value.length > 0 && value.every((v) => typeof v === 'string' && validValues.has(v))
|
|
139
|
+
: typeof value === 'string' && value !== '' && validValues.has(value);
|
|
140
|
+
if (isValid) {
|
|
141
|
+
autoPickCount = 0;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (autoPickCount >= MAX_CONSECUTIVE_AUTO_PICKS) {
|
|
145
|
+
console.warn(
|
|
146
|
+
`[InputControl] dynamic value list "${item.paramId}": options keep invalidating the ` +
|
|
147
|
+
`auto-picked selection (${autoPickCount} consecutive system picks) — the definition's ` +
|
|
148
|
+
`options likely depend on the selection. Stopping auto-reconciliation to avoid a solve loop.`
|
|
149
|
+
);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
127
152
|
if (Array.isArray(value)) {
|
|
128
153
|
const pruned = value.filter((v) => typeof v === 'string' && validValues.has(v));
|
|
129
154
|
// Empty (never selected or fully pruned) always falls back to the first option —
|
|
130
155
|
// a checklist that solves empty produces the same null cascade as a single value.
|
|
131
156
|
if (pruned.length !== value.length || value.length === 0) {
|
|
157
|
+
autoPickCount++;
|
|
132
158
|
onChange(item.paramId, pruned.length > 0 ? pruned : [firstOption], true);
|
|
133
159
|
}
|
|
134
160
|
} else if (typeof value !== 'string' || value === '' || !validValues.has(value)) {
|
|
135
161
|
// Never-selected or stale single value — fall back to the first option.
|
|
162
|
+
autoPickCount++;
|
|
136
163
|
onChange(item.paramId, firstOption, true);
|
|
137
164
|
}
|
|
138
165
|
});
|
|
139
166
|
|
|
140
167
|
function commit(newValue: SupportedTypes) {
|
|
168
|
+
// A real user pick re-arms the system fallback (see autoPickCount above).
|
|
169
|
+
autoPickCount = 0;
|
|
141
170
|
value = newValue;
|
|
142
171
|
onChange(item.paramId, newValue);
|
|
143
172
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selvajs/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-beta.0",
|
|
4
4
|
"description": "Shared UI components and utilities for Selva applications",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
"**/*.css"
|
|
38
38
|
],
|
|
39
39
|
"peerDependencies": {
|
|
40
|
-
"@selvajs/compute": "^3.0.
|
|
40
|
+
"@selvajs/compute": "^3.1.0-beta.3",
|
|
41
41
|
"@sveltejs/kit": "^2",
|
|
42
42
|
"bits-ui": "^2.18.0",
|
|
43
43
|
"svelte": "^5",
|
|
44
44
|
"tailwind-variants": "^3.2.2",
|
|
45
45
|
"three": "^0.184.0",
|
|
46
|
-
"@selvajs/schemas": "^4.
|
|
46
|
+
"@selvajs/schemas": "^4.7.0-beta.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependenciesMeta": {
|
|
49
49
|
"three": {
|
|
@@ -63,17 +63,17 @@
|
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@internationalized/date": "^3.12.1",
|
|
66
|
-
"@sveltejs/kit": "2.
|
|
66
|
+
"@sveltejs/kit": "2.69.1",
|
|
67
67
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
68
68
|
"@types/three": "^0.184.0",
|
|
69
69
|
"bits-ui": "^2.18.0",
|
|
70
70
|
"rhino3dm": "8.17.0",
|
|
71
71
|
"rimraf": "^6.0.1",
|
|
72
|
-
"svelte": "5.
|
|
72
|
+
"svelte": "5.56.4",
|
|
73
73
|
"tailwind-variants": "^3.2.2",
|
|
74
74
|
"vitest": "^3.2.6",
|
|
75
75
|
"@selvajs/config": "0.0.2",
|
|
76
|
-
"@selvajs/schemas": "4.
|
|
76
|
+
"@selvajs/schemas": "4.7.0-beta.0"
|
|
77
77
|
},
|
|
78
78
|
"scripts": {
|
|
79
79
|
"dev": "vite dev",
|
|
@@ -120,24 +120,53 @@
|
|
|
120
120
|
// — which throws NREs in downstream geometry components (e.g. Bounding Rectangle) and nulls
|
|
121
121
|
// every output beyond them. So every terminal state below resolves to a valid option; there
|
|
122
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
|
|
128
|
+
// system-initiated picks; any real user commit resets the budget.
|
|
129
|
+
const MAX_CONSECUTIVE_AUTO_PICKS = 3;
|
|
130
|
+
let autoPickCount = 0;
|
|
131
|
+
|
|
123
132
|
$effect(() => {
|
|
124
133
|
if (!isDynamicValueListWidget(item) || !dynamicListHasOptions) return;
|
|
125
134
|
const validValues = new Set(Object.values(dynamicListOptions));
|
|
126
135
|
const firstOption = Object.values(dynamicListOptions)[0];
|
|
136
|
+
// Already valid → stable; refill the auto-pick budget and stop.
|
|
137
|
+
const isValid = Array.isArray(value)
|
|
138
|
+
? value.length > 0 && value.every((v) => typeof v === 'string' && validValues.has(v))
|
|
139
|
+
: typeof value === 'string' && value !== '' && validValues.has(value);
|
|
140
|
+
if (isValid) {
|
|
141
|
+
autoPickCount = 0;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (autoPickCount >= MAX_CONSECUTIVE_AUTO_PICKS) {
|
|
145
|
+
console.warn(
|
|
146
|
+
`[InputControl] dynamic value list "${item.paramId}": options keep invalidating the ` +
|
|
147
|
+
`auto-picked selection (${autoPickCount} consecutive system picks) — the definition's ` +
|
|
148
|
+
`options likely depend on the selection. Stopping auto-reconciliation to avoid a solve loop.`
|
|
149
|
+
);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
127
152
|
if (Array.isArray(value)) {
|
|
128
153
|
const pruned = value.filter((v) => typeof v === 'string' && validValues.has(v));
|
|
129
154
|
// Empty (never selected or fully pruned) always falls back to the first option —
|
|
130
155
|
// a checklist that solves empty produces the same null cascade as a single value.
|
|
131
156
|
if (pruned.length !== value.length || value.length === 0) {
|
|
157
|
+
autoPickCount++;
|
|
132
158
|
onChange(item.paramId, pruned.length > 0 ? pruned : [firstOption], true);
|
|
133
159
|
}
|
|
134
160
|
} else if (typeof value !== 'string' || value === '' || !validValues.has(value)) {
|
|
135
161
|
// Never-selected or stale single value — fall back to the first option.
|
|
162
|
+
autoPickCount++;
|
|
136
163
|
onChange(item.paramId, firstOption, true);
|
|
137
164
|
}
|
|
138
165
|
});
|
|
139
166
|
|
|
140
167
|
function commit(newValue: SupportedTypes) {
|
|
168
|
+
// A real user pick re-arms the system fallback (see autoPickCount above).
|
|
169
|
+
autoPickCount = 0;
|
|
141
170
|
value = newValue;
|
|
142
171
|
onChange(item.paramId, newValue);
|
|
143
172
|
}
|