@lowdefy/client 6.0.0 → 7.0.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.
Files changed (51) hide show
  1. package/dist/Client.js +54 -17
  2. package/dist/Context.js +30 -12
  3. package/dist/HtmlOverlay.js +127 -0
  4. package/dist/IconProvider.js +24 -0
  5. package/dist/MountEvents.js +3 -3
  6. package/dist/adapters/createRouter.js +9 -3
  7. package/dist/adapters/url.js +1 -7
  8. package/dist/auth/createAuthMethods.js +1034 -41
  9. package/dist/block/Block.js +6 -3
  10. package/dist/block/CategorySwitch.js +6 -0
  11. package/dist/block/Container.js +3 -0
  12. package/dist/block/InputContainer.js +8 -2
  13. package/dist/block/List.js +3 -0
  14. package/dist/block/LoadingBlock.js +6 -12
  15. package/dist/block/LoadingContainer.js +6 -1
  16. package/dist/block/LoadingList.js +6 -1
  17. package/dist/block/loadingBlockMethods.js +26 -0
  18. package/dist/claimCallerReload.js +38 -0
  19. package/dist/createAntdIconConfig.js +91 -0
  20. package/dist/createCallAPI.js +6 -2
  21. package/dist/createCallRequest.js +6 -2
  22. package/dist/createHandleError.js +21 -11
  23. package/dist/createIcon.js +138 -36
  24. package/dist/createPageLifecycleManager.js +172 -0
  25. package/dist/formatIconTitle.js +26 -0
  26. package/dist/getActiveLocale.js +27 -0
  27. package/dist/getContextKey.js +29 -0
  28. package/dist/getMessageIcon.js +43 -0
  29. package/dist/hasOtherRoles.js +30 -0
  30. package/dist/index.js +1 -0
  31. package/dist/initLowdefyContext.js +103 -4
  32. package/dist/isOtherCaller.js +29 -0
  33. package/dist/recordCallerStamp.js +31 -0
  34. package/dist/renderIconNodes.js +46 -0
  35. package/dist/request.js +74 -6
  36. package/dist/setLowdefyUser.js +32 -0
  37. package/dist/setupLink.js +19 -13
  38. package/dist/shouldReloadForAuthError.js +39 -0
  39. package/dist/shouldReloadForBuild.js +44 -0
  40. package/dist/shouldReloadForCaller.js +50 -0
  41. package/dist/storage/clearUserStorage.js +36 -0
  42. package/dist/storage/clearUserStorageOnOwnerChange.js +41 -0
  43. package/dist/storage/createStorage.js +48 -0
  44. package/dist/storage/listUserStorageKeys.js +36 -0
  45. package/dist/storage/readStorageItem.js +31 -0
  46. package/dist/storage/userStorageKey.js +24 -0
  47. package/dist/storage/userStoragePrefix.js +25 -0
  48. package/dist/useDarkMode.js +17 -4
  49. package/dist/useLocale.js +18 -5
  50. package/dist/websocket/createWebSocketClient.js +239 -100
  51. package/package.json +11 -10
@@ -15,7 +15,11 @@
15
15
  */ import React from 'react';
16
16
  import { omit, type } from '@lowdefy/helpers';
17
17
  import Icon from '@ant-design/icons';
18
+ import LucideIcon from 'lucide-react/dist/esm/Icon.mjs';
19
+ import { useLucideContext } from 'lucide-react/dist/esm/context.mjs';
18
20
  import { cn, withBlockDefaults, ErrorBoundary } from '@lowdefy/block-utils';
21
+ import formatIconTitle from './formatIconTitle.js';
22
+ import renderIconNodes from './renderIconNodes.js';
19
23
  import iconStyles from './style.module.css';
