@lowdefy/engine 5.5.0 → 5.6.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/Block.js +18 -0
- package/dist/State.js +3 -3
- package/dist/actions/getFromObject.js +14 -8
- package/dist/createLink.js +46 -38
- package/dist/getHomePathname.js +34 -0
- package/dist/index.js +3 -1
- package/dist/resolveTarget.js +126 -0
- package/package.json +8 -8
package/dist/Block.js
CHANGED
|
@@ -247,6 +247,9 @@ let Block = class Block {
|
|
|
247
247
|
if (beforeVisible !== this.visibleEval.output) {
|
|
248
248
|
repeat.value = true;
|
|
249
249
|
}
|
|
250
|
+
if (this.isList() && !this.isVisible()) {
|
|
251
|
+
this.captureHiddenValue();
|
|
252
|
+
}
|
|
250
253
|
if (this.visibleEval.output !== false) {
|
|
251
254
|
this.propertiesEval = this.parse(this.properties);
|
|
252
255
|
this.requiredEval = this.parse(this.required);
|
|
@@ -347,8 +350,23 @@ let Block = class Block {
|
|
|
347
350
|
visibleEval: this.visibleEval
|
|
348
351
|
});
|
|
349
352
|
});
|
|
353
|
+
_define_property(this, "captureHiddenValue", ()=>{
|
|
354
|
+
const stateValue = get(this.context.state, this.blockId);
|
|
355
|
+
if (type.isUndefined(stateValue)) return;
|
|
356
|
+
this.hiddenValue = serializer.copy(stateValue);
|
|
357
|
+
});
|
|
358
|
+
_define_property(this, "restoreHiddenValue", ()=>{
|
|
359
|
+
if (type.isUndefined(this.hiddenValue)) return;
|
|
360
|
+
if (type.isUndefined(get(this.context.state, this.blockId))) {
|
|
361
|
+
this.context._internal.State.set(this.blockId, this.hiddenValue);
|
|
362
|
+
}
|
|
363
|
+
this.hiddenValue = undefined;
|
|
364
|
+
});
|
|
350
365
|
_define_property(this, "updateState", (toSet)=>{
|
|
351
366
|
if (!this.isVisible()) return;
|
|
367
|
+
if (this.isList()) {
|
|
368
|
+
this.restoreHiddenValue();
|
|
369
|
+
}
|
|
352
370
|
if (this.isContainer() || this.isList()) {
|
|
353
371
|
if (this.subSlots && this.subSlots.length > 0) {
|
|
354
372
|
this.loopSubSlots((subSlotsClass)=>subSlotsClass.updateState());
|
package/dist/State.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
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 { unset, get, serializer, set, swap, type } from '@lowdefy/helpers';
|
|
15
|
+
*/ import { unset, get, joinPath, serializer, set, splitPath, swap, type } from '@lowdefy/helpers';
|
|
16
16
|
let State = class State {
|
|
17
17
|
resetState() {
|
|
18
18
|
Object.keys(this.context.state).forEach((key)=>{
|
|
@@ -35,9 +35,9 @@ let State = class State {
|
|
|
35
35
|
del(field) {
|
|
36
36
|
unset(this.context.state, field);
|
|
37
37
|
// remove all empty objects from state as an effect of deleted values
|
|
38
|
-
const fields = field
|
|
38
|
+
const fields = splitPath(field);
|
|
39
39
|
if (fields.length > 1) {
|
|
40
|
-
const parent = fields.slice(0,
|
|
40
|
+
const parent = joinPath(fields.slice(0, -1));
|
|
41
41
|
const parentValue = get(this.context.state, parent);
|
|
42
42
|
if (type.isObject(parentValue) && Object.keys(parentValue).length === 0) {
|
|
43
43
|
this.del(parent);
|
|
@@ -12,7 +12,7 @@
|
|
|
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 { applyArrayIndices, get, serializer, type } from '@lowdefy/helpers';
|
|
15
|
+
*/ import { ReservedKeyError, applyArrayIndices, get, serializer, type } from '@lowdefy/helpers';
|
|
16
16
|
const getFromObject = ({ arrayIndices, location, method, object, params })=>{
|
|
17
17
|
if (params === true) params = {
|
|
18
18
|
all: true
|
|
@@ -23,20 +23,26 @@ const getFromObject = ({ arrayIndices, location, method, object, params })=>{
|
|
|
23
23
|
if (!type.isObject(params)) {
|
|
24
24
|
throw new Error(`Method Error: ${method} params must be of type string, integer, boolean or object at ${location}.`);
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
const defaultValue = get(params, 'default', {
|
|
27
27
|
default: null,
|
|
28
28
|
copy: true
|
|
29
29
|
});
|
|
30
|
+
if (params.key === null) return defaultValue;
|
|
30
31
|
if (params.all === true) return serializer.copy(object);
|
|
31
32
|
if (!type.isString(params.key) && !type.isInt(params.key)) {
|
|
32
33
|
throw new Error(`Method Error: ${method} params.key must be of type string or integer at ${location}.`);
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
default:
|
|
35
|
+
try {
|
|
36
|
+
return get(object, applyArrayIndices(arrayIndices, params.key), {
|
|
37
|
+
default: defaultValue,
|
|
37
38
|
copy: true
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
// Tier 2: a runtime read of an app-developer keypath. There is no loud failure that helps here —
|
|
42
|
+
// a thrown error in the browser is worse than the default, and the reserved rule's job (refusing
|
|
43
|
+
// the read) is already done. Author-written identifiers fail loudly at build instead.
|
|
44
|
+
if (error instanceof ReservedKeyError) return defaultValue;
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
41
47
|
};
|
|
42
48
|
export default getFromObject;
|
package/dist/createLink.js
CHANGED
|
@@ -12,60 +12,68 @@
|
|
|
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 { type
|
|
15
|
+
*/ import { type } from '@lowdefy/helpers';
|
|
16
|
+
import resolveTarget from './resolveTarget.js';
|
|
16
17
|
function createLink({ backLink, disabledLink, lowdefy, newOriginLink, noLink, sameOriginLink }) {
|
|
17
18
|
function link(props) {
|
|
18
19
|
if (props.disabled === true) {
|
|
19
20
|
return disabledLink(props);
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
!props.pageId,
|
|
23
|
-
!props.back,
|
|
24
|
-
!props.home,
|
|
25
|
-
!props.href,
|
|
26
|
-
!props.url
|
|
27
|
-
].filter((v)=>!v).length > 1) {
|
|
28
|
-
throw new Error(`Invalid Link: To avoid ambiguity, only one of 'back', 'home', 'href', 'pageId' or 'url' can be defined.`);
|
|
29
|
-
}
|
|
22
|
+
// back has no pathname to resolve and cannot carry input or urlQuery.
|
|
30
23
|
if (props.back === true) {
|
|
31
|
-
// Cannot set input or urlQuery on back
|
|
32
24
|
return backLink(props);
|
|
33
25
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const pathname = `/${lowdefy.home.configured ? '' : lowdefy.home.pageId}`;
|
|
37
|
-
return sameOriginLink({
|
|
38
|
-
...props,
|
|
39
|
-
pathname,
|
|
40
|
-
query,
|
|
41
|
-
setInput: ()=>{
|
|
42
|
-
lowdefy.inputs[`page:${lowdefy.home.pageId}`] = props.input ?? {};
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
if (type.isString(props.pageId)) {
|
|
47
|
-
return sameOriginLink({
|
|
48
|
-
...props,
|
|
49
|
-
pathname: `/${props.pageId}`,
|
|
50
|
-
query,
|
|
51
|
-
setInput: ()=>{
|
|
52
|
-
lowdefy.inputs[`page:${props.pageId}`] = props.input ?? {};
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|
|
26
|
+
// href is an HTML-attribute passthrough the <Link> component reads, not a
|
|
27
|
+
// navigation target, so it never enters the grammar resolver.
|
|
56
28
|
if (type.isString(props.href)) {
|
|
57
29
|
return newOriginLink(props);
|
|
58
30
|
}
|
|
59
|
-
|
|
60
|
-
|
|
31
|
+
const target = resolveTarget({
|
|
32
|
+
lowdefy,
|
|
33
|
+
target: {
|
|
34
|
+
home: props.home,
|
|
35
|
+
pageId: props.pageId,
|
|
36
|
+
url: props.url,
|
|
37
|
+
urlQuery: props.urlQuery
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
if (type.isNone(target)) {
|
|
41
|
+
return noLink(props);
|
|
42
|
+
}
|
|
43
|
+
if (target.kind === 'external') {
|
|
44
|
+
// The resolver's href is the whole URL with any query already folded in,
|
|
45
|
+
// so it is passed as the url prop the callback reads with an empty query.
|
|
61
46
|
return newOriginLink({
|
|
62
47
|
...props,
|
|
63
|
-
url:
|
|
64
|
-
query
|
|
48
|
+
url: target.href,
|
|
49
|
+
query: ''
|
|
65
50
|
});
|
|
66
51
|
}
|
|
67
|
-
return
|
|
52
|
+
return sameOriginLink({
|
|
53
|
+
...props,
|
|
54
|
+
pathname: target.pathname,
|
|
55
|
+
query: target.query,
|
|
56
|
+
setInput: getSetInput({
|
|
57
|
+
lowdefy,
|
|
58
|
+
props
|
|
59
|
+
})
|
|
60
|
+
});
|
|
68
61
|
}
|
|
69
62
|
return link;
|
|
70
63
|
}
|
|
64
|
+
// A page-kind url names no page, so it seeds no input - writing
|
|
65
|
+
// inputs['page:undefined'] is the bug family a no-op setInput avoids.
|
|
66
|
+
function getSetInput({ lowdefy, props }) {
|
|
67
|
+
if (props.home === true) {
|
|
68
|
+
return ()=>{
|
|
69
|
+
lowdefy.inputs[`page:${lowdefy.home.pageId}`] = props.input ?? {};
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (type.isString(props.pageId)) {
|
|
73
|
+
return ()=>{
|
|
74
|
+
lowdefy.inputs[`page:${props.pageId}`] = props.input ?? {};
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return ()=>{};
|
|
78
|
+
}
|
|
71
79
|
export default createLink;
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
// The single reading of `lowdefy.home` for every 'home' target - the Link
|
|
17
|
+
// grammar in createLink and the post-auth callbackUrl ladder in the client both
|
|
18
|
+
// resolve through here. Returns undefined when the app names no home page:
|
|
19
|
+
// getHomeAndMenus resolves pageId to null for an app with no homePageId whose
|
|
20
|
+
// authorized menu yields no link, and each caller decides what no-home means
|
|
21
|
+
// for it (an invalid link, no callback target) rather than interpolating the
|
|
22
|
+
// missing value into a path.
|
|
23
|
+
function getHomePathname({ lowdefy }) {
|
|
24
|
+
// A configured homePageId is served at the app root - the server resolves `/`
|
|
25
|
+
// to that page, so the pageId is deliberately not in the path.
|
|
26
|
+
if (lowdefy.home?.configured === true) {
|
|
27
|
+
return '/';
|
|
28
|
+
}
|
|
29
|
+
if (type.isString(lowdefy.home?.pageId)) {
|
|
30
|
+
return `/${lowdefy.home.pageId}`;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
export default getHomePathname;
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,9 @@ import Slots from './Slots.js';
|
|
|
17
17
|
import createLink from './createLink.js';
|
|
18
18
|
import Events from './Events.js';
|
|
19
19
|
import getContext from './getContext.js';
|
|
20
|
+
import getHomePathname from './getHomePathname.js';
|
|
20
21
|
import Requests from './Requests.js';
|
|
22
|
+
import resolveTarget from './resolveTarget.js';
|
|
21
23
|
import State from './State.js';
|
|
22
|
-
export { Actions, Slots, createLink, Events, Requests, State };
|
|
24
|
+
export { Actions, Slots, createLink, Events, getHomePathname, Requests, resolveTarget, State };
|
|
23
25
|
export default getContext;
|
|
@@ -0,0 +1,126 @@
|
|
|
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, urlQuery as urlQueryFn } from '@lowdefy/helpers';
|
|
16
|
+
import getHomePathname from './getHomePathname.js';
|
|
17
|
+
// Classifies a `url` grammar value into a page or external target. basePath is
|
|
18
|
+
// stripped here, never applied - the single application boundary is createUrl.
|
|
19
|
+
function classifyUrl({ lowdefy, url, query }) {
|
|
20
|
+
// The leading-slash test runs before the colon-less test: `/2fa` is an
|
|
21
|
+
// app-relative page and colon-less, and the colon-less branch would wrongly
|
|
22
|
+
// give it an `https://` scheme and parse it as an off-app origin.
|
|
23
|
+
if (url.startsWith('/')) {
|
|
24
|
+
const questionMark = url.indexOf('?');
|
|
25
|
+
const pathname = questionMark === -1 ? url : url.slice(0, questionMark);
|
|
26
|
+
const ownQuery = questionMark === -1 ? '' : url.slice(questionMark + 1);
|
|
27
|
+
// The target's own urlQuery combines with any query the string carries,
|
|
28
|
+
// matching the grammar semantics createLink resolves today.
|
|
29
|
+
const combined = [
|
|
30
|
+
ownQuery,
|
|
31
|
+
query
|
|
32
|
+
].filter((part)=>part !== '').join('&');
|
|
33
|
+
return {
|
|
34
|
+
kind: 'page',
|
|
35
|
+
pathname,
|
|
36
|
+
query: combined
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// A colon-less value like `example.com` is a schemeless hostname, not a path -
|
|
40
|
+
// prepend `https://` so the URL parser reads it as an absolute URL rather than
|
|
41
|
+
// the app-relative path `/example.com`. Confined to here by the leading-slash
|
|
42
|
+
// test above, so a colon-bearing path like `/path:1` never reaches it.
|
|
43
|
+
const value = url.includes(':') ? url : `https://${url}`;
|
|
44
|
+
const origin = lowdefy._internal?.globals?.window?.location?.origin;
|
|
45
|
+
// No window (SSR, tests): a `url` that reaches origin classification cannot be
|
|
46
|
+
// placed, so it resolves to nothing rather than dereferencing a missing window.
|
|
47
|
+
if (type.isNone(origin)) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const parsed = new URL(value, origin);
|
|
51
|
+
const basePath = lowdefy.basePath ?? '';
|
|
52
|
+
if (parsed.origin === origin) {
|
|
53
|
+
const insideBasePath = basePath === '' || parsed.pathname.startsWith(basePath);
|
|
54
|
+
if (insideBasePath) {
|
|
55
|
+
// Strip basePath so the router does not re-apply it: an absolute
|
|
56
|
+
// `https://myapp.com/app/reports` under basePath `/app` already carries the
|
|
57
|
+
// prefix, and without stripping router.push would push `/app/app/reports`.
|
|
58
|
+
const pathname = parsed.pathname.startsWith(basePath) ? parsed.pathname.slice(basePath.length) : parsed.pathname;
|
|
59
|
+
return {
|
|
60
|
+
kind: 'page',
|
|
61
|
+
pathname,
|
|
62
|
+
query: parsed.search.replace(/^\?/, '')
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Same origin but outside basePath (a marketing page at the origin root while
|
|
66
|
+
// the app lives at `/app`) is a whole URL - routing it would 404 in `/app`.
|
|
67
|
+
return {
|
|
68
|
+
kind: 'external',
|
|
69
|
+
href: parsed.href
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
kind: 'external',
|
|
74
|
+
href: parsed.href
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// The single resolver of the navigation grammar { home, pageId, url, urlQuery }
|
|
78
|
+
// for every reader. Returns a discriminated, un-prefixed target - never a string,
|
|
79
|
+
// never basePath-prefixed - so the page/external distinction is data the consumer
|
|
80
|
+
// reads rather than a shape it guesses from a leading slash.
|
|
81
|
+
function resolveTarget({ lowdefy, target, name = 'Link' }) {
|
|
82
|
+
if (!type.isObject(target)) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const { home, pageId, url, urlQuery } = target;
|
|
86
|
+
const defined = [
|
|
87
|
+
home,
|
|
88
|
+
pageId,
|
|
89
|
+
url
|
|
90
|
+
].filter((value)=>value);
|
|
91
|
+
if (defined.length > 1) {
|
|
92
|
+
throw new Error(`Invalid ${name}: To avoid ambiguity, only one of 'home', 'pageId' or 'url' can be defined.`);
|
|
93
|
+
}
|
|
94
|
+
const query = type.isNone(urlQuery) ? '' : `${urlQueryFn.stringify(urlQuery)}`;
|
|
95
|
+
if (home === true) {
|
|
96
|
+
const pathname = getHomePathname({
|
|
97
|
+
lowdefy
|
|
98
|
+
});
|
|
99
|
+
// An app whose home config names no page has no resolvable home - propagate
|
|
100
|
+
// getHomePathname's undefined rather than building the literal "/undefined".
|
|
101
|
+
if (type.isNone(pathname)) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
kind: 'page',
|
|
106
|
+
pathname,
|
|
107
|
+
query
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (type.isString(pageId)) {
|
|
111
|
+
return {
|
|
112
|
+
kind: 'page',
|
|
113
|
+
pathname: `/${pageId}`,
|
|
114
|
+
query
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (type.isString(url)) {
|
|
118
|
+
return classifyUrl({
|
|
119
|
+
lowdefy,
|
|
120
|
+
url,
|
|
121
|
+
query
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
export default resolveTarget;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/engine",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -30,16 +30,16 @@
|
|
|
30
30
|
"dist/*"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@lowdefy/errors": "5.
|
|
34
|
-
"@lowdefy/helpers": "5.
|
|
35
|
-
"@lowdefy/operators": "5.
|
|
33
|
+
"@lowdefy/errors": "5.6.0",
|
|
34
|
+
"@lowdefy/helpers": "5.6.0",
|
|
35
|
+
"@lowdefy/operators": "5.6.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@jest/globals": "28.1.3",
|
|
39
|
-
"@lowdefy/actions-core": "5.
|
|
40
|
-
"@lowdefy/build": "5.
|
|
41
|
-
"@lowdefy/operators-js": "5.
|
|
42
|
-
"@lowdefy/operators-mql": "5.
|
|
39
|
+
"@lowdefy/actions-core": "5.6.0",
|
|
40
|
+
"@lowdefy/build": "5.6.0",
|
|
41
|
+
"@lowdefy/operators-js": "5.6.0",
|
|
42
|
+
"@lowdefy/operators-mql": "5.6.0",
|
|
43
43
|
"@swc/cli": "0.8.0",
|
|
44
44
|
"@swc/core": "1.15.18",
|
|
45
45
|
"@swc/jest": "0.2.39",
|