@almadar/ui 4.0.0 → 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.
@@ -4130,12 +4130,42 @@ var init_EntitySchemaContext = __esm({
4130
4130
  function generateId() {
4131
4131
  return `slot-content-${++idCounter}-${Date.now()}`;
4132
4132
  }
4133
+ function aggregateSlot(sources) {
4134
+ if (!sources) return null;
4135
+ const entries = Object.entries(sources);
4136
+ if (entries.length === 0) return null;
4137
+ if (entries.length === 1) return entries[0][1];
4138
+ const children = entries.map(([, entry]) => ({
4139
+ type: entry.pattern,
4140
+ ...entry.props
4141
+ }));
4142
+ const stackId = `slot-content-stack-${entries.map(([k]) => k).join("-")}`;
4143
+ return {
4144
+ id: stackId,
4145
+ pattern: "stack",
4146
+ props: {
4147
+ direction: "vertical",
4148
+ gap: "lg",
4149
+ children
4150
+ },
4151
+ priority: 0,
4152
+ animation: "fade",
4153
+ sourceTrait: MULTI_SOURCE_STACK_TRAIT
4154
+ };
4155
+ }
4133
4156
  function useUISlotManager() {
4134
- const [slots, setSlots] = React126.useState(DEFAULT_SLOTS);
4157
+ const [sources, setSources] = React126.useState(DEFAULT_SOURCES);
4135
4158
  const subscribersRef = React126.useRef(/* @__PURE__ */ new Set());
4136
4159
  const timersRef = React126.useRef(/* @__PURE__ */ new Map());
4137
4160
  const traitIndexRef = React126.useRef(/* @__PURE__ */ new Map());
4138
4161
  const traitSubscribersRef = React126.useRef(/* @__PURE__ */ new Map());
4162
+ const slots = React126.useMemo(() => {
4163
+ const out = { ...DEFAULT_SLOTS };
4164
+ for (const slot of ALL_SLOTS) {
4165
+ out[slot] = aggregateSlot(sources[slot]);
4166
+ }
4167
+ return out;
4168
+ }, [sources]);
4139
4169
  React126.useEffect(() => {
4140
4170
  return () => {
4141
4171
  timersRef.current.forEach((timer) => clearTimeout(timer));
@@ -4174,103 +4204,160 @@ function useUISlotManager() {
4174
4204
  const unindexTrait = React126.useCallback((traitName) => {
4175
4205
  traitIndexRef.current.delete(traitName);
4176
4206
  }, []);
4177
- const render = React126.useCallback((config) => {
4178
- const id = generateId();
4179
- const content = {
4180
- id,
4181
- pattern: config.pattern,
4182
- props: config.props ?? {},
4183
- priority: config.priority ?? 0,
4184
- animation: config.animation ?? "fade",
4185
- onDismiss: config.onDismiss,
4186
- sourceTrait: config.sourceTrait
4187
- };
4188
- if (config.autoDismissMs && config.autoDismissMs > 0) {
4189
- content.autoDismissAt = Date.now() + config.autoDismissMs;
4190
- const timer = setTimeout(() => {
4191
- setSlots((prev) => {
4192
- if (prev[config.target]?.id === id) {
4193
- content.onDismiss?.();
4194
- notifySubscribers(config.target, null);
4195
- return { ...prev, [config.target]: null };
4196
- }
4207
+ const render = React126.useCallback(
4208
+ (config) => {
4209
+ const id = generateId();
4210
+ const sourceKey = config.sourceTrait ?? DEFAULT_SOURCE_KEY;
4211
+ const content = {
4212
+ id,
4213
+ pattern: config.pattern,
4214
+ props: config.props ?? {},
4215
+ priority: config.priority ?? 0,
4216
+ animation: config.animation ?? "fade",
4217
+ onDismiss: config.onDismiss,
4218
+ sourceTrait: config.sourceTrait
4219
+ };
4220
+ if (config.autoDismissMs && config.autoDismissMs > 0) {
4221
+ content.autoDismissAt = Date.now() + config.autoDismissMs;
4222
+ const timer = setTimeout(() => {
4223
+ setSources((prev) => {
4224
+ const slotSources = prev[config.target];
4225
+ if (slotSources && slotSources[sourceKey]?.id === id) {
4226
+ content.onDismiss?.();
4227
+ const next = { ...slotSources };
4228
+ delete next[sourceKey];
4229
+ const updated = { ...prev, [config.target]: next };
4230
+ notifySubscribers(config.target, aggregateSlot(next));
4231
+ return updated;
4232
+ }
4233
+ return prev;
4234
+ });
4235
+ timersRef.current.delete(id);
4236
+ }, config.autoDismissMs);
4237
+ timersRef.current.set(id, timer);
4238
+ }
4239
+ setSources((prev) => {
4240
+ const slotSources = prev[config.target] ?? {};
4241
+ const existing = slotSources[sourceKey];
4242
+ if (existing && existing.priority > content.priority) {
4243
+ console.warn(
4244
+ `[UISlots] Slot "${config.target}" source "${sourceKey}" already has higher priority content (${existing.priority} > ${content.priority})`
4245
+ );
4197
4246
  return prev;
4198
- });
4199
- timersRef.current.delete(id);
4200
- }, config.autoDismissMs);
4201
- timersRef.current.set(id, timer);
4202
- }
4203
- setSlots((prev) => {
4204
- const existing = prev[config.target];
4205
- if (existing && existing.priority > content.priority) {
4206
- console.warn(
4207
- `[UISlots] Slot "${config.target}" already has higher priority content (${existing.priority} > ${content.priority})`
4208
- );
4209
- return prev;
4210
- }
4211
- if (content.sourceTrait) {
4212
- indexTraitRender(content.sourceTrait, content);
4213
- notifyTraitSubscribers(content.sourceTrait, content);
4214
- }
4215
- notifySubscribers(config.target, content);
4216
- return { ...prev, [config.target]: content };
4217
- });
4218
- return id;
4219
- }, [notifySubscribers, notifyTraitSubscribers, indexTraitRender]);
4220
- const clear = React126.useCallback((slot) => {
4221
- setSlots((prev) => {
4222
- const content = prev[slot];
4223
- if (content) {
4224
- const timer = timersRef.current.get(content.id);
4225
- if (timer) {
4226
- clearTimeout(timer);
4227
- timersRef.current.delete(content.id);
4228
4247
  }
4229
- content.onDismiss?.();
4248
+ const nextSources = {
4249
+ ...slotSources,
4250
+ [sourceKey]: content
4251
+ };
4252
+ const nextAll = { ...prev, [config.target]: nextSources };
4230
4253
  if (content.sourceTrait) {
4231
- unindexTrait(content.sourceTrait);
4232
- notifyTraitSubscribers(content.sourceTrait, null);
4254
+ indexTraitRender(content.sourceTrait, content);
4255
+ notifyTraitSubscribers(content.sourceTrait, content);
4256
+ }
4257
+ notifySubscribers(config.target, aggregateSlot(nextSources));
4258
+ return nextAll;
4259
+ });
4260
+ return id;
4261
+ },
4262
+ [notifySubscribers, notifyTraitSubscribers, indexTraitRender]
4263
+ );
4264
+ const clear = React126.useCallback(
4265
+ (slot) => {
4266
+ setSources((prev) => {
4267
+ const slotSources = prev[slot];
4268
+ if (!slotSources || Object.keys(slotSources).length === 0) {
4269
+ return prev;
4270
+ }
4271
+ for (const content of Object.values(slotSources)) {
4272
+ const timer = timersRef.current.get(content.id);
4273
+ if (timer) {
4274
+ clearTimeout(timer);
4275
+ timersRef.current.delete(content.id);
4276
+ }
4277
+ content.onDismiss?.();
4278
+ if (content.sourceTrait) {
4279
+ unindexTrait(content.sourceTrait);
4280
+ notifyTraitSubscribers(content.sourceTrait, null);
4281
+ }
4233
4282
  }
4234
4283
  notifySubscribers(slot, null);
4235
- }
4236
- return { ...prev, [slot]: null };
4237
- });
4238
- }, [notifySubscribers, notifyTraitSubscribers, unindexTrait]);
4239
- const clearById = React126.useCallback((id) => {
4240
- setSlots((prev) => {
4241
- const entry = Object.entries(prev).find(([, content]) => content?.id === id);
4242
- if (entry) {
4243
- const [slot, content] = entry;
4244
- const timer = timersRef.current.get(id);
4284
+ return { ...prev, [slot]: {} };
4285
+ });
4286
+ },
4287
+ [notifySubscribers, notifyTraitSubscribers, unindexTrait]
4288
+ );
4289
+ const clearBySource = React126.useCallback(
4290
+ (slot, sourceTrait) => {
4291
+ const sourceKey = sourceTrait;
4292
+ setSources((prev) => {
4293
+ const slotSources = prev[slot];
4294
+ if (!slotSources || !(sourceKey in slotSources)) return prev;
4295
+ const content = slotSources[sourceKey];
4296
+ const timer = timersRef.current.get(content.id);
4245
4297
  if (timer) {
4246
4298
  clearTimeout(timer);
4247
- timersRef.current.delete(id);
4299
+ timersRef.current.delete(content.id);
4248
4300
  }
4249
4301
  content.onDismiss?.();
4250
4302
  if (content.sourceTrait) {
4251
4303
  unindexTrait(content.sourceTrait);
4252
4304
  notifyTraitSubscribers(content.sourceTrait, null);
4253
4305
  }
4254
- notifySubscribers(slot, null);
4255
- return { ...prev, [slot]: null };
4256
- }
4257
- return prev;
4258
- });
4259
- }, [notifySubscribers, notifyTraitSubscribers, unindexTrait]);
4306
+ const nextSources = { ...slotSources };
4307
+ delete nextSources[sourceKey];
4308
+ notifySubscribers(slot, aggregateSlot(nextSources));
4309
+ return { ...prev, [slot]: nextSources };
4310
+ });
4311
+ },
4312
+ [notifySubscribers, notifyTraitSubscribers, unindexTrait]
4313
+ );
4314
+ const clearById = React126.useCallback(
4315
+ (id) => {
4316
+ setSources((prev) => {
4317
+ for (const slot of ALL_SLOTS) {
4318
+ const slotSources = prev[slot];
4319
+ if (!slotSources) continue;
4320
+ const matchKey = Object.keys(slotSources).find(
4321
+ (k) => slotSources[k].id === id
4322
+ );
4323
+ if (!matchKey) continue;
4324
+ const content = slotSources[matchKey];
4325
+ const timer = timersRef.current.get(id);
4326
+ if (timer) {
4327
+ clearTimeout(timer);
4328
+ timersRef.current.delete(id);
4329
+ }
4330
+ content.onDismiss?.();
4331
+ if (content.sourceTrait) {
4332
+ unindexTrait(content.sourceTrait);
4333
+ notifyTraitSubscribers(content.sourceTrait, null);
4334
+ }
4335
+ const nextSources = { ...slotSources };
4336
+ delete nextSources[matchKey];
4337
+ notifySubscribers(slot, aggregateSlot(nextSources));
4338
+ return { ...prev, [slot]: nextSources };
4339
+ }
4340
+ return prev;
4341
+ });
4342
+ },
4343
+ [notifySubscribers, notifyTraitSubscribers, unindexTrait]
4344
+ );
4260
4345
  const clearAll = React126.useCallback(() => {
4261
4346
  timersRef.current.forEach((timer) => clearTimeout(timer));
4262
4347
  timersRef.current.clear();
4263
- setSlots((prev) => {
4264
- Object.entries(prev).forEach(([slot, content]) => {
4265
- if (content) {
4348
+ setSources((prev) => {
4349
+ for (const slot of ALL_SLOTS) {
4350
+ const slotSources = prev[slot];
4351
+ if (!slotSources) continue;
4352
+ for (const content of Object.values(slotSources)) {
4266
4353
  content.onDismiss?.();
4267
4354
  if (content.sourceTrait) {
4268
4355
  notifyTraitSubscribers(content.sourceTrait, null);
4269
4356
  }
4270
- notifySubscribers(slot, null);
4271
4357
  }
4272
- });
4273
- return DEFAULT_SLOTS;
4358
+ notifySubscribers(slot, null);
4359
+ }
4360
+ return DEFAULT_SOURCES;
4274
4361
  });
4275
4362
  traitIndexRef.current.clear();
4276
4363
  }, [notifySubscribers, notifyTraitSubscribers]);
@@ -4280,16 +4367,16 @@ function useUISlotManager() {
4280
4367
  subscribersRef.current.delete(callback);
4281
4368
  };
4282
4369
  }, []);
4283
- const hasContent = React126.useCallback((slot) => {
4284
- return slots[slot] !== null;
4285
- }, [slots]);
4286
- const getContent = React126.useCallback((slot) => {
4287
- return slots[slot];
4288
- }, [slots]);
4370
+ const hasContent = React126.useCallback(
4371
+ (slot) => slots[slot] !== null,
4372
+ [slots]
4373
+ );
4374
+ const getContent = React126.useCallback(
4375
+ (slot) => slots[slot],
4376
+ [slots]
4377
+ );
4289
4378
  const getTraitContent = React126.useCallback(
4290
- (traitName) => {
4291
- return traitIndexRef.current.get(traitName) ?? null;
4292
- },
4379
+ (traitName) => traitIndexRef.current.get(traitName) ?? null,
4293
4380
  []
4294
4381
  );
4295
4382
  const subscribeTrait = React126.useCallback(
@@ -4315,6 +4402,7 @@ function useUISlotManager() {
4315
4402
  slots,
4316
4403
  render,
4317
4404
  clear,
4405
+ clearBySource,
4318
4406
  clearById,
4319
4407
  clearAll,
4320
4408
  subscribe: subscribe2,
@@ -4324,24 +4412,40 @@ function useUISlotManager() {
4324
4412
  subscribeTrait
4325
4413
  };
4326
4414
  }
4327
- var DEFAULT_SLOTS, idCounter;
4415
+ var DEFAULT_SOURCE_KEY, MULTI_SOURCE_STACK_TRAIT, ALL_SLOTS, DEFAULT_SLOTS, DEFAULT_SOURCES, idCounter;
4328
4416
  var init_useUISlots = __esm({
4329
4417
  "hooks/useUISlots.ts"() {
4330
4418
  "use client";
4331
- DEFAULT_SLOTS = {
4332
- main: null,
4333
- sidebar: null,
4334
- modal: null,
4335
- drawer: null,
4336
- overlay: null,
4337
- center: null,
4338
- toast: null,
4339
- "hud-top": null,
4340
- "hud-bottom": null,
4341
- "hud-left": null,
4342
- "hud-right": null,
4343
- floating: null
4344
- };
4419
+ DEFAULT_SOURCE_KEY = "__default__";
4420
+ MULTI_SOURCE_STACK_TRAIT = "__multi_source_stack__";
4421
+ ALL_SLOTS = [
4422
+ "main",
4423
+ "sidebar",
4424
+ "modal",
4425
+ "drawer",
4426
+ "overlay",
4427
+ "center",
4428
+ "toast",
4429
+ "hud-top",
4430
+ "hud-bottom",
4431
+ "hud-left",
4432
+ "hud-right",
4433
+ "floating"
4434
+ ];
4435
+ DEFAULT_SLOTS = ALL_SLOTS.reduce(
4436
+ (acc, slot) => {
4437
+ acc[slot] = null;
4438
+ return acc;
4439
+ },
4440
+ {}
4441
+ );
4442
+ DEFAULT_SOURCES = ALL_SLOTS.reduce(
4443
+ (acc, slot) => {
4444
+ acc[slot] = {};
4445
+ return acc;
4446
+ },
4447
+ {}
4448
+ );
4345
4449
  idCounter = 0;
4346
4450
  }
4347
4451
  });
@@ -51610,7 +51714,7 @@ function useTraitStateMachine(traitBindings, slotsActions, options) {
51610
51714
  canHandleEvent
51611
51715
  };
51612
51716
  }
51613
- var DEFAULT_SOURCE_KEY = "__default__";
51717
+ var DEFAULT_SOURCE_KEY2 = "__default__";
51614
51718
  function slotEntriesInOrder(slot) {
51615
51719
  if (!slot) return [];
51616
51720
  const out = [];
@@ -51626,7 +51730,7 @@ var SlotsActionsContext = React126.createContext(null);
51626
51730
  function SlotsProvider({ children }) {
51627
51731
  const [slots, setSlots] = React126.useState({});
51628
51732
  const setSlotPatterns = React126.useCallback((slot, patterns, source) => {
51629
- const sourceKey = source?.trait ?? DEFAULT_SOURCE_KEY;
51733
+ const sourceKey = source?.trait ?? DEFAULT_SOURCE_KEY2;
51630
51734
  setSlots((prev) => {
51631
51735
  const prevSlot = prev[slot] ?? {};
51632
51736
  return {