@mulsense/xnew 0.1.11 → 0.2.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/dist/xnew.mjs CHANGED
@@ -99,17 +99,16 @@ class MapMap extends Map {
99
99
  // ticker
100
100
  //----------------------------------------------------------------------------------------------------
101
101
  class Ticker {
102
- constructor(callback) {
102
+ constructor(callback, fps = 60) {
103
103
  const self = this;
104
104
  this.id = null;
105
105
  let previous = 0;
106
106
  ticker();
107
107
  function ticker() {
108
- const time = Date.now();
109
- const fps = 60;
110
- if (time - previous > (1000 / fps) * 0.9) {
111
- callback(time);
112
- previous = time;
108
+ const delta = Date.now() - previous;
109
+ if (delta > (1000 / fps) * 0.9) {
110
+ callback();
111
+ previous += delta;
113
112
  }
114
113
  self.id = requestAnimationFrame(ticker);
115
114
  }
@@ -121,43 +120,35 @@ class Ticker {
121
120
  }
122
121
  }
123
122
  }
124
- //----------------------------------------------------------------------------------------------------
125
- // timer
126
- //----------------------------------------------------------------------------------------------------
127
123
  class Timer {
128
- constructor(transition, timeout, duration, { loop = false, easing = 'linear' } = {}) {
124
+ constructor(options) {
129
125
  var _a;
130
- this.transition = transition;
131
- this.timeout = timeout;
132
- this.duration = duration !== null && duration !== void 0 ? duration : 0;
133
- this.loop = loop;
134
- this.easing = easing;
126
+ this.options = options;
127
+ this.options.easing = (_a = this.options.easing) !== null && _a !== void 0 ? _a : 'linear';
135
128
  this.id = null;
136
129
  this.time = 0.0;
130
+ this.counter = 0;
137
131
  this.offset = 0.0;
138
132
  this.status = 0;
139
133
  this.ticker = new Ticker((time) => {
140
- var _a;
141
- let p = Math.min(this.elapsed() / this.duration, 1.0);
142
- if (easing === 'ease-out') {
134
+ var _a, _b;
135
+ let p = Math.min(this.elapsed() / this.options.duration, 1.0);
136
+ if (this.options.easing === 'ease-out') {
143
137
  p = Math.pow((1.0 - Math.pow((1.0 - p), 2.0)), 0.5);
144
138
  }
145
- else if (easing === 'ease-in') {
139
+ else if (this.options.easing === 'ease-in') {
146
140
  p = Math.pow((1.0 - Math.pow((1.0 - p), 0.5)), 2.0);
147
141
  }
148
- else if (easing === 'ease') {
149
- p = (1.0 - Math.cos(p * Math.PI)) / 2.0;
142
+ else if (this.options.easing === 'ease') {
143
+ p = (1.0 - Math.cos(p * Math.PI)) / 2.0; // todo
150
144
  }
151
- else if (easing === 'ease-in-out') {
145
+ else if (this.options.easing === 'ease-in-out') {
152
146
  p = (1.0 - Math.cos(p * Math.PI)) / 2.0;
153
147
  }
154
- (_a = this.transition) === null || _a === void 0 ? void 0 : _a.call(this, p);
148
+ (_b = (_a = this.options).transition) === null || _b === void 0 ? void 0 : _b.call(_a, p);
155
149
  });
156
150
  this.visibilitychange = () => document.hidden === false ? this._start() : this._stop();
157
151
  document.addEventListener('visibilitychange', this.visibilitychange);
158
- if (this.duration > 0.0) {
159
- (_a = this.transition) === null || _a === void 0 ? void 0 : _a.call(this, 0.0);
160
- }
161
152
  this.start();
162
153
  }
163
154
  clear() {
@@ -182,14 +173,24 @@ class Timer {
182
173
  _start() {
183
174
  if (this.status === 1 && this.id === null) {
184
175
  this.id = setTimeout(() => {
185
- var _a, _b;
186
- (_a = this.timeout) === null || _a === void 0 ? void 0 : _a.call(this);
187
- (_b = this.transition) === null || _b === void 0 ? void 0 : _b.call(this, 1.0);
176
+ var _a, _b, _c, _d;
177
+ (_b = (_a = this.options).transition) === null || _b === void 0 ? void 0 : _b.call(_a, 1.0);
178
+ (_d = (_c = this.options).timeout) === null || _d === void 0 ? void 0 : _d.call(_c, this.counter);
188
179
  this.id = null;
189
180
  this.time = 0.0;
181
+ this.counter++;
190
182
  this.offset = 0.0;
191
- this.loop ? this.start() : this.clear();
192
- }, this.duration - this.offset);
183
+ if (this.options.iterations === undefined) {
184
+ this.start();
185
+ }
186
+ else if (this.options.iterations > 1) {
187
+ this.options.iterations--;
188
+ this.start();
189
+ }
190
+ else {
191
+ this.clear();
192
+ }
193
+ }, this.options.duration - this.offset);
193
194
  this.time = Date.now();
194
195
  }
195
196
  }
@@ -206,7 +207,7 @@ class Timer {
206
207
  //----------------------------------------------------------------------------------------------------
207
208
  // utils
208
209
  //----------------------------------------------------------------------------------------------------
209
- const SYSTEM_EVENTS = ['start', 'update', 'stop', 'finalize'];
210
+ const SYSTEM_EVENTS = ['-start', '-update', '-stop', '-finalize'];
210
211
  //----------------------------------------------------------------------------------------------------
211
212
  // unit
212
213
  //----------------------------------------------------------------------------------------------------
@@ -299,10 +300,9 @@ class Unit {
299
300
  elements: [],
300
301
  promises: [],
301
302
  components: [],
302
- listeners1: new MapMap(),
303
- listeners2: new MapMap(),
303
+ listeners: new MapMap(),
304
304
  defines: {},
305
- systems: { start: [], update: [], stop: [], finalize: [] },
305
+ systems: { '-start': [], '-update': [], '-stop': [], '-finalize': [] },
306
306
  });
307
307
  // nest html element
308
308
  if (typeof unit._.target === 'string') {
@@ -318,9 +318,8 @@ class Unit {
318
318
  if (unit._.state !== 'finalized') {
319
319
  unit._.state = 'finalized';
320
320
  unit._.children.forEach((child) => child.finalize());
321
- unit._.systems.finalize.forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
321
+ unit._.systems['-finalize'].forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
322
322
  unit.off();
323
- Unit.suboff(unit, null);
324
323
  unit._.components.forEach((component) => Unit.component2units.delete(component, unit));
325
324
  if (unit._.elements.length > 0) {
326
325
  unit._.baseElement.removeChild(unit._.elements[0]);
@@ -389,7 +388,7 @@ class Unit {
389
388
  if (unit._.state === 'initialized' || unit._.state === 'stopped') {
390
389
  unit._.state = 'started';
391
390
  unit._.children.forEach((child) => Unit.start(child));
392
- unit._.systems.start.forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
391
+ unit._.systems['-start'].forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
393
392
  }
394
393
  else if (unit._.state === 'started') {
395
394
  unit._.children.forEach((child) => Unit.start(child));
@@ -399,13 +398,13 @@ class Unit {
399
398
  if (unit._.state === 'started') {
400
399
  unit._.state = 'stopped';
401
400
  unit._.children.forEach((child) => Unit.stop(child));
402
- unit._.systems.stop.forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
401
+ unit._.systems['-stop'].forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
403
402
  }
404
403
  }
405
404
  static update(unit) {
406
405
  if (unit._.state === 'started') {
407
406
  unit._.children.forEach((child) => Unit.update(child));
408
- unit._.systems.update.forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
407
+ unit._.systems['-update'].forEach((listener) => Unit.scope(Unit.snapshot(unit), listener));
409
408
  }
410
409
  }
411
410
  static reset() {
@@ -413,7 +412,7 @@ class Unit {
413
412
  (_a = Unit.root) === null || _a === void 0 ? void 0 : _a.finalize();
414
413
  Unit.current = Unit.root = new Unit(null, null);
415
414
  (_b = Unit.ticker) === null || _b === void 0 ? void 0 : _b.clear();
416
- Unit.ticker = new Ticker((time) => {
415
+ Unit.ticker = new Ticker(() => {
417
416
  Unit.start(Unit.root);
418
417
  Unit.update(Unit.root);
419
418
  });
@@ -463,9 +462,9 @@ class Unit {
463
462
  if (SYSTEM_EVENTS.includes(type)) {
464
463
  this._.systems[type].push(listener);
465
464
  }
466
- if (this._.listeners1.has(type, listener) === false) {
465
+ if (this._.listeners.has(type, listener) === false) {
467
466
  const execute = Unit.wrap(Unit.current, listener);
468
- this._.listeners1.set(type, listener, { element: this.element, execute });
467
+ this._.listeners.set(type, listener, { element: this.element, execute });
469
468
  Unit.type2units.add(type, this);
470
469
  if (/^[A-Za-z]/.test(type)) {
471
470
  this.element.addEventListener(type, execute, options);
@@ -474,21 +473,21 @@ class Unit {
474
473
  });
475
474
  }
476
475
  off(type, listener) {
477
- const types = typeof type === 'string' ? type.trim().split(/\s+/) : [...this._.listeners1.keys()];
476
+ const types = typeof type === 'string' ? type.trim().split(/\s+/) : [...this._.listeners.keys()];
478
477
  types.forEach((type) => {
479
478
  if (SYSTEM_EVENTS.includes(type)) {
480
479
  this._.systems[type] = this._.systems[type].filter((lis) => listener ? lis !== listener : false);
481
480
  }
482
- (listener ? [listener] : [...this._.listeners1.keys(type)]).forEach((listener) => {
483
- const item = this._.listeners1.get(type, listener);
481
+ (listener ? [listener] : [...this._.listeners.keys(type)]).forEach((listener) => {
482
+ const item = this._.listeners.get(type, listener);
484
483
  if (item !== undefined) {
485
- this._.listeners1.delete(type, listener);
484
+ this._.listeners.delete(type, listener);
486
485
  if (/^[A-Za-z]/.test(type)) {
487
486
  item.element.removeEventListener(type, item.execute);
488
487
  }
489
488
  }
490
489
  });
491
- if (this._.listeners1.has(type) === false) {
490
+ if (this._.listeners.has(type) === false) {
492
491
  Unit.type2units.delete(type, this);
493
492
  }
494
493
  });
@@ -498,34 +497,13 @@ class Unit {
498
497
  if (type[0] === '+') {
499
498
  (_a = Unit.type2units.get(type)) === null || _a === void 0 ? void 0 : _a.forEach((unit) => {
500
499
  var _a;
501
- (_a = unit._.listeners1.get(type)) === null || _a === void 0 ? void 0 : _a.forEach((item) => item.execute(...args));
500
+ (_a = unit._.listeners.get(type)) === null || _a === void 0 ? void 0 : _a.forEach((item) => item.execute(...args));
502
501
  });
503
502
  }
504
503
  else if (type[0] === '-') {
505
- (_b = this._.listeners1.get(type)) === null || _b === void 0 ? void 0 : _b.forEach((item) => item.execute(...args));
504
+ (_b = this._.listeners.get(type)) === null || _b === void 0 ? void 0 : _b.forEach((item) => item.execute(...args));
506
505
  }
507
506
  }
508
- static subon(unit, target, type, listener, options) {
509
- type.trim().split(/\s+/).forEach((type) => {
510
- if (unit._.listeners2.has(type, listener) === false) {
511
- const execute = Unit.wrap(unit, listener);
512
- unit._.listeners2.set(type, listener, { element: target, execute });
513
- target.addEventListener(type, execute, options);
514
- }
515
- });
516
- }
517
- static suboff(unit, target, type, listener) {
518
- const types = typeof type === 'string' ? type.trim().split(/\s+/) : [...unit._.listeners2.keys()];
519
- types.forEach((type) => {
520
- (listener ? [listener] : [...unit._.listeners2.keys(type)]).forEach((listener) => {
521
- const item = unit._.listeners2.get(type, listener);
522
- if (item !== undefined && (target === null || target === item.element)) {
523
- unit._.listeners2.delete(type, listener);
524
- item.element.removeEventListener(type, item.execute);
525
- }
526
- });
527
- });
528
- }
529
507
  }
530
508
  Unit.component2units = new MapSet();
531
509
  //----------------------------------------------------------------------------------------------------
@@ -554,54 +532,57 @@ class UnitPromise {
554
532
  // unit timer
555
533
  //----------------------------------------------------------------------------------------------------
556
534
  class UnitTimer {
557
- constructor({ transition, timeout, duration, easing, loop }) {
535
+ constructor(options) {
558
536
  this.stack = [];
559
- this.unit = new Unit(Unit.current, UnitTimer.Component, { snapshot: Unit.snapshot(Unit.current), transition, timeout, duration, easing, loop });
537
+ this.unit = new Unit(Unit.current, UnitTimer.Component, Object.assign({ snapshot: Unit.snapshot(Unit.current) }, options));
560
538
  }
561
539
  clear() {
562
540
  this.stack = [];
563
541
  this.unit.finalize();
564
542
  }
565
543
  timeout(timeout, duration = 0) {
566
- UnitTimer.execute(this, { timeout, duration });
544
+ UnitTimer.execute(this, { timeout, duration, iterations: 1 });
567
545
  return this;
568
546
  }
569
547
  transition(transition, duration = 0, easing = 'linear') {
570
- UnitTimer.execute(this, { transition, duration, easing });
548
+ UnitTimer.execute(this, { transition, duration, easing, iterations: 1 });
571
549
  return this;
572
550
  }
573
- static execute(timer, { transition, timeout, duration, easing, loop }) {
551
+ static execute(timer, options) {
574
552
  if (timer.unit._.state === 'finalized') {
575
- timer.unit = new Unit(Unit.current, UnitTimer.Component, { snapshot: Unit.snapshot(Unit.current), transition, timeout, duration, easing, loop });
553
+ timer.unit = new Unit(Unit.current, UnitTimer.Component, Object.assign({ snapshot: Unit.snapshot(Unit.current) }, options));
576
554
  }
577
555
  else if (timer.stack.length === 0) {
578
- timer.stack.push({ snapshot: Unit.snapshot(Unit.current), transition, timeout, duration, easing, loop });
579
- timer.unit.on('finalize', () => { UnitTimer.next(timer); });
556
+ timer.stack.push(Object.assign({ snapshot: Unit.snapshot(Unit.current) }, options));
557
+ timer.unit.on('-finalize', () => { UnitTimer.next(timer); });
580
558
  }
581
559
  else {
582
- timer.stack.push({ snapshot: Unit.snapshot(Unit.current), transition, timeout, duration, easing, loop });
560
+ timer.stack.push(Object.assign({ snapshot: Unit.snapshot(Unit.current) }, options));
583
561
  }
584
562
  }
585
563
  static next(timer) {
586
564
  if (timer.stack.length > 0) {
587
565
  timer.unit = new Unit(Unit.current, UnitTimer.Component, timer.stack.shift());
588
- timer.unit.on('finalize', () => { UnitTimer.next(timer); });
589
- }
590
- }
591
- static Component(unit, { snapshot, transition, timeout, duration, loop, easing }) {
592
- const timer = new Timer((x) => {
593
- if (transition !== undefined)
594
- Unit.scope(snapshot, transition, x);
595
- }, () => {
596
- if (transition !== undefined)
597
- Unit.scope(snapshot, transition, 1.0);
598
- if (timeout !== undefined)
599
- Unit.scope(snapshot, timeout);
600
- if (loop === false) {
601
- unit.finalize();
602
- }
603
- }, duration, { loop, easing });
604
- unit.on('finalize', () => timer.clear());
566
+ timer.unit.on('-finalize', () => { UnitTimer.next(timer); });
567
+ }
568
+ }
569
+ static Component(unit, options) {
570
+ const timer = new Timer({
571
+ transition: (x) => {
572
+ if (options.transition !== undefined)
573
+ Unit.scope(options.snapshot, options.transition, x);
574
+ },
575
+ timeout: (count) => {
576
+ if (options.transition !== undefined)
577
+ Unit.scope(options.snapshot, options.transition, 1.0);
578
+ if (options.timeout !== undefined)
579
+ Unit.scope(options.snapshot, options.timeout);
580
+ if (options.iterations !== undefined && count >= options.iterations - 1) {
581
+ unit.finalize();
582
+ }
583
+ }, duration: options.duration, iterations: options.iterations, easing: options.easing
584
+ });
585
+ unit.on('-finalize', () => timer.clear());
605
586
  }
606
587
  }
607
588
 
@@ -792,7 +773,7 @@ const xnew$1 = Object.assign(function (...args) {
792
773
  * // Cancel if needed: timer.clear()
793
774
  */
794
775
  timeout(timeout, duration = 0) {
795
- return new UnitTimer({ timeout, duration });
776
+ return new UnitTimer({ timeout, duration, iterations: 1 });
796
777
  },
797
778
  /**
798
779
  * Executes a callback repeatedly at specified intervals, managed by component lifecycle
@@ -803,8 +784,8 @@ const xnew$1 = Object.assign(function (...args) {
803
784
  * const timer = xnew.interval(() => console.log('Tick'), 1000)
804
785
  * // Stop when needed: timer.clear()
805
786
  */
806
- interval(timeout, duration) {
807
- return new UnitTimer({ timeout, duration, loop: true });
787
+ interval(timeout, duration, iterations) {
788
+ return new UnitTimer({ timeout, duration, iterations });
808
789
  },
809
790
  /**
810
791
  * Creates a transition animation with easing, executing callback with progress values
@@ -820,27 +801,8 @@ const xnew$1 = Object.assign(function (...args) {
820
801
  * }, 300)
821
802
  */
822
803
  transition(transition, duration = 0, easing = 'linear') {
823
- return new UnitTimer({ transition, duration, easing });
824
- },
825
- /**
826
- * Creates an event listener manager for a target element with automatic cleanup
827
- * @param target - Element, Window, or Document to attach listeners to
828
- * @returns Object with on() and off() methods for managing event listeners
829
- * @example
830
- * const mouse = xnew.listener(window)
831
- * mouse.on('mousemove', (e) => console.log(e.clientX, e.clientY))
832
- * // Automatically cleaned up when component finalizes
833
- */
834
- listener(target) {
835
- return {
836
- on(type, listener, options) {
837
- Unit.subon(Unit.current, target, type, listener, options);
838
- },
839
- off(type, listener) {
840
- Unit.suboff(Unit.current, target, type, listener);
841
- }
842
- };
843
- },
804
+ return new UnitTimer({ transition, duration, easing, iterations: 1 });
805
+ }
844
806
  });
845
807
 
846
808
  function AccordionFrame(frame, { open = false, duration = 200, easing = 'ease' } = {}) {
@@ -931,34 +893,40 @@ function ResizeEvent(resize) {
931
893
  if (resize.element) {
932
894
  observer.observe(resize.element);
933
895
  }
934
- resize.on('finalize', () => {
896
+ resize.on('-finalize', () => {
935
897
  if (resize.element) {
936
898
  observer.unobserve(resize.element);
937
899
  }
938
900
  });
939
901
  }
940
- function KeyboardEvent(unit) {
902
+ function KeyboardEvent(keyboard) {
941
903
  const state = {};
942
- xnew$1.listener(window).on('keydown', (event) => {
904
+ window.addEventListener('keydown', keydown);
905
+ window.addEventListener('keyup', keyup);
906
+ function keydown(event) {
943
907
  state[event.code] = 1;
944
- unit.emit('-keydown', { event, type: '-keydown', code: event.code });
945
- });
946
- xnew$1.listener(window).on('keyup', (event) => {
908
+ keyboard.emit('-keydown', { event, type: '-keydown', code: event.code });
909
+ if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
910
+ keyboard.emit('-keydown:arrow', { event, type: '-keydown:arrow', code: event.code, vector: getVector() });
911
+ }
912
+ }
913
+ function keyup(event) {
947
914
  state[event.code] = 0;
948
- unit.emit('-keyup', { event, type: '-keyup', code: event.code });
949
- });
950
- xnew$1.listener(window).on('keydown', (event) => {
951
- unit.emit('-arrowkeydown', { event, type: '-arrowkeydown', code: event.code, vector: getVector() });
952
- });
953
- xnew$1.listener(window).on('keyup', (event) => {
954
- unit.emit('-arrowkeyup', { event, type: '-arrowkeyup', code: event.code, vector: getVector() });
955
- });
915
+ keyboard.emit('-keyup', { event, type: '-keyup', code: event.code });
916
+ if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
917
+ keyboard.emit('-keyup:arrow', { event, type: '-keyup:arrow', code: event.code, vector: getVector() });
918
+ }
919
+ }
956
920
  function getVector() {
957
921
  return {
958
922
  x: (state['ArrowLeft'] ? -1 : 0) + (state['ArrowRight'] ? +1 : 0),
959
923
  y: (state['ArrowUp'] ? -1 : 0) + (state['ArrowDown'] ? +1 : 0)
960
924
  };
961
925
  }
926
+ keyboard.on('-finalize', () => {
927
+ window.removeEventListener('keydown', keydown);
928
+ window.removeEventListener('keyup', keyup);
929
+ });
962
930
  }
963
931
  function PointerEvent(unit) {
964
932
  const internal = xnew$1();
@@ -966,8 +934,32 @@ function PointerEvent(unit) {
966
934
  internal.on('pointermove', (event) => unit.emit('-pointermove', { event, position: getPosition(unit.element, event) }));
967
935
  internal.on('pointerup', (event) => unit.emit('-pointerup', { event, position: getPosition(unit.element, event) }));
968
936
  internal.on('wheel', (event) => unit.emit('-wheel', { event, delta: { x: event.wheelDeltaX, y: event.wheelDeltaY } }));
969
- internal.on('mouseover', (event) => unit.emit('-mouseover', { event, position: getPosition(unit.element, event) }));
970
- internal.on('mouseout', (event) => unit.emit('-mouseout', { event, position: getPosition(unit.element, event) }));
937
+ internal.on('click', (event) => unit.emit('-click', { event, position: getPosition(unit.element, event) }));
938
+ internal.on('pointerover', (event) => unit.emit('-pointerover', { event, position: getPosition(unit.element, event) }));
939
+ internal.on('pointerout', (event) => unit.emit('-pointerout', { event, position: getPosition(unit.element, event) }));
940
+ document.addEventListener('pointerdown', pointerdownoutside);
941
+ document.addEventListener('pointerup', pointerupoutside);
942
+ document.addEventListener('click', clickoutside);
943
+ unit.on('-finalize', () => {
944
+ document.removeEventListener('pointerdown', pointerdownoutside);
945
+ document.removeEventListener('pointerup', pointerupoutside);
946
+ document.removeEventListener('click', clickoutside);
947
+ });
948
+ function pointerdownoutside(event) {
949
+ if (unit.element.contains(event.target) === false) {
950
+ unit.emit('-pointerdown:outside', { event, position: getPosition(unit.element, event) });
951
+ }
952
+ }
953
+ function pointerupoutside(event) {
954
+ if (unit.element.contains(event.target) === false) {
955
+ unit.emit('-pointerup:outside', { event, position: getPosition(unit.element, event) });
956
+ }
957
+ }
958
+ function clickoutside(event) {
959
+ if (unit.element.contains(event.target) === false) {
960
+ unit.emit('-click:outside', { event, position: getPosition(unit.element, event) });
961
+ }
962
+ }
971
963
  const drag = xnew$1(DragEvent);
972
964
  drag.on('-dragstart', (...args) => unit.emit('-dragstart', ...args));
973
965
  drag.on('-dragmove', (...args) => unit.emit('-dragmove', ...args));
@@ -980,36 +972,50 @@ function PointerEvent(unit) {
980
972
  gesture.on('-gesturecancel', (...args) => unit.emit('-gesturecancel', ...args));
981
973
  }
982
974
  function DragEvent(unit) {
983
- xnew$1().on('pointerdown', (event) => {
975
+ unit.on('pointerdown', pointerdown);
976
+ function pointerdown(event) {
984
977
  const id = event.pointerId;
985
978
  const position = getPosition(unit.element, event);
986
979
  let previous = position;
987
- xnew$1(() => {
988
- xnew$1.listener(window).on('pointermove', (event) => {
980
+ xnew$1((internal) => {
981
+ let connect = true;
982
+ window.addEventListener('pointermove', pointermove);
983
+ window.addEventListener('pointerup', pointerup);
984
+ window.addEventListener('pointercancel', pointercancel);
985
+ function pointermove(event) {
989
986
  if (event.pointerId === id) {
990
987
  const position = getPosition(unit.element, event);
991
988
  const delta = { x: position.x - previous.x, y: position.y - previous.y };
992
989
  unit.emit('-dragmove', { event, position, delta });
993
990
  previous = position;
994
991
  }
995
- });
996
- xnew$1.listener(window).on('pointerup', (event) => {
992
+ }
993
+ function pointerup(event) {
997
994
  if (event.pointerId === id) {
998
995
  const position = getPosition(unit.element, event);
999
996
  unit.emit('-dragend', { event, position, });
1000
- xnew$1.listener(window).off();
997
+ remove();
1001
998
  }
1002
- });
1003
- xnew$1.listener(window).on('pointercancel', (event) => {
999
+ }
1000
+ function pointercancel(event) {
1004
1001
  if (event.pointerId === id) {
1005
1002
  const position = getPosition(unit.element, event);
1006
1003
  unit.emit('-dragcancel', { event, position, });
1007
- xnew$1.listener(window).off();
1004
+ remove();
1008
1005
  }
1009
- });
1006
+ }
1007
+ function remove() {
1008
+ if (connect === true) {
1009
+ window.removeEventListener('pointermove', pointermove);
1010
+ window.removeEventListener('pointerup', pointerup);
1011
+ window.removeEventListener('pointercancel', pointercancel);
1012
+ connect = false;
1013
+ }
1014
+ }
1015
+ internal.on('-finalize', remove);
1010
1016
  });
1011
1017
  unit.emit('-dragstart', { event, position });
1012
- });
1018
+ }
1013
1019
  }
1014
1020
  function GestureEvent(unit) {
1015
1021
  const drag = xnew$1(DragEvent);
@@ -1415,7 +1421,7 @@ function initialize() {
1415
1421
  }
1416
1422
  const audio = {
1417
1423
  load(path) {
1418
- return new AudioFile(path);
1424
+ return AudioFile.load(path);
1419
1425
  },
1420
1426
  synthesizer(props) {
1421
1427
  return new Synthesizer(props);
@@ -1441,43 +1447,69 @@ class AudioFile {
1441
1447
  .catch(() => {
1442
1448
  console.warn(`"${path}" could not be loaded.`);
1443
1449
  });
1450
+ this.amp = context.createGain();
1451
+ this.amp.gain.value = 1.0;
1452
+ this.amp.connect(master);
1453
+ this.fade = context.createGain();
1454
+ this.fade.gain.value = 1.0;
1455
+ this.fade.connect(this.amp);
1456
+ this.source = null;
1444
1457
  this.start = null;
1445
1458
  }
1446
- // set volume(value: number) {
1447
- // this.amp.gain.value = value;
1448
- // }
1449
- // get volume(): number {
1450
- // return this.amp.gain.value;
1451
- // }
1452
- play(offset = 0, loop = false) {
1459
+ set volume(value) {
1460
+ this.amp.gain.value = value;
1461
+ }
1462
+ get volume() {
1463
+ return this.amp.gain.value;
1464
+ }
1465
+ play({ offset = 0, loop = false, fade = 0 } = {}) {
1453
1466
  if (this.buffer !== undefined && this.start === null) {
1454
1467
  this.source = context.createBufferSource();
1455
1468
  this.source.buffer = this.buffer;
1456
1469
  this.source.loop = loop;
1457
- this.amp = context.createGain();
1458
- this.amp.gain.value = 1.0;
1459
- this.source.connect(this.amp);
1460
- this.amp.connect(master);
1470
+ this.source.connect(this.fade);
1461
1471
  this.start = context.currentTime;
1462
1472
  this.source.playbackRate.value = 1;
1463
1473
  this.source.start(context.currentTime, offset / 1000);
1474
+ // Apply fade-in effect if fade duration is specified
1475
+ if (fade > 0) {
1476
+ this.fade.gain.setValueAtTime(0, context.currentTime);
1477
+ this.fade.gain.linearRampToValueAtTime(1.0, context.currentTime + fade / 1000);
1478
+ }
1464
1479
  this.source.onended = () => {
1465
- var _a, _b;
1480
+ var _a;
1466
1481
  this.start = null;
1467
1482
  (_a = this.source) === null || _a === void 0 ? void 0 : _a.disconnect();
1468
- (_b = this.amp) === null || _b === void 0 ? void 0 : _b.disconnect();
1483
+ this.source = null;
1469
1484
  };
1470
1485
  }
1471
1486
  }
1472
- pause() {
1473
- var _a;
1487
+ pause({ fade = 0 } = {}) {
1488
+ var _a, _b;
1474
1489
  if (this.buffer !== undefined && this.start !== null) {
1475
- (_a = this.source) === null || _a === void 0 ? void 0 : _a.stop(context.currentTime);
1476
1490
  const elapsed = (context.currentTime - this.start) % this.buffer.duration * 1000;
1491
+ // Apply fade-out effect if fade duration is specified
1492
+ if (fade > 0) {
1493
+ this.fade.gain.setValueAtTime(1.0, context.currentTime);
1494
+ this.fade.gain.linearRampToValueAtTime(0, context.currentTime + fade / 1000);
1495
+ (_a = this.source) === null || _a === void 0 ? void 0 : _a.stop(context.currentTime + fade / 1000);
1496
+ }
1497
+ else {
1498
+ (_b = this.source) === null || _b === void 0 ? void 0 : _b.stop(context.currentTime);
1499
+ }
1477
1500
  this.start = null;
1478
1501
  return elapsed;
1479
1502
  }
1480
1503
  }
1504
+ static load(path) {
1505
+ const music = new AudioFile(path);
1506
+ return xnew$1.promise(music.promise).then(() => music);
1507
+ }
1508
+ static clear(file) {
1509
+ var _a;
1510
+ file.amp.disconnect();
1511
+ (_a = file.source) === null || _a === void 0 ? void 0 : _a.disconnect();
1512
+ }
1481
1513
  }
1482
1514
  const keymap = {
1483
1515
  'A0': 27.500, 'A#0': 29.135, 'B0': 30.868,
@@ -1562,7 +1594,7 @@ class Synthesizer {
1562
1594
  if (props.amp.envelope) {
1563
1595
  const ADSR = props.amp.envelope.ADSR;
1564
1596
  const adsr = [ADSR[0] / 1000, ADSR[1] / 1000, ADSR[2], ADSR[3] / 1000];
1565
- const rate = adsr[0] === 0.0 ? 1.0 : Math.min(end / adsr[0], 1.0);
1597
+ const rate = adsr[0] === 0.0 ? 1.0 : Math.min(end / (adsr[0] + 0.001), 1.0);
1566
1598
  stop = start + Math.max((adsr[0] + adsr[1]) * rate, end) + adsr[3];
1567
1599
  }
1568
1600
  else {
@@ -1592,7 +1624,8 @@ class Synthesizer {
1592
1624
  }, 2000);
1593
1625
  }
1594
1626
  function stopEnvelope(param, base, amount, ADSR) {
1595
- const rate = ADSR[0] === 0.0 ? 1.0 : Math.min(dv / (ADSR[0] / 1000), 1.0);
1627
+ const end = dv > 0 ? dv : (context.currentTime - start);
1628
+ const rate = ADSR[0] === 0.0 ? 1.0 : Math.min(end / (ADSR[0] / 1000), 1.0);
1596
1629
  if (rate < 1.0) {
1597
1630
  param.cancelScheduledValues(start);
1598
1631
  param.setValueAtTime(base, start);
@@ -1600,7 +1633,7 @@ class Synthesizer {
1600
1633
  param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + (ADSR[0] + ADSR[1]) / 1000 * rate);
1601
1634
  }
1602
1635
  param.linearRampToValueAtTime(base + amount * rate * ADSR[2], start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, dv));
1603
- param.linearRampToValueAtTime(base, start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, dv) + ADSR[3] / 1000);
1636
+ param.linearRampToValueAtTime(base, start + Math.max((ADSR[0] + ADSR[1]) / 1000 * rate, end) + ADSR[3] / 1000);
1604
1637
  }
1605
1638
  function startEnvelope(param, base, amount, ADSR) {
1606
1639
  param.value = base;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "keywords": [
5
5
  "Component-Oriented Programming"
6
6
  ],
7
- "version": "0.1.11",
7
+ "version": "0.2.0",
8
8
  "main": "dist/xnew.js",
9
9
  "module": "dist/xnew.mjs",
10
10
  "types": "dist/xnew.d.ts",