@quenk/wml 2.14.0 → 2.15.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/lib/view/frame.ts CHANGED
@@ -71,12 +71,96 @@ class SetList {
71
71
  }
72
72
 
73
73
  /**
74
- * ViewFrame serves as a container for a View's rendered template.
74
+ * Frame serves as a container for the DOM elements a View's template
75
+ * renders.
75
76
  *
76
- * This class containers helper methods for retreiving elements by id or
77
+ * Each time the render() method is called a new Frame is created and passed
78
+ * to the rendering logic so that a DOM tree is built along with accesible
79
+ * wml ids and groups.
80
+ */
81
+ export interface Frame {
82
+ /**
83
+ * tree when sets is the rendered DOM content of the view
84
+ */
85
+ tree?: Content;
86
+
87
+ /**
88
+ * root sets the root element of a View's tree.
89
+ */
90
+ root(el: Content): void;
91
+
92
+ /**
93
+ * node constructs a DOM node to be used in the View's tree.
94
+ *
95
+ * Any id or group assignment will be honored.
96
+ */
97
+ node(tag: string, attrs: Attrs, children: Content[]): Content;
98
+
99
+ /**
100
+ * widget constructs a DOM sub-tree for a Widget in the View's tree.
101
+ *
102
+ * Any id or group assignment will be honored.
103
+ */
104
+ widget(w: Widget, attrs: Attrs): Content;
105
+
106
+ /**
107
+ * view renders the content of another view, saving its ids and groups to
108
+ * this one.
109
+ */
110
+ view(view: View): Content;
111
+
112
+ /**
113
+ * register content and widget under the specified id.
114
+ */
115
+ register(id: WMLId, node: Content, widget?: Widget): void;
116
+
117
+ /**
118
+ * registerGroupMember registers a content and widget as a member of a group.
119
+ */
120
+ registerGroupMember(id: WMLId, node: Content, widget?: Widget): void;
121
+
122
+ /**
123
+ * findById returns the entry stored for the specified wml element.
124
+ */
125
+ findById(id: WMLId): Maybe<Entry>;
126
+
127
+ /**
128
+ * findByGroup returns all the entries stored fro a group.
129
+ */
130
+ findByGroup(id: WMLId): Entry[];
131
+
132
+ /**
133
+ * replaceByIndex performs the heavy work of replacing a WMLElement
134
+ * with the corresponding index from another ViewFrame.
135
+ *
136
+ * The replaced element will have its DOM content redrawn if a parentNode
137
+ * is detected.
138
+ */
139
+ replaceByIndex(next: ViewFrame, idx: Index): void;
140
+
141
+ /**
142
+ * replaceById allows WMLElement replacement by using an id.
143
+ */
144
+ replaceById(next: ViewFrame, id: WMLId): void;
145
+
146
+ /**
147
+ * replaceByGroup allows WMLElement replaced by using a group identifier.
148
+ */
149
+ replaceByGroup(next: ViewFrame, id: WMLId): void;
150
+
151
+ /**
152
+ * destroy the frame.
153
+ *
154
+ * Currently a no-op but may be used in the future.
155
+ */
156
+ destroy(): void;
157
+ }
158
+
159
+ /**
160
+ * ViewFrame contains helper methods for retreiving elements by id or
77
161
  * group freeing the View class of such logic.
78
162
  */
