@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.
- package/dist/Client.js +54 -17
- package/dist/Context.js +30 -12
- package/dist/HtmlOverlay.js +127 -0
- package/dist/IconProvider.js +24 -0
- package/dist/MountEvents.js +3 -3
- package/dist/adapters/createRouter.js +9 -3
- package/dist/adapters/url.js +1 -7
- package/dist/auth/createAuthMethods.js +1034 -41
- package/dist/block/Block.js +6 -3
- package/dist/block/CategorySwitch.js +6 -0
- package/dist/block/Container.js +3 -0
- package/dist/block/InputContainer.js +8 -2
- package/dist/block/List.js +3 -0
- package/dist/block/LoadingBlock.js +6 -12
- package/dist/block/LoadingContainer.js +6 -1
- package/dist/block/LoadingList.js +6 -1
- package/dist/block/loadingBlockMethods.js +26 -0
- package/dist/claimCallerReload.js +38 -0
- package/dist/createAntdIconConfig.js +91 -0
- package/dist/createCallAPI.js +6 -2
- package/dist/createCallRequest.js +6 -2
- package/dist/createHandleError.js +21 -11
- package/dist/createIcon.js +138 -36
- package/dist/createPageLifecycleManager.js +172 -0
- package/dist/formatIconTitle.js +26 -0
- package/dist/getActiveLocale.js +27 -0
- package/dist/getContextKey.js +29 -0
- package/dist/getMessageIcon.js +43 -0
- package/dist/hasOtherRoles.js +30 -0
- package/dist/index.js +1 -0
- package/dist/initLowdefyContext.js +103 -4
- package/dist/isOtherCaller.js +29 -0
- package/dist/recordCallerStamp.js +31 -0
- package/dist/renderIconNodes.js +46 -0
- package/dist/request.js +74 -6
- package/dist/setLowdefyUser.js +32 -0
- package/dist/setupLink.js +19 -13
- package/dist/shouldReloadForAuthError.js +39 -0
- package/dist/shouldReloadForBuild.js +44 -0
- package/dist/shouldReloadForCaller.js +50 -0
- package/dist/storage/clearUserStorage.js +36 -0
- package/dist/storage/clearUserStorageOnOwnerChange.js +41 -0
- package/dist/storage/createStorage.js +48 -0
- package/dist/storage/listUserStorageKeys.js +36 -0
- package/dist/storage/readStorageItem.js +31 -0
- package/dist/storage/userStorageKey.js +24 -0
- package/dist/storage/userStoragePrefix.js +25 -0
- package/dist/useDarkMode.js +17 -4
- package/dist/useLocale.js +18 -5
- package/dist/websocket/createWebSocketClient.js +239 -100
- package/package.json +11 -10
package/dist/Client.js
CHANGED
|
@@ -13,14 +13,19 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import React from 'react';
|
|
16
|
-
import {
|
|
16
|
+
import { ConfigProvider } from 'antd';
|
|
17
|
+
import { getAppContext } from '@lowdefy/engine';
|
|
18
|
+
import { pageInstanceKey, serializer } from '@lowdefy/helpers';
|
|
17
19
|
import Block from './block/Block.js';
|
|
18
20
|
import Context from './Context.js';
|
|
19
21
|
import DisplayMessage from './DisplayMessage.js';
|
|
20
22
|
import Head from './Head.js';
|
|
23
|
+
import IconProvider from './IconProvider.js';
|
|
21
24
|
import ProgressBarController from './ProgressBarController.js';
|
|
25
|
+
import createAntdIconConfig from './createAntdIconConfig.js';
|
|
26
|
+
import getMessageIcon from './getMessageIcon.js';
|
|
22
27
|
import initLowdefyContext from './initLowdefyContext.js';
|
|
23
|
-
const Client = ({ auth, Components, config: rawConfig, jsMap, lowdefy, resetContext = {
|
|
28
|
+
const Client = ({ auth, Components, config: rawConfig, jsMap, loadAllIcons, lowdefy, matchedPath, pathParams, resetContext = {
|
|
24
29
|
reset: false,
|
|
25
30
|
setReset: ()=>undefined
|
|
26
31
|
}, router, stage, types, window })=>{
|
|
@@ -32,10 +37,17 @@ const Client = ({ auth, Components, config: rawConfig, jsMap, lowdefy, resetCont
|
|
|
32
37
|
rawConfig.pageConfig,
|
|
33
38
|
rawConfig.rootConfig
|
|
34
39
|
]);
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// context
|
|
40
|
+
// Each page instance (one per set of path values) has its own engine
|
|
41
|
+
// context, so a new instance remounts the page tree instead of reconciling
|
|
42
|
+
// the previous instance's blocks against it. Dynamic pages also rebuild their
|
|
43
|
+
// context per fetch, which React can silently drop when reconciling, so their
|
|
44
|
+
// key changes with every fetch. Static page instances keep their context and
|
|
45
|
+
// tree across navigations, so their key stays stable.
|
|
46
|
+
const instanceKey = pageInstanceKey({
|
|
47
|
+
pageId: config.pageConfig.pageId,
|
|
48
|
+
path: config.pageConfig.path,
|
|
49
|
+
pathParams
|
|
50
|
+
});
|
|
39
51
|
const buildRef = React.useRef({
|
|
40
52
|
config: null,
|
|
41
53
|
build: 0
|
|
@@ -46,18 +58,35 @@ const Client = ({ auth, Components, config: rawConfig, jsMap, lowdefy, resetCont
|
|
|
46
58
|
build: buildRef.current.build + 1
|
|
47
59
|
};
|
|
48
60
|
}
|
|
49
|
-
const contextKey = config.pageConfig.dynamic === true ? `${
|
|
61
|
+
const contextKey = config.pageConfig.dynamic === true ? `${instanceKey}:${buildRef.current.build}` : instanceKey;
|
|
50
62
|
initLowdefyContext({
|
|
51
63
|
auth,
|
|
52
64
|
Components,
|
|
53
65
|
config,
|
|
66
|
+
loadAllIcons,
|
|
54
67
|
lowdefy,
|
|
68
|
+
matchedPath,
|
|
69
|
+
pathParams,
|
|
55
70
|
router,
|
|
56
71
|
stage,
|
|
57
72
|
types,
|
|
58
73
|
window
|
|
59
74
|
});
|
|
60
|
-
|
|
75
|
+
const appContext = getAppContext({
|
|
76
|
+
events: config.rootConfig.events,
|
|
77
|
+
jsMap,
|
|
78
|
+
lowdefy
|
|
79
|
+
});
|
|
80
|
+
const Icon = lowdefy._internal.components.Icon;
|
|
81
|
+
const antdIconConfig = React.useMemo(()=>createAntdIconConfig({
|
|
82
|
+
Icon
|
|
83
|
+
}), [
|
|
84
|
+
Icon
|
|
85
|
+
]);
|
|
86
|
+
return(// theme.icons is read at render: createIcon runs before the theme is set.
|
|
87
|
+
/*#__PURE__*/ React.createElement(IconProvider, {
|
|
88
|
+
icons: lowdefy.theme.icons
|
|
89
|
+
}, /*#__PURE__*/ React.createElement(ProgressBarController, {
|
|
61
90
|
id: "lowdefy-progress-bar",
|
|
62
91
|
key: `${config.pageConfig.id}-progress-bar`,
|
|
63
92
|
lowdefy: lowdefy,
|
|
@@ -69,27 +98,35 @@ const Client = ({ auth, Components, config: rawConfig, jsMap, lowdefy, resetCont
|
|
|
69
98
|
components: lowdefy._internal.components,
|
|
70
99
|
methods: {
|
|
71
100
|
registerMethod: (_, method)=>{
|
|
72
|
-
lowdefy._internal.displayMessage = method
|
|
101
|
+
lowdefy._internal.displayMessage = (args)=>method({
|
|
102
|
+
...args,
|
|
103
|
+
icon: args.icon ?? getMessageIcon({
|
|
104
|
+
status: args.status ?? 'success'
|
|
105
|
+
})
|
|
106
|
+
});
|
|
73
107
|
}
|
|
74
108
|
}
|
|
75
|
-
}), /*#__PURE__*/ React.createElement(Context, {
|
|
109
|
+
}), /*#__PURE__*/ React.createElement(ConfigProvider, antdIconConfig, /*#__PURE__*/ React.createElement(Context, {
|
|
76
110
|
key: contextKey,
|
|
111
|
+
appContext: appContext,
|
|
77
112
|
config: config.pageConfig,
|
|
78
113
|
jsMap: jsMap,
|
|
79
114
|
lowdefy: lowdefy,
|
|
115
|
+
pathParams: pathParams,
|
|
80
116
|
resetContext: resetContext
|
|
81
117
|
}, (context)=>{
|
|
82
118
|
if (!context._internal.onInitDone) return '';
|
|
83
|
-
return /*#__PURE__*/ React.createElement(
|
|
84
|
-
Component: Components.Head,
|
|
85
|
-
properties: context._internal.RootSlots.map[config.pageConfig.blockId].eval.properties
|
|
86
|
-
}), /*#__PURE__*/ React.createElement(Block, {
|
|
119
|
+
return /*#__PURE__*/ React.createElement(Block, {
|
|
87
120
|
block: context._internal.RootSlots.map[config.pageConfig.blockId],
|
|
88
121
|
Blocks: context._internal.RootSlots,
|
|
89
122
|
context: context,
|
|
90
123
|
lowdefy: lowdefy,
|
|
91
|
-
parentLoading: false
|
|
92
|
-
|
|
93
|
-
|
|
124
|
+
parentLoading: false,
|
|
125
|
+
renderHead: ({ properties })=>/*#__PURE__*/ React.createElement(Head, {
|
|
126
|
+
Component: Components.Head,
|
|
127
|
+
properties: properties
|
|
128
|
+
})
|
|
129
|
+
});
|
|
130
|
+
}))));
|
|
94
131
|
};
|
|
95
132
|
export default Client;
|
package/dist/Context.js
CHANGED
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import React, { useEffect } from 'react';
|
|
16
16
|
import getContext from '@lowdefy/engine';
|
|
17
|
+
import createPageLifecycleManager from './createPageLifecycleManager.js';
|
|
17
18
|
import createShortcutManager from './createShortcutManager.js';
|
|
19
|
+
import getContextKey from './getContextKey.js';
|
|
18
20
|
import MountEvents from './MountEvents.js';
|
|
19
21
|
const ShortcutEffect = ({ context })=>{
|
|
20
22
|
useEffect(()=>{
|
|
@@ -26,6 +28,16 @@ const ShortcutEffect = ({ context })=>{
|
|
|
26
28
|
]);
|
|
27
29
|
return null;
|
|
28
30
|
};
|
|
31
|
+
const PageLifecycleEffect = ({ context })=>{
|
|
32
|
+
useEffect(()=>{
|
|
33
|
+
const manager = createPageLifecycleManager();
|
|
34
|
+
manager.init(context);
|
|
35
|
+
return ()=>manager.destroy();
|
|
36
|
+
}, [
|
|
37
|
+
context
|
|
38
|
+
]);
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
29
41
|
const WebSocketsEffect = ({ context })=>{
|
|
30
42
|
useEffect(()=>{
|
|
31
43
|
context._internal.WebSockets.subscribeAll();
|
|
@@ -35,33 +47,39 @@ const WebSocketsEffect = ({ context })=>{
|
|
|
35
47
|
]);
|
|
36
48
|
return null;
|
|
37
49
|
};
|
|
38
|
-
const Context = ({ children, config, jsMap, lowdefy, resetContext })=>{
|
|
50
|
+
const Context = ({ appContext, children, config, jsMap, lowdefy, pathParams, resetContext })=>{
|
|
39
51
|
const context = getContext({
|
|
40
52
|
config,
|
|
41
53
|
jsMap,
|
|
42
54
|
lowdefy,
|
|
55
|
+
pathParams,
|
|
43
56
|
resetContext
|
|
44
57
|
});
|
|
58
|
+
const progress = ()=>{
|
|
59
|
+
lowdefy._internal.progress.dispatch({
|
|
60
|
+
type: 'increment'
|
|
61
|
+
});
|
|
62
|
+
};
|
|
45
63
|
return /*#__PURE__*/ React.createElement(MountEvents, {
|
|
64
|
+
key: getContextKey(context),
|
|
46
65
|
context: context,
|
|
47
66
|
triggerEvent: async ()=>{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
// The app events run once per app load, when the first page mounts. The
|
|
68
|
+
// page onInit waits for the app onInit, but not for the app onInitAsync.
|
|
69
|
+
await appContext._internal.runOnInit(progress);
|
|
70
|
+
appContext._internal.runOnInitAsync(progress);
|
|
71
|
+
await context._internal.runOnInit(progress);
|
|
53
72
|
},
|
|
54
73
|
triggerEventAsync: ()=>{
|
|
55
|
-
context._internal.runOnInitAsync(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
}
|
|
74
|
+
context._internal.runOnInitAsync(progress);
|
|
75
|
+
},
|
|
76
|
+
waitForMount: true
|
|
61
77
|
}, (loadingOnInit)=>{
|
|
62
78
|
if (loadingOnInit) return '';
|
|
63
79
|
return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(ShortcutEffect, {
|
|
64
80
|
context: context
|
|
81
|
+
}), /*#__PURE__*/ React.createElement(PageLifecycleEffect, {
|
|
82
|
+
context: context
|
|
65
83
|
}), /*#__PURE__*/ React.createElement(WebSocketsEffect, {
|
|
66
84
|
context: context
|
|
67
85
|
}), children(context));
|
|
@@ -0,0 +1,127 @@
|
|
|
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 React, { forwardRef, useEffect, useId, useImperativeHandle, useRef } from 'react';
|
|
16
|
+
import { Popconfirm, Popover, Tooltip } from 'antd';
|
|
17
|
+
// The target lives in HTML React does not own, so antd cannot wrap it. antd's
|
|
18
|
+
// trigger resolves its anchor from the child's ref, so a child that renders
|
|
19
|
+
// nothing and hands back the target element anchors the popup on the target
|
|
20
|
+
// itself, and the trigger follows its scrolling and resizing.
|
|
21
|
+
const TargetAnchor = /*#__PURE__*/ forwardRef(function TargetAnchor({ target }, ref) {
|
|
22
|
+
useImperativeHandle(ref, ()=>target, [
|
|
23
|
+
target
|
|
24
|
+
]);
|
|
25
|
+
return null;
|
|
26
|
+
});
|
|
27
|
+
// Open popovers and confirms, newest last. Escape and outside clicks close only
|
|
28
|
+
// the newest, so a confirm opened inside a popover closes before the popover,
|
|
29
|
+
// and a click in the confirm is not an outside click for the popover.
|
|
30
|
+
const dismissable = [];
|
|
31
|
+
function isTop(entry) {
|
|
32
|
+
return dismissable[dismissable.length - 1] === entry;
|
|
33
|
+
}
|
|
34
|
+
function ConfirmOverlay({ content, Icon, onClose, onConfirm, popupClassName, target }) {
|
|
35
|
+
const messageId = `${useId().replace(/[^a-zA-Z0-9_-]/g, '')}-message`;
|
|
36
|
+
const cancelRef = useRef(null);
|
|
37
|
+
return /*#__PURE__*/ React.createElement(Popconfirm, {
|
|
38
|
+
afterOpenChange: (open)=>{
|
|
39
|
+
// The least destructive action takes focus (WAI-ARIA alert dialog),
|
|
40
|
+
// once the popup is placed so focusing it cannot scroll the page.
|
|
41
|
+
if (open) cancelRef.current?.focus();
|
|
42
|
+
},
|
|
43
|
+
cancelButtonProps: {
|
|
44
|
+
'aria-describedby': messageId,
|
|
45
|
+
ref: cancelRef
|
|
46
|
+
},
|
|
47
|
+
icon: /*#__PURE__*/ React.createElement(Icon, {
|
|
48
|
+
properties: {
|
|
49
|
+
name: 'warning',
|
|
50
|
+
title: ''
|
|
51
|
+
}
|
|
52
|
+
}),
|
|
53
|
+
okButtonProps: {
|
|
54
|
+
'aria-describedby': messageId
|
|
55
|
+
},
|
|
56
|
+
onCancel: ()=>onClose('cancel'),
|
|
57
|
+
onConfirm: onConfirm,
|
|
58
|
+
open: true,
|
|
59
|
+
placement: "top",
|
|
60
|
+
rootClassName: popupClassName,
|
|
61
|
+
title: /*#__PURE__*/ React.createElement("span", {
|
|
62
|
+
id: messageId
|
|
63
|
+
}, content),
|
|
64
|
+
trigger: []
|
|
65
|
+
}, /*#__PURE__*/ React.createElement(TargetAnchor, {
|
|
66
|
+
target: target
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
function HtmlOverlay({ content, Icon, kind, onClose, onConfirm, target }) {
|
|
70
|
+
const popupClassName = `lowdefy-html-${kind}-${useId().replace(/[^a-zA-Z0-9_-]/g, '')}`;
|
|
71
|
+
useEffect(()=>{
|
|
72
|
+
// Tooltips close on Escape but take no clicks, so they are not dismissable.
|
|
73
|
+
const entry = kind === 'tooltip' ? null : {};
|
|
74
|
+
if (entry) dismissable.push(entry);
|
|
75
|
+
// A click on the trigger toggles the overlay itself; one inside the popup
|
|
76
|
+
// is inside the overlay.
|
|
77
|
+
function onPointerDown(event) {
|
|
78
|
+
if (!entry || !isTop(entry)) return;
|
|
79
|
+
if (target.contains(event.target)) return;
|
|
80
|
+
if (event.target.closest?.(`.${popupClassName}`)) return;
|
|
81
|
+
onClose('outside');
|
|
82
|
+
}
|
|
83
|
+
function onKeyDown(event) {
|
|
84
|
+
if (event.key !== 'Escape' || event.isComposing) return;
|
|
85
|
+
// Every tooltip closes on Escape; of popovers and confirms, the newest.
|
|
86
|
+
if (!entry || isTop(entry)) onClose('escape');
|
|
87
|
+
}
|
|
88
|
+
document.addEventListener('mousedown', onPointerDown);
|
|
89
|
+
document.addEventListener('keydown', onKeyDown);
|
|
90
|
+
return ()=>{
|
|
91
|
+
if (entry) dismissable.splice(dismissable.indexOf(entry), 1);
|
|
92
|
+
document.removeEventListener('mousedown', onPointerDown);
|
|
93
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
94
|
+
};
|
|
95
|
+
}, [
|
|
96
|
+
kind,
|
|
97
|
+
onClose,
|
|
98
|
+
popupClassName,
|
|
99
|
+
target
|
|
100
|
+
]);
|
|
101
|
+
if (kind === 'confirm') {
|
|
102
|
+
return /*#__PURE__*/ React.createElement(ConfirmOverlay, {
|
|
103
|
+
content: content,
|
|
104
|
+
Icon: Icon,
|
|
105
|
+
onClose: onClose,
|
|
106
|
+
onConfirm: onConfirm,
|
|
107
|
+
popupClassName: popupClassName,
|
|
108
|
+
target: target
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const Overlay = kind === 'popover' ? Popover : Tooltip;
|
|
112
|
+
const contentProps = kind === 'popover' ? {
|
|
113
|
+
content
|
|
114
|
+
} : {
|
|
115
|
+
title: content
|
|
116
|
+
};
|
|
117
|
+
return /*#__PURE__*/ React.createElement(Overlay, {
|
|
118
|
+
...contentProps,
|
|
119
|
+
open: true,
|
|
120
|
+
placement: "top",
|
|
121
|
+
rootClassName: popupClassName,
|
|
122
|
+
trigger: []
|
|
123
|
+
}, /*#__PURE__*/ React.createElement(TargetAnchor, {
|
|
124
|
+
target: target
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
export default HtmlOverlay;
|
|
@@ -0,0 +1,24 @@
|
|
|
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 React from 'react';
|
|
16
|
+
import { LucideProvider } from 'lucide-react/dist/esm/context.mjs';
|
|
17
|
+
function IconProvider({ children, icons }) {
|
|
18
|
+
return /*#__PURE__*/ React.createElement(LucideProvider, {
|
|
19
|
+
nonScalingStroke: icons.nonScalingStroke,
|
|
20
|
+
size: icons.size,
|
|
21
|
+
strokeWidth: icons.strokeWidth
|
|
22
|
+
}, children);
|
|
23
|
+
}
|
|
24
|
+
export default IconProvider;
|
package/dist/MountEvents.js
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import React, { useEffect, useState } from 'react';
|
|
16
|
-
const MountEvents = ({ children, context, triggerEvent, triggerEventAsync })=>{
|
|
17
|
-
const [loading, setLoading] = useState(
|
|
16
|
+
const MountEvents = ({ children, context, triggerEvent, triggerEventAsync, waitForMount })=>{
|
|
17
|
+
const [loading, setLoading] = useState(waitForMount);
|
|
18
18
|
const [error, setError] = useState(null);
|
|
19
19
|
useEffect(()=>{
|
|
20
|
-
setLoading(
|
|
20
|
+
setLoading(waitForMount);
|
|
21
21
|
const mount = async ()=>{
|
|
22
22
|
try {
|
|
23
23
|
await triggerEvent();
|
|
@@ -12,10 +12,13 @@
|
|
|
12
12
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
|
-
*/ import {
|
|
15
|
+
*/ import { parsePageId } from '@lowdefy/helpers';
|
|
16
|
+
import { createUrl } from './url.js';
|
|
16
17
|
// History-API router backing the @lowdefy/client router contract:
|
|
17
18
|
// push({ pathname, query }), back(), basePath — plus subscribe() for the
|
|
18
|
-
// page component and sessionStorage-backed scroll restoration.
|
|
19
|
+
// page component and sessionStorage-backed scroll restoration. A location names
|
|
20
|
+
// no page: the client learns a path's page from the server's page response, and
|
|
21
|
+
// reads it back from the path memory.
|
|
19
22
|
function createRouter({ basePath = '', window }) {
|
|
20
23
|
const listeners = new Set();
|
|
21
24
|
let entryKey = window.history.state?.lowdefyKey ?? `k${Date.now().toString(36)}`;
|
|
@@ -52,9 +55,12 @@ function createRouter({ basePath = '', window }) {
|
|
|
52
55
|
window.scrollTo(position?.x ?? 0, position?.y ?? 0);
|
|
53
56
|
});
|
|
54
57
|
}
|
|
58
|
+
// path is the pathname without basePath, the leading "/" and one trailing "/",
|
|
59
|
+
// still encoded: the form page requests and the path memory use. It is ''
|
|
60
|
+
// at the app root.
|
|
55
61
|
function getLocation() {
|
|
56
62
|
return {
|
|
57
|
-
|
|
63
|
+
path: parsePageId(window.location.href, basePath) ?? '',
|
|
58
64
|
pathname: window.location.pathname,
|
|
59
65
|
search: window.location.search
|
|
60
66
|
};
|
package/dist/adapters/url.js
CHANGED
|
@@ -26,10 +26,4 @@ function createUrl({ basePath = '', pathname, query }) {
|
|
|
26
26
|
const serializedQuery = serializeQuery(query);
|
|
27
27
|
return `${basePath}${pathname}${serializedQuery ? `?${serializedQuery}` : ''}`;
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
const pathname = new URL(url, 'http://localhost').pathname;
|
|
31
|
-
const stripped = basePath && pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
|
|
32
|
-
const pageId = stripped.replace(/^\//, '').replace(/\/$/, '');
|
|
33
|
-
return pageId === '' ? null : pageId;
|
|
34
|
-
}
|
|
35
|
-
export { createUrl, parsePageId, serializeQuery };
|
|
29
|
+
export { createUrl, serializeQuery };
|