@cloudbase/lowcode-builder 1.2.4 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,104 +1,94 @@
1
1
  import { observable, autorun, untracked } from 'mobx';
2
2
  import { styleToCss } from './style';
3
3
  import { getDeep, generateDataContext } from './util';
4
- import { compLowcodes, create$comp } from './weapp-component';
4
+ import { compLowcodes } from './weapp-component';
5
5
  import EventEmitter from './event-emitter';
6
6
  import lodashSet from 'lodash.set';
7
-
8
- function basePullAt(array, indexes) {
9
- let length = array ? indexes.length : 0;
10
- let lastIndex = length - 1;
11
-
12
- // eslint-disable-next-line no-plusplus
13
- while (length--) {
14
- let index = indexes[length];
15
- let previous;
16
- if (length == lastIndex || index !== previous) {
17
- previous = index;
18
- Array.prototype.splice.call(array, index, 1);
19
- }
20
- }
21
- return array;
22
- }
23
-
24
- function remove(array, predicate) {
25
- const result = [];
26
- if (!array?.length) {
27
- return result;
28
- }
29
- let index = -1;
30
- const indexes = [];
31
- const { length } = array;
32
-
33
- while (++index < length) {
34
- const value = array[index];
35
- if (predicate(value, index, array)) {
36
- result.push(value);
37
- indexes.push(index);
38
- }
39
- }
40
- basePullAt(array, indexes);
41
- return result;
42
- }
7
+ import { REPEATER } from './constant';
43
8
 
44
9
  /**
45
10
  * convert widget prop to data for wxml
46
11
  * @param {*} props
47
12
  */
13
+ const EXTRA_PROPS_MAP = [
14
+ /**
15
+ * create widgets
16
+ */
17
+ 'id',
18
+ 'widgetType',
19
+ 'parent',
20
+ 'children',
21
+ '_scope',
22
+ '_disposers',
23
+ '_eventListeners',
24
+ /**
25
+ * mount widget api
26
+ */
27
+ 'findWidgets',
28
+ 'getWidgetsByType',
29
+ 'getOwnerWidget',
30
+ 'getDom',
31
+ 'on',
32
+ 'off',
33
+ 'getConfig',
34
+ '_getInstanceRef',
35
+ '_methods',
36
+ '_userWidget',
37
+ /**
38
+ * 其他挂载
39
+ */
40
+ '_descendants',
41
+ '_forContext', // for root 挂载
42
+ /**
43
+ * widgetProps 附带值
44
+ */
45
+ '_id',
46
+ '_order',
47
+ 'classList',
48
+ ].reduce((map, key) => {
49
+ map[key] = true;
50
+ return map;
51
+ }, {});
52
+
48
53
  function resolveWidgetProp(props) {
49
- let { classList = [] } = props;
50
- const data = {};
51
- Object.keys(props).forEach((key) => {
52
- if (props[key] instanceof Function || props[key] === undefined) {
53
- return;
54
- }
55
- data[key] = props[key];
56
- });
57
- data.style = styleToCss(props.style);
58
- data.className = classList.join ? classList.join(' ') : classList;
59
- const extraProps = [
60
- 'classList',
61
- '_forItems',
62
- '_disposers',
63
- 'children',
64
- 'parent',
65
- '_parentId',
66
- 'id',
67
- '_order',
68
- '_scope',
69
- 'widgetType',
70
- '$comp',
71
- ];
72
- extraProps.map((prop) => {
73
- delete data[prop];
74
- });
75
- return data;
54
+ let { classList = [], ...restProps } = props;
55
+ const data = {};
56
+ Object.keys(restProps).forEach((key) => {
57
+ if (EXTRA_PROPS_MAP[key]) {
58
+ return;
59
+ }
60
+ if (restProps[key] instanceof Function || restProps[key] === undefined) {
61
+ return;
62
+ }
63
+ data[key] = restProps[key];
64
+ });
65
+ data.style = styleToCss(restProps.style);
66
+ data.className = classList.join ? classList.join(' ') : classList;
67
+ return data;
76
68
  }
77
69
 
78
70
  // widget prop -> wxml data
79
71
  export function resolveWidgetData(props) {
80
- if (!Array.isArray(props)) {
81
- return resolveWidgetProp(props);
82
- }
83
- return props.map(resolveWidgetData);
72
+ if (!Array.isArray(props)) {
73
+ return resolveWidgetProp(props);
74
+ }
75
+ return props.map(resolveWidgetData);
84
76
  }
85
77
 
