@almadar/ui 5.158.0 → 5.160.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.
@@ -1,11 +1,98 @@
1
1
  'use strict';
2
2
 
3
- var react = require('react');
3
+ var React = require('react');
4
4
  var logger = require('@almadar/logger');
5
+ var core = require('@almadar/core');
5
6
  var jsxRuntime = require('react/jsx-runtime');
6
7
  var providers = require('@almadar/ui/providers');
7
8
  var context = require('@almadar/ui/context');
8
9
 
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var React__default = /*#__PURE__*/_interopDefault(React);
13
+
14
+ function expressionEqual(a, b) {
15
+ if (Object.is(a, b)) return true;
16
+ if (Array.isArray(a) && Array.isArray(b)) {
17
+ if (a.length !== b.length) return false;
18
+ for (let i = 0; i < a.length; i++) {
19
+ if (!expressionEqual(a[i], b[i])) return false;
20
+ }
21
+ return true;
22
+ }
23
+ if (a !== null && b !== null && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b) && !(a instanceof Date) && !(b instanceof Date)) {
24
+ const aKeys = Object.keys(a);
25
+ const bKeys = Object.keys(b);
26
+ if (aKeys.length !== bKeys.length) return false;
27
+ for (const key of aKeys) {
28
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
29
+ if (!expressionEqual(
30
+ a[key],
31
+ b[key]
32
+ )) return false;
33
+ }
34
+ return true;
35
+ }
36
+ return false;
37
+ }
38
+ function isPlainSlotObject(value) {
39
+ if (value === null || value === void 0 || typeof value !== "object") return false;
40
+ if (Array.isArray(value)) return false;
41
+ if (React__default.default.isValidElement(value)) return false;
42
+ if (value instanceof Date) return false;
43
+ if (core.isRenderBindingMarker(value)) return false;
44
+ return true;
45
+ }
46
+ function shareValue(prev, next) {
47
+ if (Object.is(prev, next)) return { value: prev, equal: true };
48
+ const prevMarker = core.isRenderBindingMarker(prev) ? prev : void 0;
49
+ const nextMarker = core.isRenderBindingMarker(next) ? next : void 0;
50
+ if (prevMarker !== void 0 || nextMarker !== void 0) {
51
+ if (prevMarker !== void 0 && nextMarker !== void 0 && expressionEqual(prevMarker.expression, nextMarker.expression)) {
52
+ return { value: prev, equal: true };
53
+ }
54
+ return { value: next, equal: false };
55
+ }
56
+ if (prev instanceof Date || next instanceof Date) {
57
+ return prev instanceof Date && next instanceof Date && prev.getTime() === next.getTime() ? { value: prev, equal: true } : { value: next, equal: false };
58
+ }
59
+ if (typeof prev === "function" || typeof next === "function") return { value: next, equal: false };
60
+ if (React__default.default.isValidElement(prev) || React__default.default.isValidElement(next)) return { value: next, equal: false };
61
+ if (Array.isArray(prev) && Array.isArray(next)) {
62
+ let equal = prev.length === next.length;
63
+ const out = new Array(next.length);
64
+ for (let i = 0; i < next.length; i++) {
65
+ const child = shareValue(prev[i], next[i]);
66
+ out[i] = child.value;
67
+ if (!child.equal) equal = false;
68
+ }
69
+ return equal ? { value: prev, equal: true } : { value: out, equal: false };
70
+ }
71
+ if (isPlainSlotObject(prev) && isPlainSlotObject(next)) {
72
+ const prevKeys = Object.keys(prev);
73
+ const nextKeys = Object.keys(next);
74
+ let equal = prevKeys.length === nextKeys.length;
75
+ const out = {};
76
+ for (const key of nextKeys) {
77
+ if (!Object.prototype.hasOwnProperty.call(prev, key)) {
78
+ equal = false;
79
+ out[key] = next[key];
80
+ continue;
81
+ }
82
+ const child = shareValue(prev[key], next[key]);
83
+ out[key] = child.value;
84
+ if (!child.equal) equal = false;
85
+ }
86
+ return equal ? { value: prev, equal: true } : { value: out, equal: false };
87
+ }
88
+ return { value: next, equal: false };
89
+ }
90
+ function reconcileSlotProps(prev, next) {
91
+ const result = shareValue(prev, next);
92
+ return { value: result.value, equal: result.equal };
93
+ }
94
+
95
+ // hooks/useUISlots.ts
9
96
  var log = logger.createLogger("almadar:ui:ui-slots");
