@mulsense/xnew 0.6.1 → 0.6.3

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
@@ -207,6 +207,21 @@ class Timer {
207
207
  }
208
208
  }
209
209
 
210
+ function addEvent(target, type, execute, options) {
211
+ let initalized = false;
212
+ const id = setTimeout(() => {
213
+ initalized = true;
214
+ target.addEventListener(type, execute, options);
215
+ }, 0);
216
+ return () => {
217
+ if (initalized === false) {
218
+ clearTimeout(id);
219
+ }
220
+ else {
221
+ target.removeEventListener(type, execute);
222
+ }
223
+ };
224
+ }
210
225
  class Eventor {
211
226
  constructor() {
212
227
  this.map = new MapMap();
@@ -214,53 +229,62 @@ class Eventor {
214
229
  add(element, type, listener, options) {
215
230
  const props = { element, type, listener, options };
216
231
  let finalize;
217
- if (props.type === 'resize') {
218
- finalize = this.resize(props);
219
- }
220
- else if (props.type === 'change') {
221
- finalize = this.change(props);
222
- }
223
- else if (props.type === 'input') {
224
- finalize = this.input(props);
225
- }
226
- else if (props.type === 'wheel') {
227
- finalize = this.wheel(props);
228
- }
229
- else if (props.type === 'click') {
230
- finalize = this.click(props);
231
- }
232
- else if (props.type === 'click.outside') {
233
- finalize = this.click_outside(props);
234
- }
235
- else if (['pointerdown', 'pointermove', 'pointerup', 'pointerover', 'pointerout'].includes(props.type)) {
236
- finalize = this.pointer(props);
237
- }
238
- else if (['pointerdown.outside', 'pointermove.outside', 'pointerup.outside'].includes(props.type)) {
239
- finalize = this.pointer_outside(props);
240
- }
241
- else if (['mousedown', 'mousemove', 'mouseup', 'mouseover', 'mouseout'].includes(props.type)) {
242
- finalize = this.mouse(props);
243
- }
244
- else if (['touchstart', 'touchmove', 'touchend', 'touchcancel'].includes(props.type)) {
245
- finalize = this.touch(props);
246
- }
247
- else if (['dragstart', 'dragmove', 'dragend'].includes(props.type)) {
248
- finalize = this.drag(props);
249
- }
250
- else if (['gesturestart', 'gesturemove', 'gestureend'].includes(props.type)) {
251
- finalize = this.gesture(props);
252
- }
253
- else if (['keydown', 'keyup'].includes(props.type)) {
254
- finalize = this.key(props);
255
- }
256
- else if (['keydown.arrow', 'keyup.arrow'].includes(props.type)) {
257
- finalize = this.key_arrow(props);
232
+ if (props.type.indexOf('window.') === 0) {
233
+ if (['window.keydown', 'window.keyup'].includes(props.type)) {
234
+ finalize = this.window_key(props);
235
+ }
236
+ else if (['window.keydown.arrow', 'window.keyup.arrow'].includes(props.type)) {
237
+ finalize = this.window_key_arrow(props);
238
+ }
239
+ else if (['window.keydown.wasd', 'window.keyup.wasd'].includes(props.type)) {
240
+ finalize = this.window_key_wasd(props);
241
+ }
242
+ else {
243
+ finalize = this.window_basic(props);
244
+ }
258
245
  }
259
- else if (['keydown.wasd', 'keyup.wasd'].includes(props.type)) {
260
- finalize = this.key_wasd(props);
246
+ else if (props.type.indexOf('document.') === 0) {
247
+ {
248
+ finalize = this.document_basic(props);
249
+ }
261
250
  }
262
251
  else {
263
- finalize = this.basic(props);
252
+ if (props.type === 'resize') {
253
+ finalize = this.element_resize(props);
254
+ }
255
+ else if (props.type === 'change') {
256
+ finalize = this.element_change(props);
257
+ }
258
+ else if (props.type === 'input') {
259
+ finalize = this.element_input(props);
260
+ }
261
+ else if (props.type === 'wheel') {
262
+ finalize = this.element_wheel(props);
263
+ }
264
+ else if (props.type === 'click') {
265
+ finalize = this.element_click(props);
266
+ }
267
+ else if (props.type === 'click.outside') {
268
+ finalize = this.element_click_outside(props);
269
+ }
270
+ else if (['pointerdown', 'pointermove', 'pointerup', 'pointerover', 'pointerout'].includes(props.type)) {
271
+ finalize = this.element_pointer(props);
272
+ }
273
+ else if (['pointerdown.outside', 'pointermove.outside', 'pointerup.outside'].includes(props.type)) {
274
+ finalize = this.element_pointer_outside(props);
275
+ }
276
+ else if (['mousedown', 'mousemove', 'mouseup', 'mouseover', 'mouseout'].includes(props.type)) {
277
+ finalize = this.element_mouse(props);
278
+ }
279
+ else if (['touchstart', 'touchmove', 'touchend', 'touchcancel'].includes(props.type)) {
280
+ finalize = this.element_touch(props);
281
+ }
282
+ else if (['dragstart', 'dragmove', 'dragend'].includes(props.type)) {
283
+ finalize = this.element_drag(props);
284
+ }
285
+ else {
286
+ finalize = this.element_basic(props);
287
+ }
264
288
  }
265
289
  this.map.set(props.type, props.listener, finalize);
266
290
  }
@@ -271,16 +295,12 @@ class Eventor {
271
295
  this.map.delete(type, listener);
272
296
  }
273
297
  }
274
- basic(props) {
275
- const execute = (event) => {
298
+ element_basic(props) {
299
+ return addEvent(props.element, props.type, (event) => {
276
300
  props.listener({ event });
277
- };
278
- props.element.addEventListener(props.type, execute, props.options);
279
- return () => {
280
- props.element.removeEventListener(props.type, execute);
281
- };
301
+ }, props.options);
282
302
  }
283
- resize(props) {
303
+ element_resize(props) {
284
304
  const observer = new ResizeObserver((entries) => {
285
305
  for (const entry of entries) {
286
306
  props.listener({});
@@ -292,8 +312,8 @@ class Eventor {
292
312
  observer.unobserve(props.element);
293
313
  };
294
314
  }
295
- change(props) {
296
- const execute = (event) => {
315
+ element_change(props) {
316
+ return addEvent(props.element, props.type, (event) => {
297
317
  let value = null;
298
318
  if (event.target.type === 'checkbox') {
299
319
  value = event.target.checked;
@@ -305,14 +325,10 @@ class Eventor {
305
325
  value = event.target.value;
306
326
  }
307
327
  props.listener({ event, value });
308
- };
309
- props.element.addEventListener(props.type, execute, props.options);
310
- return () => {
311
- props.element.removeEventListener(props.type, execute);
312
- };
328
+ }, props.options);
313
329
  }
314
- input(props) {
315
- const execute = (event) => {
330
+ element_input(props) {
331
+ return addEvent(props.element, props.type, (event) => {
316
332
  let value = null;
317
333
  if (event.target.type === 'checkbox') {
318
334
  value = event.target.checked;
@@ -324,88 +340,56 @@ class Eventor {
324
340
  value = event.target.value;
325
341
  }
326
342
  props.listener({ event, value });
327
- };
328
- props.element.addEventListener(props.type, execute, props.options);
329
- return () => {
330
- props.element.removeEventListener(props.type, execute);
331
- };
343
+ }, props.options);
332
344
  }
333
- click(props) {
334
- const execute = (event) => {
345
+ element_click(props) {
346
+ return addEvent(props.element, props.type, (event) => {
335
347
  props.listener({ event, position: pointer(props.element, event).position });
336
- };
337
- props.element.addEventListener(props.type, execute, props.options);
338
- return () => {
339
- props.element.removeEventListener(props.type, execute);
340
- };
348
+ }, props.options);
341
349
  }
342
- click_outside(props) {
343
- const execute = (event) => {
350
+ element_click_outside(props) {
351
+ return addEvent(document, props.type.split('.')[0], (event) => {
344
352
  if (props.element.contains(event.target) === false) {
345
353
  props.listener({ event, position: pointer(props.element, event).position });
346
354
  }
347
- };
348
- document.addEventListener(props.type.split('.')[0], execute, props.options);
349
- return () => {
350
- document.removeEventListener(props.type.split('.')[0], execute);
351
- };
355
+ }, props.options);
352
356
  }
353
- pointer(props) {
354
- const execute = (event) => {
357
+ element_pointer(props) {
358
+ return addEvent(props.element, props.type, (event) => {
355
359
  props.listener({ event, position: pointer(props.element, event).position });
356
- };
357
- props.element.addEventListener(props.type, execute, props.options);
358
- return () => {
359
- props.element.removeEventListener(props.type, execute);
360
- };
360
+ }, props.options);
361
361
  }
362
- mouse(props) {
363
- const execute = (event) => {
362
+ element_mouse(props) {
363
+ return addEvent(props.element, props.type, (event) => {
364
364
  props.listener({ event, position: pointer(props.element, event).position });
365
- };
366
- props.element.addEventListener(props.type, execute, props.options);
367
- return () => {
368
- props.element.removeEventListener(props.type, execute);
369
- };
365
+ }, props.options);
370
366
  }
371
- touch(props) {
372
- const execute = (event) => {
367
+ element_touch(props) {
368
+ return addEvent(props.element, props.type, (event) => {
373
369
  props.listener({ event, position: pointer(props.element, event).position });
374
- };
375
- props.element.addEventListener(props.type, execute, props.options);
376
- return () => {
377
- props.element.removeEventListener(props.type, execute);
378
- };
370
+ }, props.options);
379
371
  }
380
- pointer_outside(props) {
381
- const execute = (event) => {
372
+ element_pointer_outside(props) {
373
+ return addEvent(document, props.type.split('.')[0], (event) => {
382
374
  if (props.element.contains(event.target) === false) {
383
375
  props.listener({ event, position: pointer(props.element, event).position });
384
376
  }
385
- };
386
- document.addEventListener(props.type.split('.')[0], execute, props.options);
387
- return () => {
388
- document.removeEventListener(props.type.split('.')[0], execute);
389
- };
377
+ }, props.options);
390
378
  }
391
- wheel(props) {
392
- const execute = (event) => {
379
+ element_wheel(props) {
380
+ return addEvent(props.element, props.type, (event) => {
393
381
  props.listener({ event, delta: { x: event.wheelDeltaX, y: event.wheelDeltaY } });
394
- };
395
- props.element.addEventListener(props.type, execute, props.options);
396
- return () => {
397
- props.element.removeEventListener(props.type, execute);
398
- };
382
+ }, props.options);
399
383
  }
400
- drag(props) {
384
+ element_drag(props) {
401
385
  let pointermove = null;
402
386
  let pointerup = null;
403
387
  let pointercancel = null;
404
- const pointerdown = (event) => {
388
+ const pointerdown = addEvent(props.element, 'pointerdown', (event) => {
405
389
  const id = event.pointerId;
406
390
  const position = pointer(props.element, event).position;
407
391
  let previous = position;
408
- pointermove = (event) => {
392
+ pointermove = addEvent(window, 'pointermove', (event) => {
409
393
  if (event.pointerId === id) {
410
394
  const position = pointer(props.element, event).position;
411
395
  const delta = { x: position.x - previous.x, y: position.y - previous.y };
@@ -414,8 +398,8 @@ class Eventor {
414
398
  }
415
399
  previous = position;
416
400
  }
417
- };
418
- pointerup = (event) => {
401
+ }, props.options);
402
+ pointerup = addEvent(window, 'pointerup', (event) => {
419
403
  if (event.pointerId === id) {
420
404
  const position = pointer(props.element, event).position;
421
405
  if (props.type === 'dragend') {
@@ -423,8 +407,8 @@ class Eventor {
423
407
  }
424
408
  remove();
425
409
  }
426
- };
427
- pointercancel = (event) => {
410
+ }, props.options);
411
+ pointercancel = addEvent(window, 'pointercancel', (event) => {
428
412
  if (event.pointerId === id) {
429
413
  const position = pointer(props.element, event).position;
430
414
  if (props.type === 'dragend') {
@@ -432,168 +416,102 @@ class Eventor {
432
416
  }
433
417
  remove();
434
418
  }
435
- };
436
- window.addEventListener('pointermove', pointermove);
437
- window.addEventListener('pointerup', pointerup);
438
- window.addEventListener('pointercancel', pointercancel);
419
+ }, props.options);
439
420
  if (props.type === 'dragstart') {
440
421
  props.listener({ event, position, delta: { x: 0, y: 0 } });
441
422
  }
442
- };
423
+ }, props.options);
443
424
  function remove() {
444
- if (pointermove)
445
- window.removeEventListener('pointermove', pointermove);
446
- if (pointerup)
447
- window.removeEventListener('pointerup', pointerup);
448
- if (pointercancel)
449
- window.removeEventListener('pointercancel', pointercancel);
425
+ pointermove === null || pointermove === void 0 ? void 0 : pointermove();
450
426
  pointermove = null;
427
+ pointerup === null || pointerup === void 0 ? void 0 : pointerup();
451
428
  pointerup = null;
429
+ pointercancel === null || pointercancel === void 0 ? void 0 : pointercancel();
452
430
  pointercancel = null;
453
431
  }
454
- props.element.addEventListener('pointerdown', pointerdown, props.options);
455
432
  return () => {
456
- props.element.removeEventListener('pointerdown', pointerdown);
433
+ pointerdown();
457
434
  remove();
458
435
  };
459
436
  }
460
- gesture(props) {
461
- let isActive = false;
462
- const map = new Map();
463
- const element = props.element;
464
- const options = props.options;
465
- const dragstart = ({ event, position }) => {
466
- map.set(event.pointerId, position);
467
- isActive = map.size === 2 ? true : false;
468
- if (isActive === true && props.type === 'gesturestart') {
469
- props.listener({ event });
470
- }
471
- };
472
- const dragmove = ({ event, position, delta }) => {
473
- if (map.size >= 2 && isActive === true) {
474
- const a = map.get(event.pointerId);
475
- const b = getOthers(event.pointerId)[0];
476
- let scale = 0.0;
477
- {
478
- const v = { x: a.x - b.x, y: a.y - b.y };
479
- const s = v.x * v.x + v.y * v.y;
480
- scale = 1 + (s > 0.0 ? (v.x * delta.x + v.y * delta.y) / s : 0);
481
- }
482
- // let rotate = 0.0;
483
- // {
484
- // const c = { x: a.x + delta.x, y: a.y + delta.y };
485
- // const v1 = { x: a.x - b.x, y: a.y - b.y };
486
- // const v2 = { x: c.x - b.x, y: c.y - b.y };
487
- // const l1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
488
- // const l2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
489
- // if (l1 > 0.0 && l2 > 0.0) {
490
- // const angle = Math.acos((v1.x * v2.x + v1.y * v2.y) / (l1 * l2));
491
- // const sign = v1.x * v2.y - v1.y * v2.x;
492
- // rotate = sign > 0.0 ? +angle : -angle;
493
- // }
494
- // }
495
- if (props.type === 'gesturemove') {
496
- props.listener({ event, scale });
497
- }
498
- }
499
- map.set(event.pointerId, position);
500
- };
501
- const dragend = ({ event }) => {
502
- map.delete(event.pointerId);
503
- if (isActive === true && props.type === 'gestureend') {
504
- props.listener({ event, scale: 1.0 });
505
- }
506
- isActive = false;
507
- };
508
- this.add(element, 'dragstart', dragstart, options);
509
- this.add(element, 'dragmove', dragmove, options);
510
- this.add(element, 'dragend', dragend, options);
511
- function getOthers(id) {
512
- const backup = map.get(id);
513
- map.delete(id);
514
- const others = [...map.values()];
515
- map.set(id, backup);
516
- return others;
517
- }
518
- return () => {
519
- this.remove('dragstart', dragstart);
520
- this.remove('dragmove', dragmove);
521
- this.remove('dragend', dragend);
522
- };
437
+ window_basic(props) {
438
+ const type = props.type.substring('window.'.length);
439
+ return addEvent(window, type, (event) => {
440
+ props.listener({ event });
441
+ }, props.options);
523
442
  }
524
- key(props) {
525
- const execute = (event) => {
526
- if (props.type === 'keydown' && event.repeat)
443
+ window_key(props) {
444
+ const type = props.type.substring(props.type.indexOf('.') + 1);
445
+ return addEvent(window, type, (event) => {
446
+ if (event.repeat)
527
447
  return;
528
448
  props.listener({ event, code: event.code });
529
- };
530
- window.addEventListener(props.type, execute, props.options);
531
- return () => {
532
- window.removeEventListener(props.type, execute);
533
- };
449
+ }, props.options);
534
450
  }
535
- key_arrow(props) {
451
+ window_key_arrow(props) {
536
452
  const keymap = {};
537
- const keydown = (event) => {
453
+ const keydown = addEvent(window, 'keydown', (event) => {
538
454
  if (event.repeat)
539
455
  return;
540
456
  keymap[event.code] = 1;
541
- if (props.type === 'keydown.arrow' && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
457
+ if (props.type === 'window.keydown.arrow' && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
542
458
  const vector = {
543
459
  x: (keymap['ArrowLeft'] ? -1 : 0) + (keymap['ArrowRight'] ? +1 : 0),
544
460
  y: (keymap['ArrowUp'] ? -1 : 0) + (keymap['ArrowDown'] ? +1 : 0)
545
461
  };
546
462
  props.listener({ event, code: event.code, vector });
547
463
  }
548
- };
549
- const keyup = (event) => {
464
+ }, props.options);
465
+ const keyup = addEvent(window, 'keyup', (event) => {
550
466
  keymap[event.code] = 0;
551
- if (props.type === 'keyup.arrow' && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
467
+ if (props.type === 'window.keyup.arrow' && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
552
468
  const vector = {
553
469
  x: (keymap['ArrowLeft'] ? -1 : 0) + (keymap['ArrowRight'] ? +1 : 0),
554
470
  y: (keymap['ArrowUp'] ? -1 : 0) + (keymap['ArrowDown'] ? +1 : 0)
555
471
  };
556
472
  props.listener({ event, code: event.code, vector });
557
473
  }
558
- };
559
- window.addEventListener('keydown', keydown, props.options);
560
- window.addEventListener('keyup', keyup, props.options);
474
+ }, props.options);
561
475
  return () => {
562
- window.removeEventListener('keydown', keydown);
563
- window.removeEventListener('keyup', keyup);
476
+ keydown();
477
+ keyup();
564
478
  };
565
479
  }
566
- key_wasd(props) {
480
+ window_key_wasd(props) {
567
481
  const keymap = {};
568
- const keydown = (event) => {
482
+ const finalize1 = addEvent(window, 'keydown', (event) => {
569
483
  if (event.repeat)
570
484
  return;
571
485
  keymap[event.code] = 1;
572
- if (props.type === 'keydown.wasd' && ['KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(event.code)) {
486
+ if (props.type === 'window.keydown.wasd' && ['KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(event.code)) {
573
487
  const vector = {
574
488
  x: (keymap['KeyA'] ? -1 : 0) + (keymap['KeyD'] ? +1 : 0),
575
489
  y: (keymap['KeyW'] ? -1 : 0) + (keymap['KeyS'] ? +1 : 0)
576
490
  };
577
491
  props.listener({ event, code: event.code, vector });
578
492
  }
579
- };
580
- const keyup = (event) => {
493
+ }, props.options);
494
+ const finalize2 = addEvent(window, 'keyup', (event) => {
581
495
  keymap[event.code] = 0;
582
- if (props.type === 'keyup.wasd' && ['KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(event.code)) {
496
+ if (props.type === 'window.keyup.wasd' && ['KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(event.code)) {
583
497
  const vector = {
584
498
  x: (keymap['KeyA'] ? -1 : 0) + (keymap['KeyD'] ? +1 : 0),
585
499
  y: (keymap['KeyW'] ? -1 : 0) + (keymap['KeyS'] ? +1 : 0)
586
500
  };
587
501
  props.listener({ event, code: event.code, vector });
588
502
  }
589
- };
590
- window.addEventListener('keydown', keydown, props.options);
591
- window.addEventListener('keyup', keyup, props.options);
503
+ }, props.options);
592
504
  return () => {
593
- window.removeEventListener('keydown', keydown);
594
- window.removeEventListener('keyup', keyup);
505
+ finalize1();
506
+ finalize2();
595
507
  };
596
508
  }
509
+ document_basic(props) {
510
+ const type = props.type.substring('document.'.length);
511
+ return addEvent(document, type, (event) => {
512
+ props.listener({ event });
513
+ }, props.options);
514
+ }
597
515
  }
598
516
  function pointer(element, event) {
599
517
  const rect = element.getBoundingClientRect();
@@ -713,7 +631,7 @@ class Unit {
713
631
  baseComponent = (unit) => { };
714
632
  }
715
633
  const baseContext = (_a = parent === null || parent === void 0 ? void 0 : parent._.currentContext) !== null && _a !== void 0 ? _a : { stack: null };
716
- this._ = { parent, target, baseElement, baseContext, baseComponent, props: props !== null && props !== void 0 ? props : {} };
634
+ this._ = { parent, target, baseElement, baseContext, baseComponent, props };
717
635
  parent === null || parent === void 0 ? void 0 : parent._.children.push(this);
718
636
  Unit.initialize(this, null);
719
637
  }
@@ -735,8 +653,10 @@ class Unit {
735
653
  }
736
654
  }
737
655
  reboot() {
738
- var _a, _b;
739
- const anchor = (_b = (_a = this._.elements[0]) === null || _a === void 0 ? void 0 : _a.nextElementSibling) !== null && _b !== void 0 ? _b : null;
656
+ let anchor = null;
657
+ if (this._.nestElements[0] && this._.nestElements[0].owned === true) {
658
+ anchor = this._.nestElements[0].element.nextElementSibling;
659
+ }
740
660
  Unit.stop(this);
741
661
  Unit.finalize(this);
742
662
  Unit.initialize(this, anchor);
@@ -754,7 +674,7 @@ class Unit {
754
674
  protected: false,
755
675
  ancestors: unit._.parent ? [unit._.parent, ...unit._.parent._.ancestors] : [],
756
676
  children: [],
757
- elements: [],
677
+ nestElements: [],
758
678
  promises: [],
759
679
  components: [],
760
680
  listeners: new MapMap(),
@@ -779,10 +699,12 @@ class Unit {
779
699
  [...unit._.systems.finalize].reverse().forEach(({ execute }) => execute());
780
700
  unit.off();
781
701
  unit._.components.forEach((component) => Unit.component2units.delete(component, unit));
782
- if (unit._.elements.length > 0) {
783
- unit._.baseElement.removeChild(unit._.elements[0]);
784
- unit._.currentElement = unit._.baseElement;
702
+ for (const { element, owned } of unit._.nestElements.reverse()) {
703
+ if (owned === true) {
704
+ element.remove();
705
+ }
785
706
  }
707
+ unit._.currentElement = unit._.baseElement;
786
708
  // reset defines
787
709
  Object.keys(unit._.defines).forEach((key) => {
788
710
  delete unit[key];
@@ -791,28 +713,35 @@ class Unit {
791
713
  unit._.state = 'finalized';
792
714
  }
793
715
  }
794
- static nest(unit, tag, textContent) {
795
- const match = tag.match(/<((\w+)[^>]*?)\/?>/);
796
- if (match !== null) {
797
- let element;
798
- if (unit._.anchor !== null) {
799
- unit._.anchor.insertAdjacentHTML('beforebegin', `<${match[1]}></${match[2]}>`);
800
- element = unit._.anchor.previousElementSibling;
801
- unit._.anchor = null;
716
+ static nest(unit, target, textContent) {
717
+ if (target instanceof HTMLElement || target instanceof SVGElement) {
718
+ unit._.nestElements.push({ element: target, owned: false });
719
+ unit._.currentElement = target;
720
+ return target;
721
+ }
722
+ else {
723
+ const match = target.match(/<((\w+)[^>]*?)\/?>/);
724
+ if (match !== null) {
725
+ let element;
726
+ if (unit._.anchor !== null) {
727
+ unit._.anchor.insertAdjacentHTML('beforebegin', `<${match[1]}></${match[2]}>`);
728
+ element = unit._.anchor.previousElementSibling;
729
+ unit._.anchor = null;
730
+ }
731
+ else {
732
+ unit._.currentElement.insertAdjacentHTML('beforeend', `<${match[1]}></${match[2]}>`);
733
+ element = unit._.currentElement.children[unit._.currentElement.children.length - 1];
734
+ }
735
+ unit._.currentElement = element;
736
+ if (textContent !== undefined) {
737
+ element.textContent = textContent.toString();
738
+ }
739
+ unit._.nestElements.push({ element, owned: true });
740
+ return element;
802
741
  }
803
742
  else {
804
- unit._.currentElement.insertAdjacentHTML('beforeend', `<${match[1]}></${match[2]}>`);
805
- element = unit._.currentElement.children[unit._.currentElement.children.length - 1];
743
+ throw new Error(`xnew.nest: invalid html string [${target}]`);
806
744
  }
807
- unit._.currentElement = element;
808
- if (textContent !== undefined) {
809
- element.textContent = textContent.toString();
810
- }
811
- unit._.elements.push(element);
812
- return element;
813
- }
814
- else {
815
- throw new Error(`xnew.nest: invalid html string [${tag}]`);
816
745
  }
817
746
  }
818
747
  static extend(unit, component, props) {
@@ -823,7 +752,7 @@ class Unit {
823
752
  else {
824
753
  const backupComponent = unit._.currentComponent;
825
754
  unit._.currentComponent = component;
826
- const defines = (_a = component(unit, props)) !== null && _a !== void 0 ? _a : {};
755
+ const defines = (_a = component(unit, props !== null && props !== void 0 ? props : {})) !== null && _a !== void 0 ? _a : {};
827
756
  unit._.currentComponent = backupComponent;
828
757
  Unit.component2units.add(component, unit);
829
758
  unit._.components.push(component);
@@ -1023,22 +952,22 @@ const xnew$1 = Object.assign(function (...args) {
1023
952
  }, {
1024
953
  /**
1025
954
  * Creates a nested HTML/SVG element within the current component
1026
- * @param tag - HTML or SVG tag string (e.g., '<div class="my-class">', '<span style="color:red">', '<svg viewBox="0 0 24 24">')
955
+ * @param target - HTML or SVG tag string (e.g., '<div class="my-class">', '<span style="color:red">', '<svg viewBox="0 0 24 24">')
1027
956
  * @returns The created HTML/SVG element
1028
957
  * @throws Error if called after component initialization
1029
958
  * @example
1030
959
  * const div = xnew.nest('<div>')
1031
960
  * div.textContent = 'Hello'
1032
961
  */
1033
- nest(tag) {
962
+ nest(target) {
1034
963
  try {
1035
964
  if (Unit.currentUnit._.state !== 'invoked') {
1036
965
  throw new Error('xnew.nest can not be called after initialized.');
1037
966
  }
1038
- return Unit.nest(Unit.currentUnit, tag);
967
+ return Unit.nest(Unit.currentUnit, target);
1039
968
  }
1040
969
  catch (error) {
1041
- console.error('xnew.nest(tag: string): ', error);
970
+ console.error('xnew.nest(target: HTMLElement | SVGElement | string): ', error);
1042
971
  throw error;
1043
972
  }
1044
973
  },
@@ -1058,7 +987,8 @@ const xnew$1 = Object.assign(function (...args) {
1058
987
  }
1059
988
  const defines = Unit.extend(Unit.currentUnit, component, props);
1060
989
  if (typeof component === 'function') {
1061
- return Unit.context(Unit.currentUnit, component, Unit.currentUnit);
990
+ Unit.context(Unit.currentUnit, component, Unit.currentUnit);
991
+ Unit.context(Unit.currentUnit._.parent, component, Unit.currentUnit);
1062
992
  }
1063
993
  return defines;
1064
994
  }
@@ -1267,48 +1197,39 @@ const xnew$1 = Object.assign(function (...args) {
1267
1197
  },
1268
1198
  });
1269
1199
 
1270
- function OpenAndClose(unit, { open = false } = {}) {
1200
+ function OpenAndClose(unit, { open = true, transition = { duration: 200, easing: 'ease' } }) {
1271
1201
  let state = open ? 1.0 : 0.0;
1272
- let direction = state === 1.0 ? +1 : (state === 0.0 ? -1 : null);
1202
+ let sign = open ? +1 : -1;
1273
1203
  let timer = xnew$1.timeout(() => xnew$1.emit('-transition', { state }));
1274
1204
  return {
1275
- toggle(duration = 200, easing = 'ease') {
1276
- if (direction === null || direction < 0) {
1277
- unit.open(duration, easing);
1278
- }
1279
- else {
1280
- unit.close(duration, easing);
1281
- }
1205
+ toggle() {
1206
+ sign < 0 ? unit.open() : unit.close();
1282
1207
  },
1283
- open(duration = 200, easing = 'ease') {
1284
- if (direction === null || direction < 0) {
1285
- direction = +1;
1286
- const d = 1 - state;
1287
- timer === null || timer === void 0 ? void 0 : timer.clear();
1288
- timer = xnew$1.transition((x) => {
1289
- const y = x < 1.0 ? (1 - x) * d : 0.0;
1290
- state = 1.0 - y;
1291
- xnew$1.emit('-transition', { state });
1292
- }, duration * d, easing)
1293
- .timeout(() => {
1294
- xnew$1.emit('-opened', { state });
1295
- });
1296
- }
1208
+ open() {
1209
+ var _a, _b;
1210
+ sign = +1;
1211
+ const d = 1 - state;
1212
+ const duration = ((_a = transition === null || transition === void 0 ? void 0 : transition.duration) !== null && _a !== void 0 ? _a : 200) * d;
1213
+ const easing = (_b = transition === null || transition === void 0 ? void 0 : transition.easing) !== null && _b !== void 0 ? _b : 'ease';
1214
+ timer === null || timer === void 0 ? void 0 : timer.clear();
1215
+ timer = xnew$1.transition((x) => {
1216
+ state = 1.0 - (x < 1.0 ? (1 - x) * d : 0.0);
1217
+ xnew$1.emit('-transition', { state });
1218
+ }, duration, easing)
1219
+ .timeout(() => xnew$1.emit('-opened', { state }));
1297
1220
  },
1298
- close(duration = 200, easing = 'ease') {
1299
- if (direction === null || direction > 0) {
1300
- direction = -1;
1301
- const d = state;
1302
- timer === null || timer === void 0 ? void 0 : timer.clear();
1303
- timer = xnew$1.transition((x) => {
1304
- const y = x < 1.0 ? (1 - x) * d : 0.0;
1305
- state = y;
1306
- xnew$1.emit('-transition', { state });
1307
- }, duration * d, easing)
1308
- .timeout(() => {
1309
- xnew$1.emit('-closed', { state });
1310
- });
1311
- }
1221
+ close() {
1222
+ var _a, _b;
1223
+ sign = -1;
1224
+ const d = state;
1225
+ const duration = ((_a = transition === null || transition === void 0 ? void 0 : transition.duration) !== null && _a !== void 0 ? _a : 200) * d;
1226
+ const easing = (_b = transition === null || transition === void 0 ? void 0 : transition.easing) !== null && _b !== void 0 ? _b : 'ease';
1227
+ timer === null || timer === void 0 ? void 0 : timer.clear();
1228
+ timer = xnew$1.transition((x) => {
1229
+ state = x < 1.0 ? (1 - x) * d : 0.0;
1230
+ xnew$1.emit('-transition', { state });
1231
+ }, duration, easing)
1232
+ .timeout(() => xnew$1.emit('-closed', { state }));
1312
1233
  },
1313
1234
  };
1314
1235
  }
@@ -1321,17 +1242,14 @@ function Accordion(unit) {
1321
1242
  outer.style.opacity = state.toString();
1322
1243
  });
1323
1244
  }
1324
- function Modal(unit, { background = 'rgba(0, 0, 0, 0.1)' } = {}) {
1245
+ function Popup(unit) {
1325
1246
  const system = xnew$1.context(OpenAndClose);
1326
1247
  system.on('-closed', () => unit.finalize());
1327
- xnew$1.nest('<div style="position: fixed; inset: 0; z-index: 1000;">');
1328
- unit.on('click', ({ event }) => system.close());
1329
- const outer = xnew$1.nest(`<div style="width: 100%; height: 100%; opacity: 0;"">`);
1330
- xnew$1.nest('<div style="position: absolute; inset: 0; margin: auto; width: max-content; height: max-content;">');
1331
- unit.on('click', ({ event }) => event.stopPropagation());
1332
- outer.style.background = background;
1248
+ system.open();
1249
+ xnew$1.nest('<div style="position: fixed; inset: 0; z-index: 1000; opacity: 0;">');
1250
+ unit.on('click', ({ event }) => event.target === unit.element && system.close());
1333
1251
  system.on('-transition', ({ state }) => {
1334
- outer.style.opacity = state.toString();
1252
+ unit.element.style.opacity = state.toString();
1335
1253
  });
1336
1254
  }
1337
1255
 
@@ -1383,7 +1301,7 @@ function AnalogStick(unit, { stroke = 'currentColor', strokeOpacity = 0.8, strok
1383
1301
  target.element.style.left = `${vector.x * size / 4}px`;
1384
1302
  target.element.style.top = `${vector.y * size / 4}px`;
1385
1303
  const nexttype = { dragstart: '-down', dragmove: '-move' }[type];
1386
- xnew$1.emit(nexttype, { type: nexttype, vector });
1304
+ xnew$1.emit(nexttype, { vector });
1387
1305
  });
1388
1306
  unit.on('dragend', () => {
1389
1307
  const size = unit.element.clientWidth;
@@ -1391,7 +1309,7 @@ function AnalogStick(unit, { stroke = 'currentColor', strokeOpacity = 0.8, strok
1391
1309
  target.element.style.filter = '';
1392
1310
  target.element.style.left = `${vector.x * size / 4}px`;
1393
1311
  target.element.style.top = `${vector.y * size / 4}px`;
1394
- xnew$1.emit('-up', { type: '-up', vector });
1312
+ xnew$1.emit('-up', { vector });
1395
1313
  });
1396
1314
  }
1397
1315
  function DPad(unit, { diagonal = true, stroke = 'currentColor', strokeOpacity = 0.8, strokeWidth = 1, strokeLinejoin = 'round', fill = '#FFF', fillOpacity = 0.8 } = {}) {
@@ -1456,54 +1374,42 @@ function DPad(unit, { diagonal = true, stroke = 'currentColor', strokeOpacity =
1456
1374
  });
1457
1375
  }
1458
1376
 
1459
- function GUIPanel(unit, { name, open = false, params }) {
1377
+ const currentColorA = 'color-mix(in srgb, currentColor 70%, transparent)';
1378
+ const currentColorB = 'color-mix(in srgb, currentColor 10%, transparent)';
1379
+ function Panel(unit, { name, open = false, params }) {
1460
1380
  const object = params !== null && params !== void 0 ? params : {};
1461
1381
  xnew$1.extend(Group, { name, open });
1462
1382
  return {
1463
1383
  group({ name, open, params }, inner) {
1464
1384
  const group = xnew$1((unit) => {
1465
- xnew$1.extend(GUIPanel, { name, open, params: params !== null && params !== void 0 ? params : object });
1385
+ xnew$1.extend(Panel, { name, open, params: params !== null && params !== void 0 ? params : object });
1466
1386
  inner(unit);
1467
1387
  });
1468
- group.on('-eventcapture', ({ event, key, value }) => {
1469
- xnew$1.emit('-eventcapture', { event, key, value });
1470
- });
1471
1388
  return group;
1472
1389
  },
1473
1390
  button(key) {
1474
1391
  const button = xnew$1(Button, { key });
1475
- button.on('click', ({ event }) => {
1476
- xnew$1.emit('-eventcapture', { event, key });
1477
- });
1478
1392
  return button;
1479
1393
  },
1480
1394
  select(key, { options = [] } = {}) {
1481
1395
  var _a, _b;
1482
1396
  object[key] = (_b = (_a = object[key]) !== null && _a !== void 0 ? _a : options[0]) !== null && _b !== void 0 ? _b : '';
1483
1397
  const select = xnew$1(Select, { key, value: object[key], options });
1484
- select.on('input', ({ event, value }) => {
1485
- xnew$1.emit('-eventcapture', { event, key, value });
1486
- });
1398
+ select.on('input', ({ value }) => object[key] = value);
1487
1399
  return select;
1488
1400
  },
1489
1401
  range(key, options = {}) {
1490
- var _a;
1491
- object[key] = (_a = object[key]) !== null && _a !== void 0 ? _a : 0;
1402
+ var _a, _b;
1403
+ object[key] = (_b = (_a = object[key]) !== null && _a !== void 0 ? _a : options.min) !== null && _b !== void 0 ? _b : 0;
1492
1404
  const number = xnew$1(Range, Object.assign({ key, value: object[key] }, options));
1493
- number.on('input', ({ event, value }) => {
1494
- object[key] = value;
1495
- xnew$1.emit('-eventcapture', { event, key, value });
1496
- });
1405
+ number.on('input', ({ value }) => object[key] = value);
1497
1406
  return number;
1498
1407
  },
1499
1408
  checkbox(key) {
1500
1409
  var _a;
1501
1410
  object[key] = (_a = object[key]) !== null && _a !== void 0 ? _a : false;
1502
1411
  const checkbox = xnew$1(Checkbox, { key, value: object[key] });
1503
- checkbox.on('input', ({ event, value }) => {
1504
- object[key] = value;
1505
- xnew$1.emit('-eventcapture', { event, key, value });
1506
- });
1412
+ checkbox.on('input', ({ value }) => object[key] = value);
1507
1413
  return checkbox;
1508
1414
  },
1509
1415
  separator() {
@@ -1514,7 +1420,7 @@ function GUIPanel(unit, { name, open = false, params }) {
1514
1420
  function Group(group, { name, open = false }) {
1515
1421
  xnew$1.extend(OpenAndClose, { open });
1516
1422
  if (name) {
1517
- xnew$1('<div style="display: flex; align-items: center; cursor: pointer;">', (unit) => {
1423
+ xnew$1('<div style="height: 2rem; margin: 0.125rem 0; display: flex; align-items: center; cursor: pointer; user-select: none;">', (unit) => {
1518
1424
  unit.on('click', () => group.toggle());
1519
1425
  xnew$1('<svg viewBox="0 0 12 12" style="width: 1rem; height: 1rem; margin-right: 0.25rem;" fill="none" stroke="currentColor">', (unit) => {
1520
1426
  xnew$1('<path d="M6 2 10 6 6 10" />');
@@ -1526,11 +1432,11 @@ function Group(group, { name, open = false }) {
1526
1432
  xnew$1.extend(Accordion);
1527
1433
  }
1528
1434
  function Button(unit, { key = '' }) {
1529
- xnew$1.nest('<button style="margin: 0.125rem; padding: 0.125rem; border: 1px solid; border-radius: 0.25rem; cursor: pointer;">');
1435
+ xnew$1.nest('<button style="margin: 0.125rem 0; height: 2rem; border: 1px solid; border-radius: 0.25rem; cursor: pointer;">');
1530
1436
  unit.element.textContent = key;
1531
1437
  unit.on('pointerover', () => {
1532
- unit.element.style.background = 'rgba(0, 0, 128, 0.1)';
1533
- unit.element.style.borderColor = 'blue';
1438
+ unit.element.style.background = currentColorB;
1439
+ unit.element.style.borderColor = currentColorA;
1534
1440
  });
1535
1441
  unit.on('pointerout', () => {
1536
1442
  unit.element.style.background = '';
@@ -1544,31 +1450,101 @@ function Button(unit, { key = '' }) {
1544
1450
  });
1545
1451
  }
1546
1452
  function Separator(unit) {
1547
- xnew$1.nest('<div style="margin: 0.5rem 0; border-top: 1px solid;">');
1453
+ xnew$1.nest(`<div style="margin: 0.5rem 0; border-top: 1px solid ${currentColorA};">`);
1548
1454
  }
1549
1455
  function Range(unit, { key = '', value, min = 0, max = 100, step = 1 }) {
1550
- xnew$1.nest(`<div style="margin: 0.125rem;">`);
1551
- const status = xnew$1('<div style="display: flex; justify-content: space-between;">', (unit) => {
1552
- xnew$1('<div style="flex: 1;">', key);
1553
- xnew$1('<div key="status" style="flex: none;">', value);
1456
+ value = value !== null && value !== void 0 ? value : min;
1457
+ xnew$1.nest(`<div style="position: relative; height: 2rem; margin: 0.125rem 0; cursor: pointer; user-select: none;">`);
1458
+ // fill bar
1459
+ const ratio = (value - min) / (max - min);
1460
+ const fill = xnew$1(`<div style="position: absolute; top: 0; left: 0; bottom: 0; width: ${ratio * 100}%; background: ${currentColorB}; border: 1px solid ${currentColorA}; border-radius: 0.25rem; transition: width 0.05s;">`);
1461
+ // overlay labels
1462
+ const status = xnew$1('<div style="position: absolute; inset: 0; padding: 0 0.5rem; display: flex; justify-content: space-between; align-items: center; pointer-events: none;">', (unit) => {
1463
+ xnew$1('<div>', key);
1464
+ xnew$1('<div key="status">', value);
1554
1465
  });
1555
- xnew$1.nest(`<input type="range" name="${key}" min="${min}" max="${max}" step="${step}" value="${value}" style="width: 100%; cursor: pointer;">`);
1466
+ // hidden native input for interaction
1467
+ xnew$1.nest(`<input type="range" name="${key}" min="${min}" max="${max}" step="${step}" value="${value}" style="position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; margin: 0;">`);
1556
1468
  unit.on('input', ({ event }) => {
1557
- status.element.querySelector('[key="status"]').textContent = event.target.value;
1469
+ const v = Number(event.target.value);
1470
+ const r = (v - min) / (max - min);
1471
+ fill.element.style.width = `${r * 100}%`;
1472
+ status.element.querySelector('[key="status"]').textContent = String(v);
1558
1473
  });
1559
1474
  }
1560
1475
  function Checkbox(unit, { key = '', value } = {}) {
1561
- xnew$1.nest(`<label style="margin: 0.125rem; display: flex; align-items: center; cursor: pointer;">`);
1476
+ xnew$1.nest(`<div style="position: relative; height: 2rem; margin: 0.125rem 0; padding: 0 0.5rem; display: flex; align-items: center; cursor: pointer; user-select: none;">`);
1562
1477
  xnew$1('<div style="flex: 1;">', key);
1563
- xnew$1.nest(`<input type="checkbox" name="${key}" ${value ? 'checked' : ''} style="margin-right: 0.25rem;">`);
1478
+ const box = xnew$1(`<div style="width: 1.25rem; height: 1.25rem; border: 1px solid ${currentColorA}; border-radius: 0.25rem; display: flex; align-items: center; justify-content: center; transition: background 0.1s;">`, () => {
1479
+ xnew$1(`<svg viewBox="0 0 12 12" style="width: 1.25rem; height: 1.25rem; opacity: 0; transition: opacity 0.1s;" fill="none" stroke="${currentColorA}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">`, () => {
1480
+ xnew$1('<path d="M2 6 5 9 10 3" />');
1481
+ });
1482
+ });
1483
+ const check = box.element.querySelector('svg');
1484
+ const update = (checked) => {
1485
+ box.element.style.background = checked ? currentColorB : '';
1486
+ check.style.opacity = checked ? '1' : '0';
1487
+ };
1488
+ update(!!value);
1489
+ xnew$1.nest(`<input type="checkbox" name="${key}" ${value ? 'checked' : ''} style="position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; margin: 0;">`);
1490
+ unit.on('input', ({ event, value }) => {
1491
+ update(value);
1492
+ });
1564
1493
  }
1565
- function Select(unit, { key = '', value, options = [] } = {}) {
1566
- xnew$1.nest(`<div style="margin: 0.125rem; display: flex; align-items: center;">`);
1494
+ function Select(_, { key = '', value, options = [] } = {}) {
1495
+ var _a;
1496
+ const initial = (_a = value !== null && value !== void 0 ? value : options[0]) !== null && _a !== void 0 ? _a : '';
1497
+ xnew$1.nest(`<div style="position: relative; height: 2rem; margin: 0.125rem 0; padding: 0 0.5rem; display: flex; align-items: center;">`);
1567
1498
  xnew$1('<div style="flex: 1;">', key);
1568
- xnew$1.nest(`<select name="${key}" style="padding: 0.125rem; border: 1px solid; border-radius: 0.25rem; cursor: pointer;">`);
1569
- for (const option of options) {
1570
- xnew$1(`<option value="${option}" ${option === value ? 'selected' : ''}>`, option);
1571
- }
1499
+ const native = xnew$1(`<select name="${key}" style="display: none;">`, () => {
1500
+ for (const option of options) {
1501
+ xnew$1(`<option value="${option}" ${option === initial ? 'selected' : ''}>`, option);
1502
+ }
1503
+ });
1504
+ const button = xnew$1(`<div style="height: 2rem; padding: 0 1.5rem 0 0.5rem; display: flex; align-items: center; border: 1px solid ${currentColorA}; border-radius: 0.25rem; cursor: pointer; user-select: none; min-width: 3rem; white-space: nowrap;">`, initial);
1505
+ xnew$1(`<svg viewBox="0 0 12 12" style="position: absolute; right: 1.0rem; width: 0.75rem; height: 0.75rem; pointer-events: none;" fill="none" stroke="${currentColorA}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">`, () => {
1506
+ xnew$1('<path d="M2 4 6 8 10 4" />');
1507
+ });
1508
+ button.on('click', () => {
1509
+ xnew$1((list) => {
1510
+ xnew$1.nest(`<div style="position: fixed; border: 1px solid ${currentColorA}; border-radius: 0.25rem; overflow: hidden; z-index: 1000;">`);
1511
+ const updatePosition = () => {
1512
+ const rect = button.element.getBoundingClientRect();
1513
+ list.element.style.right = (window.innerWidth - rect.right) + 'px';
1514
+ list.element.style.top = rect.bottom + 'px';
1515
+ list.element.style.minWidth = rect.width + 'px';
1516
+ };
1517
+ updatePosition();
1518
+ list.element.style.background = getEffectiveBg(button.element);
1519
+ window.addEventListener('scroll', updatePosition, true);
1520
+ list.on('finalize', () => window.removeEventListener('scroll', updatePosition, true));
1521
+ for (const option of options) {
1522
+ const item = xnew$1(`<div style="height: 2rem; padding: 0 0.5rem; display: flex; align-items: center; cursor: pointer; user-select: none;">`, option);
1523
+ item.on('pointerover', () => item.element.style.background = currentColorB);
1524
+ item.on('pointerout', () => item.element.style.background = '');
1525
+ item.on('click', () => {
1526
+ button.element.textContent = option;
1527
+ native.element.value = option;
1528
+ native.element.dispatchEvent(new Event('input', { bubbles: false }));
1529
+ list.finalize();
1530
+ });
1531
+ }
1532
+ list.on('click.outside', () => {
1533
+ list.finalize();
1534
+ });
1535
+ });
1536
+ });
1537
+ xnew$1.nest(native.element);
1538
+ }
1539
+ function getEffectiveBg(el) {
1540
+ let current = el.parentElement;
1541
+ while (current) {
1542
+ const bg = getComputedStyle(current).backgroundColor;
1543
+ if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent')
1544
+ return bg;
1545
+ current = current.parentElement;
1546
+ }
1547
+ return 'Canvas';
1572
1548
  }
1573
1549
 
1574
1550
  const context = new window.AudioContext();
@@ -1800,9 +1776,9 @@ const basics = {
1800
1776
  OpenAndClose,
1801
1777
  AnalogStick,
1802
1778
  DPad,
1803
- GUIPanel,
1779
+ Panel,
1804
1780
  Accordion,
1805
- Modal,
1781
+ Popup,
1806
1782
  };
1807
1783
  const audio = {
1808
1784
  load(path) {