@mulsense/xnew 0.6.8 → 0.7.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.
package/README.md CHANGED
@@ -13,7 +13,7 @@ providing a flexible architecture well-suited for applications with dynamic scen
13
13
  ### Via CDN
14
14
  Include the following script in your HTML file:
15
15
  ```html
16
- <script src="https://unpkg.com/@mulsense/xnew@0.6.x/dist/xnew.js"></script>
16
+ <script src="https://unpkg.com/@mulsense/xnew@0.7.x/dist/xnew.js"></script>
17
17
  ```
18
18
 
19
19
  ### Via CDN (ESM)
@@ -22,7 +22,7 @@ Use the ES module version with an import map:
22
22
  <script type="importmap">
23
23
  {
24
24
  "imports": {
25
- "@mulsense/xnew": "https://unpkg.com/@mulsense/xnew@0.6.x/dist/xnew.mjs"
25
+ "@mulsense/xnew": "https://unpkg.com/@mulsense/xnew@0.7.x/dist/xnew.mjs"
26
26
  }
27
27
  }
28
28
  </script>
@@ -37,7 +37,7 @@ import xnew from '@mulsense/xnew';
37
37
  ### Via npm
38
38
  Install `xnew` using npm:
39
39
  ```bash
40
- npm install @mulsense/xnew@0.6.x
40
+ npm install @mulsense/xnew@0.7.x
41
41
  ```
42
42
 
43
43
  Then import it in your JavaScript file:
@@ -29,6 +29,11 @@
29
29
  },
30
30
  nest(object) {
31
31
  xnew(Nest, { object });
32
+ xnew.extend(() => {
33
+ return {
34
+ get pixiObject() { return object; }
35
+ };
36
+ });
32
37
  return object;
33
38
  },