86
- export function createWidgets(widgetProps, dataBinds, widgetHolder, ownerMpInst) {
87
- const rootNode = createWidgetDataTree(widgetProps, dataBinds);
88
- const failedBinds = [];
89
- const result = createSubWidgetTree(
90
- rootNode,
91
- widgetProps,
92
- dataBinds,
93
- ownerMpInst,
94
- widgetHolder,
95
- {},
96
- null,
97
- failedBinds,
98
- undefined,
99
- );
100
- retryFailedBinds(failedBinds, true);
101
- return result;
78
+ export function createWidgets(widgetProps, dataBinds, ownerMpInst) {
79
+ const rootNode = createWidgetDataTree(widgetProps, dataBinds);
80
+ const failedBinds = [];
81
+ const result = createSubWidgetTree(
82
+ { ownerForWidgetHolder: {} },
83
+ rootNode,
84
+ dataBinds,
85
+ ownerMpInst,
86
+ {},
87
+ failedBinds,
88
+ undefined,
89
+ );
90
+ retryFailedBinds(failedBinds, true);
91
+ return result;
102
92
  }
103
93
 
104
94
  /**
@@ -108,466 +98,714 @@ export function createWidgets(widgetProps, dataBinds, widgetHolder, ownerMpInst)
108
98
  * @returns {widgets: {id1:[], id2}, rootWidget: {children: [], _disposers: [], ...otherProps}}
109
99
  */
110
100
  function createSubWidgetTree(
111
- curForNode,
112
- widgetProps,
113
- dataBinds,
114
- ownerMpInst,
115
- widgetHolder = {},
116
- forItems = {},
117
- ownerForWidgetHolder = null,
118
- failedBinds = [],
119
- defaultParent = { children: observable([]), _disposers: [] },
101
+ ctx,
102
+ curForNode,
103
+ dataBinds,
104
+ ownerMpInst,
105
+ forContext = {},
106
+ failedBinds = [],
107
+ parentWidget = { children: observable([]), _disposers: [] },
120
108
  ) {
121
- const indexPostfix = (forItems.lists || [])
122
- .slice()
123
- .reverse()
124
- .map((list) => ID_SEPARATOR + list.currentIndex)
125
- .join('');
126
-
127
- // traverse down the tree to set up all widgets
128
- dfsTree(curForNode, (node, parentNode, cache) => {
129
- const parentForWidgetArr = ownerForWidgetHolder?.[node.id] || [];
109
+ const { ownerForWidgetHolder = {}, existingWidgetMap = {} } = ctx;
110
+ /**
111
+ * 不能类似web实现记录额外的 _disposers
112
+ * const widgetHolder = { _disposers: [] }
113
+ * 因为 merger-render.initMergeRenderer 里面没有特殊处理
114
+ */
115
+ const widgetHolder = {};
116
+ const { lists: forLists = [] } = forContext;
117
+ const currentIndex = forLists[0]?.currentIndex;
118
+ const indexPostfix = (forContext.lists || [])
119
+ .slice()
120
+ .reverse()
121
+ .map(({ currentIndex }) => ID_SEPARATOR + currentIndex)
122
+ .join('');
123
+
124
+ // traverse down the tree to set up all widgets
125
+ dfsTree(
126
+ curForNode,
127
+ (node, parentNode, cache) => {
128
+ const parentForWidgetArr = ownerForWidgetHolder[node.id] || [];
130
129
  const { _waForKey } = node.value;
131
- const key = forItems.itemsById?.[node.id]?.[_waForKey];
132
- const index = cache[parentNode?.id]
133
- ? cache[parentNode.id].index
134
- : parentForWidgetArr.findIndex((widget) => key && widget._key === key);
135
- const existedWidget = index !== -1 ? parentForWidgetArr[index] : null; // try to reuse previous node when rerun for
136
-
137
- if (existedWidget) {
138
- cache[node.id] = {
139
- index,
140
- };
130
+ const key = forContext.forItems?.[node.id]?.[_waForKey];
131
+ let forExistingWidgetMap = {};
132
+ let forExsitWidget;
133
+
134
+ /**
135
+ * for 起始节点,根据 existingWidgetMap 判断复用
136
+ */
137
+ if (node.id === curForNode.id) {
138
+ if (existingWidgetMap[key] && existingWidgetMap[key].index === currentIndex) {
139
+ forExistingWidgetMap = existingWidgetMap[key].widgets || {};
140
+ forExsitWidget = forExistingWidgetMap[node.id];
141
+ if (forExsitWidget) {
142
+ cache[node.id] = {
143
+ widgets: forExistingWidgetMap,
144
+ };
145
+ }
146
+ }
147
+ } else if (cache[parentNode?.id]) {
148
+ forExsitWidget = cache[parentNode?.id].widgets[node.id] || null;
149
+ if (forExsitWidget) {
150
+ cache[node.id] = cache[parentNode?.id];
151
+ }
141
152
  }
153
+ const existedWidget = forExsitWidget || null;
142
154
 
143
155
  if (node.forCount === curForNode.forCount) {
144
- // Leaf node
156
+ /**
157
+ * 同一层循环作用域内,当前节点与 curForNode(循环根节点)在同一级循环作用域中
158
+ * 即没有再开辟新级别的 for 循环
159
+ * Leaf node
160
+ */
145
161
  let w = existedWidget;
146
162
  if (!existedWidget) {
147
163
  const parentNode = node.parent;
148
- let parentWidget = null;
149
- if (parentNode) {
150
- parentWidget = widgetHolder[parentNode.id] || ownerForWidgetHolder[parentNode.id];
151
- }
152
- w = createAWidget(widgetProps[node.id], node.id, indexPostfix, parentWidget, ownerMpInst);
164
+ let parent = parentNode ? widgetHolder[parentNode.id] || ownerForWidgetHolder[parentNode.id] : null;
165
+ w = createWidget(node.value, node.id, indexPostfix, parent, ownerMpInst, forContext.forItems?.[node.id]);
153
166
  w._key = key;
154
- if (!parentWidget) {
155
- defaultParent.children.push(w);
167
+
168
+ if (!parent) {
169
+ parentWidget.children.push(w);
156
170
  }
157
- parentForWidgetArr.push(w);
158
171
  } else {
172
+ w.id = `${node.id}${indexPostfix}`;
159
173
  disposeWidget(existedWidget, true);
160
174
  }
161
- setUpWidgetDataBinds(w, dataBinds[node.id], forItems, failedBinds, ownerMpInst.getWeAppInst());
175
+ parentForWidgetArr.push?.(w);
176
+ setUpWidgetDataBinds(w, dataBinds[node.id], forContext, failedBinds, ownerMpInst._getInstance());
162
177
  widgetHolder[node.id] = w;
163
- } else if (!existedWidget) {
164
- const len = parentForWidgetArr.push(observable([]));
165
- widgetHolder[node.id] = parentForWidgetArr[len - 1];
178
+ if (widgetHolder?.[node._ancestorId]) {
179
+ // 在虚拟项 RepeaterItem 下挂载所有的子孙 __descendants
180
+ // 这里不要用 lodashSet,否则会出现刷新几次,出现 Error: [mobx.array] Index out of bounds 的错误,原因暂不明
181
+ widgetHolder[node._ancestorId]._descendants = widgetHolder[node._ancestorId]._descendants || {};
182
+ widgetHolder[node._ancestorId]._descendants[node.id] = widgetHolder[node.id];
183
+ }
166
184
  } else {
167
- // Reuse existed for widget array
168
- widgetHolder[node.id] = existedWidget;
185
+ if (!existedWidget) {
186
+ widgetHolder[node.id] = observable([]);
187
+ } else {
188
+ // Reuse existed for widget array
189
+ widgetHolder[node.id] = existedWidget;
190
+ }
191
+ if (parentForWidgetArr) {
192
+ parentForWidgetArr.push(widgetHolder[node.id]);
193
+ }
169
194
  }
170
- });
195
+ },
196
+ undefined,
197
+ );
171
198
 
172
- // run for of next level
173
- dfsTree(curForNode, (node) => {
199
+ // run for of next level
200
+ dfsTree(
201
+ curForNode,
202
+ (node) => {
174
203
  if (node.forCount === curForNode.forCount + 1 && dataBinds[node.id] && dataBinds[node.id]._waFor) {
175
204
  // find the node bound with next level for
176
- const parent = node.parent ? widgetHolder[node.parent.id] : defaultParent;
177
- const dispose = runFor(node, widgetProps, dataBinds, ownerMpInst, forItems, widgetHolder, failedBinds, parent);
205
+ const parent = getNodeParentWidget(node, widgetHolder, parentWidget);
206
+ const dispose = runFor(node, dataBinds, ownerMpInst, forContext, widgetHolder, failedBinds, parent);
178
207
  parent._disposers.push(dispose); // Add the for bind dispose to the parent node of forNode
179
208
  }
180
- });
209
+ },
210
+ undefined,
211
+ );
181
212
 
182
- retryFailedBinds(failedBinds);
213
+ retryFailedBinds(failedBinds);
183
214
 
184
- return { widgets: widgetHolder, rootWidget: widgetHolder[curForNode.id] || defaultParent };
215
+ return { widgets: widgetHolder, rootWidget: widgetHolder[curForNode.id] || parentWidget };
185
216
  }
186
217
 
187
218
  // Retry failed databinds
188
219
  function retryFailedBinds(failedBinds, finalTry) {
189
- const len = failedBinds.length;
190
- for (let i = 0; i < len; i++) {
191
- const setUpDataBind = failedBinds.shift();
192
- setUpDataBind(finalTry);
193
- }
220
+ const len = failedBinds.length;
221
+ for (let i = 0; i < len; i++) {
222
+ const setUpDataBind = failedBinds.shift();
223
+ setUpDataBind(finalTry);
224
+ }
194
225
  }
195
226
 
196
227
  /**
197
228
  *
198
229
  * @param {*} curForNode
199
- * @param {*} forItems
230
+ * @param {*} forContext
200
231
  * @param {*} parentForWidgets
201
232
  * @param {*} parentWidget
202
233
  * @returns top level widgets or for dispose
203
234
  */
204
235
  const _FOR_ERROR_CACHE_MAP = {};
205
- function runFor(
206
- curForNode,
207
- widgetProps,
208
- dataBinds,
209
- ownerMpInst,
210
- forItems,
211
- ownerForWidgetHolder,
212
- failedBinds,
213
- defaultParent,
214
- ) {
215
- const nodeId = curForNode.id;
216
- const { _waForKey } = curForNode.value;
217
-
218
- const dispose = autorun(() => {
219
- let forList = [];
220
- try {
221
- clearTimeout(_FOR_ERROR_CACHE_MAP[nodeId]);
222
-
223
- const $instance = ownerMpInst.getWeAppInst();
224
- const dataContext = untracked(()=>generateDataContext(defaultParent));
225
-
226
- forList = dataBinds[nodeId]._waFor.call(
227
- $instance,
228
- $instance,
229
- forItems.lists,
230
- forItems.itemsById,
231
- undefined,
232
- dataContext,
233
- );
234
- if (!Array.isArray(forList)) {
235
- forList = [];
236
- }
237
- } catch (e) {
236
+ function runFor(curForNode, dataBinds, ownerMpInst, forContext, ownerForWidgetHolder, failedBinds, parentWidget) {
237
+ const nodeId = curForNode.id;
238
+ const { _waForKey } = curForNode.value;
239
+
240
+ const dispose = autorun(() => {
241
+ let forList = [];
242
+ try {
243
+ clearTimeout(_FOR_ERROR_CACHE_MAP[nodeId]);
244
+
245
+ const $instance = ownerMpInst._getInstance();
246
+ const dataContext = untracked(() => generateDataContext(parentWidget));
247
+ const $w = untracked(() => generateWidgetAPIContext($instance?.__internal__?.$w, parentWidget, forContext));
248
+
249
+ forList = dataBinds[nodeId]._waFor.call(
250
+ $instance,
251
+ $instance,
252
+ forContext.lists,
253
+ forContext.forItems,
254
+ undefined,
255
+ dataContext,
256
+ $w,
257
+ );
258
+ if (!Array.isArray(forList)) {
238
259
  forList = [];
239
- _FOR_ERROR_CACHE_MAP[nodeId] = setTimeout(() => {
240
- console.warn('For binding error', nodeId, e);
241
- }, 1000);
242
260
  }
261
+ } catch (e) {
262
+ forList = [];
263
+ _FOR_ERROR_CACHE_MAP[nodeId] = setTimeout(() => {
264
+ console.warn('For binding error', nodeId, e);
265
+ }, 1000);
266
+ }
243
267
 
244
- // Track list change (e.g. push)
245
- forList.forEach((e) => {});
268
+ // Track list change (e.g. push)
269
+ forList.forEach((e) => {});
246
270
 
247
- untracked(() => {
248
- // dispose widgets before reused instead
249
- // disposeWidgets(parentForWidgets[curForNode.id])
250
- const exsitMap = forList.reduce((map, item) => {
251
- if (item[_waForKey]) {
252
- map[item[_waForKey]] = true;
253
- }
254
- return map;
255
- }, {});
256
- const forWidgets = ownerForWidgetHolder[nodeId];
257
- const existedWidgetIndex = [];
258
- const extraWidgetsIndex = [];
259
- const extraWidgets = [];
260
- forWidgets.forEach((widget, index) => {
261
- if (exsitMap[widget._key]) {
262
- existedWidgetIndex.push(index);
263
- // need to use uqique key
264
- exsitMap[widget._key] = undefined;
265
- } else {
266
- extraWidgetsIndex.push(index);
267
- }
268
- });
269
- const extraWidgetsIndexMap = extraWidgetsIndex.reduce((map, item) => {
270
- map[item] = true;
271
- extraWidgets.push(forWidgets[item]);
272
- return map;
273
- }, {});
274
-
275
- // clean extra widgets of previous for run
276
- dfsTree(curForNode, (node) => {
277
- const arr = ownerForWidgetHolder[node.id];
278
- remove(arr, (_, index) => {
279
- return extraWidgetsIndexMap[index];
280
- });
281
- });
271
+ untracked(() => {
272
+ // dispose widgets before reused instead
273
+ // disposeWidgets(parentForWidgets[curForNode.id])
274
+ const exsitMap = forList.reduce((map, item, index) => {
275
+ const cache = item?.[_waForKey];
276
+ if (map[cache] === undefined) {
277
+ map[cache] = index;
278
+ }
279
+ return map;
280
+ }, {});
281
+ const forWidgets = ownerForWidgetHolder[nodeId];
282
+ const existingWidgetMap = {};
283
+ const existingWidgetIndexMap = {};
284
+ const extraWidgetsIndexMap = {};
285
+ forWidgets.forEach((widget, index) => {
286
+ if (exsitMap[widget._key] !== undefined) {
287
+ const nodeId = widget.id?.split(ID_SEPARATOR)[0];
288
+ existingWidgetMap[widget._key] = { index: exsitMap[widget._key], widgets: { [nodeId]: widget } };
289
+ /**
290
+ * 此处依赖了 existingWidgetMap[widget._key].widgets 的引用,
291
+ * 为了直接可以通过 index 访问到 existingWidgetMap 里的值进行编辑
292
+ * 但是依赖引用关系,存在维护风险
293
+ */
294
+ existingWidgetIndexMap[index] = existingWidgetMap[widget._key].widgets;
295
+ // need to use uqique key
296
+ exsitMap[widget._key] = undefined;
297
+ } else {
298
+ extraWidgetsIndexMap[index] = widget;
299
+ }
300
+ });
282
301
 
283
- // 清理根 for autorun, 并递归清理子节点
284
- extraWidgets.map((w) => {
285
- disposeWidget(w);
286
- const { children } = w.parent || defaultParent;
287
- children.remove(w);
288
- // w.parent = null
289
- });
302
+ // clean extra widgets of previous for run
303
+ dfsTree(
304
+ curForNode,
305
+ (node) => {
306
+ const arr = ownerForWidgetHolder[node.id] || [];
307
+ /**
308
+ * clone 上次 for 已有的 widgets
309
+ */
310
+ if (node.id !== curForNode.id) {
311
+ arr.forEach((item, index) => {
312
+ if (existingWidgetIndexMap[index]) {
313
+ existingWidgetIndexMap[index][node.id] = item;
314
+ }
315
+ });
316
+ }
317
+ /**
318
+ * 重头开始生成
319
+ * 清空原有 arr 并保持引用不变
320
+ */
321
+ arr.splice(0, arr.length);
322
+ },
323
+ undefined,
324
+ );
325
+
326
+ /**
327
+ * 明确已经不会复用的节点,清除 mobx observer
328
+ * 并递归清理子节点
329
+ */
330
+ for (const index in extraWidgetsIndexMap) {
331
+ const w = extraWidgetsIndexMap[index];
332
+ disposeWidget(w);
333
+ const { children } = w.parent || parentWidget;
334
+ children.remove(w);
335
+ // w.parent = null
336
+ }
290
337
 
291
- forList.forEach((item, index) => {
292
- let { lists = [], itemsById = {} } = forItems;
293
- const _forItems = {
294
- lists: [{ currentItem: item, currentIndex: index }, ...lists],
295
- itemsById: { ...itemsById, [nodeId]: item },
338
+ const isInRepeaterChild = parentWidget?.widgetType === `${REPEATER.MODULE_NAME}:${REPEATER.REPEATER_NAME}`;
339
+ forList.forEach((item, index) => {
340
+ let forContextListAlias;
341
+ let { lists = [], forItems = {} } = forContext;
342
+ const listMeta = { currentItem: item, currentIndex: index };
343
+ if (isInRepeaterChild) {
344
+ forContextListAlias = {
345
+ [`${parentWidget.forIndex}` || 'currentIndex']: listMeta.currentIndex,
346
+ [`${parentWidget.forItem}` || 'currentItem']: listMeta.currentItem,
296
347
  };
297
- const { rootWidget } = createSubWidgetTree(
298
- curForNode,
299
- widgetProps,
300
- dataBinds,
301
- ownerMpInst,
302
- {},
303
- _forItems,
304
- ownerForWidgetHolder,
305
- failedBinds,
306
- defaultParent,
307
- );
308
- rootWidget._forItems = _forItems;
309
- });
348
+ }
349
+ const _forContext = {
350
+ lists: [{ ...listMeta, alias: forContextListAlias }, ...lists],
351
+ forItems: { ...forItems, [nodeId]: item },
352
+ };
353
+ const { rootWidget } = createSubWidgetTree(
354
+ { ownerForWidgetHolder, existingWidgetMap },
355
+ curForNode,
356
+ dataBinds,
357
+ ownerMpInst,
358
+ _forContext,
359
+ failedBinds,
360
+ parentWidget,
361
+ );
362
+ rootWidget._forContext = _forContext;
310
363
  });
311
364
  });
365
+ });
312
366
 
313
- return dispose;
367
+ return dispose;
314
368
  }
315
369
 
316
- function createAWidget(props, nodeId, indexPostfix, parent, ownerMpInst) {
317
- const w = observable(props);
318
- const id = `${nodeId}${indexPostfix}`;
319
-
320
- // Builtin props
321
- Object.defineProperty(w, 'id', { value: id });
322
- const { widgetType } = w;
323
- delete w.widgetType;
324
- Object.defineProperty(w, 'widgetType', { value: widgetType });
325
- Object.defineProperty(w, '_scope', {
326
- value: observable({
327
- id: nodeId,
328
- _parent: null,
329
- dataContext: {},
330
- }),
331
- });
370
+ function createWidget(props, nodeId, indexPostfix, parent, ownerMpInst, forContext) {
371
+ const { widgetType, _parentId, ...restProps } = props;
372
+ const w = observable(restProps);
373
+ const id = `${nodeId}${indexPostfix}`;
374
+
375
+ // Builtin props
376
+ w.id = id; // 重用之后要修改id
377
+ // Object.defineProperty(w, 'id', { value: id });
378
+ Object.defineProperty(w, 'widgetType', { value: widgetType });
379
+ Object.defineProperty(w, '_scope', {
380
+ value: observable({
381
+ id: nodeId,
382
+ dataContext: {},
383
+ /**
384
+ * 当前节点的 for currentItem 对象
385
+ */
386
+ forContext,
387
+ }),
388
+ });
389
+
390
+ // w._disposers = []
391
+ // w.children = []
392
+ Object.defineProperty(w, 'children', { value: observable([]) });
393
+ Object.defineProperty(w, '_disposers', { value: observable([]) });
394
+ Object.defineProperty(w, '_eventListeners', { value: new EventEmitter() });
395
+ if (parent) {
396
+ // w.parent = parent
397
+ Object.defineProperty(w, 'parent', { value: parent });
398
+ parent.children.push(w);
399
+ }
332
400
 
333
- // w._disposers = []
334
- // w.children = []
335
- Object.defineProperty(w, 'children', { value: observable([]) });
336
- Object.defineProperty(w, '_disposers', { value: observable([]) });
337
- Object.defineProperty(w, '_eventListeners', { value: new EventEmitter() });
338
- if (parent) {
339
- // w.parent = parent
340
- Object.defineProperty(w, 'parent', { value: parent });
341
- w._scope.parent = parent._scope;
342
- parent.children.push(w);
401
+ switch (widgetType) {
402
+ case `${REPEATER.MODULE_NAME}:${REPEATER.REPEATER_NAME}`: {
403
+ if (!w.items) {
404
+ Object.defineProperty(w, 'items', {
405
+ get() {
406
+ return (w.children || []).map((item) => {
407
+ const descendants = {};
408
+ Object.keys(item?._descendants || {}).forEach((key) => {
409
+ descendants[key] = item._descendants[key]._userWidget;
410
+ });
411
+ return descendants;
412
+ });
413
+ },
414
+ });
415
+ }
416
+ break;
343
417
  }
344
- delete w._parentId;
418
+ }
345
419
 
346
- Object.defineProperty(w, '$comp', { value: create$comp(w) });
347
- mountBuiltinWigetsAPI(w, ownerMpInst);
348
- return w;
420
+ mountBuiltinWigetsAPI(w, ownerMpInst);
421
+ return w;
349
422
  }
350
423
 
351
- function setUpWidgetDataBinds(w, dataBinds, forItems, failedBinds, ctx) {
352
- Object.keys(dataBinds || {})
353
- .sort((a, b) => {
354
- return a.length - b.length > 0 ? 1 : -1;
355
- })
356
- .map((prop) => {
357
- if (prop === '_waFor') {
358
- return;
359
- }
360
- let timer = null;
361
- const setUpDataBind = (isFinalTry) => {
362
- let ran = false;
363
- const dispose = autorun((reaction) => {
364
- try {
365
- clearTimeout(timer);
366
-
367
- const dataContext = untracked(()=>generateDataContext(w));
368
-
369
- // Computed data bind in the next tick since data bind may read widgets data
370
- const value = dataBinds[prop].call(ctx, ctx, forItems.lists, forItems.itemsById, undefined, dataContext);
371
- const paths = prop.split('.').filter((key) => !!key);
372
- if (paths.length > 1) {
373
- // 一定要 untracked 不然爆栈了
374
- untracked(() => lodashSet(w, prop, value));
375
- } else {
376
- // 普通 key 直接赋值
377
- w[prop] = value;
378
- }
379
- } catch (e) {
380
- if (prop === '_waIf') {
381
- w[prop] = false;
382
- }
424
+ function setUpWidgetDataBinds(w, dataBinds, forContext, failedBinds, ctx) {
425
+ Object.keys(dataBinds || {})
426
+ .sort((a, b) => {
427
+ return a.length - b.length > 0 ? 1 : -1;
428
+ })
429
+ .map((prop) => {
430
+ if (prop === '_waFor') {
431
+ return;
432
+ }
433
+ let timer = null;
434
+ const setUpDataBind = (isFinalTry) => {
435
+ let ran = false;
436
+ const dispose = autorun((reaction) => {
437
+ try {
438
+ clearTimeout(timer);
439
+
440
+ const dataContext = untracked(() => generateDataContext(w));
441
+ const $w = untracked(() => generateWidgetAPIContext(ctx?.__internal__?.$w, w, forContext));
442
+
443
+ // Computed data bind in the next tick since data bind may read widgets data
444
+ const value = dataBinds[prop].call(
445
+ ctx,
446
+ ctx,
447
+ forContext.lists,
448
+ forContext.forItems,
449
+ undefined,
450
+ dataContext,
451
+ $w,
452
+ );
453
+ const paths = prop.split('.').filter((key) => !!key);
454
+ if (paths.length > 1) {
455
+ // 一定要 untracked 不然爆栈了
456
+ untracked(() => lodashSet(w, prop, value));
457
+ } else {
458
+ // 普通 key 直接赋值
459
+ w[prop] = value;
460
+ }
461
+ } catch (e) {
462
+ if (prop === '_waIf') {
463
+ w[prop] = false;
464
+ }
383
465
 
384
- if (isFinalTry || ran) {
385
- timer = setTimeout(() => {
386
- console.warn(`Error computing data bind ${w.id}.${prop}`, e);
387
- }, 1000);
388
- } else {
466
+ if (isFinalTry || ran) {
467
+ timer = setTimeout(() => {
468
+ console.warn(`Error computing data bind ${w.id}.${prop}`, e);
469
+ }, 1000);
470
+ } else {
471
+ failedBinds.push((...args) => {
389
472
  reaction.dispose();
390
- failedBinds.push(setUpDataBind);
391
- }
392
-
393
- ran = true;
473
+ return setUpDataBind(...args);
474
+ });
394
475
  }
395
- });
396
- w._disposers.push(dispose);
397
- };
398
- setUpDataBind();
399
- });
476
+
477
+ ran = true;
478
+ }
479
+ });
480
+ w._disposers.push(dispose);
481
+ };
482
+ setUpDataBind();
483
+ });
400
484
  }
401
485
 
402
- export function findForItemsOfWidget(widget) {
403
- const forItems = widget._forItems;
404
- if (forItems) return forItems;
405
- if (widget.parent) return findForItemsOfWidget(widget.parent);
486
+ export function generateForContextOfWidget(widget) {
487
+ const forContext = widget._forContext;
488
+ if (forContext) return forContext;
489
+ if (widget.parent) return generateForContextOfWidget(widget.parent);
406
490
  }
407
491
 
408
492
  export const ID_SEPARATOR = '-';
409
493
  export function getWidget(widgets, id) {
410
- return getDeep(widgets, id, ID_SEPARATOR);
494
+ return getDeep(widgets, id, ID_SEPARATOR);
495
+ }
496
+
497
+ function getNodeParentWidget(node, widgets, defaultParent = { children: observable([]), _disposers: [] }) {
498
+ return (node?.parent?.id && widgets?.[node.parent.id]) || defaultParent;
411
499
  }
412
500
 
413
501
  /**
414
502
  * Add parent, children to widget
415
503
  */
416
504
  function createWidgetDataTree(widgets, dataBinds) {
417
- const virtualRoot = { children: [], forCount: 0 };
418
- const nodes = Object.keys(widgets).reduce((result, id) => {
419
- const w = widgets[id];
420
- result[id] = { id, value: w, _order: w._order, children: [], parent: null, forCount: 0 };
421
- return result;
422
- }, {});
423
-
424
- // Create widgets tree API
425
- Object.keys(nodes).map((id) => {
426
- const curNode = nodes[id];
427
- const parent = nodes[widgets[id]._parentId];
428
- // delete widgets[id]._parentId
429
- if (!parent) {
430
- virtualRoot.children.push(curNode);
431
- return;
432
- }
433
- curNode.parent = parent;
434
- parent.children.push(curNode);
435
- });
436
-
437
- // Sort children
438
- Object.keys(nodes).map((id) => {
439
- nodes[id].children.sort((a, b) => a._order - b._order);
440
- });
441
-
442
- virtualRoot.children.map(addForCount);
443
-
444
- // dfs, add forCount
445
- function addForCount(node) {
446
- if (node.parent) {
447
- node.forCount = node.parent.forCount;
505
+ const virtualRoot = { children: [], forCount: 0 };
506
+ const nodes = Object.keys(widgets).reduce((result, id) => {
507
+ const w = widgets[id];
508
+ result[id] = { id, value: w, _order: w._order, children: [], parent: null, forCount: 0 };
509
+ return result;
510
+ }, {});
511
+
512
+ // Create widgets tree API
513
+ Object.keys(nodes).map((id) => {
514
+ const curNode = nodes[id];
515
+ const parent = nodes[widgets[id]._parentId];
516
+ // delete widgets[id]._parentId
517
+ if (!parent) {
518
+ virtualRoot.children.push(curNode);
519
+ return;
520
+ }
521
+ curNode.parent = parent;
522
+ parent.children.push(curNode);
523
+ });
524
+
525
+ // Sort children
526
+ Object.keys(nodes).map((id) => {
527
+ nodes[id].children.sort((a, b) => a._order - b._order);
528
+ });
529
+
530
+ virtualRoot.children.map(addForCount);
531
+
532
+ // dfs, add forCount
533
+ function addForCount(node) {
534
+ if (node.parent) {
535
+ node.forCount = node.parent.forCount;
536
+ const { widgetType } = node.parent.value;
537
+ if (widgetType === `${REPEATER.MODULE_NAME}:${REPEATER.REPEATER_ITEM_NAME}`) {
538
+ node._ancestorId = node.parent.id;
448
539
  }
449
- if (dataBinds[node.id]?._waFor) {
450
- node.forCount += 1;
540
+ if (node.parent?._ancestorId) {
541
+ // Repeater 作用域内的所有子孙(排除里面的 Repeater 组件,它本身又产生深一层的作用域)继承父级的循环信息
542
+ if (!node._ancestorId) {
543
+ node._ancestorId = node.parent._ancestorId;
544
+ }
451
545
  }
452
- node.children.map(addForCount);
453
546
  }
547
+ if (dataBinds[node.id]?._waFor) {
548
+ node.forCount += 1;
549
+ }
550
+ node.children.map(addForCount);
551
+ }
454
552
 
455
- return virtualRoot;
553
+ return virtualRoot;
456
554
  }
457
555
 
458
556
  function dfsTree(node, fn, parent, cache = {}) {
459
- node.value && fn(node, parent, cache);
460
- node.children.map((e) => dfsTree(e, fn, node.value ? node : null, cache));
557
+ node.value && fn(node, parent, cache);
558
+ node.children.map((e) => dfsTree(e, fn, node.value ? node : null, cache));
461
559
  }
462
560
 
463
561
  // dispose autorun, widget can be the virtual root widget
464
- export function disposeWidget(widget, noRecursive) {
465
- const disposers = widget._disposers;
466
- disposers.map((dispose) => dispose());
467
- disposers.splice(0, disposers.length);
468
- !noRecursive && widget.children.forEach((w) => disposeWidget(w));
562
+ export function disposeWidget(widget, noRecursive = false) {
563
+ const disposers = widget._disposers;
564
+ disposers.map((dispose) => dispose());
565
+ disposers.splice(0, disposers.length);
566
+ !noRecursive && widget.children.forEach((w) => disposeWidget(w));
469
567
  }
470
568
 
471
569
  export function createInitData(widgets, dataBinds, keyPrefix = '') {
472
- return Object.keys(widgets).reduce((result, id) => {
473
- if (!isWidgetInFor(id, widgets, dataBinds)) {
474
- result[keyPrefix + id] = resolveWidgetData(widgets[id]);
475
- } else {
476
- result[keyPrefix + id] = [];
477
- }
478
- return result;
479
- }, {});
570
+ return Object.keys(widgets).reduce((result, id) => {
571
+ if (!isWidgetInFor(id, widgets, dataBinds)) {
572
+ result[keyPrefix + id] = resolveWidgetData(widgets[id]);
573
+ } else {
574
+ result[keyPrefix + id] = [];
575
+ }
576
+ return result;
577
+ }, {});
480
578
  }
481
579
 
482
580
  function isWidgetInFor(id, widgets, dataBinds) {
483
- let curNode = widgets[id];
484
- let nodeId = id;
485
- while (curNode) {
486
- if (dataBinds[nodeId]?._waFor) {
487
- return true;
488
- }
489
- nodeId = curNode._parentId;
490
- curNode = widgets[nodeId];
581
+ let curNode = widgets[id];
582
+ let nodeId = id;
583
+ while (curNode) {
584
+ if (dataBinds[nodeId]?._waFor) {
585
+ return true;
491
586
  }
587
+ nodeId = curNode._parentId;
588
+ curNode = widgets[nodeId];
589
+ }
492
590
  }
493
591
 
494
592
  function mountBuiltinWigetsAPI(widget, owner) {
495
- // #1 builtin APIs
496
- widget.findWidgets = function (filter, includeInvisibleDescendants) {
497
- let { children = [] } = this;
498
- if (!includeInvisibleDescendants) {
499
- // include visible widgets only by default
500
- children = children.filter((e) => e._waIf !== false);
593
+ // #1 builtin APIs
594
+ widget.findWidgets = function (filter, includeInvisibleDescendants) {
595
+ let { children = [] } = this;
596
+ if (!includeInvisibleDescendants) {
597
+ // include visible widgets only by default
598
+ children = children.filter((e) => e._waIf !== false);
599
+ }
600
+ const matched = [];
601
+ children.forEach((w) => {
602
+ if (filter(w)) {
603
+ matched.push(w);
501
604
  }
502
- const matched = [];
503
- children.forEach((w) => {
504
- if (filter(w)) {
505
- matched.push(w);
605
+ matched.push(...w.findWidgets(filter, includeInvisibleDescendants));
606
+ });
607
+ return matched;
608
+ };
609
+
610
+ widget.getWidgetsByType = function (type, includeInvisibleDescendants) {
611
+ return this.findWidgets((w) => w.widgetType === type, includeInvisibleDescendants);
612
+ };
613
+
614
+ /**
615
+ * Similar to selectOwnerComponent of WX MP: https://developers.weixin.qq.com/miniprogram/dev/reference/api/Component.html
616
+ */
617
+ widget.getOwnerWidget = function () {
618
+ return owner?._getInstance?.()?.node;
619
+ };
620
+
621
+ // Will be overwritten by composited component
622
+ widget.getDom = function (fields) {
623
+ return new Promise((resolve, reject) => {
624
+ const query = (owner || wx).createSelectorQuery();
625
+ query
626
+ .select(`#${this.id}`)
627
+ .fields(fields, (res) => {
628
+ resolve(res);
629
+ })
630
+ .exec();
631
+ });
632
+ };
633
+
634
+ widget.on = function (type, listener) {
635
+ this._eventListeners.on(type, listener);
636
+ };
637
+
638
+ widget.off = function (type, listener) {
639
+ this._eventListeners.off(type, listener);
640
+ };
641
+
642
+ widget.getConfig = () => ({});
643
+
644
+ const lowcode = compLowcodes[widget.widgetType];
645
+ if (lowcode) {
646
+ const { config } = lowcode;
647
+ widget.getConfig = () => config;
648
+ }
649
+
650
+ widget._getInstanceRef = () => null;
651
+
652
+ /**
653
+ * @deprecated
654
+ */
655
+ widget._methods = {};
656
+
657
+ Object.defineProperty(widget, '_userWidget', {
658
+ value: untracked(() => {
659
+ return new UserWidget(widget);
660
+ }),
661
+ });
662
+ }
663
+
664
+ /**
665
+ * 对外(用户)的 Widget
666
+ */
667
+ class UserWidget {
668
+ _widget = null;
669
+
670
+ get description() {
671
+ const { id } = this._widget;
672
+ return `
673
+ 使用说明:
674
+ 1. w 命名空间下为内置方法,可通过 $w.${id}.w.<成员> 或 $w.${id}.<成员> 访问。
675
+ 访问 id 示例: $w.${id}.id 或 $w.${id}.w.id
676
+ 2. custom 命名空间下为用户自定义对外暴露的成员,可通过 $w.${id}.custom.<成员> 或 $w.${id}.<成员> 访问。
677
+ 访问自定义方法 hello 示例: $w.${id}.hello() 或 $w.${id}.custom.hello()
678
+ 3. 通过 $w.${id}.<成员> 访问时,如果自定义成员与内置成员重名,则自定义成员覆盖内置成员。
679
+ 4. [注意]: 请不要直接访问 _widget,它里面的成员为内部实现,随时可能进行调整
680
+ `;
681
+ }
682
+
683
+ constructor(widget) {
684
+ this._widget = widget;
685
+ return new Proxy(this, {
686
+ get(target, prop) {
687
+ if (prop in target) {
688
+ return target[prop];
506
689
  }
507
- matched.push(...w.findWidgets(filter, includeInvisibleDescendants));
508
- });
509
- return matched;
510
- };
511
690
 
512
- widget.getWidgetsByType = function (type, includeInvisibleDescendants) {
513
- return this.findWidgets((w) => w.widgetType === type, includeInvisibleDescendants);
514
- };
691
+ // 优先 custom
692
+ if (target.custom[prop]) {
693
+ return target.custom[prop];
694
+ }
515
695
 
516
- /**
517
- * Similar to selectOwnerComponent of WX MP: https://developers.weixin.qq.com/miniprogram/dev/reference/api/Component.html
518
- */
519
- widget.getOwnerWidget = function () {
520
- return owner?.getWeAppInst?.()?.node;
521
- };
696
+ // 再尝试内置的方法和属性
697
+ const buildinMember = target.sys[prop];
698
+ if (buildinMember) return buildinMember;
522
699
 
523
- // Will be overwritten by composited component
524
- widget.getDom = function (fields) {
525
- return new Promise((resolve, reject) => {
526
- const query = (owner || wx).createSelectorQuery();
527
- query
528
- .select(`#${this.id}`)
529
- .fields(fields, (res) => {
530
- resolve(res);
531
- })
532
- .exec();
533
- });
534
- };
700
+ // 兼容原来的 widget。最后直接代理到 widget 上,主要是访问组件的属性
701
+ return target._widget[prop];
702
+ },
703
+ });
704
+ }
535
705
 
536
- widget.on = function (type, listener) {
537
- this._eventListeners.on(type, listener);
538
- };
706
+ // 内置属性和方法命名空间
707
+ get sys() {
708
+ const widget = this._widget;
709
+ const [module, component] = widget.widgetType?.split?.(':') || [];
710
+ return {
711
+ /**
712
+ * 内置属性
713
+ */
714
+
715
+ get id() {
716
+ return widget.id;
717
+ },
718
+
719
+ get module() {
720
+ return module;
721
+ },
539
722
 
540
- widget.off = function (type, listener) {
541
- this._eventListeners.off(type, listener);
723
+ get component() {
724
+ return component;
725
+ },
726
+
727
+ get name() {
728
+ return widget.widgetType;
729
+ },
730
+
731
+ get parent() {
732
+ return widget.parent?._userWidget;
733
+ },
734
+
735
+ get children() {
736
+ return widget.children?.map((item) => item._userWidget) || [];
737
+ },
738
+
739
+ /**
740
+ * 内置方法
741
+ */
742
+
743
+ closest(filter) {
744
+ let { parent } = this;
745
+ if (!filter) return parent;
746
+
747
+ while (parent) {
748
+ const matched = filter(parent);
749
+ if (matched) return parent;
750
+
751
+ parent = parent.parent;
752
+ }
753
+
754
+ return null;
755
+ },
756
+
757
+ /**
758
+ * 为了兼容 2.17+ button 组件,使用 $node.parent.getConfig
759
+ * 挂载代理
760
+ */
761
+ getConfig(...args) {
762
+ return widget.getConfig?.(...args);
763
+ },
542
764
  };
765
+ }
543
766
 
544
- widget.getConfig = () => ({});
767
+ get custom() {
768
+ const { methods = {}, ...restInstance } = this._widget._getInstanceRef?.()?.current || {};
545
769
 
546
- const lowcode = compLowcodes[widget.widgetType];
547
- if (lowcode) {
548
- const { index = {}, config } = lowcode;
549
- const builtinProps = Object.keys(widget.$comp);
770
+ const userCustomMember = {
771
+ ...this._widget._methods,
772
+ ...restInstance,
773
+ ...methods,
774
+ };
550
775
 
551
- widget.getConfig = () => config;
776
+ return userCustomMember;
777
+ }
778
+ }
552
779
 
553
- // #2 User defined APIs
554
- const { publicMethods = {} } = index;
555
- Object.keys(publicMethods).map((name) => {
556
- if (builtinProps.indexOf(name) > -1) {
557
- console.error(
558
- `Invalid publicMethod name(${name}) of Component(${widget.widgetType}), builtin name can't be used, please rename this method.`,
559
- );
560
- return;
780
+ export function generateWidgetAPIContext($w = {}, widget, forContext) {
781
+ return new Proxy($w, {
782
+ get(target, prop) {
783
+ // debugger;
784
+
785
+ /**
786
+ * for context 变量优先
787
+ */
788
+ const { lists = [] } = forContext;
789
+ for (const meta of lists) {
790
+ const map = meta.alias || {};
791
+ if (prop in map) {
792
+ return map[prop];
561
793
  }
562
- const method = publicMethods[name];
563
- if (method instanceof Function) {
564
- Object.defineProperty(widget, name, { value: method.bind(widget.$comp) });
565
- Object.defineProperty(widget.$comp, name, { value: method });
566
- } else {
567
- console.error(`Component(${widget.widgetType}) method(${name}) is not a function.`);
794
+ }
795
+
796
+ // 尝试代理同级 widget
797
+ if (widget) {
798
+ let { parent } = widget;
799
+ while (parent) {
800
+ if (parent._descendants?.[prop]) {
801
+ return parent._descendants[prop]._userWidget;
802
+ }
803
+ parent = parent.parent;
568
804
  }
569
- });
570
- }
805
+ }
571
806
 
572
- widget._methods = {};
807
+ // 尝试代理到全局的 $w
808
+ return target[prop];
809
+ },
810
+ });
573
811
  }