@almadar/ui 4.0.1 → 4.1.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.
package/dist/avl/index.js CHANGED
@@ -4084,12 +4084,42 @@ var init_EntitySchemaContext = __esm({
4084
4084
  function generateId() {
4085
4085
  return `slot-content-${++idCounter}-${Date.now()}`;
4086
4086
  }
4087
+ function aggregateSlot(sources) {
4088
+ if (!sources) return null;
4089
+ const entries = Object.entries(sources);
4090
+ if (entries.length === 0) return null;
4091
+ if (entries.length === 1) return entries[0][1];
4092
+ const children = entries.map(([, entry]) => ({
4093
+ type: entry.pattern,
4094
+ ...entry.props
4095
+ }));
4096
+ const stackId = `slot-content-stack-${entries.map(([k]) => k).join("-")}`;
4097
+ return {
4098
+ id: stackId,
4099
+ pattern: "stack",
4100
+ props: {
4101
+ direction: "vertical",
4102
+ gap: "lg",
4103
+ children
4104
+ },
4105
+ priority: 0,
4106
+ animation: "fade",
4107
+ sourceTrait: MULTI_SOURCE_STACK_TRAIT
4108
+ };
4109
+ }
4087
4110
  function useUISlotManager() {
4088
- const [slots, setSlots] = useState(DEFAULT_SLOTS);
4111
+ const [sources, setSources] = useState(DEFAULT_SOURCES);
4089
4112
  const subscribersRef = useRef(/* @__PURE__ */ new Set());
4090
4113
  const timersRef = useRef(/* @__PURE__ */ new Map());
4091
4114
  const traitIndexRef = useRef(/* @__PURE__ */ new Map());
4092
4115
  const traitSubscribersRef = useRef(/* @__PURE__ */ new Map());
4116
+ const slots = useMemo(() => {
4117
+ const out = { ...DEFAULT_SLOTS };
4118
+ for (const slot of ALL_SLOTS) {
4119
+ out[slot] = aggregateSlot(sources[slot]);
4120
+ }
4121
+ return out;
4122
+ }, [sources]);
4093
4123
  useEffect(() => {
4094
4124
  return () => {
4095
4125
  timersRef.current.forEach((timer) => clearTimeout(timer));
@@ -4128,103 +4158,160 @@ function useUISlotManager() {
4128
4158
  const unindexTrait = useCallback((traitName) => {
4129
4159
  traitIndexRef.current.delete(traitName);
4130
4160
  }, []);
4131
- const render = useCallback((config) => {
4132
- const id = generateId();
4133
- const content = {
4134
- id,
4135
- pattern: config.pattern,
4136
- props: config.props ?? {},
4137
- priority: config.priority ?? 0,
4138
- animation: config.animation ?? "fade",
4139
- onDismiss: config.onDismiss,
4140
- sourceTrait: config.sourceTrait
4141
- };
4142
- if (config.autoDismissMs && config.autoDismissMs > 0) {
4143
- content.autoDismissAt = Date.now() + config.autoDismissMs;
4144
- const timer = setTimeout(() => {
4145
- setSlots((prev) => {
4146
- if (prev[config.target]?.id === id) {
4147
- content.onDismiss?.();
4148
- notifySubscribers(config.target, null);
4149
- return { ...prev, [config.target]: null };
4150
- }
4161
+ const render = useCallback(
4162
+ (config) => {
4163
+ const id = generateId();
4164
+ const sourceKey = config.sourceTrait ?? DEFAULT_SOURCE_KEY;
4165
+ const content = {
4166
+ id,
4167
+ pattern: config.pattern,
4168
+ props: config.props ?? {},
4169
+ priority: config.priority ?? 0,
4170
+ animation: config.animation ?? "fade",
4171
+ onDismiss: config.onDismiss,
4172
+ sourceTrait: config.sourceTrait
4173
+ };
4174
+ if (config.autoDismissMs && config.autoDismissMs > 0) {
4175
+ content.autoDismissAt = Date.now() + config.autoDismissMs;
4176
+ const timer = setTimeout(() => {
4177
+ setSources((prev) => {
4178
+ const slotSources = prev[config.target];
4179
+ if (slotSources && slotSources[sourceKey]?.id === id) {
4180
+ content.onDismiss?.();
4181
+ const next = { ...slotSources };
4182
+ delete next[sourceKey];
4183
+ const updated = { ...prev, [config.target]: next };
4184
+ notifySubscribers(config.target, aggregateSlot(next));
4185
+ return updated;
4186
+ }
4187
+ return prev;
4188
+ });
4189
+ timersRef.current.delete(id);
4190
+ }, config.autoDismissMs);
4191
+ timersRef.current.set(id, timer);
4192
+ }
4193
+ setSources((prev) => {
4194
+ const slotSources = prev[config.target] ?? {};
4195
+ const existing = slotSources[sourceKey];
4196
+ if (existing && existing.priority > content.priority) {
4197
+ console.warn(
4198
+ `[UISlots] Slot "${config.target}" source "${sourceKey}" already has higher priority content (${existing.priority} > ${content.priority})`
4199
+ );
4151
4200
  return prev;
4152
- });
4153
- timersRef.current.delete(id);
4154
- }, config.autoDismissMs);
4155
- timersRef.current.set(id, timer);
4156
- }
4157
- setSlots((prev) => {
4158
- const existing = prev[config.target];
4159
- if (existing && existing.priority > content.priority) {
4160
- console.warn(
4161
- `[UISlots] Slot "${config.target}" already has higher priority content (${existing.priority} > ${content.priority})`
4162
- );
4163
- return prev;
4164
- }
4165
- if (content.sourceTrait) {
4166
- indexTraitRender(content.sourceTrait, content);
4167
- notifyTraitSubscribers(content.sourceTrait, content);
4168
- }
4169
- notifySubscribers(config.target, content);
4170
- return { ...prev, [config.target]: content };
4171
- });
4172
- return id;
4173
- }, [notifySubscribers, notifyTraitSubscribers, indexTraitRender]);
4174
- const clear = useCallback((slot) => {
4175
- setSlots((prev) => {
4176
- const content = prev[slot];
4177
- if (content) {
4178
- const timer = timersRef.current.get(content.id);
4179
- if (timer) {
4180
- clearTimeout(timer);
4181
- timersRef.current.delete(content.id);
4182
4201
  }
4183
- content.onDismiss?.();
4202
+ const nextSources = {
4203
+ ...slotSources,
4204
+ [sourceKey]: content
4205
+ };
4206
+ const nextAll = { ...prev, [config.target]: nextSources };
4184
4207
  if (content.sourceTrait) {
4185
- unindexTrait(content.sourceTrait);
4186
- notifyTraitSubscribers(content.sourceTrait, null);
4208
+ indexTraitRender(content.sourceTrait, content);
4209
+ notifyTraitSubscribers(content.sourceTrait, content);
4210
+ }
4211
+ notifySubscribers(config.target, aggregateSlot(nextSources));
4212
+ return nextAll;
4213
+ });
4214
+ return id;
4215
+ },
4216
+ [notifySubscribers, notifyTraitSubscribers, indexTraitRender]
4217
+ );
4218
+ const clear = useCallback(
4219
+ (slot) => {
4220
+ setSources((prev) => {
4221
+ const slotSources = prev[slot];
4222
+ if (!slotSources || Object.keys(slotSources).length === 0) {
4223
+ return prev;
4224
+ }
4225
+ for (const content of Object.values(slotSources)) {
4226
+ const timer = timersRef.current.get(content.id);
4227
+ if (timer) {
4228
+ clearTimeout(timer);
4229
+ timersRef.current.delete(content.id);
4230
+ }
4231
+ content.onDismiss?.();
4232
+ if (content.sourceTrait) {
4233
+ unindexTrait(content.sourceTrait);
4234
+ notifyTraitSubscribers(content.sourceTrait, null);
4235
+ }
4187
4236
  }
4188
4237
  notifySubscribers(slot, null);
4189
- }
4190
- return { ...prev, [slot]: null };
4191
- });
4192
- }, [notifySubscribers, notifyTraitSubscribers, unindexTrait]);
4193
- const clearById = useCallback((id) => {
4194
- setSlots((prev) => {
4195
- const entry = Object.entries(prev).find(([, content]) => content?.id === id);
4196
- if (entry) {
4197
- const [slot, content] = entry;
4198
- const timer = timersRef.current.get(id);
4238
+ return { ...prev, [slot]: {} };
4239
+ });
4240
+ },
4241
+ [notifySubscribers, notifyTraitSubscribers, unindexTrait]
4242
+ );
4243
+ const clearBySource = useCallback(
4244
+ (slot, sourceTrait) => {
4245
+ const sourceKey = sourceTrait;
4246
+ setSources((prev) => {
4247
+ const slotSources = prev[slot];
4248
+ if (!slotSources || !(sourceKey in slotSources)) return prev;
4249
+ const content = slotSources[sourceKey];
4250
+ const timer = timersRef.current.get(content.id);
4199
4251
  if (timer) {
4200
4252
  clearTimeout(timer);
4201
- timersRef.current.delete(id);
4253
+ timersRef.current.delete(content.id);
4202
4254
  }
4203
4255
  content.onDismiss?.();
4204
4256
  if (content.sourceTrait) {
4205
4257
  unindexTrait(content.sourceTrait);
4206
4258
  notifyTraitSubscribers(content.sourceTrait, null);
4207
4259
  }
4208
- notifySubscribers(slot, null);
4209
- return { ...prev, [slot]: null };
4210
- }
4211
- return prev;
4212
- });
4213
- }, [notifySubscribers, notifyTraitSubscribers, unindexTrait]);
4260
+ const nextSources = { ...slotSources };
4261
+ delete nextSources[sourceKey];
4262
+ notifySubscribers(slot, aggregateSlot(nextSources));
4263
+ return { ...prev, [slot]: nextSources };
4264
+ });
4265
+ },
4266
+ [notifySubscribers, notifyTraitSubscribers, unindexTrait]
4267
+ );
4268
+ const clearById = useCallback(
4269
+ (id) => {
4270
+ setSources((prev) => {
4271
+ for (const slot of ALL_SLOTS) {
4272
+ const slotSources = prev[slot];
4273
+ if (!slotSources) continue;
4274
+ const matchKey = Object.keys(slotSources).find(
4275
+ (k) => slotSources[k].id === id
4276
+ );
4277
+ if (!matchKey) continue;
4278
+ const content = slotSources[matchKey];
4279
+ const timer = timersRef.current.get(id);
4280
+ if (timer) {
4281
+ clearTimeout(timer);
4282
+ timersRef.current.delete(id);
4283
+ }
4284
+ content.onDismiss?.();
4285
+ if (content.sourceTrait) {
4286
+ unindexTrait(content.sourceTrait);
4287
+ notifyTraitSubscribers(content.sourceTrait, null);
4288
+ }
4289
+ const nextSources = { ...slotSources };
4290
+ delete nextSources[matchKey];
4291
+ notifySubscribers(slot, aggregateSlot(nextSources));
4292
+ return { ...prev, [slot]: nextSources };
4293
+ }
4294
+ return prev;
4295
+ });
4296
+ },
4297
+ [notifySubscribers, notifyTraitSubscribers, unindexTrait]
4298
+ );
4214
4299
  const clearAll = useCallback(() => {
4215
4300
  timersRef.current.forEach((timer) => clearTimeout(timer));
4216
4301
  timersRef.current.clear();
4217
- setSlots((prev) => {
4218
- Object.entries(prev).forEach(([slot, content]) => {
4219
- if (content) {
4302
+ setSources((prev) => {
4303
+ for (const slot of ALL_SLOTS) {
4304
+ const slotSources = prev[slot];
4305
+ if (!slotSources) continue;
4306
+ for (const content of Object.values(slotSources)) {
4220
4307
  content.onDismiss?.();
4221
4308
  if (content.sourceTrait) {
4222
4309
  notifyTraitSubscribers(content.sourceTrait, null);
4223
4310
  }
4224
- notifySubscribers(slot, null);
4225
4311
  }
4226
- });
4227
- return DEFAULT_SLOTS;
4312
+ notifySubscribers(slot, null);
4313
+ }
4314
+ return DEFAULT_SOURCES;
4228
4315
  });
4229
4316
  traitIndexRef.current.clear();
4230
4317
  }, [notifySubscribers, notifyTraitSubscribers]);
@@ -4234,16 +4321,16 @@ function useUISlotManager() {
4234
4321
  subscribersRef.current.delete(callback);
4235
4322
  };
4236
4323
  }, []);
4237
- const hasContent = useCallback((slot) => {
4238
- return slots[slot] !== null;
4239
- }, [slots]);
4240
- const getContent = useCallback((slot) => {
4241
- return slots[slot];
4242
- }, [slots]);
4324
+ const hasContent = useCallback(
4325
+ (slot) => slots[slot] !== null,
4326
+ [slots]
4327
+ );
4328
+ const getContent = useCallback(
4329
+ (slot) => slots[slot],
4330
+ [slots]
4331
+ );
4243
4332
  const getTraitContent = useCallback(
4244
- (traitName) => {
4245
- return traitIndexRef.current.get(traitName) ?? null;
4246
- },
4333
+ (traitName) => traitIndexRef.current.get(traitName) ?? null,
4247
4334
  []
4248
4335
  );
4249
4336
  const subscribeTrait = useCallback(
@@ -4269,6 +4356,7 @@ function useUISlotManager() {
4269
4356
  slots,
4270
4357
  render,
4271
4358
  clear,
4359
+ clearBySource,
4272
4360
  clearById,
4273
4361
  clearAll,
4274
4362
  subscribe: subscribe2,
@@ -4278,24 +4366,40 @@ function useUISlotManager() {
4278
4366
  subscribeTrait
4279
4367
  };
4280
4368
  }
4281
- var DEFAULT_SLOTS, idCounter;
4369
+ var DEFAULT_SOURCE_KEY, MULTI_SOURCE_STACK_TRAIT, ALL_SLOTS, DEFAULT_SLOTS, DEFAULT_SOURCES, idCounter;
4282
4370
  var init_useUISlots = __esm({
4283
4371
  "hooks/useUISlots.ts"() {
4284
4372
  "use client";
4285
- DEFAULT_SLOTS = {
4286
- main: null,
4287
- sidebar: null,
4288
- modal: null,
4289
- drawer: null,
4290
- overlay: null,
4291
- center: null,
4292
- toast: null,
4293
- "hud-top": null,
4294
- "hud-bottom": null,
4295
- "hud-left": null,
4296
- "hud-right": null,
4297
- floating: null
4298
- };
4373
+ DEFAULT_SOURCE_KEY = "__default__";
4374
+ MULTI_SOURCE_STACK_TRAIT = "__multi_source_stack__";
4375
+ ALL_SLOTS = [
4376
+ "main",
4377
+ "sidebar",
4378
+ "modal",
4379
+ "drawer",
4380
+ "overlay",
4381
+ "center",
4382
+ "toast",
4383
+ "hud-top",
4384
+ "hud-bottom",
4385
+ "hud-left",
4386
+ "hud-right",
4387
+ "floating"
4388
+ ];
4389
+ DEFAULT_SLOTS = ALL_SLOTS.reduce(
4390
+ (acc, slot) => {
4391
+ acc[slot] = null;
4392
+ return acc;
4393
+ },
4394
+ {}
4395
+ );
4396
+ DEFAULT_SOURCES = ALL_SLOTS.reduce(
4397
+ (acc, slot) => {
4398
+ acc[slot] = {};
4399
+ return acc;
4400
+ },
4401
+ {}
4402
+ );
4299
4403
  idCounter = 0;
4300
4404
  }
4301
4405
  });
@@ -51564,7 +51668,7 @@ function useTraitStateMachine(traitBindings, slotsActions, options) {
51564
51668
  canHandleEvent
51565
51669
  };
51566
51670
  }
51567
- var DEFAULT_SOURCE_KEY = "__default__";
51671
+ var DEFAULT_SOURCE_KEY2 = "__default__";
51568
51672
  function slotEntriesInOrder(slot) {
51569
51673
  if (!slot) return [];
51570
51674
  const out = [];
@@ -51580,7 +51684,7 @@ var SlotsActionsContext = createContext(null);
51580
51684
  function SlotsProvider({ children }) {
51581
51685
  const [slots, setSlots] = useState({});
51582
51686
  const setSlotPatterns = useCallback((slot, patterns, source) => {
51583
- const sourceKey = source?.trait ?? DEFAULT_SOURCE_KEY;
51687
+ const sourceKey = source?.trait ?? DEFAULT_SOURCE_KEY2;
51584
51688
  setSlots((prev) => {
51585
51689
  const prevSlot = prev[slot] ?? {};
51586
51690
  return {
@@ -51906,13 +52010,11 @@ function SlotBridge() {
51906
52010
  return null;
51907
52011
  }
51908
52012
  function applyServerEffects(effects, uiSlots, onNavigate) {
51909
- const perSlotRenders = /* @__PURE__ */ new Map();
51910
52013
  for (const eff of effects) {
51911
52014
  if (eff.type === "render-ui" && eff.slot && eff.pattern) {
51912
52015
  const patternRecord = eff.pattern;
51913
52016
  const { type: patternType, children, ...inlineProps } = patternRecord;
51914
52017
  const normalizedChildren = Array.isArray(children) ? children.map((c) => normalizeChild(c)) : children;
51915
- const sourceTrait = eff.traitName ?? "server";
51916
52018
  uiSlots.render({
51917
52019
  target: eff.slot,
51918
52020
  pattern: patternType,
@@ -51920,38 +52022,12 @@ function applyServerEffects(effects, uiSlots, onNavigate) {
51920
52022
  ...inlineProps,
51921
52023
  ...normalizedChildren !== void 0 ? { children: normalizedChildren } : {}
51922
52024
  },
51923
- sourceTrait
52025
+ sourceTrait: eff.traitName ?? "server"
51924
52026
  });
51925
- const bucket = perSlotRenders.get(eff.slot) ?? [];
51926
- bucket.push({
51927
- sourceTrait,
51928
- pattern: {
51929
- type: patternType,
51930
- ...inlineProps,
51931
- ...normalizedChildren !== void 0 ? { children: normalizedChildren } : {}
51932
- }
51933
- });
51934
- perSlotRenders.set(eff.slot, bucket);
51935
52027
  } else if (eff.type === "navigate" && eff.route && onNavigate) {
51936
52028
  onNavigate(eff.route, eff.params);
51937
52029
  }
51938
52030
  }
51939
- for (const [slot, bucket] of perSlotRenders) {
51940
- const distinctSources = new Set(bucket.map((b) => b.sourceTrait));
51941
- if (distinctSources.size <= 1) continue;
51942
- uiSlots.render({
51943
- target: slot,
51944
- pattern: "stack",
51945
- props: {
51946
- direction: "vertical",
51947
- gap: "lg",
51948
- children: bucket.map((b) => b.pattern)
51949
- },
51950
- // Use a synthetic wrapper source trait; individual traits' frames
51951
- // already live in the per-trait index from the per-effect loop.
51952
- sourceTrait: "__multi_source_stack__"
51953
- });
51954
- }
51955
52031
  }
51956
52032
  function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFallback, persistence }) {
51957
52033
  const slotsActions = useSlotsActions();