34
39
  get renderer() {
@@ -7,6 +7,11 @@ var xpixi = {
7
7
  },
8
8
  nest(object) {
9
9
  xnew(Nest, { object });
10
+ xnew.extend(() => {
11
+ return {
12
+ get pixiObject() { return object; }
13
+ };
14
+ });
10
15
  return object;
11
16
  },
12
17
  get renderer() {
@@ -29,6 +29,11 @@
29
29
  },
30
30
  nest(object) {
31
31
  xnew(Nest, { object });
32
+ xnew.extend(() => {
33
+ return {
34
+ get threeObject() { return object; }
35
+ };
36
+ });
32
37
  return object;
33
38
  },
34
39
  get renderer() {
@@ -67,6 +72,17 @@
67
72
  parent.add(object);
68
73
  unit.on('finalize', () => {
69
74
  parent.remove(object);
75
+ object.traverse((obj) => {
76
+ if (obj.isMesh) {
77
+ obj.geometry.dispose();
78
+ if (Array.isArray(obj.material)) {
79
+ obj.material.forEach((mat) => mat.dispose());
80
+ }
81
+ else {
82
+ obj.material.dispose();
83
+ }
84
+ }
85
+ });
70
86
  });
71
87
  return {
72
88
  get threeObject() { return object; },
@@ -7,6 +7,11 @@ var xthree = {
7
7
  },
8
8
  nest(object) {
9
9
  xnew(Nest, { object });
10
+ xnew.extend(() => {
11
+ return {
12
+ get threeObject() { return object; }
13
+ };
14
+ });
10
15
  return object;
11
16
  },
12
17
  get renderer() {
@@ -45,6 +50,17 @@ function Nest(unit, { object }) {
45
50
  parent.add(object);
46
51
  unit.on('finalize', () => {
47
52
  parent.remove(object);
53
+ object.traverse((obj) => {
54
+ if (obj.isMesh) {
55
+ obj.geometry.dispose();
56
+ if (Array.isArray(obj.material)) {
57
+ obj.material.forEach((mat) => mat.dispose());
58
+ }
59
+ else {
60
+ obj.material.dispose();
61
+ }
62
+ }
63
+ });
48
64
  });
49
65
  return {
50
66
  get threeObject() { return object; },
package/dist/xnew.d.ts CHANGED
@@ -44,6 +44,7 @@ declare class Eventor {
44
44
  }
45
45
 
46
46
  type UnitElement = HTMLElement | SVGElement;
47
+ type UnitArgs = [Component?: Function | string, props?: Object] | [target: UnitElement | string, Component?: Function | string, props?: Object];
47
48
  interface Context {
48
49
  previous: Context | null;
49
50
  key?: any;
@@ -55,39 +56,39 @@ interface Snapshot {
55
56
  element: UnitElement;
56
57
  Component: Function | null;
57
58
  }
58
- interface Internal {
59
- parent: Unit | null;
60
- ancestors: Unit[];
61
- children: Unit[];
62
- state: string;
63
- tostart: boolean;
64
- protected: boolean;
65
- promises: UnitPromise[];
66
- results: Record<string, any>;
67
- defines: Record<string, any>;
68
- systems: Record<string, {
69
- listener: Function;
70
- execute: Function;
71
- }[]>;
72
- currentElement: UnitElement;
73
- currentContext: Context;
74
- currentComponent: Function | null;
75
- nestElements: {
76
- element: UnitElement;
77
- owned: boolean;
78
- }[];
79
- Components: Function[];
80
- listeners: MapMap<string, Function, {
81
- element: UnitElement;
82
- Component: Function | null;
83
- execute: Function;
84
- }>;
85
- eventor: Eventor;
86
- }
87
59
  declare class Unit {
88
60
  [key: string]: any;
89
- _: Internal;
90
- constructor(parent: Unit | null, target: UnitElement | string | null, Component?: Function | string | number, props?: Object);
61
+ _: {
62
+ parent: Unit | null;
63
+ ancestors: Unit[];
64
+ children: Unit[];
65
+ state: string;
66
+ tostart: boolean;
67
+ protected: boolean;
68
+ promises: UnitPromise[];
69
+ results: Record<string, any>;
70
+ defines: Record<string, any>;
71
+ systems: Record<string, {
72
+ listener: Function;
73
+ execute: Function;
74
+ }[]>;
75
+ currentElement: UnitElement;
76
+ currentContext: Context;
77
+ currentComponent: Function | null;
78
+ afterSnapshot: Snapshot | null;
79
+ nestElements: {
80
+ element: UnitElement;
81
+ owned: boolean;
82
+ }[];
83
+ Components: Function[];
84
+ listeners: MapMap<string, Function, {
85
+ element: UnitElement;
86
+ Component: Function | null;
87
+ execute: Function;
88
+ }>;
89
+ eventor: Eventor;
90
+ };
91
+ constructor(parent: Unit | null, ...args: UnitArgs);
91
92
  get element(): UnitElement;
92
93
  start(): void;
93
94
  stop(): void;
@@ -120,49 +121,26 @@ declare class Unit {
120
121
  static emit(type: string, props?: object): void;
121
122
  }
122
123
  declare class UnitPromise {
123
- promise: Promise<any>;
124
- Component: Function | null;
125
- constructor(promise: Promise<any>, Component: Function | null);
124
+ private promise;
125
+ constructor(promise: Promise<any>);
126
126
  then(callback: Function): UnitPromise;
127
127
  catch(callback: Function): UnitPromise;
128
128
  finally(callback: Function): UnitPromise;
129
+ static all(promises: UnitPromise[]): UnitPromise;
129
130
  private wrap;
130
131
  }
131
132
  declare class UnitTimer {
132
133
  private unit;
133
134
  private queue;
134
135
  clear(): void;
135
- timeout(callback: Function, duration?: number): UnitTimer;
136
- interval(callback: Function, duration?: number, iterations?: number): UnitTimer;
136
+ timeout(timeout: Function, duration?: number): UnitTimer;
137
+ interval(timeout: Function, duration?: number, iterations?: number): UnitTimer;
137
138
  transition(transition: Function, duration?: number, easing?: string): UnitTimer;
138
139
  private static execute;
139
140
  private static next;
140
141
  private static Component;
141
142
  }
142
143
 
143
- interface CreateUnit {
144
- /**
145
- * creates a new Unit component
146
- * @param Component - component function
147
- * @param props - properties for component function
148
- * @returns a new Unit instance
149
- * @example
150
- * const unit = xnew(MyComponent, { data: 0 })
151
- */
152
- (Component?: Function | string, props?: Object): Unit;
153
- /**
154
- * creates a new Unit component
155
- * @param target - HTMLElement | SVGElement, or HTML tag for new element
156
- * @param Component - component function
157
- * @param props - properties for component function
158
- * @returns a new Unit instance
159
- * @example
160
- * const unit = xnew(element, MyComponent, { data: 0 })
161
- * const unit = xnew('<div>', MyComponent, { data: 0 })
162
- */
163
- (target: HTMLElement | SVGElement | string, Component?: Function | string, props?: Object): Unit;
164
- }
165
-
166
144
  interface TransitionOptions {
167
145
  duration?: number;
168
146
  easing?: string;
@@ -230,12 +208,19 @@ declare function Panel(unit: Unit, { params }: PanelOptions): {
230
208
  separator(): void;
231
209
  };
232
210
 
233
- declare function Flow(unit: Unit): {
234
- get scene(): Unit | null;
235
- set scene(value: Unit);
236
- next(Component: Function, props?: any): void;
211
+ declare function Scene(unit: Unit): {
212
+ moveTo(Component: Function, props?: any): void;
213
+ append(Component: Function, props?: any): void;
237
214
  };
238
215
 
216
+ declare class XImage {
217
+ canvas: HTMLCanvasElement;
218
+ constructor(canvas: HTMLCanvasElement);
219
+ constructor(width: number, height: number);
220
+ crop(x: number, y: number, width: number, height: number): XImage;
221
+ download(filename: string): void;
222
+ }
223
+
239
224
  type SynthesizerOptions = {
240
225
  oscillator: OscillatorOptions;
241
226
  amp: AmpOptions;
@@ -280,17 +265,22 @@ declare namespace xnew {
280
265
  type Unit = InstanceType<typeof Unit>;
281
266
  type UnitTimer = InstanceType<typeof UnitTimer>;
282
267
  }
283
- declare const xnew: CreateUnit & {
268
+ declare const xnew: ((...args: UnitArgs) => Unit) & {
284
269
  nest(target: UnitElement | string): HTMLElement | SVGElement;
285
270
  extend(Component: Function, props?: Object): {
286
271
  [key: string]: any;
287
272
  };
273
+ append(parent: Unit, ...args: UnitArgs): void;
288
274
  context(key: any): any;
289
275
  promise(promise: Function | Promise<any> | Unit): UnitPromise;
290
276
  then(callback: Function): UnitPromise;
291
277
  catch(callback: Function): UnitPromise;
292
- commit(object?: Record<string, any>): void;
293
278
  finally(callback: Function): UnitPromise;
279
+ resolvers(): {
280
+ resolve(): void;
281
+ reject(): void;
282
+ };
283
+ output(object?: Record<string, any>): void;
294
284
  scope(callback: any): any;
295
285
  find(Component: Function): Unit[];
296
286
  emit(type: string, ...args: any[]): void;
@@ -307,13 +297,16 @@ declare const xnew: CreateUnit & {
307
297
  Panel: typeof Panel;
308
298
  Accordion: typeof Accordion;
309
299
  Popup: typeof Popup;
310
- Flow: typeof Flow;
300
+ Scene: typeof Scene;
311
301
  };
312
302
  audio: {
313
303
  load(path: string): UnitPromise;
314
304
  synthesizer(props: SynthesizerOptions): Synthesizer;
315
305
  volume: number;
316
306
  };
307
+ image: {
308
+ from(canvas: HTMLCanvasElement): XImage;
309
+ };
317
310
  };
318
311
 
319
312
  export { xnew as default };
package/dist/xnew.js CHANGED
@@ -192,7 +192,7 @@
192
192
  this.id = null;
193
193
  this.time = { start: 0.0, processed: 0.0 };
194
194
  (_b = (_a = this.options).transition) === null || _b === void 0 ? void 0 : _b.call(_a, 1.0);
195
- (_d = (_c = this.options).callback) === null || _d === void 0 ? void 0 : _d.call(_c);
195
+ (_d = (_c = this.options).timeout) === null || _d === void 0 ? void 0 : _d.call(_c);
196
196
  this.clear();
197
197
  }, this.options.duration - this.time.processed);
198
198
  this.time.start = Date.now();
@@ -519,16 +519,26 @@
519
519
  return { position };
520
520
  }
521
521
 
522
- //----------------------------------------------------------------------------------------------------
523
- // utils
524
- //----------------------------------------------------------------------------------------------------
525
522
  const SYSTEM_EVENTS = ['start', 'update', 'render', 'stop', 'finalize'];
526
523
  //----------------------------------------------------------------------------------------------------
527
524
  // unit
528
525
  //----------------------------------------------------------------------------------------------------
529
526
  class Unit {
530
- constructor(parent, target, Component, props) {
527
+ constructor(parent, ...args) {
531
528
  var _a;
529
+ let target;
530
+ let Component;
531
+ let props;
532
+ if (args[0] instanceof HTMLElement || args[0] instanceof SVGElement || typeof args[0] === 'string') {
533
+ target = args[0];
534
+ Component = args[1];
535
+ props = args[2];
536
+ }
537
+ else {
538
+ target = null;
539
+ Component = args[0];
540
+ props = args[1];
541
+ }
532
542
  const backup = Unit.currentUnit;
533
543
  Unit.currentUnit = this;
534
544
  parent === null || parent === void 0 ? void 0 : parent._.children.push(this);
@@ -561,6 +571,7 @@
561
571
  currentElement: baseElement,
562
572
  currentContext: baseContext,
563
573
  currentComponent: null,
574
+ afterSnapshot: null,
564
575
  ancestors: parent ? [parent, ...parent._.ancestors] : [],
565
576
  children: [],
566
577
  nestElements: [],
@@ -581,6 +592,7 @@
581
592
  if (this._.state === 'invoked') {
582
593
  this._.state = 'initialized';
583
594
  }
595
+ this._.afterSnapshot = Unit.snapshot(this);
584
596
  Unit.currentUnit = backup;
585
597
  }
586
598
  get element() {
@@ -650,7 +662,7 @@
650
662
  return element;
651
663
  }
652
664
  else {
653
- throw new Error(`xnew.nest: invalid html string [${target}]`);
665
+ throw new Error(`xnew.nest: invalid tag string [${target}]`);
654
666
  }
655
667
  }
656
668
  }
@@ -692,7 +704,7 @@
692
704
  Object.defineProperty(unit._.defines, key, wrapper);
693
705
  Object.defineProperty(unit, key, wrapper);
694
706
  });
695
- return defines;
707
+ return Object.assign({}, unit._.defines);
696
708
  }
697
709
  }
698
710
  static start(unit) {
@@ -729,7 +741,7 @@
729
741
  static reset() {
730
742
  var _a;
731
743
  (_a = Unit.rootUnit) === null || _a === void 0 ? void 0 : _a.finalize();
732
- Unit.currentUnit = Unit.rootUnit = new Unit(null, null);
744
+ Unit.currentUnit = Unit.rootUnit = new Unit(null);
733
745
  const ticker = new AnimationTicker(() => {
734
746
  Unit.start(Unit.rootUnit);
735
747
  Unit.update(Unit.rootUnit);
@@ -769,7 +781,7 @@
769
781
  }
770
782
  static getContext(unit, key) {
771
783
  for (let context = unit._.currentContext; context.previous !== null; context = context.previous) {
772
- if (context.value === Unit.currentUnit && key === Unit.currentUnit._.currentComponent)
784
+ if (context.value === Unit.currentUnit)
773
785
  continue;
774
786
  if (context.key === key)
775
787
  return context.value;
@@ -848,13 +860,13 @@
848
860
  // extensions
849
861
  //----------------------------------------------------------------------------------------------------
850
862
  class UnitPromise {
851
- constructor(promise, Component) {
852
- this.promise = promise;
853
- this.Component = Component;
854
- }
863
+ constructor(promise) { this.promise = promise; }
855
864
  then(callback) { return this.wrap('then', callback); }
856
865
  catch(callback) { return this.wrap('catch', callback); }
857
866
  finally(callback) { return this.wrap('finally', callback); }
867
+ static all(promises) {
868
+ return new UnitPromise(Promise.all(promises.map(p => p.promise)));
869
+ }
858
870
  wrap(key, callback) {
859
871
  const snapshot = Unit.snapshot(Unit.currentUnit);
860
872
  this.promise = this.promise[key]((...args) => Unit.scope(snapshot, callback, ...args));
@@ -872,11 +884,11 @@
872
884
  (_a = this.unit) === null || _a === void 0 ? void 0 : _a.finalize();
873
885
  this.unit = null;
874
886
  }
875
- timeout(callback, duration = 0) {
876
- return UnitTimer.execute(this, { callback, duration }, 1);
887
+ timeout(timeout, duration = 0) {
888
+ return UnitTimer.execute(this, { timeout, duration }, 1);
877
889
  }
878
- interval(callback, duration = 0, iterations = 0) {
879
- return UnitTimer.execute(this, { callback, duration }, iterations);
890
+ interval(timeout, duration = 0, iterations = 0) {
891
+ return UnitTimer.execute(this, { timeout, duration }, iterations);
880
892
  }
881
893
  transition(transition, duration = 0, easing) {
882
894
  return UnitTimer.execute(this, { transition, duration, easing }, 1);
@@ -884,7 +896,7 @@
884
896
  static execute(timer, options, iterations) {
885
897
  const props = { options, iterations, snapshot: Unit.snapshot(Unit.currentUnit) };
886
898
  if (timer.unit === null || timer.unit._.state === 'finalized') {
887
- timer.unit = new Unit(Unit.currentUnit, null, UnitTimer.Component, props);
899
+ timer.unit = new Unit(Unit.currentUnit, UnitTimer.Component, props);
888
900
  }
889
901
  else if (timer.queue.length === 0) {
890
902
  timer.queue.push(props);
@@ -897,18 +909,18 @@
897
909
  }
898
910
  static next(timer) {
899
911
  if (timer.queue.length > 0) {
900
- timer.unit = new Unit(Unit.currentUnit, null, UnitTimer.Component, timer.queue.shift());
912
+ timer.unit = new Unit(Unit.currentUnit, UnitTimer.Component, timer.queue.shift());
901
913
  timer.unit.on('finalize', () => UnitTimer.next(timer));
902
914
  }
903
915
  }
904
916
  static Component(unit, { options, iterations, snapshot }) {
905
917
  let counter = 0;
906
- let timer = new Timer({ callback, transition, duration: options.duration, easing: options.easing });
907
- function callback() {
908
- if (options.callback)
909
- Unit.scope(snapshot, options.callback);
918
+ let timer = new Timer({ timeout, transition, duration: options.duration, easing: options.easing });
919
+ function timeout() {
920
+ if (options.timeout)
921
+ Unit.scope(snapshot, options.timeout);
910
922
  if (iterations <= 0 || counter < iterations - 1) {
911
- timer = new Timer({ callback, transition, duration: options.duration, easing: options.easing });
923
+ timer = new Timer({ timeout, transition, duration: options.duration, easing: options.easing });
912
924
  }
913
925
  else {
914
926
  unit.finalize();
@@ -923,29 +935,31 @@
923
935
  }
924
936
  }
925
937
 
926
- const xnew$1 = Object.assign(function (...args) {
938
+ const xnew$1 = Object.assign(
939
+ /**
940
+ * creates a new Unit component
941
+ * xnew(Component?: Function | string, props?: Object): Unit;
942
+ * xnew(target: HTMLElement | SVGElement | string, Component?: Function | string, props?: Object): Unit;
943
+ * @param target - HTMLElement | SVGElement, or HTML tag for new element
944
+ * @param Component - component function
945
+ * @param props - properties for component function
946
+ * @returns a new Unit instance
947
+ * @example
948
+ * const unit = xnew(MyComponent, { data: 0 })
949
+ * const unit = xnew(element, MyComponent, { data: 0 })
950
+ * const unit = xnew('<div>', MyComponent, { data: 0 })
951
+ */
952
+ function (...args) {
927
953
  if (Unit.rootUnit === undefined)
928
954
  Unit.reset();
929
- let target;
930
- if (args[0] instanceof HTMLElement || args[0] instanceof SVGElement) {
931
- target = args.shift(); // an existing html element
932
- }
933
- else if (typeof args[0] === 'string' && args[0].match(/<((\w+)[^>]*?)\/?>/)) {
934
- target = args.shift();
935
- }
936
- else {
937
- target = null;
938
- }
939
- const Component = args.shift();
940
- const props = args.shift();
941
- const unit = new Unit(Unit.currentUnit, target, Component, props);
942
- return unit;
955
+ return new Unit(Unit.currentUnit, ...args);
943
956
  }, {
944
957
  /**
945
- * Creates a nested HTML/SVG element within the current component
946
- * @param target - HTML or SVG tag string (e.g., '<div class="my-class">', '<span style="color:red">', '<svg viewBox="0 0 24 24">')
947
- * @returns The created HTML/SVG element
948
- * @throws Error if called after component initialization
958
+ * Creates a child HTML/SVG element inside the current component's element.
959
+ * Must be called during component initialization (before setup completes).
960
+ * @param target - An existing HTML/SVG element, or a tag string like `'<div>'`
961
+ * @returns The provided element, or the newly created element
962
+ * @throws Error if called after the component has finished initializing
949
963
  * @example
950
964
  * const div = xnew.nest('<div>')
951
965
  * div.textContent = 'Hello'
@@ -958,7 +972,7 @@
958
972
  return Unit.nest(Unit.currentUnit, target);
959
973
  }
960
974
  catch (error) {
961
- console.error('xnew.nest(target: HTMLElement | SVGElement | string): ', error);
975
+ console.error('xnew.nest(target: UnitElement | string): ', error);
962
976
  throw error;
963
977
  }
964
978
  },
@@ -984,17 +998,43 @@
984
998
  throw error;
985
999
  }
986
1000
  },
1001
+ append(parent, ...args) {
1002
+ var _a;
1003
+ try {
1004
+ const snapshot = (_a = parent._.afterSnapshot) !== null && _a !== void 0 ? _a : Unit.snapshot(parent);
1005
+ Unit.scope(snapshot, () => {
1006
+ new Unit(parent, ...args);
1007
+ });
1008
+ }
1009
+ catch (error) {
1010
+ console.error('xnew.append(parent: Unit, ...args: UnitArgs): ', error);
1011
+ throw error;
1012
+ }
1013
+ },
1014
+ next(unit, ...args) {
1015
+ var _a;
1016
+ try {
1017
+ const parent = unit._.parent;
1018
+ const snapshot = (_a = parent._.afterSnapshot) !== null && _a !== void 0 ? _a : Unit.snapshot(parent);
1019
+ Unit.scope(snapshot, () => {
1020
+ new Unit(parent, ...args);
1021
+ });
1022
+ }
1023
+ catch (error) {
1024
+ console.error('xnew.next(unit: Unit, ...args: UnitArgs): ', error);
1025
+ throw error;
1026
+ }
1027
+ },
987
1028
  /**
988
- * Gets a context value that can be accessed in follow context
989
- * @param key - component function
990
- * @returns The context value
1029
+ * Gets the Unit instance associated with the given component in the ancestor context chain
1030
+ * @param key - component function used as context key
1031
+ * @returns The Unit instance registered with the given component, or undefined if not found
991
1032
  * @example
992
- * // Create unit
993
- * const a = xnew(A);
994
- * ------------------------------
1033
+ * // Create parent unit with component A
1034
+ * const parent = xnew(A);
995
1035
  *
996
- * // Get context in child
997
- * const a = xnew.context(A)
1036
+ * // Inside a child component, get the parent unit
1037
+ * const parentUnit = xnew.context(A)
998
1038
  */
999
1039
  context(key) {
1000
1040
  try {
@@ -1007,24 +1047,22 @@
1007
1047
  },
1008
1048
  /**
1009
1049
  * Registers a promise with the current component for lifecycle management
1010
- * @param promise - Promise to register
1050
+ * @param promise - A Promise, async function, or Unit to register
1011
1051
  * @returns UnitPromise wrapper for chaining
1012
1052
  * @example
1013
1053
  * xnew.promise(fetchData()).then(data => console.log(data))
1014
1054
  */
1015
1055
  promise(promise) {
1016
1056
  try {
1017
- const Component = Unit.currentUnit._.currentComponent;
1018
1057
  let unitPromise;
1019
1058
  if (promise instanceof Unit) {
1020
- unitPromise = new UnitPromise(Promise.all(promise._.promises.map(p => p.promise)), Component)
1021
- .then(() => promise._.results);
1059
+ unitPromise = UnitPromise.all(promise._.promises).then(() => promise._.results);
1022
1060
  }
1023
1061
  else if (promise instanceof Promise) {
1024
- unitPromise = new UnitPromise(promise, Component);
1062
+ unitPromise = new UnitPromise(promise);
1025
1063
  }
1026
1064
  else {
1027
- unitPromise = new UnitPromise(new Promise(xnew$1.scope(promise)), Component);
1065
+ unitPromise = new UnitPromise(new Promise(xnew$1.scope(promise)));
1028
1066
  }
1029
1067
  Unit.currentUnit._.promises.push(unitPromise);
1030
1068
  return unitPromise;
@@ -1044,8 +1082,7 @@
1044
1082
  then(callback) {
1045
1083
  try {
1046
1084
  const currentUnit = Unit.currentUnit;
1047
- return new UnitPromise(Promise.all(Unit.currentUnit._.promises.map(p => p.promise)), null)
1048
- .then(() => callback(currentUnit._.results));
1085
+ return UnitPromise.all(Unit.currentUnit._.promises).then(() => callback(currentUnit._.results));
1049
1086
  }
1050
1087
  catch (error) {
1051
1088
  console.error('xnew.then(callback: Function): ', error);
@@ -1061,7 +1098,7 @@
1061
1098
  */
1062
1099
  catch(callback) {
1063
1100
  try {
1064
- return new UnitPromise(Promise.all(Unit.currentUnit._.promises.map(p => p.promise)), null)
1101
+ return UnitPromise.all(Unit.currentUnit._.promises)
1065
1102
  .catch(callback);
1066
1103
  }
1067
1104
  catch (error) {
@@ -1070,35 +1107,67 @@
1070
1107
  }
1071
1108
  },
1072
1109
  /**
1073
- * Commits a value to the current unit's promise results
1074
- * @param object - object to commit to the promise
1075
- * @returns void
1110
+ * Executes callback after all registered promises settle (resolve or reject)
1111
+ * @param callback - Function to call after promises settle
1112
+ * @returns UnitPromise for chaining
1076
1113
  * @example
1077
- * xnew.commit({ data: 123});
1114
+ * xnew.finally(() => console.log('All promises settled'))
1078
1115
  */
1079
- commit(object) {
1116
+ finally(callback) {
1080
1117
  try {
1081
- Object.assign(Unit.currentUnit._.results, object);
1118
+ return UnitPromise.all(Unit.currentUnit._.promises).finally(callback);
1082
1119
  }
1083
1120
  catch (error) {
1084
- console.error('xnew.commit(object?: Record<string, any>): ', error);
1121
+ console.error('xnew.finally(callback: Function): ', error);
1085
1122
  throw error;
1086
1123
  }
1087
1124
  },
1125
+ resolvers() {
1126
+ let state = null;
1127
+ let resolve = null;
1128
+ let reject = null;
1129
+ const unitPromise = new UnitPromise(new Promise((res, rej) => {
1130
+ if (state === 'resolved') {
1131
+ res(null);
1132
+ }
1133
+ else if (state === 'rejected') {
1134
+ rej();
1135
+ }
1136
+ else {
1137
+ resolve = res;
1138
+ reject = rej;
1139
+ state = 'pending';
1140
+ }
1141
+ }));
1142
+ Unit.currentUnit._.promises.push(unitPromise);
1143
+ return {
1144
+ resolve() {
1145
+ if (state === 'pending') {
1146
+ resolve === null || resolve === void 0 ? void 0 : resolve(null);
1147
+ }
1148
+ state = 'resolved';
1149
+ },
1150
+ reject() {
1151
+ if (state === 'pending') {
1152
+ reject === null || reject === void 0 ? void 0 : reject();
1153
+ }
1154
+ state = 'rejected';
1155
+ }
1156
+ };
1157
+ },
1088
1158
  /**
1089
- * Executes callback after all registered promises settle (resolve or reject)
1090
- * @param callback - Function to call after promises settle
1091
- * @returns UnitPromise for chaining
1159
+ * Outputs a value to the current unit's promise results
1160
+ * @param object - object to output for the promise
1161
+ * @returns void
1092
1162
  * @example
1093
- * xnew.finally(() => console.log('All promises settled'))
1163
+ * xnew.output({ data: 123});
1094
1164
  */
1095
- finally(callback) {
1165
+ output(object) {
1096
1166
  try {
1097
- return new UnitPromise(Promise.all(Unit.currentUnit._.promises.map(p => p.promise)), null)
1098
- .finally(callback);
1167
+ Object.assign(Unit.currentUnit._.results, object);
1099
1168
  }
1100
1169
  catch (error) {
1101
- console.error('xnew.finally(callback: Function): ', error);
1170
+ console.error('xnew.output(object?: Record<string, any>): ', error);
1102
1171
  throw error;
1103
1172
  }
1104
1173
  },
@@ -1136,7 +1205,7 @@
1136
1205
  /**
1137
1206
  * Emits a custom event to components
1138
1207
  * @param type - Event type to emit (prefix with '+' for global events, '-' for local events)
1139
- * @param args - Additional arguments to pass to event listeners
1208
+ * @param props - Event properties object to pass to listeners
1140
1209
  * @returns void
1141
1210
  * @example
1142
1211
  * xnew.emit('+globalevent', { data: 123 }); // Global event
@@ -1153,7 +1222,7 @@
1153
1222
  },
1154
1223
  /**
1155
1224
  * Executes a callback once after a delay, managed by component lifecycle
1156
- * @param callback - Function to execute after Duration
1225
+ * @param callback - Function to execute after duration
1157
1226
  * @param duration - Duration in milliseconds
1158
1227
  * @returns Object with clear() method to cancel the timeout
1159
1228
  * @example
@@ -1275,7 +1344,7 @@
1275
1344
  }
1276
1345
  const canvas = xnew$1(`<canvas width="${width}" height="${height}" style="width: 100%; height: 100%; vertical-align: bottom;">`);
1277
1346
  return {
1278
- get canvas() { return canvas.element; }
1347
+ get canvas() { return canvas.element; },
1279
1348
  };
1280
1349
  }
1281
1350
 
@@ -1561,24 +1630,46 @@
1561
1630
  }
1562
1631
  }
1563
1632
 
1564
- function Flow(unit) {
1565
- let scene = null;
1633
+ function Scene(unit) {
1566
1634
  return {
1567
- set scene(value) {
1568
- scene = value;
1635
+ moveTo(Component, props) {
1636
+ xnew$1.next(unit, Component, props);
1637
+ unit.finalize();
1569
1638
  },
1570
- get scene() {
1571
- return scene;
1572
- },
1573
- next(Component, props) {
1574
- var _a;
1575
- // scene change
1576
- (_a = unit.scene) === null || _a === void 0 ? void 0 : _a.finalize();
1577
- unit.scene = xnew$1(Component, props);
1639
+ append(Component, props) {
1640
+ xnew$1.append(unit, Component, props);
1578
1641
  }
1579
1642
  };
1580
1643
  }
1581
1644
 
1645
+ class XImage {
1646
+ constructor(...args) {
1647
+ if (args[0] instanceof HTMLCanvasElement) {
1648
+ this.canvas = args[0];
1649
+ }
1650
+ else {
1651
+ const canvas = document.createElement('canvas');
1652
+ canvas.width = args[0];
1653
+ canvas.height = args[1];
1654
+ this.canvas = canvas;
1655
+ }
1656
+ }
1657
+ crop(x, y, width, height) {
1658
+ var _a;
1659
+ const canvas = document.createElement('canvas');
1660
+ canvas.width = width;
1661
+ canvas.height = height;
1662
+ (_a = canvas.getContext('2d')) === null || _a === void 0 ? void 0 : _a.drawImage(this.canvas, x, y, width, height, 0, 0, width, height);
1663
+ return new XImage(canvas);
1664
+ }
1665
+ download(filename) {
1666
+ const link = document.createElement('a');
1667
+ link.download = filename;
1668
+ link.href = this.canvas.toDataURL('image/png');
1669
+ link.click();
1670
+ }
1671
+ }
1672
+
1582
1673
  const context = new window.AudioContext();
1583
1674
  const master = context.createGain();
1584
1675
  //----------------------------------------------------------------------------------------------------
@@ -1811,7 +1902,7 @@
1811
1902
  Panel,
1812
1903
  Accordion,
1813
1904
  Popup,
1814
- Flow,
1905
+ Scene,
1815
1906
  };
1816
1907
  const audio = {
1817
1908
  load(path) {
@@ -1840,7 +1931,12 @@
1840
1931
  master.gain.value = value;
1841
1932
  }
1842
1933
  };
1843
- const xnew = Object.assign(xnew$1, { basics, audio });
1934
+ const image = {
1935
+ from(canvas) {
1936
+ return new XImage(canvas);
1937
+ }
1938
+ };
1939
+ const xnew = Object.assign(xnew$1, { basics, audio, image });
1844
1940
 
1845
1941
  return xnew;
1846
1942
 
package/dist/xnew.mjs CHANGED
@@ -186,7 +186,7 @@ class Timer {
186
186
  this.id = null;
187
187
  this.time = { start: 0.0, processed: 0.0 };
188
188
  (_b = (_a = this.options).transition) === null || _b === void 0 ? void 0 : _b.call(_a, 1.0);
189
- (_d = (_c = this.options).callback) === null || _d === void 0 ? void 0 : _d.call(_c);
189
+ (_d = (_c = this.options).timeout) === null || _d === void 0 ? void 0 : _d.call(_c);
190
190
  this.clear();
191
191
  }, this.options.duration - this.time.processed);
192
192
  this.time.start = Date.now();
@@ -513,16 +513,26 @@ function pointer(element, event) {
513
513
  return { position };
514
514
  }
515
515
 
516
- //----------------------------------------------------------------------------------------------------
517
- // utils
518
- //----------------------------------------------------------------------------------------------------
519
516
  const SYSTEM_EVENTS = ['start', 'update', 'render', 'stop', 'finalize'];
520
517
  //----------------------------------------------------------------------------------------------------
521
518
  // unit
522
519
  //----------------------------------------------------------------------------------------------------
523
520
  class Unit {
524
- constructor(parent, target, Component, props) {
521
+ constructor(parent, ...args) {
525
522
  var _a;
523
+ let target;
524
+ let Component;
525
+ let props;
526
+ if (args[0] instanceof HTMLElement || args[0] instanceof SVGElement || typeof args[0] === 'string') {
527
+ target = args[0];
528
+ Component = args[1];
529
+ props = args[2];
530
+ }
531
+ else {
532
+ target = null;
533
+ Component = args[0];
534
+ props = args[1];
535
+ }
526
536
  const backup = Unit.currentUnit;
527
537
  Unit.currentUnit = this;
528
538
  parent === null || parent === void 0 ? void 0 : parent._.children.push(this);
@@ -555,6 +565,7 @@ class Unit {
555
565
  currentElement: baseElement,
556
566
  currentContext: baseContext,
557
567
  currentComponent: null,
568
+ afterSnapshot: null,
558
569
  ancestors: parent ? [parent, ...parent._.ancestors] : [],
559
570
  children: [],
560
571
  nestElements: [],
@@ -575,6 +586,7 @@ class Unit {
575
586
  if (this._.state === 'invoked') {
576
587
  this._.state = 'initialized';
577
588
  }
589
+ this._.afterSnapshot = Unit.snapshot(this);
578
590
  Unit.currentUnit = backup;
579
591
  }
580
592
  get element() {
@@ -644,7 +656,7 @@ class Unit {
644
656
  return element;
645
657
  }
646
658
  else {
647
- throw new Error(`xnew.nest: invalid html string [${target}]`);
659
+ throw new Error(`xnew.nest: invalid tag string [${target}]`);
648
660
  }
649
661
  }
650
662
  }
@@ -686,7 +698,7 @@ class Unit {
686
698
  Object.defineProperty(unit._.defines, key, wrapper);
687
699
  Object.defineProperty(unit, key, wrapper);
688
700
  });
689
- return defines;
701
+ return Object.assign({}, unit._.defines);
690
702
  }
691
703
  }
692
704
  static start(unit) {
@@ -723,7 +735,7 @@ class Unit {
723
735
  static reset() {
724
736
  var _a;
725
737
  (_a = Unit.rootUnit) === null || _a === void 0 ? void 0 : _a.finalize();
726
- Unit.currentUnit = Unit.rootUnit = new Unit(null, null);
738
+ Unit.currentUnit = Unit.rootUnit = new Unit(null);
727
739
  const ticker = new AnimationTicker(() => {
728
740
  Unit.start(Unit.rootUnit);
729
741
  Unit.update(Unit.rootUnit);
@@ -763,7 +775,7 @@ class Unit {
763
775
  }
764
776
  static getContext(unit, key) {
765
777
  for (let context = unit._.currentContext; context.previous !== null; context = context.previous) {
766
- if (context.value === Unit.currentUnit && key === Unit.currentUnit._.currentComponent)
778
+ if (context.value === Unit.currentUnit)
767
779
  continue;
768
780
  if (context.key === key)
769
781
  return context.value;
@@ -842,13 +854,13 @@ Unit.type2units = new MapSet();
842
854
  // extensions
843
855
  //----------------------------------------------------------------------------------------------------
844
856
  class UnitPromise {
845
- constructor(promise, Component) {
846
- this.promise = promise;
847
- this.Component = Component;
848
- }
857
+ constructor(promise) { this.promise = promise; }
849
858
  then(callback) { return this.wrap('then', callback); }
850
859
  catch(callback) { return this.wrap('catch', callback); }
851
860
  finally(callback) { return this.wrap('finally', callback); }
861
+ static all(promises) {
862
+ return new UnitPromise(Promise.all(promises.map(p => p.promise)));
863
+ }
852
864
  wrap(key, callback) {
853
865
  const snapshot = Unit.snapshot(Unit.currentUnit);
854
866
  this.promise = this.promise[key]((...args) => Unit.scope(snapshot, callback, ...args));
@@ -866,11 +878,11 @@ class UnitTimer {
866
878
  (_a = this.unit) === null || _a === void 0 ? void 0 : _a.finalize();
867
879
  this.unit = null;
868
880
  }
869
- timeout(callback, duration = 0) {
870
- return UnitTimer.execute(this, { callback, duration }, 1);
881
+ timeout(timeout, duration = 0) {
882
+ return UnitTimer.execute(this, { timeout, duration }, 1);
871
883
  }
872
- interval(callback, duration = 0, iterations = 0) {
873
- return UnitTimer.execute(this, { callback, duration }, iterations);
884
+ interval(timeout, duration = 0, iterations = 0) {
885
+ return UnitTimer.execute(this, { timeout, duration }, iterations);
874
886
  }
875
887
  transition(transition, duration = 0, easing) {
876
888
  return UnitTimer.execute(this, { transition, duration, easing }, 1);
@@ -878,7 +890,7 @@ class UnitTimer {
878
890
  static execute(timer, options, iterations) {
879
891
  const props = { options, iterations, snapshot: Unit.snapshot(Unit.currentUnit) };
880
892
  if (timer.unit === null || timer.unit._.state === 'finalized') {
881
- timer.unit = new Unit(Unit.currentUnit, null, UnitTimer.Component, props);
893
+ timer.unit = new Unit(Unit.currentUnit, UnitTimer.Component, props);
882
894
  }
883
895
  else if (timer.queue.length === 0) {
884
896
  timer.queue.push(props);
@@ -891,18 +903,18 @@ class UnitTimer {
891
903
  }
892
904
  static next(timer) {
893
905
  if (timer.queue.length > 0) {
894
- timer.unit = new Unit(Unit.currentUnit, null, UnitTimer.Component, timer.queue.shift());
906
+ timer.unit = new Unit(Unit.currentUnit, UnitTimer.Component, timer.queue.shift());
895
907
  timer.unit.on('finalize', () => UnitTimer.next(timer));
896
908
  }
897
909
  }
898
910
  static Component(unit, { options, iterations, snapshot }) {
899
911
  let counter = 0;
900
- let timer = new Timer({ callback, transition, duration: options.duration, easing: options.easing });
901
- function callback() {
902
- if (options.callback)
903
- Unit.scope(snapshot, options.callback);
912
+ let timer = new Timer({ timeout, transition, duration: options.duration, easing: options.easing });
913
+ function timeout() {
914
+ if (options.timeout)
915
+ Unit.scope(snapshot, options.timeout);
904
916
  if (iterations <= 0 || counter < iterations - 1) {
905
- timer = new Timer({ callback, transition, duration: options.duration, easing: options.easing });
917
+ timer = new Timer({ timeout, transition, duration: options.duration, easing: options.easing });
906
918
  }
907
919
  else {
908
920
  unit.finalize();
@@ -917,29 +929,31 @@ class UnitTimer {
917
929
  }
918
930
  }
919
931
 
920
- const xnew$1 = Object.assign(function (...args) {
932
+ const xnew$1 = Object.assign(
933
+ /**
934
+ * creates a new Unit component
935
+ * xnew(Component?: Function | string, props?: Object): Unit;
936
+ * xnew(target: HTMLElement | SVGElement | string, Component?: Function | string, props?: Object): Unit;
937
+ * @param target - HTMLElement | SVGElement, or HTML tag for new element
938
+ * @param Component - component function
939
+ * @param props - properties for component function
940
+ * @returns a new Unit instance
941
+ * @example
942
+ * const unit = xnew(MyComponent, { data: 0 })
943
+ * const unit = xnew(element, MyComponent, { data: 0 })
944
+ * const unit = xnew('<div>', MyComponent, { data: 0 })
945
+ */
946
+ function (...args) {
921
947
  if (Unit.rootUnit === undefined)
922
948
  Unit.reset();
923
- let target;
924
- if (args[0] instanceof HTMLElement || args[0] instanceof SVGElement) {
925
- target = args.shift(); // an existing html element
926
- }
927
- else if (typeof args[0] === 'string' && args[0].match(/<((\w+)[^>]*?)\/?>/)) {
928
- target = args.shift();
929
- }
930
- else {
931
- target = null;
932
- }
933
- const Component = args.shift();
934
- const props = args.shift();
935
- const unit = new Unit(Unit.currentUnit, target, Component, props);
936
- return unit;
949
+ return new Unit(Unit.currentUnit, ...args);
937
950
  }, {
938
951
  /**
939
- * Creates a nested HTML/SVG element within the current component
940
- * @param target - HTML or SVG tag string (e.g., '<div class="my-class">', '<span style="color:red">', '<svg viewBox="0 0 24 24">')
941
- * @returns The created HTML/SVG element
942
- * @throws Error if called after component initialization
952
+ * Creates a child HTML/SVG element inside the current component's element.
953
+ * Must be called during component initialization (before setup completes).
954
+ * @param target - An existing HTML/SVG element, or a tag string like `'<div>'`
955
+ * @returns The provided element, or the newly created element
956
+ * @throws Error if called after the component has finished initializing
943
957
  * @example
944
958
  * const div = xnew.nest('<div>')
945
959
  * div.textContent = 'Hello'
@@ -952,7 +966,7 @@ const xnew$1 = Object.assign(function (...args) {
952
966
  return Unit.nest(Unit.currentUnit, target);
953
967
  }
954
968
  catch (error) {
955
- console.error('xnew.nest(target: HTMLElement | SVGElement | string): ', error);
969
+ console.error('xnew.nest(target: UnitElement | string): ', error);
956
970
  throw error;
957
971
  }
958
972
  },
@@ -978,17 +992,43 @@ const xnew$1 = Object.assign(function (...args) {
978
992
  throw error;
979
993
  }
980
994
  },
995
+ append(parent, ...args) {
996
+ var _a;
997
+ try {
998
+ const snapshot = (_a = parent._.afterSnapshot) !== null && _a !== void 0 ? _a : Unit.snapshot(parent);
999
+ Unit.scope(snapshot, () => {
1000
+ new Unit(parent, ...args);
1001
+ });
1002
+ }
1003
+ catch (error) {
1004
+ console.error('xnew.append(parent: Unit, ...args: UnitArgs): ', error);
1005
+ throw error;
1006
+ }
1007
+ },
1008
+ next(unit, ...args) {
1009
+ var _a;
1010
+ try {
1011
+ const parent = unit._.parent;
1012
+ const snapshot = (_a = parent._.afterSnapshot) !== null && _a !== void 0 ? _a : Unit.snapshot(parent);
1013
+ Unit.scope(snapshot, () => {
1014
+ new Unit(parent, ...args);
1015
+ });
1016
+ }
1017
+ catch (error) {
1018
+ console.error('xnew.next(unit: Unit, ...args: UnitArgs): ', error);
1019
+ throw error;
1020
+ }
1021
+ },
981
1022
  /**
982
- * Gets a context value that can be accessed in follow context
983
- * @param key - component function
984
- * @returns The context value
1023
+ * Gets the Unit instance associated with the given component in the ancestor context chain
1024
+ * @param key - component function used as context key
1025
+ * @returns The Unit instance registered with the given component, or undefined if not found
985
1026
  * @example
986
- * // Create unit
987
- * const a = xnew(A);
988
- * ------------------------------
1027
+ * // Create parent unit with component A
1028
+ * const parent = xnew(A);
989
1029
  *
990
- * // Get context in child
991
- * const a = xnew.context(A)
1030
+ * // Inside a child component, get the parent unit
1031
+ * const parentUnit = xnew.context(A)
992
1032
  */
993
1033
  context(key) {
994
1034
  try {
@@ -1001,24 +1041,22 @@ const xnew$1 = Object.assign(function (...args) {
1001
1041
  },
1002
1042
  /**
1003
1043
  * Registers a promise with the current component for lifecycle management
1004
- * @param promise - Promise to register
1044
+ * @param promise - A Promise, async function, or Unit to register
1005
1045
  * @returns UnitPromise wrapper for chaining
1006
1046
  * @example
1007
1047
  * xnew.promise(fetchData()).then(data => console.log(data))
1008
1048
  */
1009
1049
  promise(promise) {
1010
1050
  try {
1011
- const Component = Unit.currentUnit._.currentComponent;
1012
1051
  let unitPromise;
1013
1052
  if (promise instanceof Unit) {
1014
- unitPromise = new UnitPromise(Promise.all(promise._.promises.map(p => p.promise)), Component)
1015
- .then(() => promise._.results);
1053
+ unitPromise = UnitPromise.all(promise._.promises).then(() => promise._.results);
1016
1054
  }
1017
1055
  else if (promise instanceof Promise) {
1018
- unitPromise = new UnitPromise(promise, Component);
1056
+ unitPromise = new UnitPromise(promise);
1019
1057
  }
1020
1058
  else {
1021
- unitPromise = new UnitPromise(new Promise(xnew$1.scope(promise)), Component);
1059
+ unitPromise = new UnitPromise(new Promise(xnew$1.scope(promise)));
1022
1060
  }
1023
1061
  Unit.currentUnit._.promises.push(unitPromise);
1024
1062
  return unitPromise;
@@ -1038,8 +1076,7 @@ const xnew$1 = Object.assign(function (...args) {
1038
1076
  then(callback) {
1039
1077
  try {
1040
1078
  const currentUnit = Unit.currentUnit;
1041
- return new UnitPromise(Promise.all(Unit.currentUnit._.promises.map(p => p.promise)), null)
1042
- .then(() => callback(currentUnit._.results));
1079
+ return UnitPromise.all(Unit.currentUnit._.promises).then(() => callback(currentUnit._.results));
1043
1080
  }
1044
1081
  catch (error) {
1045
1082
  console.error('xnew.then(callback: Function): ', error);
@@ -1055,7 +1092,7 @@ const xnew$1 = Object.assign(function (...args) {
1055
1092
  */
1056
1093
  catch(callback) {
1057
1094
  try {
1058
- return new UnitPromise(Promise.all(Unit.currentUnit._.promises.map(p => p.promise)), null)
1095
+ return UnitPromise.all(Unit.currentUnit._.promises)
1059
1096
  .catch(callback);
1060
1097
  }
1061
1098
  catch (error) {
@@ -1064,35 +1101,67 @@ const xnew$1 = Object.assign(function (...args) {
1064
1101
  }
1065
1102
  },
1066
1103
  /**
1067
- * Commits a value to the current unit's promise results
1068
- * @param object - object to commit to the promise
1069
- * @returns void
1104
+ * Executes callback after all registered promises settle (resolve or reject)
1105
+ * @param callback - Function to call after promises settle
1106
+ * @returns UnitPromise for chaining
1070
1107
  * @example
1071
- * xnew.commit({ data: 123});
1108
+ * xnew.finally(() => console.log('All promises settled'))
1072
1109
  */
1073
- commit(object) {
1110
+ finally(callback) {
1074
1111
  try {
1075
- Object.assign(Unit.currentUnit._.results, object);
1112
+ return UnitPromise.all(Unit.currentUnit._.promises).finally(callback);
1076
1113
  }
1077
1114
  catch (error) {
1078
- console.error('xnew.commit(object?: Record<string, any>): ', error);
1115
+ console.error('xnew.finally(callback: Function): ', error);
1079
1116
  throw error;
1080
1117
  }
1081
1118
  },
1119
+ resolvers() {
1120
+ let state = null;
1121
+ let resolve = null;
1122
+ let reject = null;
1123
+ const unitPromise = new UnitPromise(new Promise((res, rej) => {
1124
+ if (state === 'resolved') {
1125
+ res(null);
1126
+ }
1127
+ else if (state === 'rejected') {
1128
+ rej();
1129
+ }
1130
+ else {
1131
+ resolve = res;
1132
+ reject = rej;
1133
+ state = 'pending';
1134
+ }
1135
+ }));
1136
+ Unit.currentUnit._.promises.push(unitPromise);
1137
+ return {
1138
+ resolve() {
1139
+ if (state === 'pending') {
1140
+ resolve === null || resolve === void 0 ? void 0 : resolve(null);
1141
+ }
1142
+ state = 'resolved';
1143
+ },
1144
+ reject() {
1145
+ if (state === 'pending') {
1146
+ reject === null || reject === void 0 ? void 0 : reject();
1147
+ }
1148
+ state = 'rejected';
1149
+ }
1150
+ };
1151
+ },
1082
1152
  /**
1083
- * Executes callback after all registered promises settle (resolve or reject)
1084
- * @param callback - Function to call after promises settle
1085
- * @returns UnitPromise for chaining
1153
+ * Outputs a value to the current unit's promise results
1154
+ * @param object - object to output for the promise
1155
+ * @returns void
1086
1156
  * @example
1087
- * xnew.finally(() => console.log('All promises settled'))
1157
+ * xnew.output({ data: 123});
1088
1158
  */
1089
- finally(callback) {
1159
+ output(object) {
1090
1160
  try {
1091
- return new UnitPromise(Promise.all(Unit.currentUnit._.promises.map(p => p.promise)), null)
1092
- .finally(callback);
1161
+ Object.assign(Unit.currentUnit._.results, object);
1093
1162
  }
1094
1163
  catch (error) {
1095
- console.error('xnew.finally(callback: Function): ', error);
1164
+ console.error('xnew.output(object?: Record<string, any>): ', error);
1096
1165
  throw error;
1097
1166
  }
1098
1167
  },
@@ -1130,7 +1199,7 @@ const xnew$1 = Object.assign(function (...args) {
1130
1199
  /**
1131
1200
  * Emits a custom event to components
1132
1201
  * @param type - Event type to emit (prefix with '+' for global events, '-' for local events)
1133
- * @param args - Additional arguments to pass to event listeners
1202
+ * @param props - Event properties object to pass to listeners
1134
1203
  * @returns void
1135
1204
  * @example
1136
1205
  * xnew.emit('+globalevent', { data: 123 }); // Global event
@@ -1147,7 +1216,7 @@ const xnew$1 = Object.assign(function (...args) {
1147
1216
  },
1148
1217
  /**
1149
1218
  * Executes a callback once after a delay, managed by component lifecycle
1150
- * @param callback - Function to execute after Duration
1219
+ * @param callback - Function to execute after duration
1151
1220
  * @param duration - Duration in milliseconds
1152
1221
  * @returns Object with clear() method to cancel the timeout
1153
1222
  * @example
@@ -1269,7 +1338,7 @@ function Screen(unit, { width = 800, height = 600, fit = 'contain' } = {}) {
1269
1338
  }
1270
1339
  const canvas = xnew$1(`<canvas width="${width}" height="${height}" style="width: 100%; height: 100%; vertical-align: bottom;">`);
1271
1340
  return {
1272
- get canvas() { return canvas.element; }
1341
+ get canvas() { return canvas.element; },
1273
1342
  };
1274
1343
  }
1275
1344
 
@@ -1555,24 +1624,46 @@ function Select(_, { key = '', value, items = [] } = {}) {
1555
1624
  }
1556
1625
  }
1557
1626
 
1558
- function Flow(unit) {
1559
- let scene = null;
1627
+ function Scene(unit) {
1560
1628
  return {
1561
- set scene(value) {
1562
- scene = value;
1629
+ moveTo(Component, props) {
1630
+ xnew$1.next(unit, Component, props);
1631
+ unit.finalize();
1563
1632
  },
1564
- get scene() {
1565
- return scene;
1566
- },
1567
- next(Component, props) {
1568
- var _a;
1569
- // scene change
1570
- (_a = unit.scene) === null || _a === void 0 ? void 0 : _a.finalize();
1571
- unit.scene = xnew$1(Component, props);
1633
+ append(Component, props) {
1634
+ xnew$1.append(unit, Component, props);
1572
1635
  }
1573
1636
  };
1574
1637
  }
1575
1638
 
1639
+ class XImage {
1640
+ constructor(...args) {
1641
+ if (args[0] instanceof HTMLCanvasElement) {
1642
+ this.canvas = args[0];
1643
+ }
1644
+ else {
1645
+ const canvas = document.createElement('canvas');
1646
+ canvas.width = args[0];
1647
+ canvas.height = args[1];
1648
+ this.canvas = canvas;
1649
+ }
1650
+ }
1651
+ crop(x, y, width, height) {
1652
+ var _a;
1653
+ const canvas = document.createElement('canvas');
1654
+ canvas.width = width;
1655
+ canvas.height = height;
1656
+ (_a = canvas.getContext('2d')) === null || _a === void 0 ? void 0 : _a.drawImage(this.canvas, x, y, width, height, 0, 0, width, height);
1657
+ return new XImage(canvas);
1658
+ }
1659
+ download(filename) {
1660
+ const link = document.createElement('a');
1661
+ link.download = filename;
1662
+ link.href = this.canvas.toDataURL('image/png');
1663
+ link.click();
1664
+ }
1665
+ }
1666
+
1576
1667
  const context = new window.AudioContext();
1577
1668
  const master = context.createGain();
1578
1669
  //----------------------------------------------------------------------------------------------------
@@ -1805,7 +1896,7 @@ const basics = {
1805
1896
  Panel,
1806
1897
  Accordion,
1807
1898
  Popup,
1808
- Flow,
1899
+ Scene,
1809
1900
  };
1810
1901
  const audio = {
1811
1902
  load(path) {
@@ -1834,6 +1925,11 @@ const audio = {
1834
1925
  master.gain.value = value;
1835
1926
  }
1836
1927
  };
1837
- const xnew = Object.assign(xnew$1, { basics, audio });
1928
+ const image = {
1929
+ from(canvas) {
1930
+ return new XImage(canvas);
1931
+ }
1932
+ };
1933
+ const xnew = Object.assign(xnew$1, { basics, audio, image });
1838
1934
 
1839
1935
  export { xnew as default };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "keywords": [
5
5
  "Component-Oriented Programming"
6
6
  ],
7
- "version": "0.6.8",
7
+ "version": "0.7.1",
8
8
  "main": "dist/xnew.js",
9
9
  "module": "dist/xnew.mjs",
10
10
  "types": "dist/xnew.d.ts",