@zcomponent/core 0.0.14 → 0.0.15

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.
@@ -24,7 +24,7 @@ export class ActionBehavior extends Behavior {
24
24
  }
25
25
  };
26
26
  const env = contextManager.get(EnvironmentContext);
27
- this.register(env.editTime, this._updateRegistration);
28
- this.register(this.enabledResolved, this._updateRegistration);
27
+ this.register(env.editTime, this._updateRegistration, { bindWhenDisabled: true });
28
+ this.register(this.enabledResolved, this._updateRegistration, { bindWhenDisabled: true });
29
29
  }
30
30
  }
@@ -37,7 +37,7 @@ export class CanvasContext extends Context {
37
37
  this.size = new Observable([this.canvas.clientWidth, this.canvas.clientHeight]);
38
38
  this._resizeObserver.observe(this.canvas);
39
39
  let lastRect;
40
- this.onBeforeRender.bindfn(() => {
40
+ this.onBeforeRender.addListener(() => {
41
41
  const rect = this.canvas.getBoundingClientRect();
42
42
  if (lastRect && lastRect.top === rect.top && lastRect.left === rect.left && lastRect.width === rect.width && lastRect.height === rect.height)
43
43
  return;
@@ -49,8 +49,8 @@ export class CanvasContext extends Context {
49
49
  });
50
50
  }
51
51
  dispose() {
52
- this.onBeforeRender.clear();
53
- this.onAfterRender.clear();
52
+ this.onBeforeRender.clearListeners();
53
+ this.onAfterRender.clearListeners();
54
54
  this._resizeObserver.disconnect();
55
55
  if (!this.constructorProps.canvas)
56
56
  this.canvas.remove();
@@ -0,0 +1,25 @@
1
+ import { Observable } from "./observable";
2
+ export declare class Emitter<Args extends Array<any> = []> {
3
+ hasListeners: Observable<boolean, never>;
4
+ private _funcs;
5
+ private _emitting;
6
+ private _toUnbind;
7
+ private _needsSort;
8
+ clearListeners(): void;
9
+ /**
10
+ * Add a new handler function
11
+ * @param f - The callback function to be bound.
12
+ */
13
+ addListener(f: (...args: Args) => void, priority?: number): void;
14
+ /**
15
+ * Unbind an existing function
16
+ * @param f - The callback function to be unbound.
17
+ */
18
+ removeListener(f: (...args: Args) => void): void;
19
+ /**
20
+ * Emit an event
21
+ *
22
+ * @param a - The argument to pass to handler functions.
23
+ */
24
+ protected _emit(...args: Args): void;
25
+ }
package/lib/emitter.js ADDED
@@ -0,0 +1,69 @@
1
+ import { Observable } from "./observable";
2
+ export class Emitter {
3
+ constructor() {
4
+ this.hasListeners = new Observable(false);
5
+ this._funcs = [];
6
+ this._emitting = false;
7
+ this._toUnbind = new Set();
8
+ this._needsSort = false;
9
+ }
10
+ clearListeners() {
11
+ this._funcs = [];
12
+ this.hasListeners.value = false;
13
+ this._needsSort = false;
14
+ }
15
+ /**
16
+ * Add a new handler function
17
+ * @param f - The callback function to be bound.
18
+ */
19
+ addListener(f, priority = 0) {
20
+ this._funcs.push([f, priority]);
21
+ this._needsSort = true;
22
+ if (!this.hasListeners.value)
23
+ this.hasListeners.value = true;
24
+ }
25
+ /**
26
+ * Unbind an existing function
27
+ * @param f - The callback function to be unbound.
28
+ */
29
+ removeListener(f) {
30
+ if (this._emitting) {
31
+ this._toUnbind.add(f);
32
+ return;
33
+ }
34
+ this._toUnbind.delete(f);
35
+ const indx = this._funcs.findIndex(entry => entry[0] === f);
36
+ if (indx > -1) {
37
+ this._funcs.splice(indx, 1);
38
+ }
39
+ if (this._funcs.length === 0 && this.hasListeners.value)
40
+ this.hasListeners.value = false;
41
+ }
42
+ /**
43
+ * Emit an event
44
+ *
45
+ * @param a - The argument to pass to handler functions.
46
+ */
47
+ _emit(...args) {
48
+ if (this._needsSort) {
49
+ this._funcs.sort((a, b) => b[1] - a[1]);
50
+ this._needsSort = false;
51
+ }
52
+ this._emitting = true;
53
+ for (let i = 0, total = this._funcs.length; i < total; i++) {
54
+ const fn = this._funcs[i][0];
55
+ try {
56
+ if (this._toUnbind.has(fn))
57
+ continue;
58
+ fn(...args);
59
+ }
60
+ catch (ex) {
61
+ console.log('Exception in event handler', ex);
62
+ }
63
+ }
64
+ this._emitting = false;
65
+ if (this._toUnbind.size > 0) {
66
+ this._toUnbind.forEach(fn => this.removeListener(fn));
67
+ }
68
+ }
69
+ }
package/lib/entity.d.ts CHANGED
@@ -6,6 +6,7 @@ import { ZComponent } from "./zcomponent";
6
6
  export declare class Entity {
7
7
  readonly contextManager: ContextManager;
8
8
  private _registered;
9
+ private _handlersBound;
9
10
  private _zcomponent;
10
11
  private _disposed;
11
12
  /**
@@ -64,6 +65,7 @@ export declare class Entity {
64
65
  getZComponentInstance<T extends ZComponent = ZComponent>(type?: ConstructorForComponent<T>): T;
65
66
  /**
66
67
  * Register a function to be called when an Event is fired, or an Observable's value changes.
68
+ * The function will only be bound to the underlying Event or Observable while this entity is enabled.
67
69
  *
68
70
  * Using this function, rather than attaching your handler directly to the Event or Observable,
69
71
  * ensures your handler is automatically released when this entity is disposed.
@@ -71,8 +73,10 @@ export declare class Entity {
71
73
  * @param evt The Event or Observable to listen to
72
74
  * @param fn A function that will be called when the event fires, or the Observable value changes
73
75
  */
74
- register<Args extends Array<any>>(evt: Event<Args>, fn: (...args: Args) => void): any;
75
- register<Type>(observable: Observable<Type>, fn: (v: Type) => void): any;
76
+ register<Args extends Array<any>>(evt: Event<Args>, fn: (...args: Args) => void, priority?: number): any;
77
+ register<Args extends Array<any>>(evt: Event<Args>, fn: (...args: Args) => void, options?: RegisterOptions): any;
78
+ register<Type>(observable: Observable<Type>, fn: (v: Type) => void, priority?: number): any;
79
+ register<Type>(observable: Observable<Type>, fn: (v: Type) => void, options?: RegisterOptions): any;
76
80
  /**
77
81
  * Unregisters a function that was previously registered to an Event or Observable.
78
82
  *
@@ -81,6 +85,7 @@ export declare class Entity {
81
85
  */
82
86
  unregister<Args extends Array<any>>(evt: Event<Args>, fn: (...args: Args) => void): any;
83
87
  unregister<Type>(observable: Observable<Type>, fn: (v: Type) => void): any;
88
+ private _updateHandlers;
84
89
  /**
85
90
  * Destroy this entity, cleaning up any resources that it has created and
86
91
  * handler functions or callbacks it has registered.
@@ -102,3 +107,7 @@ export declare class Entity {
102
107
  dispose(): never;
103
108
  private static callSuperDispose;
104
109
  }
110
+ export interface RegisterOptions {
111
+ priority?: number;
112
+ bindWhenDisabled?: boolean;
113
+ }
package/lib/entity.js CHANGED
@@ -5,6 +5,7 @@ export class Entity {
5
5
  constructor(contextManager) {
6
6
  this.contextManager = contextManager;
7
7
  this._registered = [];
8
+ this._handlersBound = false;
8
9
  this._disposed = false;
9
10
  /**
10
11
  * An event that is fired as the last act of this entity being destroyed.
@@ -46,7 +47,24 @@ export class Entity {
46
47
  * Disabled entities will typically remain visible (if they have a visible appearance).
47
48
  */
48
49
  this.enabledResolved = new Observable(true);
50
+ this._updateHandlers = (enabled) => {
51
+ if (enabled && !this._handlersBound) {
52
+ for (const [e, fn, priority] of this._registered) {
53
+ e.addListener(fn, priority);
54
+ }
55
+ this._handlersBound = true;
56
+ }
57
+ if (!enabled && this._handlersBound) {
58
+ for (const [e, fn, _, bindWhenDisabled] of this._registered) {
59
+ if (bindWhenDisabled)
60
+ continue;
61
+ e.removeListener(fn);
62
+ }
63
+ this._handlersBound = false;
64
+ }
65
+ };
49
66
  this._zcomponent = getCurrentZComponentConstruction();
67
+ this.enabledResolved.addListener(this._updateHandlers);
50
68
  }
51
69
  get disposed() {
52
70
  return this._disposed;
@@ -73,22 +91,20 @@ export class Entity {
73
91
  return this._zcomponent;
74
92
  throw new Error("getZComponentInstance called in entity passing wrong kind of ZComponent");
75
93
  }
76
- register(e, fn) {
94
+ register(e, fn, priorityOrOptions) {
95
+ const priority = (typeof priorityOrOptions === 'number' ? priorityOrOptions : priorityOrOptions?.priority);
96
+ const bindWhenDisabled = (typeof priorityOrOptions === 'number' ? false : priorityOrOptions?.bindWhenDisabled);
77
97
  for (const entry of this._registered) {
78
98
  if (entry[0] === e && entry[1] === fn)
79
99
  return;
80
100
  }
81
- if (e instanceof Event)
82
- e.bindfn(fn);
83
- else if (e instanceof Observable)
84
- e.withValue(fn);
85
- this._registered.push([e, fn]);
101
+ this._registered.push([e, fn, priority, bindWhenDisabled ?? false]);
102
+ if (this._handlersBound || bindWhenDisabled) {
103
+ e.addListener(fn, priority);
104
+ }
86
105
  }
87
106
  unregister(e, fn) {
88
- if (e instanceof Event)
89
- e.unbindfn(fn);
90
- else if (e instanceof Observable)
91
- e.removeWithValue(fn);
107
+ e.removeListener(fn);
92
108
  this._registered = this._registered.filter(entry => (entry[0] !== e || entry[1] !== fn));
93
109
  }
94
110
  /**
@@ -110,15 +126,13 @@ export class Entity {
110
126
  * @returns The result of a call to super.dispose()
111
127
  */
112
128
  dispose() {
129
+ this.enabledResolved.removeListener(this._updateHandlers);
113
130
  for (const entry of this._registered) {
114
- if (entry[0] instanceof Event)
115
- entry[0].unbindfn(entry[1]);
116
- else if (entry[0] instanceof Observable)
117
- entry[0].removeWithValue(entry[1]);
131
+ entry[0].removeListener(entry[1]);
118
132
  }
119
133
  this._registered = [];
120
134
  this.onDispose.emit();
121
- this.onDispose.clear();
135
+ this.onDispose.clearListeners();
122
136
  this.disposed = true;
123
137
  return undefined;
124
138
  }
package/lib/event.d.ts CHANGED
@@ -1,20 +1,5 @@
1
- import { Observable } from "./observable";
2
- export declare class Event<Args extends Array<any> = []> {
3
- hasBindings: Observable<boolean, never>;
4
- private _funcs;
5
- private _emitting;
6
- private _toUnbind;
7
- clear(): void;
8
- /**
9
- * Bind new handler function.
10
- * @param f - The callback function to be bound.
11
- */
12
- bindfn(f: (...args: Args) => void): void;
13
- /**
14
- * Unbind an existing function.
15
- * @param f - The callback function to be unbound.
16
- */
17
- unbindfn(f: (...args: Args) => void): void;
1
+ import { Emitter } from "./emitter";
2
+ export declare class Event<Args extends Array<any> = []> extends Emitter<Args> {
18
3
  /**
19
4
  * Emit an event.
20
5
  *
package/lib/event.js CHANGED
@@ -1,61 +1,11 @@
1
- import { Observable } from "./observable";
2
- export class Event {
3
- constructor() {
4
- this.hasBindings = new Observable(false);
5
- this._funcs = [];
6
- this._emitting = false;
7
- this._toUnbind = new Set();
8
- }
9
- clear() {
10
- this._funcs = [];
11
- this.hasBindings.value = false;
12
- }
13
- /**
14
- * Bind new handler function.
15
- * @param f - The callback function to be bound.
16
- */
17
- bindfn(f) {
18
- this._funcs.push(f);
19
- if (!this.hasBindings.value)
20
- this.hasBindings.value = true;
21
- }
22
- /**
23
- * Unbind an existing function.
24
- * @param f - The callback function to be unbound.
25
- */
26
- unbindfn(f) {
27
- if (this._emitting) {
28
- this._toUnbind.add(f);
29
- return;
30
- }
31
- this._toUnbind.delete(f);
32
- const indx = this._funcs.indexOf(f);
33
- if (indx > -1) {
34
- this._funcs.splice(indx, 1);
35
- }
36
- if (this._funcs.length === 0 && this.hasBindings.value)
37
- this.hasBindings.value = false;
38
- }
1
+ import { Emitter } from "./emitter";
2
+ export class Event extends Emitter {
39
3
  /**
40
4
  * Emit an event.
41
5
  *
42
6
  * @param a - The argument to pass to handler functions.
43
7
  */
44
8
  emit(...args) {
45
- this._emitting = true;
46
- for (let i = 0, total = this._funcs.length; i < total; i++) {
47
- try {
48
- if (this._toUnbind.has(this._funcs[i]))
49
- continue;
50
- this._funcs[i](...args);
51
- }
52
- catch (ex) {
53
- console.log('Exception in event handler', ex);
54
- }
55
- }
56
- this._emitting = false;
57
- if (this._toUnbind.size > 0) {
58
- this._toUnbind.forEach(fn => this.unbindfn(fn));
59
- }
9
+ super._emit(...args);
60
10
  }
61
11
  }
@@ -1,3 +1,4 @@
1
+ import { Emitter } from "./emitter";
1
2
  type Resolved<T, M> = [T] extends [never] ? M : T;
2
3
  /**
3
4
  * A class that holds a value and watches it for changes (deeply by default).
@@ -31,14 +32,11 @@ type Resolved<T, M> = [T] extends [never] ? M : T;
31
32
  * @typeParam Type - The type of the contained value
32
33
  * @typeParam TypeInternal - An internal type used to improve automatic type detection
33
34
  */
34
- export declare class Observable<Type = never, TypeInternal extends any[] | [] | never = never> {
35
+ export declare class Observable<Type = never, TypeInternal extends any[] | [] | never = never> extends Emitter<[Resolved<Type, TypeInternal>]> {
35
36
  private _default;
36
37
  private _deep;
37
38
  private _proxies;
38
39
  private _value;
39
- private _nextToken;
40
- private _handlersByToken;
41
- private _tokenByHandler;
42
40
  /**
43
41
  * Constructs a new Observable
44
42
  *
@@ -56,8 +54,7 @@ export declare class Observable<Type = never, TypeInternal extends any[] | [] |
56
54
  get value(): Resolved<Type, TypeInternal>;
57
55
  private _wrap;
58
56
  set value(v: Resolved<Type, TypeInternal> | undefined);
59
- withValue(fn: (v: Resolved<Type, TypeInternal>) => void): number;
60
- removeWithValue(fnOrToken: number | ((v: Resolved<Type, TypeInternal>) => void)): void;
57
+ addListener(fn: (v: Resolved<Type, TypeInternal>) => void, priority?: number): void;
61
58
  private _emitValue;
62
59
  }
63
60
  export {};
package/lib/observable.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { Emitter } from "./emitter";
1
2
  /**
2
3
  * A class that holds a value and watches it for changes (deeply by default).
3
4
  *
@@ -30,14 +31,12 @@
30
31
  * @typeParam Type - The type of the contained value
31
32
  * @typeParam TypeInternal - An internal type used to improve automatic type detection
32
33
  */
33
- export class Observable {
34
+ export class Observable extends Emitter {
34
35
  constructor(_default, withHandler, _deep = true) {
36
+ super();
35
37
  this._default = _default;
36
38
  this._deep = _deep;
37
39
  this._proxies = new WeakMap();
38
- this._nextToken = 0;
39
- this._handlersByToken = new Map();
40
- this._tokenByHandler = new Map();
41
40
  let initial = this._default;
42
41
  if (Array.isArray(this._default) && this._deep) {
43
42
  initial = this._default.slice();
@@ -49,7 +48,7 @@ export class Observable {
49
48
  }
50
49
  this._value = this._wrap(initial);
51
50
  if (withHandler)
52
- this.withValue(withHandler);
51
+ this.addListener(withHandler);
53
52
  }
54
53
  /**
55
54
  * Access the current value for this Observable. Changing this value (or, unless deep inspection is disabled, its recursive elements and keys) will trigger any registered handlers to be called with the new value.
@@ -117,40 +116,16 @@ export class Observable {
117
116
  this._value = this._wrap(v);
118
117
  this._emitValue();
119
118
  }
120
- withValue(fn) {
121
- const token = this._nextToken++;
122
- this._handlersByToken.set(token, fn);
123
- this._tokenByHandler.set(fn, token);
119
+ addListener(fn, priority = 0) {
120
+ this.addListener(fn);
124
121
  try {
125
122
  fn(this._value.proxy);
126
123
  }
127
124
  catch (err) {
128
125
  console.error(err);
129
126
  }
130
- return token;
131
- }
132
- removeWithValue(fnOrToken) {
133
- if (typeof fnOrToken === 'number') {
134
- const handler = this._handlersByToken.get(fnOrToken);
135
- if (handler !== undefined)
136
- this._tokenByHandler.delete(handler);
137
- this._handlersByToken.delete(fnOrToken);
138
- }
139
- else {
140
- const token = this._tokenByHandler.get(fnOrToken);
141
- if (token !== undefined)
142
- this._handlersByToken.delete(token);
143
- this._tokenByHandler.delete(fnOrToken);
144
- }
145
127
  }
146
128
  _emitValue() {
147
- for (let [key, h] of this._handlersByToken) {
148
- try {
149
- h(this._value.proxy);
150
- }
151
- catch (err) {
152
- console.error(err);
153
- }
154
- }
129
+ this._emit(this._value.proxy);
155
130
  }
156
131
  }
package/lib/selectors.js CHANGED
@@ -218,7 +218,7 @@ function typeOutputForProp(prop) {
218
218
  const comments = (prop.comments && prop.comments.length > 0) ? '\n\t* ' + prop.comments.map(makeCommentSafe).join('\n\t*\n\t* ') + '\n\t* ' : '';
219
219
  return ` /**${comments}
220
220
  * @zprop
221
- * ${prop.default ? `@zdefault ${JSON.stringify(prop.default)}` : ''}
221
+ * ${prop.default !== undefined ? `@zdefault ${JSON.stringify(prop.default)}` : ''}
222
222
  */
223
223
  public ${prop.name}: Observable<${outputForType(prop.type, true)}>;`;
224
224
  }
@@ -226,7 +226,7 @@ function typeOutputForConstructorProp(prop) {
226
226
  const comments = (prop.comments && prop.comments.length > 0) ? '\n\t* ' + prop.comments.map(makeCommentSafe).join('\n\t*\n\t* ') + '\n\t* ' : '';
227
227
  return ` /**${comments}
228
228
  * @zprop
229
- * ${prop.default && `@zdefault ${JSON.stringify(prop.default)}`}
229
+ * ${prop.default !== undefined && `@zdefault ${JSON.stringify(prop.default)}`}
230
230
  */
231
231
  ${prop.name}: ${outputForType(prop.type, true)};`;
232
232
  }
@@ -38,6 +38,7 @@ export declare class ZComponent<RootType = any> extends Component<RootType> {
38
38
  };
39
39
  entityByID: Map<string, Entity>;
40
40
  entityByLabel: Map<string, Entity>;
41
+ nodeByLabel: Map<string, Component<any, ConstructorProps>>;
41
42
  private _constructedResolve;
42
43
  constructed: Promise<void>;
43
44
  isConstructed: boolean;
package/lib/zcomponent.js CHANGED
@@ -23,6 +23,7 @@ export class ZComponent extends Component {
23
23
  this.nodes = {};
24
24
  this.entityByID = new Map();
25
25
  this.entityByLabel = new Map();
26
+ this.nodeByLabel = new Map();
26
27
  this.constructed = new Promise(resolve => this._constructedResolve = resolve);
27
28
  this.isConstructed = false;
28
29
  this._nodesById = new Map();
@@ -75,8 +76,10 @@ export class ZComponent extends Component {
75
76
  setCurrentZComponentConstruction(that);
76
77
  super(contextManager, constructorProps);
77
78
  that.entityByID.set(nodeId, this);
78
- if (node?.label)
79
+ if (node?.label) {
79
80
  that.entityByLabel.set(node.label, this);
81
+ that.nodeByLabel.set(node.label, this);
82
+ }
80
83
  that._nodesById.set(nodeId, this);
81
84
  for (const element of this.elementsResolved) {
82
85
  that.idByElement.set(element, nodeId);
@@ -224,7 +227,7 @@ export class ZComponent extends Component {
224
227
  if (!v)
225
228
  continue;
226
229
  if (v instanceof Observable)
227
- v.withValue(val => this._setEntityPropPath(entity, override.entityPropPath, val));
230
+ this.register(v, val => this._setEntityPropPath(entity, override.entityPropPath, val), { bindWhenDisabled: true });
228
231
  else
229
232
  this._setEntityPropPath(entity, override.entityPropPath, v);
230
233
  break;
@@ -236,7 +239,7 @@ export class ZComponent extends Component {
236
239
  if (!v)
237
240
  continue;
238
241
  if (v instanceof Observable)
239
- v.withValue(val => this._setEntityPropPath(entity, override.entityPropPath, val));
242
+ this.register(v, val => this._setEntityPropPath(entity, override.entityPropPath, val), { bindWhenDisabled: true });
240
243
  else
241
244
  this._setEntityPropPath(entity, override.entityPropPath, v);
242
245
  break;
@@ -255,7 +258,7 @@ export class ZComponent extends Component {
255
258
  if (!v)
256
259
  continue;
257
260
  if (v instanceof Observable)
258
- v.withValue(val => this._setEntityPropPath(entity, override.entityPropPath, val));
261
+ this.register(v, val => this._setEntityPropPath(entity, override.entityPropPath, val), { bindWhenDisabled: true });
259
262
  else
260
263
  this._setEntityPropPath(entity, override.entityPropPath, v);
261
264
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zcomponent/core",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",