20
24
  const lowdefyProps = [
21
25
  'actionLog',
@@ -27,65 +31,163 @@ const lowdefyProps = [
27
31
  'loading',
28
32
  'menus',
29
33
  'pageId',
34
+ 'pathParams',
30
35
  'registerEvent',
31
36
  'registerMethod',
32
37
  'schemaErrors',
33
38
  'styles',
34
39
  'validation'
35
40
  ];
36
- const createIcon = (Icons)=>{
37
- const AiOutlineLoading3Quarters = Icons['AiOutlineLoading3Quarters'];
38
- const AiOutlineExclamationCircle = Icons['AiOutlineExclamationCircle'];
39
- const formatTitle = (title)=>{
40
- if (!title || !type.isString(title)) {
41
- return '';
41
+ // Drawn while every icon loads: an empty svg keeps the icon's box, so nothing
42
+ // shifts and no fallback flashes before the icon arrives.
43
+ const PENDING_ICON = {
44
+ node: []
45
+ };
46
+ function createIcon({ icons, loadAllIcons }) {
47
+ // Without a loader the map already holds every icon the app bundles.
48
+ let allIconsLoaded = type.isNone(loadAllIcons);
49
+ // icons is the live icon map: page loads, dev JIT and loadAllIcons add names
50
+ // to it, so every lookup happens at render.
51
+ function getIconData(name) {
52
+ if (type.isString(name) && Object.hasOwn(icons, name)) {
53
+ return icons[name];
42
54
  }
43
- let spacedTitle = title.replace(/([A-Z])/g, ' $1').trim();
44
- return spacedTitle.substring(spacedTitle.indexOf(' ') + 1);
45
- };
46
- const IconBlock = ({ blockId, classNames = {}, events, methods, onClick, properties, styles = {}, ...props })=>{
55
+ return null;
56
+ }
57
+ // A name missing from the page's icons may be one of the app's, so the first
58
+ // miss waits for every icon to load before it draws the fallback. After a
59
+ // load, a missing name is unknown. A failed load (logged by the loader)
60
+ // draws the fallback, and a later miss tries again.
61
+ function useIconData(name) {
62
+ const data = getIconData(name);
63
+ const [settledName, setSettledName] = React.useState(null);
64
+ const pending = data === null && type.isString(name) && !allIconsLoaded && settledName !== name;
65
+ React.useEffect(()=>{
66
+ if (!pending) return undefined;
67
+ let mounted = true;
68
+ loadAllIcons().then(()=>{
69
+ allIconsLoaded = true;
70
+ }, ()=>undefined).then(()=>{
71
+ if (mounted) setSettledName(name);
72
+ });
73
+ return ()=>{
74
+ mounted = false;
75
+ };
76
+ }, [
77
+ pending,
78
+ name
79
+ ]);
80
+ return {
81
+ data,
82
+ pending
83
+ };
84
+ }
85
+ function IconSvg({ data, nonScalingStroke, svgProps, title }) {
86
+ return /*#__PURE__*/ React.createElement(LucideIcon, {
87
+ icon: {
88
+ node: [],
89
+ size: data.size,
90
+ width: data.width,
91
+ height: data.height
92
+ },
93
+ ...data.attrs,
94
+ ...svgProps
95
+ }, [
96
+ title ? /*#__PURE__*/ React.createElement("title", {
97
+ key: "title"
98
+ }, title) : null,
99
+ ...renderIconNodes({
100
+ node: data.node,
101
+ nonScalingStroke
102
+ })
103
+ ]);
104
+ }
105
+ function IconBlock({ blockId, classNames = {}, events, methods, onClick, properties, styles = {}, ...props }) {
106
+ const lucideContext = useLucideContext();
47
107
  const propertiesObj = type.isString(properties) ? {
48
108
  name: properties
49
109
  } : properties;
110
+ const { data, pending } = useIconData(propertiesObj.name);
50
111
  const spin = (propertiesObj.spin || events.onClick?.loading) && !propertiesObj.disableLoadingIcon;
51
- const iconProps = {
112
+ const title = propertiesObj.title ?? formatIconTitle(propertiesObj.name);
113
+ const labelled = Boolean(title) || !type.isNone(props['aria-label']);
114
+ const nonScalingStroke = propertiesObj.nonScalingStroke ?? lucideContext.nonScalingStroke;
115
+ const triggerClick = events.onClick && (()=>methods.triggerEvent({
116
+ name: 'onClick'
117
+ }));
118
+ const svgProps = {
52
119
  id: blockId,
53
120
  className: cn(classNames.element, {
54
121
  [iconStyles['icon-spin']]: spin
55
122
  }),
56
123
  style: {
57
124
  cursor: onClick || events.onClick ? 'pointer' : undefined,
125
+ // CSS colour and transform, not SVG attributes: every set draws with
126
+ // currentColor, so one colour reaches stroke- and fill-based icons.
127
+ color: propertiesObj.color,
128
+ transform: propertiesObj.rotate ? `rotate(${propertiesObj.rotate}deg)` : undefined,
58
129
  ...styles.element
59
130
  },
60
- rotate: propertiesObj.rotate,
61
- color: propertiesObj.color,
62
- title: propertiesObj.title ?? formatTitle(propertiesObj.name),
63
131
  size: propertiesObj.size,
64
- // twoToneColor: propertiesObj.color, // TODO: track https://github.com/react-icons/react-icons/issues/508
132
+ strokeWidth: propertiesObj.strokeWidth,
133
+ 'aria-hidden': labelled ? undefined : 'true',
134
+ onClick: onClick ?? triggerClick,
65
135
  ...omit(props, lowdefyProps)
66
136
  };
67
- let IconComp = Icons[propertiesObj.name];
68
- if (!IconComp) {
69
- IconComp = AiOutlineExclamationCircle;
137
+ const missingSvgProps = {
138
+ ...svgProps,
139
+ style: {
140
+ ...svgProps.style,
141
+ color: '#F00'
142
+ }
143
+ };
144
+ const missingIcon = /*#__PURE__*/ React.createElement(IconSvg, {
145
+ data: getIconData('icon-missing'),
146
+ nonScalingStroke: nonScalingStroke,
147
+ svgProps: missingSvgProps,
148
+ title: title
149
+ });
150
+ if (spin) {
151
+ return /*#__PURE__*/ React.createElement(IconSvg, {
152
+ data: getIconData('loading'),
153
+ nonScalingStroke: nonScalingStroke,
154
+ svgProps: svgProps,
155
+ title: title
156
+ });
70
157
  }
71
- return /*#__PURE__*/ React.createElement(React.Fragment, null, spin ? /*#__PURE__*/ React.createElement(AiOutlineLoading3Quarters, iconProps) : /*#__PURE__*/ React.createElement(ErrorBoundary, {
72
- fallback: ()=>/*#__PURE__*/ React.createElement(AiOutlineExclamationCircle, {
73
- ...iconProps,
74
- color: '#F00'
75
- })
76
- }, /*#__PURE__*/ React.createElement(IconComp, {
77
- id: blockId,
78
- onClick: onClick || events.onClick && (()=>methods.triggerEvent({
79
- name: 'onClick'
80
- })),
81
- size: propertiesObj.size,
82
- title: propertiesObj.title,
83
- ...iconProps
158
+ if (pending) {
159
+ return /*#__PURE__*/ React.createElement(IconSvg, {
160
+ data: PENDING_ICON,
161
+ nonScalingStroke: nonScalingStroke,
162
+ svgProps: svgProps,
163
+ title: title
164
+ });
165
+ }
166
+ if (!data) {
167
+ return missingIcon;
168
+ }
169
+ return(// Keyed so an icon that failed recovers when its name changes.
170
+ /*#__PURE__*/ React.createElement(ErrorBoundary, {
171
+ key: propertiesObj.name,
172
+ fallback: ()=>missingIcon
173
+ }, /*#__PURE__*/ React.createElement(IconSvg, {
174
+ data: data,
175
+ nonScalingStroke: nonScalingStroke,
176
+ svgProps: svgProps,
177
+ title: title
84
178
  })));
85
- };
86
- const AntIcon = (all)=>/*#__PURE__*/ React.createElement(Icon, {
87
- component: ()=>/*#__PURE__*/ React.createElement(IconBlock, all)
88
- });
179
+ }
180
+ // antd's Icon renders `component` with React.createElement, so the component
181
+ // must keep its identity across renders or React remounts the <svg> every
182
+ // time. The per-render props travel through a render-function child instead.
183
+ const IconHost = ({ children })=>children();
184
+ // antd components add their classes (collapse arrow, tree switcher, menu
185
+ // expand icon) to the icon element they are given; their CSS expects them on
186
+ // the .anticon span, as on antd's own icons.
187
+ const AntIcon = ({ className, ...all })=>/*#__PURE__*/ React.createElement(Icon, {
188
+ className: className,
189
+ component: IconHost
190
+ }, ()=>/*#__PURE__*/ React.createElement(IconBlock, all));
89
191
  return withBlockDefaults(AntIcon);
90
- };
192
+ }
91
193
  export default createIcon;
@@ -0,0 +1,172 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ // Page lifecycle events. Browser signals a page can react to, so apps do not
16
+ // have to poll to notice that a tab went away and came back, that the network
17
+ // dropped, or that the window was resized.
18
+ //
19
+ // - onVisible document became visible again, or the window regained focus.
20
+ // - onHidden document became hidden, or the window lost focus.
21
+ // - onOnline the browser reports the network is back.
22
+ // - onOffline the browser reports the network is gone.
23
+ // - onResize the window was resized.
24
+ //
25
+ // None of these fire on the initial page load - the active state is seeded from
26
+ // the document when the page mounts, and only transitions away from that seed
27
+ // trigger an event.
28
+ //
29
+ // The same resize listener keeps _media current: once a resize settles, the
30
+ // engine re-evaluates the blocks that read a media value that changed.
31
+ const VISIBILITY_DEBOUNCE_MS = 300;
32
+ const RESIZE_DEBOUNCE_MS = 150;
33
+ function createPageLifecycleManager() {
34
+ let destroyed = false;
35
+ let removeListeners = [];
36
+ let timeouts = [];
37
+ function init(context) {
38
+ destroyed = false;
39
+ const globals = context?._internal?.lowdefy?._internal?.globals ?? {};
40
+ const win = globals.window;
41
+ const doc = globals.document;
42
+ // Server side render guard - there are no browser listeners to attach to.
43
+ if (typeof document === 'undefined' || !win || !doc) return;
44
+ if (typeof win.addEventListener !== 'function' || typeof doc.addEventListener !== 'function') {
45
+ return;
46
+ }
47
+ const events = context._internal.rootBlock?.events ?? {};
48
+ const configured = (name)=>!!events[name];
49
+ const trigger = (name, event)=>{
50
+ if (destroyed) return;
51
+ // Page lifecycle chains run like onMountAsync - non blocking, and a
52
+ // failure in the chain must not break the page.
53
+ try {
54
+ Promise.resolve(context._internal.triggerPageEvent({
55
+ name,
56
+ event
57
+ })).catch((error)=>{
58
+ context._internal.lowdefy._internal.handleError?.(error);
59
+ });
60
+ } catch (error) {
61
+ context._internal.lowdefy._internal.handleError?.(error);
62
+ }
63
+ };
64
+ const setTimer = (fn, ms)=>{
65
+ const timeout = setTimeout(()=>{
66
+ timeouts = timeouts.filter((t)=>t !== timeout);
67
+ fn();
68
+ }, ms);
69
+ timeouts.push(timeout);
70
+ return timeout;
71
+ };
72
+ const listen = (target, name, handler)=>{
73
+ target.addEventListener(name, handler);
74
+ removeListeners.push(()=>target.removeEventListener(name, handler));
75
+ };
76
+ // --- onVisible / onHidden ------------------------------------------------
77
+ if (configured('onVisible') || configured('onHidden')) {
78
+ // Seeded from the document so the initial load never fires an event.
79
+ let active = doc.visibilityState !== 'hidden';
80
+ let pending = null;
81
+ const flush = ()=>{
82
+ const next = pending;
83
+ pending = null;
84
+ if (destroyed || !next) return;
85
+ // A focus that only restores a state we are already in is not an event.
86
+ if (next.active === active) return;
87
+ active = next.active;
88
+ trigger(next.active ? 'onVisible' : 'onHidden', {
89
+ visible: next.active,
90
+ reason: next.reason
91
+ });
92
+ };
93
+ // A tab coming back to the front fires visibilitychange and focus. Both
94
+ // are collected into one window so the event chain only runs once.
95
+ const signal = (nextActive, reason)=>{
96
+ if (destroyed) return;
97
+ if (pending) {
98
+ pending.active = nextActive;
99
+ // A visibilitychange is the more precise reason, so it wins the pair.
100
+ if (reason === 'visibility') pending.reason = 'visibility';
101
+ return;
102
+ }
103
+ pending = {
104
+ active: nextActive,
105
+ reason
106
+ };
107
+ setTimer(flush, VISIBILITY_DEBOUNCE_MS);
108
+ };
109
+ listen(doc, 'visibilitychange', ()=>{
110
+ signal(doc.visibilityState !== 'hidden', 'visibility');
111
+ });
112
+ listen(win, 'focus', ()=>signal(true, 'focus'));
113
+ listen(win, 'blur', ()=>signal(false, 'focus'));
114
+ }
115
+ // --- onOnline / onOffline ------------------------------------------------
116
+ if (configured('onOnline')) {
117
+ listen(win, 'online', ()=>trigger('onOnline', {
118
+ online: true
119
+ }));
120
+ }
121
+ if (configured('onOffline')) {
122
+ listen(win, 'offline', ()=>trigger('onOffline', {
123
+ online: false
124
+ }));
125
+ }
126
+ // --- onResize and _media -------------------------------------------------
127
+ // The operator registry holds the operators of every page loaded so far, so
128
+ // a page that can not reach _media attaches no listener for it.
129
+ const watchMedia = !!context._internal.lowdefy._internal.operators._media;
130
+ const onResize = configured('onResize');
131
+ if (watchMedia || onResize) {
132
+ let resizeTimeout = null;
133
+ // One trailing debounce for the whole page: a drag fires resize at frame
134
+ // rate, and this evaluates once when it settles. A requestAnimationFrame
135
+ // throttle would evaluate every frame of the drag instead.
136
+ const flush = ()=>{
137
+ resizeTimeout = null;
138
+ if (destroyed) return;
139
+ // Blocks first, so onResize actions see the page evaluated at the new size.
140
+ if (watchMedia) {
141
+ context._internal.updateMedia();
142
+ }
143
+ if (onResize) {
144
+ trigger('onResize', {
145
+ width: win.innerWidth,
146
+ height: win.innerHeight
147
+ });
148
+ }
149
+ };
150
+ listen(win, 'resize', ()=>{
151
+ if (resizeTimeout) {
152
+ clearTimeout(resizeTimeout);
153
+ timeouts = timeouts.filter((t)=>t !== resizeTimeout);
154
+ }
155
+ resizeTimeout = setTimer(flush, RESIZE_DEBOUNCE_MS);
156
+ });
157
+ }
158
+ }
159
+ function destroy() {
160
+ destroyed = true;
161
+ removeListeners.forEach((remove)=>remove());
162
+ removeListeners = [];
163
+ timeouts.forEach((timeout)=>clearTimeout(timeout));
164
+ timeouts = [];
165
+ }
166
+ return {
167
+ init,
168
+ destroy
169
+ };
170
+ }
171
+ export { RESIZE_DEBOUNCE_MS, VISIBILITY_DEBOUNCE_MS };
172
+ export default createPageLifecycleManager;
@@ -0,0 +1,26 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type } from '@lowdefy/helpers';
16
+ // Pencil -> "Pencil", ArrowLeftRight -> "Arrow left right", more-vertical ->
17
+ // "More vertical", lucide:Pencil -> "Pencil".
18
+ function formatIconTitle(name) {
19
+ if (!type.isString(name)) {
20
+ return '';
21
+ }
22
+ const iconName = name.slice(name.indexOf(':') + 1);
23
+ const words = iconName.replace(/-/g, ' ').replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase();
24
+ return words.charAt(0).toUpperCase() + words.slice(1);
25
+ }
26
+ export default formatIconTitle;
@@ -0,0 +1,27 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type } from '@lowdefy/helpers';
16
+ // A language tag: a 2–3 letter language and optional subtags ("en", "en-US",
17
+ // "zh-Hant-TW"). Intl throws on anything else, such as "en_US".
18
+ const LANGUAGE_TAG = /^[A-Za-z]{2,3}(-[A-Za-z0-9]{1,8})*$/;
19
+ // The app's active locale that useLocale publishes, for formatting HTML dates
20
+ // and numbers. Undefined (the browser's locale) when the app has no i18n or the
21
+ // code is not a language tag Intl accepts.
22
+ function getActiveLocale(window) {
23
+ const locale = window.__lowdefy_locale;
24
+ if (!type.isString(locale) || !LANGUAGE_TAG.test(locale)) return undefined;
25
+ return locale;
26
+ }
27
+ export default getActiveLocale;
@@ -0,0 +1,29 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ const contextKeys = new WeakMap();
16
+ let lastKey = 0;
17
+ // A React key per engine context object. A page context rebuilt in place - an
18
+ // organization switch, a dev config reset - is a new object under the same
19
+ // instance key, and keying the page tree by it remounts the tree onto the new
20
+ // context rather than reconciling mounted blocks against it, which React can
21
+ // silently drop.
22
+ function getContextKey(context) {
23
+ if (!contextKeys.has(context)) {
24
+ lastKey += 1;
25
+ contextKeys.set(context, lastKey);
26
+ }
27
+ return contextKeys.get(context);
28
+ }
29
+ export default getContextKey;
@@ -0,0 +1,43 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ // Engine and DisplayMessage messages render through antd's App holders, above
16
+ // the page's ConfigProvider, so each message carries its status icon itself.
17
+ const messageIcons = {
18
+ error: {
19
+ name: 'error',
20
+ title: ''
21
+ },
22
+ info: {
23
+ name: 'info',
24
+ title: ''
25
+ },
26
+ loading: {
27
+ name: 'loading',
28
+ spin: true,
29
+ title: ''
30
+ },
31
+ success: {
32
+ name: 'success',
33
+ title: ''
34
+ },
35
+ warning: {
36
+ name: 'warning',
37
+ title: ''
38
+ }
39
+ };
40
+ function getMessageIcon({ status }) {
41
+ return messageIcons[status];
42
+ }
43
+ export default getMessageIcon;
@@ -0,0 +1,30 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { rolesHash, type } from '@lowdefy/helpers';
16
+ // True when the server answered the rendered caller with other roles than the
17
+ // page renders - a role was granted or taken away since the page loaded - so
18
+ // _user.roles and role-gated blocks would show access the person no longer
19
+ // holds, or hide access they now have. stamp is the response's parsed
20
+ // x-lowdefy-caller header, user the rendered caller. A stamp that names no
21
+ // roles (no header, or an anonymous caller) claims none.
22
+ function hasOtherRoles({ stamp, user }) {
23
+ if (type.isNone(user) || type.isNone(stamp?.roles)) {
24
+ return false;
25
+ }
26
+ return stamp.roles !== rolesHash({
27
+ user
28
+ });
29
+ }
30
+ export default hasOtherRoles;
package/dist/index.js CHANGED
@@ -15,5 +15,6 @@
15
15
  */ import Client from './Client.js';
16
16
  export default Client;
17
17
  export { default as getOrCreateAntdCssContainer } from './getOrCreateAntdCssContainer.js';
18
+ export { default as IconProvider } from './IconProvider.js';
18
19
  export { default as useDarkMode } from './useDarkMode.js';
19
20
  export { default as useLocale } from './useLocale.js';