79
- export class ViewFrame {
163
+ export class ViewFrame implements Frame {
80
164
  constructor(
81
165
  public nodes = new SetList(),
82
166
  public widgets = new Map<Index, Widget>(),
@@ -86,7 +170,7 @@ export class ViewFrame {
86
170
  public tree?: Content,
87
171
  ) {}
88
172
 
89
- _register(id: WMLId, node: Content, widget?: Widget) {
173
+ register(id: WMLId, node: Content, widget?: Widget) {
90
174
  let idx = this.nodes.add(node);
91
175
  this.ids.set(id, idx);
92
176
  this.indexes.set(idx, id);
@@ -95,7 +179,7 @@ export class ViewFrame {
95
179
  }
96
180
  }
97
181
 
98
- _registerGroupMember(id: WMLId, node: Content, widget?: Widget) {
182
+ registerGroupMember(id: WMLId, node: Content, widget?: Widget) {
99
183
  let group = this.groups.get(id) ?? [];
100
184
  let idx = this.nodes.add(node);
101
185
  group.push(idx);
@@ -105,18 +189,10 @@ export class ViewFrame {
105
189
  this.groups.set(id, group);
106
190
  }
107
191
 
108
- /**
109
- * root sets the root element of a View's tree.
110
- */
111
192
  root(el: Content) {
112
193
  this.tree = el;
113
194
  }
114
195
 
115
- /**
116
- * node constructs a DOM node to be used in the View's tree.
117
- *
118
- * Any id or group assignment will be honored.
119
- */
120
196
  node(tag: string, attrs: Attrs, children: Content[]): Content {
121
197
  let elm = dom.createElement(
122
198
  tag,
@@ -125,34 +201,25 @@ export class ViewFrame {
125
201
  (attrs.wml && attrs.wml.ns) || "",
126
202
  );
127
203
 
128
- if (attrs?.wml?.id) this._register(attrs.wml.id, elm);
204
+ if (attrs?.wml?.id) this.register(attrs.wml.id, elm);
129
205
 
130
- if (attrs?.wml?.group) this._registerGroupMember(attrs.wml.group, elm);
206
+ if (attrs?.wml?.group) this.registerGroupMember(attrs.wml.group, elm);
131
207
 
132
208
  return elm;
133
209
  }
134
210
 
135
- /**
136
- * widget constructs a DOM sub-tree for a Widget in the View's tree.
137
- *
138
- * Any id or group assignment will be honored.
139
- */
140
211
  widget(w: Widget, attrs: Attrs): Content {
141
212
  let tree = w.render();
142
213
 
143
214
  DOMMonitor.getInstance().monitor(tree, w);
144
215
 
145
- if (attrs?.wml?.id) this._register(attrs.wml.id, tree, w);
216
+ if (attrs?.wml?.id) this.register(attrs.wml.id, tree, w);
146
217
 
147
- if (attrs?.wml?.group) this._registerGroupMember(attrs.wml.group, tree, w);
218
+ if (attrs?.wml?.group) this.registerGroupMember(attrs.wml.group, tree, w);
148
219
 
149
220
  return tree;
150
221
  }
151
222
 
152
- /**
153
- * view renders the content of another view, saving its ids and groups to
154
- * this one.
155
- */
156
223
  view(view: View): Content {
157
224
  let { root } = this;
158
225
  let tree = view.render(this);
@@ -160,9 +227,6 @@ export class ViewFrame {
160
227
  return tree;
161
228
  }
162
229
 
163
- /**
164
- * findById returns the entry stored for the specified wml element.
165
- */
166
230
  findById(id: WMLId): Maybe<Entry> {
167
231
  let idx = this.ids.get(id) ?? -1;
168
232
  let node = this.nodes.get(idx);
@@ -173,9 +237,6 @@ export class ViewFrame {
173
237
  });
174
238
  }
175
239
 
176
- /**
177
- * findByGroup returns all the entries stored fro a group.
178
- */
179
240
  findByGroup(id: WMLId): Entry[] {
180
241
  let result = [];
181
242
  for (let idx of this.groups.get(id) ?? []) {
@@ -187,19 +248,11 @@ export class ViewFrame {
187
248
  return result;
188
249
  }
189
250
 
190
- /**
191
- * replaceByIndex performs the heavy work of replacing a WMLElement
192
- * with the corresponding index from another ViewFrame.
193
- *
194
- * The replaced element will have its DOM content redrawn if a parentNode
195
- * is detected.
196
- */
197
251
  replaceByIndex(next: ViewFrame, idx: Index) {
198
252
  let originalNode = this.nodes.get(idx);
199
- let id = this.indexes.get(idx) ?? "";
200
253
 
201
254
  if (idx == null || originalNode == null) {
202
- console.warn(`Not replacing missing WMLElement for id "${id}"!`);
255
+ console.warn(`Not replacing missing element by index ${idx}!`);
203
256
  return;
204
257
  }
205
258
 
@@ -207,12 +260,16 @@ export class ViewFrame {
207
260
 
208
261
  let node = next.nodes.get(idx);
209
262
  let widget = next.widgets.get(idx);
263
+
210
264
  if (node == null) {
211
265
  // Remove references since the node no longer exists.
212
266
  this.nodes.delete(idx);
213
267
  this.widgets.delete(idx);
268
+
269
+ let id = this.indexes.get(idx) ?? "";
214
270
  this.ids.delete(id);
215
271
  this.indexes.delete(idx);
272
+
216
273
  if (parentNode) parentNode.removeChild(originalNode);
217
274
  return;
218
275
  }
@@ -225,16 +282,10 @@ export class ViewFrame {
225
282
  if (parentNode) parentNode.replaceChild(node, originalNode);
226
283
  }
227
284
 
228
- /**
229
- * replaceById allows WMLElement replacement by using an id.
230
- */
231
285
  replaceById(next: ViewFrame, id: WMLId) {
232
286
  this.replaceByIndex(next, this.ids.get(id) ?? -1);
233
287
  }
234
288
 
235
- /**
236
- * replaceByGroup allows WMLElement replaced by using a group identifier.
237
- */
238
289
  replaceByGroup(next: ViewFrame, id: WMLId) {
239
290
  let group = this.groups.get(id) ?? [];
240
291
 
@@ -246,4 +297,6 @@ export class ViewFrame {
246
297
 
247
298
  this.groups.set(id, newGroup);
248
299
  }
300
+
301
+ destroy() {}
249
302
  }
@@ -1,10 +1,10 @@
1
1
  import { Maybe } from "@quenk/noni/lib/data/maybe";
2
2
  import { Content, WMLElement, WMLId } from "..";
3
- import { ViewFrame } from "./frame";
3
+ import { Frame, ViewFrame } from "./frame";
4
4
  /**
5
5
  * Renderer is a function that builds up a ViewFrame on behalf of the view.
6
6
  */
7
- export type Renderer = (frame: ViewFrame) => ViewFrame;
7
+ export type Renderer = (frame: Frame) => void;
8
8
  /**
9
9
  * View instances are compiled from wml template files.
10
10
  *
@@ -15,10 +15,11 @@ export interface View {
15
15
  /**
16
16
  * render the View.
17
17
  *
18
- * If a ViewFrame is provided, it will be used instead of creating a new one
19
- * internally. In this case the View itself will not be mutated internally.
18
+ * If parent is provided, it is treated as the parent Frame to the internal
19
+ * frame the view will be created. This allows ids from this frame to be
20
+ * accessed in the parent scope.
20
21
  */
21
- render(frame?: ViewFrame): Content;
22
+ render(parent?: ViewFrame): Content;
22
23
  /**
23
24
  * invalidate this View causing the DOM to be re-rendered.
24
25
  *
@@ -49,10 +50,10 @@ export interface View {
49
50
  export declare class BaseView implements View {
50
51
  context: object;
51
52
  renderer: Renderer;
52
- frame: ViewFrame;
53
- constructor(context: object, renderer: Renderer, frame?: ViewFrame);
53
+ frame: Frame;
54
+ constructor(context: object, renderer: Renderer, frame?: Frame);
54
55
  findById<E extends WMLElement>(id: string): Maybe<E>;
55
56
  findGroupById<E extends WMLElement>(name: string): E[];
56
57
  invalidate(id?: WMLId): void;
57
- render(frame?: ViewFrame): Content;
58
+ render(): Content;
58
59
  }
package/lib/view/index.js CHANGED
@@ -27,7 +27,8 @@ class BaseView {
27
27
  if (frame.tree == null || frame.tree.parentNode == null)
28
28
  return console.warn("invalidate(): cannot invalidate this view, it has no parent node!");
29
29
  if (id) {
30
- let next = this.renderer(new frame_1.ViewFrame());
30
+ let next = new frame_1.ViewFrame();
31
+ this.renderer(next);
31
32
  if (id[0] === "$")
32
33
  frame.replaceByGroup(next, id.slice(1));
33
34
  else
@@ -37,10 +38,10 @@ class BaseView {
37
38
  frame.tree.parentNode.replaceChild(this.render(), frame.tree);
38
39
  }
39
40
  }
40
- render(frame) {
41
- if (frame)
42
- return this.renderer(frame).tree;
43
- this.frame = this.renderer(new frame_1.ViewFrame());
41
+ render() {
42
+ this.frame?.destroy();
43
+ this.frame = new frame_1.ViewFrame();
44
+ this.renderer(this.frame);
44
45
  return this.frame.tree;
45
46
  }
46
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;AAGA,mCAA2C;AA8C3C;;;;;GAKG;AACH,MAAa,QAAQ;IACnB,YACS,OAAe,EACf,QAAkB,EAClB,QAAQ,IAAI,iBAAS,EAAE;QAFvB,YAAO,GAAP,OAAO,CAAQ;QACf,aAAQ,GAAR,QAAQ,CAAU;QAClB,UAAK,GAAL,KAAK,CAAkB;IAC7B,CAAC;IAEJ,QAAQ,CAAuB,EAAU;QACvC,OAAO,IAAI,CAAC,KAAK;aACd,QAAQ,CAAC,EAAE,CAAC;aACZ,GAAG,CAAC,CAAC,GAAU,EAAE,EAAE,CAAI,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,aAAa,CAAuB,IAAY;QAC9C,OAAY,CACV,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,CACxE,CAAC;IACJ,CAAC;IAED,UAAU,CAAC,EAAU;QACnB,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;QAErB,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI;YACrD,OAAO,OAAO,CAAC,IAAI,CACjB,mEAAmE,CACpE,CAAC;QAEJ,IAAI,EAAE,EAAE,CAAC;YACP,IAAI,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,iBAAS,EAAE,CAAC,CAAC;YAC1C,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG;gBAAE,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;;gBACtD,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACnC,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED,MAAM,CAAC,KAAiB;QACtB,IAAI,KAAK;YAAE,OAAgB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;QACrD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,iBAAS,EAAE,CAAC,CAAC;QAC5C,OAAgB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IAClC,CAAC;CACF;AAzCD,4BAyCC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;;AAGA,mCAAkD;AA+ClD;;;;;GAKG;AACH,MAAa,QAAQ;IACnB,YACS,OAAe,EACf,QAAkB,EAClB,QAAe,IAAI,iBAAS,EAAE;QAF9B,YAAO,GAAP,OAAO,CAAQ;QACf,aAAQ,GAAR,QAAQ,CAAU;QAClB,UAAK,GAAL,KAAK,CAAyB;IACpC,CAAC;IAEJ,QAAQ,CAAuB,EAAU;QACvC,OAAO,IAAI,CAAC,KAAK;aACd,QAAQ,CAAC,EAAE,CAAC;aACZ,GAAG,CAAC,CAAC,GAAU,EAAE,EAAE,CAAI,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,aAAa,CAAuB,IAAY;QAC9C,OAAY,CACV,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,CACxE,CAAC;IACJ,CAAC;IAED,UAAU,CAAC,EAAU;QACnB,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;QAErB,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI;YACrD,OAAO,OAAO,CAAC,IAAI,CACjB,mEAAmE,CACpE,CAAC;QAEJ,IAAI,EAAE,EAAE,CAAC;YACP,IAAI,IAAI,GAAG,IAAI,iBAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACpB,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG;gBAAE,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;;gBACtD,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACnC,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;QACtB,IAAI,CAAC,KAAK,GAAG,IAAI,iBAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1B,OAAgB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IAClC,CAAC;CACF;AA3CD,4BA2CC"}
package/lib/view/index.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { Maybe } from "@quenk/noni/lib/data/maybe";
2
2
 
3
3
  import { Content, WMLElement, WMLId } from "..";
4
- import { Entry, ViewFrame } from "./frame";
4
+ import { Entry, Frame, ViewFrame } from "./frame";
5
5
 
6
6
  /**
7
7
  * Renderer is a function that builds up a ViewFrame on behalf of the view.
8
8
  */
9
- export type Renderer = (frame: ViewFrame) => ViewFrame;
9
+ export type Renderer = (frame: Frame) => void;
10
10
 
11
11
  /**
12
12
  * View instances are compiled from wml template files.
@@ -18,10 +18,11 @@ export interface View {
18
18
  /**
19
19
  * render the View.
20
20
  *
21
- * If a ViewFrame is provided, it will be used instead of creating a new one
22
- * internally. In this case the View itself will not be mutated internally.
21
+ * If parent is provided, it is treated as the parent Frame to the internal
22
+ * frame the view will be created. This allows ids from this frame to be
23
+ * accessed in the parent scope.
23
24
  */
24
- render(frame?: ViewFrame): Content;
25
+ render(parent?: ViewFrame): Content;
25
26
 
26
27
  /**
27
28
  * invalidate this View causing the DOM to be re-rendered.
@@ -57,7 +58,7 @@ export class BaseView implements View {
57
58
  constructor(
58
59
  public context: object,
59
60
  public renderer: Renderer,
60
- public frame = new ViewFrame(),
61
+ public frame: Frame = new ViewFrame(),
61
62
  ) {}
62
63
 
63
64
  findById<E extends WMLElement>(id: string): Maybe<E> {
@@ -81,7 +82,8 @@ export class BaseView implements View {
81
82
  );
82
83
 
83
84
  if (id) {
84
- let next = this.renderer(new ViewFrame());
85
+ let next = new ViewFrame();
86
+ this.renderer(next);
85
87
  if (id[0] === "$") frame.replaceByGroup(next, id.slice(1));
86
88
  else frame.replaceById(next, id);
87
89
  } else {
@@ -89,9 +91,10 @@ export class BaseView implements View {
89
91
  }
90
92
  }
91
93
 
92
- render(frame?: ViewFrame): Content {
93
- if (frame) return <Content>this.renderer(frame).tree;
94
- this.frame = this.renderer(new ViewFrame());
94
+ render(): Content {
95
+ this.frame?.destroy();
96
+ this.frame = new ViewFrame();
97
+ this.renderer(this.frame);
95
98
  return <Content>this.frame.tree;
96
99
  }
97
100
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@quenk/wml",
3
- "version": "2.14.0",
3
+ "version": "2.15.0",
4
4
  "description": "(WML) is a DSL for describing user interfaces in web applications.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
7
- "test": "./node_modules/.bin/mocha $(find -name *_test.ts)"
7
+ "test": "./node_modules/.bin/jest test"
8
8
  },
9
9
  "types": "lib/index.d.ts",
10
10
  "files": [
@@ -16,13 +16,17 @@
16
16
  "author": "Quenk Technologies Limited <info@quenk.com> (https://quenk.com)",
17
17
  "license": "Apache-2.0",
18
18
  "devDependencies": {
19
+ "@jest/globals": "^30.2.0",
19
20
  "@quenk/test": "^2.4.0",
20
21
  "@types/docopt": "^0.6.31",
22
+ "@types/jest": "^30.0.0",
21
23
  "@types/mocha": "^8.2.2",
22
24
  "@types/node": "^24.3.0",
25
+ "jest": "^30.2.0",
23
26
  "jison-gho": "^0.6.1-216",
24
27
  "mocha": "^9.0.0",
25
28
  "prettier": "^3.5.3",
29
+ "ts-jest": "^29.4.6",
26
30
  "ts-node": "^10.5.0",
27
31
  "typescript": "^5.9.2"
28
32
  },