10
97
  var DEFAULT_SOURCE_KEY = "__default__";
11
98
  var MULTI_SOURCE_STACK_TRAIT = "__multi_source_stack__";
@@ -60,25 +147,25 @@ function aggregateSlot(sources) {
60
147
  };
61
148
  }
62
149
  function useUISlotManager() {
63
- const [sources, setSources] = react.useState(DEFAULT_SOURCES);
64
- const subscribersRef = react.useRef(/* @__PURE__ */ new Set());
65
- const timersRef = react.useRef(/* @__PURE__ */ new Map());
66
- const traitIndexRef = react.useRef(/* @__PURE__ */ new Map());
67
- const traitSubscribersRef = react.useRef(/* @__PURE__ */ new Map());
68
- const slots = react.useMemo(() => {
150
+ const [sources, setSources] = React.useState(DEFAULT_SOURCES);
151
+ const subscribersRef = React.useRef(/* @__PURE__ */ new Set());
152
+ const timersRef = React.useRef(/* @__PURE__ */ new Map());
153
+ const traitIndexRef = React.useRef(/* @__PURE__ */ new Map());
154
+ const traitSubscribersRef = React.useRef(/* @__PURE__ */ new Map());
155
+ const slots = React.useMemo(() => {
69
156
  const out = { ...DEFAULT_SLOTS };
70
157
  for (const slot of ALL_SLOTS) {
71
158
  out[slot] = aggregateSlot(sources[slot]);
72
159
  }
73
160
  return out;
74
161
  }, [sources]);
75
- react.useEffect(() => {
162
+ React.useEffect(() => {
76
163
  return () => {
77
164
  timersRef.current.forEach((timer) => clearTimeout(timer));
78
165
  timersRef.current.clear();
79
166
  };
80
167
  }, []);
81
- const notifySubscribers = react.useCallback((slot, content) => {
168
+ const notifySubscribers = React.useCallback((slot, content) => {
82
169
  subscribersRef.current.forEach((callback) => {
83
170
  try {
84
171
  callback(slot, content);
@@ -87,7 +174,7 @@ function useUISlotManager() {
87
174
  }
88
175
  });
89
176
  }, []);
90
- const notifyTraitSubscribers = react.useCallback(
177
+ const notifyTraitSubscribers = React.useCallback(
91
178
  (traitName, content) => {
92
179
  const subs = traitSubscribersRef.current.get(traitName);
93
180
  if (!subs) return;
@@ -101,16 +188,16 @@ function useUISlotManager() {
101
188
  },
102
189
  []
103
190
  );
104
- const indexTraitRender = react.useCallback(
191
+ const indexTraitRender = React.useCallback(
105
192
  (traitName, content) => {
106
193
  traitIndexRef.current.set(traitName, content);
107
194
  },
108
195
  []
109
196
  );
110
- const unindexTrait = react.useCallback((traitName) => {
197
+ const unindexTrait = React.useCallback((traitName) => {
111
198
  traitIndexRef.current.delete(traitName);
112
199
  }, []);
113
- const render = react.useCallback(
200
+ const render = React.useCallback(
114
201
  (config) => {
115
202
  const sourceKey = config.sourceTrait ?? DEFAULT_SOURCE_KEY;
116
203
  const id = `slot-content-${config.target}-${sourceKey}`;
@@ -160,6 +247,14 @@ function useUISlotManager() {
160
247
  });
161
248
  return prev;
162
249
  }
250
+ if (existing && existing.priority === content.priority && existing.pattern === content.pattern && existing.animation === content.animation && existing.transitionEvent === content.transitionEvent && existing.fromState === content.fromState && existing.entity === content.entity && existing.nodeId === content.nodeId && existing.autoDismissAt === void 0 && content.autoDismissAt === void 0 && existing.onDismiss === void 0 === (content.onDismiss === void 0)) {
251
+ const reconciled = reconcileSlotProps(existing.props, content.props);
252
+ if (reconciled.equal) {
253
+ log.debug("slot:flush-bail", { slot: config.target, sourceKey, pattern: content.pattern });
254
+ return prev;
255
+ }
256
+ content.props = reconciled.value;
257
+ }
163
258
  const nextSources = {
164
259
  ...slotSources,
165
260
  [sourceKey]: content
@@ -191,7 +286,7 @@ function useUISlotManager() {
191
286
  },
192
287
  [notifySubscribers, notifyTraitSubscribers, indexTraitRender]
193
288
  );
194
- const clear = react.useCallback(
289
+ const clear = React.useCallback(
195
290
  (slot) => {
196
291
  setSources((prev) => {
197
292
  const slotSources = prev[slot];
@@ -216,7 +311,7 @@ function useUISlotManager() {
216
311
  },
217
312
  [notifySubscribers, notifyTraitSubscribers, unindexTrait]
218
313
  );
219
- const clearBySource = react.useCallback(
314
+ const clearBySource = React.useCallback(
220
315
  (slot, sourceTrait) => {
221
316
  const sourceKey = sourceTrait;
222
317
  setSources((prev) => {
@@ -245,7 +340,7 @@ function useUISlotManager() {
245
340
  },
246
341
  [notifySubscribers, notifyTraitSubscribers, unindexTrait]
247
342
  );
248
- const clearById = react.useCallback(
343
+ const clearById = React.useCallback(
249
344
  (id) => {
250
345
  setSources((prev) => {
251
346
  for (const slot of ALL_SLOTS) {
@@ -276,7 +371,7 @@ function useUISlotManager() {
276
371
  },
277
372
  [notifySubscribers, notifyTraitSubscribers, unindexTrait]
278
373
  );
279
- const clearAll = react.useCallback(() => {
374
+ const clearAll = React.useCallback(() => {
280
375
  timersRef.current.forEach((timer) => clearTimeout(timer));
281
376
  timersRef.current.clear();
282
377
  setSources((prev) => {
@@ -295,25 +390,25 @@ function useUISlotManager() {
295
390
  });
296
391
  traitIndexRef.current.clear();
297
392
  }, [notifySubscribers, notifyTraitSubscribers]);
298
- const subscribe = react.useCallback((callback) => {
393
+ const subscribe = React.useCallback((callback) => {
299
394
  subscribersRef.current.add(callback);
300
395
  return () => {
301
396
  subscribersRef.current.delete(callback);
302
397
  };
303
398
  }, []);
304
- const hasContent = react.useCallback(
399
+ const hasContent = React.useCallback(
305
400
  (slot) => slots[slot] !== null,
306
401
  [slots]
307
402
  );
308
- const getContent = react.useCallback(
403
+ const getContent = React.useCallback(
309
404
  (slot) => slots[slot] ?? null,
310
405
  [slots]
311
406
  );
312
- const getTraitContent = react.useCallback(
407
+ const getTraitContent = React.useCallback(
313
408
  (traitName) => traitIndexRef.current.get(traitName) ?? null,
314
409
  []
315
410
  );
316
- const subscribeTrait = react.useCallback(
411
+ const subscribeTrait = React.useCallback(
317
412
  (traitName, callback) => {
318
413
  let set = traitSubscribersRef.current.get(traitName);
319
414
  if (!set) {
@@ -332,7 +427,7 @@ function useUISlotManager() {
332
427
  },
333
428
  []
334
429
  );
335
- const updateTraitContent = react.useCallback(
430
+ const updateTraitContent = React.useCallback(
336
431
  (traitName, content) => {
337
432
  const id = `slot-content-trait-${traitName}`;
338
433
  const fullContent = { ...content, id, sourceTrait: traitName };
@@ -357,14 +452,14 @@ function useUISlotManager() {
357
452
  updateTraitContent
358
453
  };
359
454
  }
360
- var UISlotContext = react.createContext(null);
455
+ var UISlotContext = React.createContext(null);
361
456
  function UISlotProvider({ children }) {
362
457
  const slotManager = useUISlotManager();
363
- const contextValue = react.useMemo(() => slotManager, [slotManager]);
458
+ const contextValue = React.useMemo(() => slotManager, [slotManager]);
364
459
  return /* @__PURE__ */ jsxRuntime.jsx(UISlotContext.Provider, { value: contextValue, children });
365
460
  }
366
461
  function useUISlots() {
367
- const context = react.useContext(UISlotContext);
462
+ const context = React.useContext(UISlotContext);
368
463
  if (!context) {
369
464
  throw new Error(
370
465
  "useUISlots must be used within a UISlotProvider. Make sure your component tree is wrapped with <UISlotProvider>."
@@ -512,7 +607,7 @@ var BUILT_IN_THEMES = [
512
607
  hasDarkMode: true
513
608
  }
514
609
  ];
515
- var ThemeContext = react.createContext(void 0);
610
+ var ThemeContext = React.createContext(void 0);
516
611
  var THEME_STORAGE_KEY = "theme";
517
612
  var MODE_STORAGE_KEY = "theme-mode";
518
613
  function getSystemMode() {
@@ -532,14 +627,14 @@ var ThemeProvider = ({
532
627
  defaultMode = "system",
533
628
  targetRef
534
629
  }) => {
535
- const availableThemes = react.useMemo(() => {
630
+ const availableThemes = React.useMemo(() => {
536
631
  const themeMap = /* @__PURE__ */ new Map();
537
632
  BUILT_IN_THEMES.forEach((t) => themeMap.set(t.name, t));
538
633
  themes.forEach((t) => themeMap.set(t.name, t));
539
634
  return Array.from(themeMap.values());
540
635
  }, [themes]);
541
636
  const isScoped = !!targetRef;
542
- const [theme, setThemeState] = react.useState(() => {
637
+ const [theme, setThemeState] = React.useState(() => {
543
638
  if (isScoped || typeof window === "undefined") return defaultTheme;
544
639
  const stored = localStorage.getItem(THEME_STORAGE_KEY);
545
640
  const validThemes = [
@@ -551,7 +646,7 @@ var ThemeProvider = ({
551
646
  }
552
647
  return defaultTheme;
553
648
  });
554
- const [mode, setModeState] = react.useState(() => {
649
+ const [mode, setModeState] = React.useState(() => {
555
650
  if (isScoped || typeof window === "undefined") return defaultMode;
556
651
  const stored = localStorage.getItem(MODE_STORAGE_KEY);
557
652
  if (stored === "light" || stored === "dark" || stored === "system") {
@@ -559,14 +654,14 @@ var ThemeProvider = ({
559
654
  }
560
655
  return defaultMode;
561
656
  });
562
- const [resolvedMode, setResolvedMode] = react.useState(
657
+ const [resolvedMode, setResolvedMode] = React.useState(
563
658
  () => resolveMode(mode)
564
659
  );
565
- const appliedTheme = react.useMemo(
660
+ const appliedTheme = React.useMemo(
566
661
  () => `${theme}-${resolvedMode}`,
567
662
  [theme, resolvedMode]
568
663
  );
569
- react.useEffect(() => {
664
+ React.useEffect(() => {
570
665
  const updateResolved = () => {
571
666
  setResolvedMode(resolveMode(mode));
572
667
  };
@@ -579,7 +674,7 @@ var ThemeProvider = ({
579
674
  }
580
675
  return void 0;
581
676
  }, [mode]);
582
- react.useEffect(() => {
677
+ React.useEffect(() => {
583
678
  if (isScoped) {
584
679
  if (targetRef?.current) {
585
680
  targetRef.current.setAttribute("data-theme", appliedTheme);
@@ -594,7 +689,7 @@ var ThemeProvider = ({
594
689
  root.classList.remove("light", "dark");
595
690
  root.classList.add(resolvedMode);
596
691
  }, [appliedTheme, resolvedMode, targetRef, isScoped]);
597
- const setTheme = react.useCallback(
692
+ const setTheme = React.useCallback(
598
693
  (newTheme) => {
599
694
  const validTheme = availableThemes.find((t) => t.name === newTheme);
600
695
  if (validTheme) {
@@ -611,17 +706,17 @@ var ThemeProvider = ({
611
706
  },
612
707
  [availableThemes]
613
708
  );
614
- const setMode = react.useCallback((newMode) => {
709
+ const setMode = React.useCallback((newMode) => {
615
710
  setModeState(newMode);
616
711
  if (!isScoped && typeof window !== "undefined") {
617
712
  localStorage.setItem(MODE_STORAGE_KEY, newMode);
618
713
  }
619
714
  }, []);
620
- const toggleMode = react.useCallback(() => {
715
+ const toggleMode = React.useCallback(() => {
621
716
  const newMode = resolvedMode === "dark" ? "light" : "dark";
622
717
  setMode(newMode);
623
718
  }, [resolvedMode, setMode]);
624
- const contextValue = react.useMemo(
719
+ const contextValue = React.useMemo(
625
720
  () => ({
626
721
  theme,
627
722
  mode,
@@ -646,7 +741,7 @@ var ThemeProvider = ({
646
741
  return /* @__PURE__ */ jsxRuntime.jsx(ThemeContext.Provider, { value: contextValue, children });
647
742
  };
648
743
  function useTheme() {
649
- const context = react.useContext(ThemeContext);
744
+ const context = React.useContext(ThemeContext);
650
745
  if (context === void 0) {
651
746
  return {
652
747
  theme: "wireframe",
@@ -1,9 +1,92 @@
1
- import { createContext, useMemo, useContext, useState, useEffect, useCallback, useRef } from 'react';
1
+ import React, { createContext, useMemo, useContext, useState, useEffect, useCallback, useRef } from 'react';
2
2
  import { createLogger } from '@almadar/logger';
3
+ import { isRenderBindingMarker } from '@almadar/core';
3
4
  import { jsx } from 'react/jsx-runtime';
4
5
  export { ANONYMOUS_USER, CurrentPagePathContext, CurrentPagePathProvider, OrbitalThemeProvider, UserContext, UserProvider, useCurrentPagePath, useHasPermission, useHasRole, useUser, useUserForEvaluation } from '@almadar/ui/providers';
5
6
  import { ThemeProvider as ThemeProvider$1, useTheme as useTheme$1 } from '@almadar/ui/context';
6
7
 
8
+ function expressionEqual(a, b) {
9
+ if (Object.is(a, b)) return true;
10
+ if (Array.isArray(a) && Array.isArray(b)) {
11
+ if (a.length !== b.length) return false;
12
+ for (let i = 0; i < a.length; i++) {
13
+ if (!expressionEqual(a[i], b[i])) return false;
14
+ }
15
+ return true;
16
+ }
17
+ if (a !== null && b !== null && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b) && !(a instanceof Date) && !(b instanceof Date)) {
18
+ const aKeys = Object.keys(a);
19
+ const bKeys = Object.keys(b);
20
+ if (aKeys.length !== bKeys.length) return false;
21
+ for (const key of aKeys) {
22
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
23
+ if (!expressionEqual(
24
+ a[key],
25
+ b[key]
26
+ )) return false;
27
+ }
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ function isPlainSlotObject(value) {
33
+ if (value === null || value === void 0 || typeof value !== "object") return false;
34
+ if (Array.isArray(value)) return false;
35
+ if (React.isValidElement(value)) return false;
36
+ if (value instanceof Date) return false;
37
+ if (isRenderBindingMarker(value)) return false;
38
+ return true;
39
+ }
40
+ function shareValue(prev, next) {
41
+ if (Object.is(prev, next)) return { value: prev, equal: true };
42
+ const prevMarker = isRenderBindingMarker(prev) ? prev : void 0;
43
+ const nextMarker = isRenderBindingMarker(next) ? next : void 0;
44
+ if (prevMarker !== void 0 || nextMarker !== void 0) {
45
+ if (prevMarker !== void 0 && nextMarker !== void 0 && expressionEqual(prevMarker.expression, nextMarker.expression)) {
46
+ return { value: prev, equal: true };
47
+ }
48
+ return { value: next, equal: false };
49
+ }
50
+ if (prev instanceof Date || next instanceof Date) {
51
+ return prev instanceof Date && next instanceof Date && prev.getTime() === next.getTime() ? { value: prev, equal: true } : { value: next, equal: false };
52
+ }
53
+ if (typeof prev === "function" || typeof next === "function") return { value: next, equal: false };
54
+ if (React.isValidElement(prev) || React.isValidElement(next)) return { value: next, equal: false };
55
+ if (Array.isArray(prev) && Array.isArray(next)) {
56
+ let equal = prev.length === next.length;
57
+ const out = new Array(next.length);
58
+ for (let i = 0; i < next.length; i++) {
59
+ const child = shareValue(prev[i], next[i]);
60
+ out[i] = child.value;
61
+ if (!child.equal) equal = false;
62
+ }
63
+ return equal ? { value: prev, equal: true } : { value: out, equal: false };
64
+ }
65
+ if (isPlainSlotObject(prev) && isPlainSlotObject(next)) {
66
+ const prevKeys = Object.keys(prev);
67
+ const nextKeys = Object.keys(next);
68
+ let equal = prevKeys.length === nextKeys.length;
69
+ const out = {};
70
+ for (const key of nextKeys) {
71
+ if (!Object.prototype.hasOwnProperty.call(prev, key)) {
72
+ equal = false;
73
+ out[key] = next[key];
74
+ continue;
75
+ }
76
+ const child = shareValue(prev[key], next[key]);
77
+ out[key] = child.value;
78
+ if (!child.equal) equal = false;
79
+ }
80
+ return equal ? { value: prev, equal: true } : { value: out, equal: false };
81
+ }
82
+ return { value: next, equal: false };
83
+ }
84
+ function reconcileSlotProps(prev, next) {
85
+ const result = shareValue(prev, next);
86
+ return { value: result.value, equal: result.equal };
87
+ }
88
+
89
+ // hooks/useUISlots.ts
7
90
  var log = createLogger("almadar:ui:ui-slots");
8
91
  var DEFAULT_SOURCE_KEY = "__default__";
9
92
  var MULTI_SOURCE_STACK_TRAIT = "__multi_source_stack__";
@@ -158,6 +241,14 @@ function useUISlotManager() {
158
241
  });
159
242
  return prev;
160
243
  }
244
+ if (existing && existing.priority === content.priority && existing.pattern === content.pattern && existing.animation === content.animation && existing.transitionEvent === content.transitionEvent && existing.fromState === content.fromState && existing.entity === content.entity && existing.nodeId === content.nodeId && existing.autoDismissAt === void 0 && content.autoDismissAt === void 0 && existing.onDismiss === void 0 === (content.onDismiss === void 0)) {
245
+ const reconciled = reconcileSlotProps(existing.props, content.props);
246
+ if (reconciled.equal) {
247
+ log.debug("slot:flush-bail", { slot: config.target, sourceKey, pattern: content.pattern });
248
+ return prev;
249
+ }
250
+ content.props = reconciled.value;
251
+ }
161
252
  const nextSources = {
162
253
  ...slotSources,
163
254
  [sourceKey]: content