@almadar/ui 2.58.0 → 2.59.3
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/avl/index.cjs +45078 -40666
- package/dist/avl/index.js +44821 -40409
- package/dist/components/atoms/TraitFrame.d.ts +54 -0
- package/dist/components/atoms/index.d.ts +1 -0
- package/dist/components/index.cjs +37022 -34434
- package/dist/components/index.js +36700 -33935
- package/dist/context/index.cjs +73 -5
- package/dist/context/index.js +73 -5
- package/dist/hooks/index.cjs +73 -5
- package/dist/hooks/index.js +73 -5
- package/dist/hooks/useUISlots.d.ts +28 -0
- package/dist/providers/index.cjs +37905 -4379
- package/dist/providers/index.js +37925 -4399
- package/dist/renderer/trait-binding-resolver.d.ts +53 -0
- package/dist/runtime/index.cjs +28622 -24075
- package/dist/runtime/index.js +25143 -20596
- package/dist/runtime/useTraitStateMachine.d.ts +3 -2
- package/package.json +5 -5
package/dist/context/index.cjs
CHANGED
|
@@ -25,6 +25,8 @@ function useUISlotManager() {
|
|
|
25
25
|
const [slots, setSlots] = react.useState(DEFAULT_SLOTS);
|
|
26
26
|
const subscribersRef = react.useRef(/* @__PURE__ */ new Set());
|
|
27
27
|
const timersRef = react.useRef(/* @__PURE__ */ new Map());
|
|
28
|
+
const traitIndexRef = react.useRef(/* @__PURE__ */ new Map());
|
|
29
|
+
const traitSubscribersRef = react.useRef(/* @__PURE__ */ new Map());
|
|
28
30
|
react.useEffect(() => {
|
|
29
31
|
return () => {
|
|
30
32
|
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
@@ -40,6 +42,29 @@ function useUISlotManager() {
|
|
|
40
42
|
}
|
|
41
43
|
});
|
|
42
44
|
}, []);
|
|
45
|
+
const notifyTraitSubscribers = react.useCallback(
|
|
46
|
+
(traitName, content) => {
|
|
47
|
+
const subs = traitSubscribersRef.current.get(traitName);
|
|
48
|
+
if (!subs) return;
|
|
49
|
+
subs.forEach((callback) => {
|
|
50
|
+
try {
|
|
51
|
+
callback(content);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(`[UISlots] Trait subscriber error (${traitName}):`, error);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
[]
|
|
58
|
+
);
|
|
59
|
+
const indexTraitRender = react.useCallback(
|
|
60
|
+
(traitName, content) => {
|
|
61
|
+
traitIndexRef.current.set(traitName, content);
|
|
62
|
+
},
|
|
63
|
+
[]
|
|
64
|
+
);
|
|
65
|
+
const unindexTrait = react.useCallback((traitName) => {
|
|
66
|
+
traitIndexRef.current.delete(traitName);
|
|
67
|
+
}, []);
|
|
43
68
|
const render = react.useCallback((config) => {
|
|
44
69
|
const id = generateId();
|
|
45
70
|
const content = {
|
|
@@ -74,11 +99,15 @@ function useUISlotManager() {
|
|
|
74
99
|
);
|
|
75
100
|
return prev;
|
|
76
101
|
}
|
|
102
|
+
if (content.sourceTrait) {
|
|
103
|
+
indexTraitRender(content.sourceTrait, content);
|
|
104
|
+
notifyTraitSubscribers(content.sourceTrait, content);
|
|
105
|
+
}
|
|
77
106
|
notifySubscribers(config.target, content);
|
|
78
107
|
return { ...prev, [config.target]: content };
|
|
79
108
|
});
|
|
80
109
|
return id;
|
|
81
|
-
}, [notifySubscribers]);
|
|
110
|
+
}, [notifySubscribers, notifyTraitSubscribers, indexTraitRender]);
|
|
82
111
|
const clear = react.useCallback((slot) => {
|
|
83
112
|
setSlots((prev) => {
|
|
84
113
|
const content = prev[slot];
|
|
@@ -89,11 +118,15 @@ function useUISlotManager() {
|
|
|
89
118
|
timersRef.current.delete(content.id);
|
|
90
119
|
}
|
|
91
120
|
content.onDismiss?.();
|
|
121
|
+
if (content.sourceTrait) {
|
|
122
|
+
unindexTrait(content.sourceTrait);
|
|
123
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
124
|
+
}
|
|
92
125
|
notifySubscribers(slot, null);
|
|
93
126
|
}
|
|
94
127
|
return { ...prev, [slot]: null };
|
|
95
128
|
});
|
|
96
|
-
}, [notifySubscribers]);
|
|
129
|
+
}, [notifySubscribers, notifyTraitSubscribers, unindexTrait]);
|
|
97
130
|
const clearById = react.useCallback((id) => {
|
|
98
131
|
setSlots((prev) => {
|
|
99
132
|
const entry = Object.entries(prev).find(([, content]) => content?.id === id);
|
|
@@ -105,12 +138,16 @@ function useUISlotManager() {
|
|
|
105
138
|
timersRef.current.delete(id);
|
|
106
139
|
}
|
|
107
140
|
content.onDismiss?.();
|
|
141
|
+
if (content.sourceTrait) {
|
|
142
|
+
unindexTrait(content.sourceTrait);
|
|
143
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
144
|
+
}
|
|
108
145
|
notifySubscribers(slot, null);
|
|
109
146
|
return { ...prev, [slot]: null };
|
|
110
147
|
}
|
|
111
148
|
return prev;
|
|
112
149
|
});
|
|
113
|
-
}, [notifySubscribers]);
|
|
150
|
+
}, [notifySubscribers, notifyTraitSubscribers, unindexTrait]);
|
|
114
151
|
const clearAll = react.useCallback(() => {
|
|
115
152
|
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
116
153
|
timersRef.current.clear();
|
|
@@ -118,12 +155,16 @@ function useUISlotManager() {
|
|
|
118
155
|
Object.entries(prev).forEach(([slot, content]) => {
|
|
119
156
|
if (content) {
|
|
120
157
|
content.onDismiss?.();
|
|
158
|
+
if (content.sourceTrait) {
|
|
159
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
160
|
+
}
|
|
121
161
|
notifySubscribers(slot, null);
|
|
122
162
|
}
|
|
123
163
|
});
|
|
124
164
|
return DEFAULT_SLOTS;
|
|
125
165
|
});
|
|
126
|
-
|
|
166
|
+
traitIndexRef.current.clear();
|
|
167
|
+
}, [notifySubscribers, notifyTraitSubscribers]);
|
|
127
168
|
const subscribe = react.useCallback((callback) => {
|
|
128
169
|
subscribersRef.current.add(callback);
|
|
129
170
|
return () => {
|
|
@@ -136,6 +177,31 @@ function useUISlotManager() {
|
|
|
136
177
|
const getContent = react.useCallback((slot) => {
|
|
137
178
|
return slots[slot];
|
|
138
179
|
}, [slots]);
|
|
180
|
+
const getTraitContent = react.useCallback(
|
|
181
|
+
(traitName) => {
|
|
182
|
+
return traitIndexRef.current.get(traitName) ?? null;
|
|
183
|
+
},
|
|
184
|
+
[]
|
|
185
|
+
);
|
|
186
|
+
const subscribeTrait = react.useCallback(
|
|
187
|
+
(traitName, callback) => {
|
|
188
|
+
let set = traitSubscribersRef.current.get(traitName);
|
|
189
|
+
if (!set) {
|
|
190
|
+
set = /* @__PURE__ */ new Set();
|
|
191
|
+
traitSubscribersRef.current.set(traitName, set);
|
|
192
|
+
}
|
|
193
|
+
set.add(callback);
|
|
194
|
+
return () => {
|
|
195
|
+
const s = traitSubscribersRef.current.get(traitName);
|
|
196
|
+
if (!s) return;
|
|
197
|
+
s.delete(callback);
|
|
198
|
+
if (s.size === 0) {
|
|
199
|
+
traitSubscribersRef.current.delete(traitName);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
[]
|
|
204
|
+
);
|
|
139
205
|
return {
|
|
140
206
|
slots,
|
|
141
207
|
render,
|
|
@@ -144,7 +210,9 @@ function useUISlotManager() {
|
|
|
144
210
|
clearAll,
|
|
145
211
|
subscribe,
|
|
146
212
|
hasContent,
|
|
147
|
-
getContent
|
|
213
|
+
getContent,
|
|
214
|
+
getTraitContent,
|
|
215
|
+
subscribeTrait
|
|
148
216
|
};
|
|
149
217
|
}
|
|
150
218
|
var UISlotContext = react.createContext(null);
|
package/dist/context/index.js
CHANGED
|
@@ -23,6 +23,8 @@ function useUISlotManager() {
|
|
|
23
23
|
const [slots, setSlots] = useState(DEFAULT_SLOTS);
|
|
24
24
|
const subscribersRef = useRef(/* @__PURE__ */ new Set());
|
|
25
25
|
const timersRef = useRef(/* @__PURE__ */ new Map());
|
|
26
|
+
const traitIndexRef = useRef(/* @__PURE__ */ new Map());
|
|
27
|
+
const traitSubscribersRef = useRef(/* @__PURE__ */ new Map());
|
|
26
28
|
useEffect(() => {
|
|
27
29
|
return () => {
|
|
28
30
|
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
@@ -38,6 +40,29 @@ function useUISlotManager() {
|
|
|
38
40
|
}
|
|
39
41
|
});
|
|
40
42
|
}, []);
|
|
43
|
+
const notifyTraitSubscribers = useCallback(
|
|
44
|
+
(traitName, content) => {
|
|
45
|
+
const subs = traitSubscribersRef.current.get(traitName);
|
|
46
|
+
if (!subs) return;
|
|
47
|
+
subs.forEach((callback) => {
|
|
48
|
+
try {
|
|
49
|
+
callback(content);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error(`[UISlots] Trait subscriber error (${traitName}):`, error);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
[]
|
|
56
|
+
);
|
|
57
|
+
const indexTraitRender = useCallback(
|
|
58
|
+
(traitName, content) => {
|
|
59
|
+
traitIndexRef.current.set(traitName, content);
|
|
60
|
+
},
|
|
61
|
+
[]
|
|
62
|
+
);
|
|
63
|
+
const unindexTrait = useCallback((traitName) => {
|
|
64
|
+
traitIndexRef.current.delete(traitName);
|
|
65
|
+
}, []);
|
|
41
66
|
const render = useCallback((config) => {
|
|
42
67
|
const id = generateId();
|
|
43
68
|
const content = {
|
|
@@ -72,11 +97,15 @@ function useUISlotManager() {
|
|
|
72
97
|
);
|
|
73
98
|
return prev;
|
|
74
99
|
}
|
|
100
|
+
if (content.sourceTrait) {
|
|
101
|
+
indexTraitRender(content.sourceTrait, content);
|
|
102
|
+
notifyTraitSubscribers(content.sourceTrait, content);
|
|
103
|
+
}
|
|
75
104
|
notifySubscribers(config.target, content);
|
|
76
105
|
return { ...prev, [config.target]: content };
|
|
77
106
|
});
|
|
78
107
|
return id;
|
|
79
|
-
}, [notifySubscribers]);
|
|
108
|
+
}, [notifySubscribers, notifyTraitSubscribers, indexTraitRender]);
|
|
80
109
|
const clear = useCallback((slot) => {
|
|
81
110
|
setSlots((prev) => {
|
|
82
111
|
const content = prev[slot];
|
|
@@ -87,11 +116,15 @@ function useUISlotManager() {
|
|
|
87
116
|
timersRef.current.delete(content.id);
|
|
88
117
|
}
|
|
89
118
|
content.onDismiss?.();
|
|
119
|
+
if (content.sourceTrait) {
|
|
120
|
+
unindexTrait(content.sourceTrait);
|
|
121
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
122
|
+
}
|
|
90
123
|
notifySubscribers(slot, null);
|
|
91
124
|
}
|
|
92
125
|
return { ...prev, [slot]: null };
|
|
93
126
|
});
|
|
94
|
-
}, [notifySubscribers]);
|
|
127
|
+
}, [notifySubscribers, notifyTraitSubscribers, unindexTrait]);
|
|
95
128
|
const clearById = useCallback((id) => {
|
|
96
129
|
setSlots((prev) => {
|
|
97
130
|
const entry = Object.entries(prev).find(([, content]) => content?.id === id);
|
|
@@ -103,12 +136,16 @@ function useUISlotManager() {
|
|
|
103
136
|
timersRef.current.delete(id);
|
|
104
137
|
}
|
|
105
138
|
content.onDismiss?.();
|
|
139
|
+
if (content.sourceTrait) {
|
|
140
|
+
unindexTrait(content.sourceTrait);
|
|
141
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
142
|
+
}
|
|
106
143
|
notifySubscribers(slot, null);
|
|
107
144
|
return { ...prev, [slot]: null };
|
|
108
145
|
}
|
|
109
146
|
return prev;
|
|
110
147
|
});
|
|
111
|
-
}, [notifySubscribers]);
|
|
148
|
+
}, [notifySubscribers, notifyTraitSubscribers, unindexTrait]);
|
|
112
149
|
const clearAll = useCallback(() => {
|
|
113
150
|
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
114
151
|
timersRef.current.clear();
|
|
@@ -116,12 +153,16 @@ function useUISlotManager() {
|
|
|
116
153
|
Object.entries(prev).forEach(([slot, content]) => {
|
|
117
154
|
if (content) {
|
|
118
155
|
content.onDismiss?.();
|
|
156
|
+
if (content.sourceTrait) {
|
|
157
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
158
|
+
}
|
|
119
159
|
notifySubscribers(slot, null);
|
|
120
160
|
}
|
|
121
161
|
});
|
|
122
162
|
return DEFAULT_SLOTS;
|
|
123
163
|
});
|
|
124
|
-
|
|
164
|
+
traitIndexRef.current.clear();
|
|
165
|
+
}, [notifySubscribers, notifyTraitSubscribers]);
|
|
125
166
|
const subscribe = useCallback((callback) => {
|
|
126
167
|
subscribersRef.current.add(callback);
|
|
127
168
|
return () => {
|
|
@@ -134,6 +175,31 @@ function useUISlotManager() {
|
|
|
134
175
|
const getContent = useCallback((slot) => {
|
|
135
176
|
return slots[slot];
|
|
136
177
|
}, [slots]);
|
|
178
|
+
const getTraitContent = useCallback(
|
|
179
|
+
(traitName) => {
|
|
180
|
+
return traitIndexRef.current.get(traitName) ?? null;
|
|
181
|
+
},
|
|
182
|
+
[]
|
|
183
|
+
);
|
|
184
|
+
const subscribeTrait = useCallback(
|
|
185
|
+
(traitName, callback) => {
|
|
186
|
+
let set = traitSubscribersRef.current.get(traitName);
|
|
187
|
+
if (!set) {
|
|
188
|
+
set = /* @__PURE__ */ new Set();
|
|
189
|
+
traitSubscribersRef.current.set(traitName, set);
|
|
190
|
+
}
|
|
191
|
+
set.add(callback);
|
|
192
|
+
return () => {
|
|
193
|
+
const s = traitSubscribersRef.current.get(traitName);
|
|
194
|
+
if (!s) return;
|
|
195
|
+
s.delete(callback);
|
|
196
|
+
if (s.size === 0) {
|
|
197
|
+
traitSubscribersRef.current.delete(traitName);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
[]
|
|
202
|
+
);
|
|
137
203
|
return {
|
|
138
204
|
slots,
|
|
139
205
|
render,
|
|
@@ -142,7 +208,9 @@ function useUISlotManager() {
|
|
|
142
208
|
clearAll,
|
|
143
209
|
subscribe,
|
|
144
210
|
hasContent,
|
|
145
|
-
getContent
|
|
211
|
+
getContent,
|
|
212
|
+
getTraitContent,
|
|
213
|
+
subscribeTrait
|
|
146
214
|
};
|
|
147
215
|
}
|
|
148
216
|
var UISlotContext = createContext(null);
|
package/dist/hooks/index.cjs
CHANGED
|
@@ -1041,6 +1041,8 @@ function useUISlotManager() {
|
|
|
1041
1041
|
const [slots, setSlots] = React.useState(DEFAULT_SLOTS);
|
|
1042
1042
|
const subscribersRef = React.useRef(/* @__PURE__ */ new Set());
|
|
1043
1043
|
const timersRef = React.useRef(/* @__PURE__ */ new Map());
|
|
1044
|
+
const traitIndexRef = React.useRef(/* @__PURE__ */ new Map());
|
|
1045
|
+
const traitSubscribersRef = React.useRef(/* @__PURE__ */ new Map());
|
|
1044
1046
|
React.useEffect(() => {
|
|
1045
1047
|
return () => {
|
|
1046
1048
|
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
@@ -1056,6 +1058,29 @@ function useUISlotManager() {
|
|
|
1056
1058
|
}
|
|
1057
1059
|
});
|
|
1058
1060
|
}, []);
|
|
1061
|
+
const notifyTraitSubscribers = React.useCallback(
|
|
1062
|
+
(traitName, content) => {
|
|
1063
|
+
const subs = traitSubscribersRef.current.get(traitName);
|
|
1064
|
+
if (!subs) return;
|
|
1065
|
+
subs.forEach((callback) => {
|
|
1066
|
+
try {
|
|
1067
|
+
callback(content);
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
console.error(`[UISlots] Trait subscriber error (${traitName}):`, error);
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
},
|
|
1073
|
+
[]
|
|
1074
|
+
);
|
|
1075
|
+
const indexTraitRender = React.useCallback(
|
|
1076
|
+
(traitName, content) => {
|
|
1077
|
+
traitIndexRef.current.set(traitName, content);
|
|
1078
|
+
},
|
|
1079
|
+
[]
|
|
1080
|
+
);
|
|
1081
|
+
const unindexTrait = React.useCallback((traitName) => {
|
|
1082
|
+
traitIndexRef.current.delete(traitName);
|
|
1083
|
+
}, []);
|
|
1059
1084
|
const render = React.useCallback((config) => {
|
|
1060
1085
|
const id = generateId();
|
|
1061
1086
|
const content = {
|
|
@@ -1090,11 +1115,15 @@ function useUISlotManager() {
|
|
|
1090
1115
|
);
|
|
1091
1116
|
return prev;
|
|
1092
1117
|
}
|
|
1118
|
+
if (content.sourceTrait) {
|
|
1119
|
+
indexTraitRender(content.sourceTrait, content);
|
|
1120
|
+
notifyTraitSubscribers(content.sourceTrait, content);
|
|
1121
|
+
}
|
|
1093
1122
|
notifySubscribers(config.target, content);
|
|
1094
1123
|
return { ...prev, [config.target]: content };
|
|
1095
1124
|
});
|
|
1096
1125
|
return id;
|
|
1097
|
-
}, [notifySubscribers]);
|
|
1126
|
+
}, [notifySubscribers, notifyTraitSubscribers, indexTraitRender]);
|
|
1098
1127
|
const clear = React.useCallback((slot) => {
|
|
1099
1128
|
setSlots((prev) => {
|
|
1100
1129
|
const content = prev[slot];
|
|
@@ -1105,11 +1134,15 @@ function useUISlotManager() {
|
|
|
1105
1134
|
timersRef.current.delete(content.id);
|
|
1106
1135
|
}
|
|
1107
1136
|
content.onDismiss?.();
|
|
1137
|
+
if (content.sourceTrait) {
|
|
1138
|
+
unindexTrait(content.sourceTrait);
|
|
1139
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
1140
|
+
}
|
|
1108
1141
|
notifySubscribers(slot, null);
|
|
1109
1142
|
}
|
|
1110
1143
|
return { ...prev, [slot]: null };
|
|
1111
1144
|
});
|
|
1112
|
-
}, [notifySubscribers]);
|
|
1145
|
+
}, [notifySubscribers, notifyTraitSubscribers, unindexTrait]);
|
|
1113
1146
|
const clearById = React.useCallback((id) => {
|
|
1114
1147
|
setSlots((prev) => {
|
|
1115
1148
|
const entry = Object.entries(prev).find(([, content]) => content?.id === id);
|
|
@@ -1121,12 +1154,16 @@ function useUISlotManager() {
|
|
|
1121
1154
|
timersRef.current.delete(id);
|
|
1122
1155
|
}
|
|
1123
1156
|
content.onDismiss?.();
|
|
1157
|
+
if (content.sourceTrait) {
|
|
1158
|
+
unindexTrait(content.sourceTrait);
|
|
1159
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
1160
|
+
}
|
|
1124
1161
|
notifySubscribers(slot, null);
|
|
1125
1162
|
return { ...prev, [slot]: null };
|
|
1126
1163
|
}
|
|
1127
1164
|
return prev;
|
|
1128
1165
|
});
|
|
1129
|
-
}, [notifySubscribers]);
|
|
1166
|
+
}, [notifySubscribers, notifyTraitSubscribers, unindexTrait]);
|
|
1130
1167
|
const clearAll = React.useCallback(() => {
|
|
1131
1168
|
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
1132
1169
|
timersRef.current.clear();
|
|
@@ -1134,12 +1171,16 @@ function useUISlotManager() {
|
|
|
1134
1171
|
Object.entries(prev).forEach(([slot, content]) => {
|
|
1135
1172
|
if (content) {
|
|
1136
1173
|
content.onDismiss?.();
|
|
1174
|
+
if (content.sourceTrait) {
|
|
1175
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
1176
|
+
}
|
|
1137
1177
|
notifySubscribers(slot, null);
|
|
1138
1178
|
}
|
|
1139
1179
|
});
|
|
1140
1180
|
return DEFAULT_SLOTS;
|
|
1141
1181
|
});
|
|
1142
|
-
|
|
1182
|
+
traitIndexRef.current.clear();
|
|
1183
|
+
}, [notifySubscribers, notifyTraitSubscribers]);
|
|
1143
1184
|
const subscribe2 = React.useCallback((callback) => {
|
|
1144
1185
|
subscribersRef.current.add(callback);
|
|
1145
1186
|
return () => {
|
|
@@ -1152,6 +1193,31 @@ function useUISlotManager() {
|
|
|
1152
1193
|
const getContent = React.useCallback((slot) => {
|
|
1153
1194
|
return slots[slot];
|
|
1154
1195
|
}, [slots]);
|
|
1196
|
+
const getTraitContent = React.useCallback(
|
|
1197
|
+
(traitName) => {
|
|
1198
|
+
return traitIndexRef.current.get(traitName) ?? null;
|
|
1199
|
+
},
|
|
1200
|
+
[]
|
|
1201
|
+
);
|
|
1202
|
+
const subscribeTrait = React.useCallback(
|
|
1203
|
+
(traitName, callback) => {
|
|
1204
|
+
let set = traitSubscribersRef.current.get(traitName);
|
|
1205
|
+
if (!set) {
|
|
1206
|
+
set = /* @__PURE__ */ new Set();
|
|
1207
|
+
traitSubscribersRef.current.set(traitName, set);
|
|
1208
|
+
}
|
|
1209
|
+
set.add(callback);
|
|
1210
|
+
return () => {
|
|
1211
|
+
const s = traitSubscribersRef.current.get(traitName);
|
|
1212
|
+
if (!s) return;
|
|
1213
|
+
s.delete(callback);
|
|
1214
|
+
if (s.size === 0) {
|
|
1215
|
+
traitSubscribersRef.current.delete(traitName);
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
},
|
|
1219
|
+
[]
|
|
1220
|
+
);
|
|
1155
1221
|
return {
|
|
1156
1222
|
slots,
|
|
1157
1223
|
render,
|
|
@@ -1160,7 +1226,9 @@ function useUISlotManager() {
|
|
|
1160
1226
|
clearAll,
|
|
1161
1227
|
subscribe: subscribe2,
|
|
1162
1228
|
hasContent,
|
|
1163
|
-
getContent
|
|
1229
|
+
getContent,
|
|
1230
|
+
getTraitContent,
|
|
1231
|
+
subscribeTrait
|
|
1164
1232
|
};
|
|
1165
1233
|
}
|
|
1166
1234
|
var UI_PREFIX = "UI:";
|
package/dist/hooks/index.js
CHANGED
|
@@ -1035,6 +1035,8 @@ function useUISlotManager() {
|
|
|
1035
1035
|
const [slots, setSlots] = useState(DEFAULT_SLOTS);
|
|
1036
1036
|
const subscribersRef = useRef(/* @__PURE__ */ new Set());
|
|
1037
1037
|
const timersRef = useRef(/* @__PURE__ */ new Map());
|
|
1038
|
+
const traitIndexRef = useRef(/* @__PURE__ */ new Map());
|
|
1039
|
+
const traitSubscribersRef = useRef(/* @__PURE__ */ new Map());
|
|
1038
1040
|
useEffect(() => {
|
|
1039
1041
|
return () => {
|
|
1040
1042
|
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
@@ -1050,6 +1052,29 @@ function useUISlotManager() {
|
|
|
1050
1052
|
}
|
|
1051
1053
|
});
|
|
1052
1054
|
}, []);
|
|
1055
|
+
const notifyTraitSubscribers = useCallback(
|
|
1056
|
+
(traitName, content) => {
|
|
1057
|
+
const subs = traitSubscribersRef.current.get(traitName);
|
|
1058
|
+
if (!subs) return;
|
|
1059
|
+
subs.forEach((callback) => {
|
|
1060
|
+
try {
|
|
1061
|
+
callback(content);
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
console.error(`[UISlots] Trait subscriber error (${traitName}):`, error);
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
},
|
|
1067
|
+
[]
|
|
1068
|
+
);
|
|
1069
|
+
const indexTraitRender = useCallback(
|
|
1070
|
+
(traitName, content) => {
|
|
1071
|
+
traitIndexRef.current.set(traitName, content);
|
|
1072
|
+
},
|
|
1073
|
+
[]
|
|
1074
|
+
);
|
|
1075
|
+
const unindexTrait = useCallback((traitName) => {
|
|
1076
|
+
traitIndexRef.current.delete(traitName);
|
|
1077
|
+
}, []);
|
|
1053
1078
|
const render = useCallback((config) => {
|
|
1054
1079
|
const id = generateId();
|
|
1055
1080
|
const content = {
|
|
@@ -1084,11 +1109,15 @@ function useUISlotManager() {
|
|
|
1084
1109
|
);
|
|
1085
1110
|
return prev;
|
|
1086
1111
|
}
|
|
1112
|
+
if (content.sourceTrait) {
|
|
1113
|
+
indexTraitRender(content.sourceTrait, content);
|
|
1114
|
+
notifyTraitSubscribers(content.sourceTrait, content);
|
|
1115
|
+
}
|
|
1087
1116
|
notifySubscribers(config.target, content);
|
|
1088
1117
|
return { ...prev, [config.target]: content };
|
|
1089
1118
|
});
|
|
1090
1119
|
return id;
|
|
1091
|
-
}, [notifySubscribers]);
|
|
1120
|
+
}, [notifySubscribers, notifyTraitSubscribers, indexTraitRender]);
|
|
1092
1121
|
const clear = useCallback((slot) => {
|
|
1093
1122
|
setSlots((prev) => {
|
|
1094
1123
|
const content = prev[slot];
|
|
@@ -1099,11 +1128,15 @@ function useUISlotManager() {
|
|
|
1099
1128
|
timersRef.current.delete(content.id);
|
|
1100
1129
|
}
|
|
1101
1130
|
content.onDismiss?.();
|
|
1131
|
+
if (content.sourceTrait) {
|
|
1132
|
+
unindexTrait(content.sourceTrait);
|
|
1133
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
1134
|
+
}
|
|
1102
1135
|
notifySubscribers(slot, null);
|
|
1103
1136
|
}
|
|
1104
1137
|
return { ...prev, [slot]: null };
|
|
1105
1138
|
});
|
|
1106
|
-
}, [notifySubscribers]);
|
|
1139
|
+
}, [notifySubscribers, notifyTraitSubscribers, unindexTrait]);
|
|
1107
1140
|
const clearById = useCallback((id) => {
|
|
1108
1141
|
setSlots((prev) => {
|
|
1109
1142
|
const entry = Object.entries(prev).find(([, content]) => content?.id === id);
|
|
@@ -1115,12 +1148,16 @@ function useUISlotManager() {
|
|
|
1115
1148
|
timersRef.current.delete(id);
|
|
1116
1149
|
}
|
|
1117
1150
|
content.onDismiss?.();
|
|
1151
|
+
if (content.sourceTrait) {
|
|
1152
|
+
unindexTrait(content.sourceTrait);
|
|
1153
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
1154
|
+
}
|
|
1118
1155
|
notifySubscribers(slot, null);
|
|
1119
1156
|
return { ...prev, [slot]: null };
|
|
1120
1157
|
}
|
|
1121
1158
|
return prev;
|
|
1122
1159
|
});
|
|
1123
|
-
}, [notifySubscribers]);
|
|
1160
|
+
}, [notifySubscribers, notifyTraitSubscribers, unindexTrait]);
|
|
1124
1161
|
const clearAll = useCallback(() => {
|
|
1125
1162
|
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
1126
1163
|
timersRef.current.clear();
|
|
@@ -1128,12 +1165,16 @@ function useUISlotManager() {
|
|
|
1128
1165
|
Object.entries(prev).forEach(([slot, content]) => {
|
|
1129
1166
|
if (content) {
|
|
1130
1167
|
content.onDismiss?.();
|
|
1168
|
+
if (content.sourceTrait) {
|
|
1169
|
+
notifyTraitSubscribers(content.sourceTrait, null);
|
|
1170
|
+
}
|
|
1131
1171
|
notifySubscribers(slot, null);
|
|
1132
1172
|
}
|
|
1133
1173
|
});
|
|
1134
1174
|
return DEFAULT_SLOTS;
|
|
1135
1175
|
});
|
|
1136
|
-
|
|
1176
|
+
traitIndexRef.current.clear();
|
|
1177
|
+
}, [notifySubscribers, notifyTraitSubscribers]);
|
|
1137
1178
|
const subscribe2 = useCallback((callback) => {
|
|
1138
1179
|
subscribersRef.current.add(callback);
|
|
1139
1180
|
return () => {
|
|
@@ -1146,6 +1187,31 @@ function useUISlotManager() {
|
|
|
1146
1187
|
const getContent = useCallback((slot) => {
|
|
1147
1188
|
return slots[slot];
|
|
1148
1189
|
}, [slots]);
|
|
1190
|
+
const getTraitContent = useCallback(
|
|
1191
|
+
(traitName) => {
|
|
1192
|
+
return traitIndexRef.current.get(traitName) ?? null;
|
|
1193
|
+
},
|
|
1194
|
+
[]
|
|
1195
|
+
);
|
|
1196
|
+
const subscribeTrait = useCallback(
|
|
1197
|
+
(traitName, callback) => {
|
|
1198
|
+
let set = traitSubscribersRef.current.get(traitName);
|
|
1199
|
+
if (!set) {
|
|
1200
|
+
set = /* @__PURE__ */ new Set();
|
|
1201
|
+
traitSubscribersRef.current.set(traitName, set);
|
|
1202
|
+
}
|
|
1203
|
+
set.add(callback);
|
|
1204
|
+
return () => {
|
|
1205
|
+
const s = traitSubscribersRef.current.get(traitName);
|
|
1206
|
+
if (!s) return;
|
|
1207
|
+
s.delete(callback);
|
|
1208
|
+
if (s.size === 0) {
|
|
1209
|
+
traitSubscribersRef.current.delete(traitName);
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
},
|
|
1213
|
+
[]
|
|
1214
|
+
);
|
|
1149
1215
|
return {
|
|
1150
1216
|
slots,
|
|
1151
1217
|
render,
|
|
@@ -1154,7 +1220,9 @@ function useUISlotManager() {
|
|
|
1154
1220
|
clearAll,
|
|
1155
1221
|
subscribe: subscribe2,
|
|
1156
1222
|
hasContent,
|
|
1157
|
-
getContent
|
|
1223
|
+
getContent,
|
|
1224
|
+
getTraitContent,
|
|
1225
|
+
subscribeTrait
|
|
1158
1226
|
};
|
|
1159
1227
|
}
|
|
1160
1228
|
var UI_PREFIX = "UI:";
|
|
@@ -54,6 +54,16 @@ export interface RenderUIConfig {
|
|
|
54
54
|
* Callback for slot changes
|
|
55
55
|
*/
|
|
56
56
|
export type SlotChangeCallback = (slot: UISlot, content: SlotContent | null) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Fires when a specific trait's current render-ui output changes.
|
|
59
|
+
* Used by `<TraitFrame>` to subscribe only to the trait it embeds,
|
|
60
|
+
* avoiding wide re-renders when unrelated slots update.
|
|
61
|
+
*
|
|
62
|
+
* Per-trait index stores exactly one entry per trait (the most recent
|
|
63
|
+
* render), so the callback signature carries just the new content —
|
|
64
|
+
* no per-slot disambiguation needed.
|
|
65
|
+
*/
|
|
66
|
+
export type TraitChangeCallback = (content: SlotContent | null) => void;
|
|
57
67
|
/**
|
|
58
68
|
* UI Slot Manager interface
|
|
59
69
|
*/
|
|
@@ -74,6 +84,24 @@ export interface UISlotManager {
|
|
|
74
84
|
hasContent: (slot: UISlot) => boolean;
|
|
75
85
|
/** Get content for a slot */
|
|
76
86
|
getContent: (slot: UISlot) => SlotContent | null;
|
|
87
|
+
/**
|
|
88
|
+
* Look up the most recent `render-ui` output a given trait produced,
|
|
89
|
+
* keyed by `SlotContent.sourceTrait`. Returns `null` when the trait
|
|
90
|
+
* hasn't rendered yet.
|
|
91
|
+
*
|
|
92
|
+
* Backs the `@trait.X` binding's client-side resolution via
|
|
93
|
+
* `<TraitFrame>`. See `docs/Almadar_Std_Gaps.md` §3.8. The binding is
|
|
94
|
+
* single-segment — there's intentionally no slot disambiguation.
|
|
95
|
+
*/
|
|
96
|
+
getTraitContent: (traitName: string) => SlotContent | null;
|
|
97
|
+
/**
|
|
98
|
+
* Subscribe to changes in a specific trait's render output. Returns
|
|
99
|
+
* an unsubscribe function.
|
|
100
|
+
*
|
|
101
|
+
* Scoped per-trait so `<TraitFrame>` components only re-render when
|
|
102
|
+
* the trait they embed actually changes — not on every slot update.
|
|
103
|
+
*/
|
|
104
|
+
subscribeTrait: (traitName: string, callback: TraitChangeCallback) => () => void;
|
|
77
105
|
}
|
|
78
106
|
declare const DEFAULT_SLOTS: Record<UISlot, SlotContent | null>;
|
|
79
107
|
/**
|