@lowdefy/engine 0.0.0-experimental-20260929101721 → 0.0.0-experimental-20260929135001
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 +3 -2
- package/dist/Events.js +8 -2
- package/dist/actions/createCallMethod.js +4 -0
- package/dist/claimDomEvent.js +41 -14
- package/dist/createContext.js +151 -0
- package/dist/domEventClaims.js +22 -0
- package/dist/exemptFromDomEvent.js +27 -0
- package/dist/getAppContext.js +66 -0
- package/dist/getContext.js +4 -132
- package/dist/getDomEvent.js +25 -0
- package/dist/getPathIndex.js +23 -0
- package/dist/index.js +2 -1
- package/package.json +8 -8
package/dist/Block.js
CHANGED
|
@@ -507,10 +507,11 @@ let Block = class Block {
|
|
|
507
507
|
toDelete,
|
|
508
508
|
toSet
|
|
509
509
|
}));
|
|
510
|
-
|
|
510
|
+
} else {
|
|
511
|
+
this.context._internal.State.republish(this.blockId, type.enforceType(this.meta.valueType, null));
|
|
511
512
|
}
|
|
512
|
-
this.context._internal.State.republish(this.blockId, type.enforceType(this.meta.valueType, null));
|
|
513
513
|
}
|
|
514
|
+
// After the slots, so an input-container (an input with slots) republishes its own value.
|
|
514
515
|
if (this.isInput()) {
|
|
515
516
|
this.context._internal.State.republish(this.blockId, this.value);
|
|
516
517
|
}
|
package/dist/Events.js
CHANGED
|
@@ -31,8 +31,13 @@ let Events = class Events {
|
|
|
31
31
|
this.events[eventName] = this.initEvent(this.block.events[eventName]);
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
+
// Events a block registers for its own machinery (Upload's policy request, a Table's row
|
|
35
|
+
// fetch). They are internal: see claimDomEvent.
|
|
34
36
|
registerEvent({ name, actions }) {
|
|
35
|
-
this.events[name] =
|
|
37
|
+
this.events[name] = {
|
|
38
|
+
...this.initEvent(actions),
|
|
39
|
+
internal: true
|
|
40
|
+
};
|
|
36
41
|
}
|
|
37
42
|
triggerEvent({ name, event, progress }) {
|
|
38
43
|
this.context._internal.lowdefy.eventCallback?.({
|
|
@@ -55,7 +60,8 @@ let Events = class Events {
|
|
|
55
60
|
const handledBy = claimDomEvent({
|
|
56
61
|
blockId: this.block.blockId,
|
|
57
62
|
bubble: eventDescription?.bubble === true,
|
|
58
|
-
hasActions: !type.isUndefined(eventDescription)
|
|
63
|
+
hasActions: !type.isUndefined(eventDescription),
|
|
64
|
+
internal: eventDescription?.internal === true
|
|
59
65
|
});
|
|
60
66
|
if (!type.isNull(handledBy)) {
|
|
61
67
|
result.handledBy = handledBy;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { applyArrayIndices, type } from '@lowdefy/helpers';
|
|
16
|
+
import exemptFromDomEvent from '../exemptFromDomEvent.js';
|
|
16
17
|
function createCallMethod({ arrayIndices, context }) {
|
|
17
18
|
return function callMethod(params) {
|
|
18
19
|
const { blockId, method, args = [] } = params;
|
|
@@ -29,6 +30,9 @@ function createCallMethod({ arrayIndices, context }) {
|
|
|
29
30
|
if (!type.isFunction(blockMethod)) {
|
|
30
31
|
throw new Error(`Failed to call method "${method}" on block "${blockId}". Check if "${method}" is a valid block method for block "${blockId}".`);
|
|
31
32
|
}
|
|
33
|
+
exemptFromDomEvent({
|
|
34
|
+
blockId: block.blockId
|
|
35
|
+
});
|
|
32
36
|
return blockMethod(...args);
|
|
33
37
|
};
|
|
34
38
|
}
|
package/dist/claimDomEvent.js
CHANGED
|
@@ -12,21 +12,48 @@
|
|
|
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
|
-
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// the
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
*/ import domEventClaims from './domEventClaims.js';
|
|
16
|
+
import getDomEvent from './getDomEvent.js';
|
|
17
|
+
import getPathIndex from './getPathIndex.js';
|
|
18
|
+
// A DOM event bubbles through every block that wraps its target, and each of those blocks may fire
|
|
19
|
+
// an event for it: a Button click also reaches the clickable Card around it. The innermost block on
|
|
20
|
+
// the event's path with actions for the event handles it, and the blocks further out on the path
|
|
21
|
+
// skip their events for the same DOM event, unless the handling event sets `bubble: true`.
|
|
22
|
+
//
|
|
23
|
+
// Only blocks the DOM event passed through take part. Blocks fire events for other reasons while
|
|
24
|
+
// a DOM event is dispatched, and those are never skipped:
|
|
25
|
+
// - A block off the path: a Table next to a Button fires onSelectionChange from the effect that
|
|
26
|
+
// commits the selection the Button's CallMethod cleared. React flushes that effect in a
|
|
27
|
+
// microtask inside the click listener, so window.event is still the click. The same holds for
|
|
28
|
+
// effects in React's scheduler `message` events, whose path has no blocks.
|
|
29
|
+
// - A block inside the handling block: a Table in a clickable Card fires onSelectionChange for a
|
|
30
|
+
// checkbox after the Card's onClick ran. It runs, and holds the claim from then on.
|
|
31
|
+
// - A block an action called a method on (exemptFromDomEvent), even when it is on the path: a
|
|
32
|
+
// Button in a Table's bulk action slot clearing the Table's selection.
|
|
33
|
+
// - An internal event (registered by the block itself, like a Table's row fetch). It still claims
|
|
34
|
+
// an unclaimed DOM event, as the block's own handler would.
|
|
35
|
+
function claimDomEvent({ blockId, bubble, hasActions, internal }) {
|
|
36
|
+
const domEvent = getDomEvent();
|
|
37
|
+
if (domEvent === null) return null;
|
|
38
|
+
const path = domEvent.composedPath();
|
|
39
|
+
const index = getPathIndex({
|
|
40
|
+
blockId,
|
|
41
|
+
path
|
|
42
|
+
});
|
|
43
|
+
if (index === -1) return null;
|
|
44
|
+
if (domEventClaims.exempt.get(domEvent)?.has(blockId)) return null;
|
|
45
|
+
const handledBy = domEventClaims.handledBy.get(domEvent);
|
|
46
|
+
if (handledBy !== undefined && handledBy !== blockId) {
|
|
47
|
+
const handledByIsInner = getPathIndex({
|
|
48
|
+
blockId: handledBy,
|
|
49
|
+
path
|
|
50
|
+
}) < index;
|
|
51
|
+
if (handledByIsInner) {
|
|
52
|
+
return internal === true ? null : handledBy;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
28
55
|
if (hasActions && !bubble) {
|
|
29
|
-
|
|
56
|
+
domEventClaims.handledBy.set(domEvent, blockId);
|
|
30
57
|
}
|
|
31
58
|
return null;
|
|
32
59
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
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 { serializer } from '@lowdefy/helpers';
|
|
16
|
+
import { WebParser } from '@lowdefy/operators';
|
|
17
|
+
import Actions from './Actions.js';
|
|
18
|
+
import DependencyTracker from './tracking/DependencyTracker.js';
|
|
19
|
+
import readMediaViewport from './tracking/readMediaViewport.js';
|
|
20
|
+
import Slots from './Slots.js';
|
|
21
|
+
import Requests from './Requests.js';
|
|
22
|
+
import State from './State.js';
|
|
23
|
+
import updateMedia from './tracking/updateMedia.js';
|
|
24
|
+
import WebSockets from './WebSockets.js';
|
|
25
|
+
const blockData = (config)=>{
|
|
26
|
+
const { slots, blockId, blocks, class: blockClass, events, field, id, layout, loading, pageId, properties, requests, required, skeleton, style, subscriptions, type, validate, visible } = config;
|
|
27
|
+
const result = {
|
|
28
|
+
slots,
|
|
29
|
+
blockId,
|
|
30
|
+
blocks,
|
|
31
|
+
class: blockClass,
|
|
32
|
+
events,
|
|
33
|
+
field,
|
|
34
|
+
id,
|
|
35
|
+
layout,
|
|
36
|
+
loading,
|
|
37
|
+
pageId,
|
|
38
|
+
properties,
|
|
39
|
+
requests,
|
|
40
|
+
required,
|
|
41
|
+
skeleton,
|
|
42
|
+
style,
|
|
43
|
+
subscriptions,
|
|
44
|
+
type,
|
|
45
|
+
validate,
|
|
46
|
+
visible
|
|
47
|
+
};
|
|
48
|
+
// Preserve ~k (configKey) for error tracing - it's non-enumerable so must be copied explicitly
|
|
49
|
+
if (config['~k']) {
|
|
50
|
+
Object.defineProperty(result, '~k', {
|
|
51
|
+
value: config['~k'],
|
|
52
|
+
enumerable: false,
|
|
53
|
+
writable: true,
|
|
54
|
+
configurable: true
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
// Builds an engine context around one root block: the page's root block for a
|
|
60
|
+
// page context, and a block-less root carrying the app events for the app context.
|
|
61
|
+
function createContext({ config, jsMap, lowdefy }) {
|
|
62
|
+
const { id } = config;
|
|
63
|
+
const ctx = {
|
|
64
|
+
id,
|
|
65
|
+
pageId: config.pageId,
|
|
66
|
+
eventLog: [],
|
|
67
|
+
jsMap,
|
|
68
|
+
requests: {},
|
|
69
|
+
state: {},
|
|
70
|
+
_internal: {
|
|
71
|
+
lowdefy,
|
|
72
|
+
// Config object reference for dynamic page memoization — identity marks
|
|
73
|
+
// which fetch this context was built from.
|
|
74
|
+
pageConfig: config,
|
|
75
|
+
// The viewport the page's blocks last evaluated _media against, or null without a window.
|
|
76
|
+
// updateMedia diffs a resize against it.
|
|
77
|
+
media: readMediaViewport({
|
|
78
|
+
lowdefy
|
|
79
|
+
}),
|
|
80
|
+
// The read recorder of the block evaluating itself, or null. Managed by DependencyTracker.
|
|
81
|
+
readRecorder: null,
|
|
82
|
+
rootBlock: blockData(config),
|
|
83
|
+
update: ()=>{},
|
|
84
|
+
// React updaters register here per block id when the context's Block
|
|
85
|
+
// components mount — scoped per context so rebuilding over a live
|
|
86
|
+
// context (dynamic page navigation, reset) never notifies the previous
|
|
87
|
+
// context's still-mounted components.
|
|
88
|
+
updaters: {}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const _internal = ctx._internal;
|
|
92
|
+
_internal.DependencyTracker = new DependencyTracker(ctx);
|
|
93
|
+
_internal.parser = new WebParser({
|
|
94
|
+
context: ctx,
|
|
95
|
+
operators: lowdefy._internal.operators
|
|
96
|
+
});
|
|
97
|
+
_internal.State = new State(ctx);
|
|
98
|
+
_internal.Actions = new Actions(ctx);
|
|
99
|
+
_internal.Requests = new Requests(ctx);
|
|
100
|
+
_internal.WebSockets = new WebSockets(ctx);
|
|
101
|
+
_internal.RootSlots = new Slots({
|
|
102
|
+
slots: {
|
|
103
|
+
root: {
|
|
104
|
+
blocks: [
|
|
105
|
+
_internal.rootBlock
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
context: ctx
|
|
110
|
+
});
|
|
111
|
+
_internal.RootSlots.init(serializer.copy(ctx.state));
|
|
112
|
+
// update({ changes }) is a dependency-tracked pass; a bare update() is a full pass.
|
|
113
|
+
_internal.update = (options)=>{
|
|
114
|
+
_internal.RootSlots.update(options);
|
|
115
|
+
};
|
|
116
|
+
_internal.runOnInit = async (progress)=>{
|
|
117
|
+
progress();
|
|
118
|
+
if (!_internal.onInitDone) {
|
|
119
|
+
await _internal.RootSlots.slots.root.blocks[0].triggerEvent({
|
|
120
|
+
name: 'onInit',
|
|
121
|
+
progress
|
|
122
|
+
});
|
|
123
|
+
_internal.update();
|
|
124
|
+
_internal.State.freezeState();
|
|
125
|
+
_internal.onInitDone = true;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
_internal.runOnInitAsync = async (progress)=>{
|
|
129
|
+
if (_internal.onInitDone && !_internal.onInitAsyncDone) {
|
|
130
|
+
await _internal.RootSlots.slots.root.blocks[0].triggerEvent({
|
|
131
|
+
name: 'onInitAsync',
|
|
132
|
+
progress
|
|
133
|
+
});
|
|
134
|
+
_internal.onInitAsyncDone = true;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
// Called by the client's debounced resize listener.
|
|
138
|
+
_internal.updateMedia = ()=>updateMedia({
|
|
139
|
+
context: ctx
|
|
140
|
+
});
|
|
141
|
+
// Page lifecycle events (onVisible, onHidden, onOnline, onOffline, onResize) are
|
|
142
|
+
// triggered on the page's root block by browser listeners attached in the client.
|
|
143
|
+
_internal.triggerPageEvent = ({ name, event, progress = ()=>undefined })=>_internal.RootSlots.slots.root.blocks[0].triggerEvent({
|
|
144
|
+
name,
|
|
145
|
+
event,
|
|
146
|
+
progress
|
|
147
|
+
});
|
|
148
|
+
ctx._internal.update();
|
|
149
|
+
return ctx;
|
|
150
|
+
}
|
|
151
|
+
export default createContext;
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
*/ // Per DOM event, weak so dispatched events are collected:
|
|
16
|
+
// - handledBy: the id of the block whose event handled it.
|
|
17
|
+
// - exempt: ids of blocks an action called a method on while it was dispatched.
|
|
18
|
+
const domEventClaims = {
|
|
19
|
+
handledBy: new WeakMap(),
|
|
20
|
+
exempt: new WeakMap()
|
|
21
|
+
};
|
|
22
|
+
export default domEventClaims;
|
|
@@ -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 domEventClaims from './domEventClaims.js';
|
|
16
|
+
import getDomEvent from './getDomEvent.js';
|
|
17
|
+
// A block an action calls a method on acts for that action, not for the DOM event being
|
|
18
|
+
// dispatched: the events it fires for it (at once, or from the effects the method causes) are
|
|
19
|
+
// never skipped as copies of the DOM event bubbling through it. See claimDomEvent.
|
|
20
|
+
function exemptFromDomEvent({ blockId }) {
|
|
21
|
+
const domEvent = getDomEvent();
|
|
22
|
+
if (domEvent === null) return;
|
|
23
|
+
const exempt = domEventClaims.exempt.get(domEvent) ?? new Set();
|
|
24
|
+
exempt.add(blockId);
|
|
25
|
+
domEventClaims.exempt.set(domEvent, exempt);
|
|
26
|
+
}
|
|
27
|
+
export default exemptFromDomEvent;
|
|
@@ -0,0 +1,66 @@
|
|
|
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 createContext from './createContext.js';
|
|
16
|
+
// App events are not page config: the app context runs them on a root without
|
|
17
|
+
// blocks, so they have no page state, inputs or requests. Box is the root because
|
|
18
|
+
// every app bundles it (a container, so it has no value in state).
|
|
19
|
+
function getAppContext({ events, jsMap = {}, lowdefy }) {
|
|
20
|
+
if (lowdefy.appContext) {
|
|
21
|
+
return lowdefy.appContext;
|
|
22
|
+
}
|
|
23
|
+
const ctx = createContext({
|
|
24
|
+
config: {
|
|
25
|
+
id: 'app',
|
|
26
|
+
blockId: 'app',
|
|
27
|
+
type: 'Box',
|
|
28
|
+
events
|
|
29
|
+
},
|
|
30
|
+
jsMap,
|
|
31
|
+
lowdefy
|
|
32
|
+
});
|
|
33
|
+
const _internal = ctx._internal;
|
|
34
|
+
// Global and API responses are app-wide: a change an app event makes must
|
|
35
|
+
// re-render the pages, and reportAppChange has already reported it to them.
|
|
36
|
+
const updateAppRoot = _internal.update;
|
|
37
|
+
_internal.update = (options)=>{
|
|
38
|
+
updateAppRoot(options);
|
|
39
|
+
Object.values(lowdefy.contexts).forEach((pageContext)=>{
|
|
40
|
+
pageContext._internal.update({
|
|
41
|
+
changes: []
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
// Every page mount asks for the app events, and a navigation can mount the
|
|
46
|
+
// next page before they finish, so each runs once and later callers share it.
|
|
47
|
+
const runOnInit = _internal.runOnInit;
|
|
48
|
+
const runOnInitAsync = _internal.runOnInitAsync;
|
|
49
|
+
let onInitPromise = null;
|
|
50
|
+
let onInitAsyncPromise = null;
|
|
51
|
+
_internal.runOnInit = (progress)=>{
|
|
52
|
+
if (!onInitPromise) {
|
|
53
|
+
onInitPromise = runOnInit(progress);
|
|
54
|
+
}
|
|
55
|
+
return onInitPromise;
|
|
56
|
+
};
|
|
57
|
+
_internal.runOnInitAsync = (progress)=>{
|
|
58
|
+
if (!onInitAsyncPromise) {
|
|
59
|
+
onInitAsyncPromise = _internal.runOnInit(progress).then(()=>runOnInitAsync(progress));
|
|
60
|
+
}
|
|
61
|
+
return onInitAsyncPromise;
|
|
62
|
+
};
|
|
63
|
+
lowdefy.appContext = ctx;
|
|
64
|
+
return ctx;
|
|
65
|
+
}
|
|
66
|
+
export default getAppContext;
|
package/dist/getContext.js
CHANGED
|
@@ -13,50 +13,7 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import { LowdefyInternalError } from '@lowdefy/errors';
|
|
16
|
-
import
|
|
17
|
-
import { WebParser } from '@lowdefy/operators';
|
|
18
|
-
import Actions from './Actions.js';
|
|
19
|
-
import DependencyTracker from './tracking/DependencyTracker.js';
|
|
20
|
-
import readMediaViewport from './tracking/readMediaViewport.js';
|
|
21
|
-
import Slots from './Slots.js';
|
|
22
|
-
import Requests from './Requests.js';
|
|
23
|
-
import State from './State.js';
|
|
24
|
-
import updateMedia from './tracking/updateMedia.js';
|
|
25
|
-
import WebSockets from './WebSockets.js';
|
|
26
|
-
const blockData = (config)=>{
|
|
27
|
-
const { slots, blockId, blocks, class: blockClass, events, field, id, layout, loading, pageId, properties, requests, required, skeleton, style, subscriptions, type, validate, visible } = config;
|
|
28
|
-
const result = {
|
|
29
|
-
slots,
|
|
30
|
-
blockId,
|
|
31
|
-
blocks,
|
|
32
|
-
class: blockClass,
|
|
33
|
-
events,
|
|
34
|
-
field,
|
|
35
|
-
id,
|
|
36
|
-
layout,
|
|
37
|
-
loading,
|
|
38
|
-
pageId,
|
|
39
|
-
properties,
|
|
40
|
-
requests,
|
|
41
|
-
required,
|
|
42
|
-
skeleton,
|
|
43
|
-
style,
|
|
44
|
-
subscriptions,
|
|
45
|
-
type,
|
|
46
|
-
validate,
|
|
47
|
-
visible
|
|
48
|
-
};
|
|
49
|
-
// Preserve ~k (configKey) for error tracing - it's non-enumerable so must be copied explicitly
|
|
50
|
-
if (config['~k']) {
|
|
51
|
-
Object.defineProperty(result, '~k', {
|
|
52
|
-
value: config['~k'],
|
|
53
|
-
enumerable: false,
|
|
54
|
-
writable: true,
|
|
55
|
-
configurable: true
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
return result;
|
|
59
|
-
};
|
|
16
|
+
import createContext from './createContext.js';
|
|
60
17
|
function getContext({ config, jsMap = {}, lowdefy, resetContext = {
|
|
61
18
|
reset: false,
|
|
62
19
|
setReset: ()=>undefined
|
|
@@ -75,10 +32,6 @@ function getContext({ config, jsMap = {}, lowdefy, resetContext = {
|
|
|
75
32
|
// memoize context if already created, eg between page transitions, unless the reset flag is
|
|
76
33
|
// raised. A full pass: this render-time update is how changes with no reporting hook reach the
|
|
77
34
|
// page - user, i18n, theme, menus, inputs and the URL.
|
|
78
|
-
// The pass reads the current viewport, which a resize while the page was away changed.
|
|
79
|
-
lowdefy.contexts[id]._internal.media = readMediaViewport({
|
|
80
|
-
lowdefy
|
|
81
|
-
});
|
|
82
35
|
lowdefy.contexts[id]._internal.update();
|
|
83
36
|
return lowdefy.contexts[id];
|
|
84
37
|
}
|
|
@@ -92,92 +45,11 @@ function getContext({ config, jsMap = {}, lowdefy, resetContext = {
|
|
|
92
45
|
if (!lowdefy.inputs[id]) {
|
|
93
46
|
lowdefy.inputs[id] = {};
|
|
94
47
|
}
|
|
95
|
-
const ctx = {
|
|
96
|
-
|
|
97
|
-
pageId: config.pageId,
|
|
98
|
-
eventLog: [],
|
|
48
|
+
const ctx = createContext({
|
|
49
|
+
config,
|
|
99
50
|
jsMap,
|
|
100
|
-
|
|
101
|
-
state: {},
|
|
102
|
-
_internal: {
|
|
103
|
-
lowdefy,
|
|
104
|
-
// Config object reference for dynamic page memoization — identity marks
|
|
105
|
-
// which fetch this context was built from.
|
|
106
|
-
pageConfig: config,
|
|
107
|
-
// The viewport the page's blocks last evaluated _media against, or null without a window.
|
|
108
|
-
// updateMedia diffs a resize against it.
|
|
109
|
-
media: readMediaViewport({
|
|
110
|
-
lowdefy
|
|
111
|
-
}),
|
|
112
|
-
// The read recorder of the block evaluating itself, or null. Managed by DependencyTracker.
|
|
113
|
-
readRecorder: null,
|
|
114
|
-
rootBlock: blockData(config),
|
|
115
|
-
update: ()=>{},
|
|
116
|
-
// React updaters register here per block id when the context's Block
|
|
117
|
-
// components mount — scoped per context so rebuilding over a live
|
|
118
|
-
// context (dynamic page navigation, reset) never notifies the previous
|
|
119
|
-
// context's still-mounted components.
|
|
120
|
-
updaters: {}
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
const _internal = ctx._internal;
|
|
124
|
-
_internal.DependencyTracker = new DependencyTracker(ctx);
|
|
125
|
-
_internal.parser = new WebParser({
|
|
126
|
-
context: ctx,
|
|
127
|
-
operators: lowdefy._internal.operators
|
|
128
|
-
});
|
|
129
|
-
_internal.State = new State(ctx);
|
|
130
|
-
_internal.Actions = new Actions(ctx);
|
|
131
|
-
_internal.Requests = new Requests(ctx);
|
|
132
|
-
_internal.WebSockets = new WebSockets(ctx);
|
|
133
|
-
_internal.RootSlots = new Slots({
|
|
134
|
-
slots: {
|
|
135
|
-
root: {
|
|
136
|
-
blocks: [
|
|
137
|
-
_internal.rootBlock
|
|
138
|
-
]
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
context: ctx
|
|
51
|
+
lowdefy
|
|
142
52
|
});
|
|
143
|
-
_internal.RootSlots.init(serializer.copy(ctx.state));
|
|
144
|
-
// update({ changes }) is a dependency-tracked pass; a bare update() is a full pass.
|
|
145
|
-
_internal.update = (options)=>{
|
|
146
|
-
_internal.RootSlots.update(options);
|
|
147
|
-
};
|
|
148
|
-
_internal.runOnInit = async (progress)=>{
|
|
149
|
-
progress();
|
|
150
|
-
if (!_internal.onInitDone) {
|
|
151
|
-
await _internal.RootSlots.slots.root.blocks[0].triggerEvent({
|
|
152
|
-
name: 'onInit',
|
|
153
|
-
progress
|
|
154
|
-
});
|
|
155
|
-
_internal.update();
|
|
156
|
-
_internal.State.freezeState();
|
|
157
|
-
_internal.onInitDone = true;
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
_internal.runOnInitAsync = async (progress)=>{
|
|
161
|
-
if (_internal.onInitDone && !_internal.onInitAsyncDone) {
|
|
162
|
-
await _internal.RootSlots.slots.root.blocks[0].triggerEvent({
|
|
163
|
-
name: 'onInitAsync',
|
|
164
|
-
progress
|
|
165
|
-
});
|
|
166
|
-
_internal.onInitAsyncDone = true;
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
// Called by the client's debounced resize listener.
|
|
170
|
-
_internal.updateMedia = ()=>updateMedia({
|
|
171
|
-
context: ctx
|
|
172
|
-
});
|
|
173
|
-
// Page lifecycle events (onVisible, onHidden, onOnline, onOffline, onResize) are
|
|
174
|
-
// triggered on the page's root block by browser listeners attached in the client.
|
|
175
|
-
_internal.triggerPageEvent = ({ name, event, progress = ()=>undefined })=>_internal.RootSlots.slots.root.blocks[0].triggerEvent({
|
|
176
|
-
name,
|
|
177
|
-
event,
|
|
178
|
-
progress
|
|
179
|
-
});
|
|
180
|
-
ctx._internal.update();
|
|
181
53
|
lowdefy.contexts[id] = ctx;
|
|
182
54
|
return ctx;
|
|
183
55
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
*/ // The DOM event whose listeners are running: window.event is set while a listener runs, including
|
|
16
|
+
// the microtasks that run before the listener returns (React flushes discrete updates and their
|
|
17
|
+
// effects there), and undefined outside a DOM dispatch.
|
|
18
|
+
function getDomEvent() {
|
|
19
|
+
// The engine's unit tests run without a window.
|
|
20
|
+
if (typeof window === 'undefined') return null;
|
|
21
|
+
const domEvent = window.event;
|
|
22
|
+
if (!(domEvent instanceof Event)) return null;
|
|
23
|
+
return domEvent;
|
|
24
|
+
}
|
|
25
|
+
export default getDomEvent;
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
*/ // The client renders every block inside a layout element with the id `bl-<blockId>`
|
|
16
|
+
// (packages/client/src/block). Its position on the DOM event's path, innermost first, or -1 when
|
|
17
|
+
// the event did not pass through the block.
|
|
18
|
+
function getPathIndex({ blockId, path }) {
|
|
19
|
+
const element = document.getElementById(`bl-${blockId}`);
|
|
20
|
+
if (element === null) return -1;
|
|
21
|
+
return path.indexOf(element);
|
|
22
|
+
}
|
|
23
|
+
export default getPathIndex;
|
package/dist/index.js
CHANGED
|
@@ -17,11 +17,12 @@ import Slots from './Slots.js';
|
|
|
17
17
|
import createLink from './createLink.js';
|
|
18
18
|
import decodeServerError, { getDevError } from './decodeServerError.js';
|
|
19
19
|
import Events from './Events.js';
|
|
20
|
+
import getAppContext from './getAppContext.js';
|
|
20
21
|
import getContext from './getContext.js';
|
|
21
22
|
import getHomePathname from './getHomePathname.js';
|
|
22
23
|
import Requests from './Requests.js';
|
|
23
24
|
import resolveTarget from './resolveTarget.js';
|
|
24
25
|
import State from './State.js';
|
|
25
26
|
import stopChain from './stopChain.js';
|
|
26
|
-
export { Actions, Slots, createLink, decodeServerError, Events, getDevError, getHomePathname, Requests, resolveTarget, State, stopChain };
|
|
27
|
+
export { Actions, Slots, createLink, decodeServerError, Events, getAppContext, getDevError, getHomePathname, Requests, resolveTarget, State, stopChain };
|
|
27
28
|
export default getContext;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/engine",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-20260929135001",
|
|
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": "0.0.0-experimental-
|
|
34
|
-
"@lowdefy/helpers": "0.0.0-experimental-
|
|
35
|
-
"@lowdefy/operators": "0.0.0-experimental-
|
|
33
|
+
"@lowdefy/errors": "0.0.0-experimental-20260929135001",
|
|
34
|
+
"@lowdefy/helpers": "0.0.0-experimental-20260929135001",
|
|
35
|
+
"@lowdefy/operators": "0.0.0-experimental-20260929135001"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@jest/globals": "28.1.3",
|
|
39
|
-
"@lowdefy/actions-core": "0.0.0-experimental-
|
|
40
|
-
"@lowdefy/build": "0.0.0-experimental-
|
|
41
|
-
"@lowdefy/operators-js": "0.0.0-experimental-
|
|
42
|
-
"@lowdefy/operators-mql": "0.0.0-experimental-
|
|
39
|
+
"@lowdefy/actions-core": "0.0.0-experimental-20260929135001",
|
|
40
|
+
"@lowdefy/build": "0.0.0-experimental-20260929135001",
|
|
41
|
+
"@lowdefy/operators-js": "0.0.0-experimental-20260929135001",
|
|
42
|
+
"@lowdefy/operators-mql": "0.0.0-experimental-20260929135001",
|
|
43
43
|
"@swc/cli": "0.8.1",
|
|
44
44
|
"@swc/core": "1.15.32",
|
|
45
45
|
"@swc/jest": "0.2.39",
|