@mulsense/xnew 0.6.2 → 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.js CHANGED
@@ -213,6 +213,21 @@
213
213
  }
214
214
  }
215
215
 
216
+ function addEvent(target, type, execute, options) {
217
+ let initalized = false;
218
+ const id = setTimeout(() => {
219
+ initalized = true;
220
+ target.addEventListener(type, execute, options);
221
+ }, 0);
222
+ return () => {
223
+ if (initalized === false) {
224
+ clearTimeout(id);
225
+ }
226
+ else {
227
+ target.removeEventListener(type, execute);
228
+ }
229
+ };
230
+ }
216
231
  class Eventor {
217
232
  constructor() {
218
233
  this.map = new MapMap();
@@ -220,53 +235,62 @@
220
235
  add(element, type, listener, options) {
221
236
  const props = { element, type, listener, options };
222
237
  let finalize;
223
- if (props.type === 'resize') {
224
- finalize = this.resize(props);
225
- }
226
- else if (props.type === 'change') {
227
- finalize = this.change(props);
228
- }
229
- else if (props.type === 'input') {
230
- finalize = this.input(props);
231
- }
232
- else if (props.type === 'wheel') {
233
- finalize = this.wheel(props);
234
- }
235
- else if (props.type === 'click') {
236
- finalize = this.click(props);
237
- }
238
- else if (props.type === 'click.outside') {
239
- finalize = this.click_outside(props);
240
- }
241
- else if (['pointerdown', 'pointermove', 'pointerup', 'pointerover', 'pointerout'].includes(props.type)) {
242
- finalize = this.pointer(props);
243
- }
244
- else if (['pointerdown.outside', 'pointermove.outside', 'pointerup.outside'].includes(props.type)) {
245
- finalize = this.pointer_outside(props);
246
- }
247
- else if (['mousedown', 'mousemove', 'mouseup', 'mouseover', 'mouseout'].includes(props.type)) {
248
- finalize = this.mouse(props);
249
- }
250
- else if (['touchstart', 'touchmove', 'touchend', 'touchcancel'].includes(props.type)) {
251
- finalize = this.touch(props);
252
- }
253
- else if (['dragstart', 'dragmove', 'dragend'].includes(props.type)) {
254
- finalize = this.drag(props);
255
- }
256
- else if (['gesturestart', 'gesturemove', 'gestureend'].includes(props.type)) {
257
- finalize = this.gesture(props);
258
- }
259
- else if (['keydown', 'keyup'].includes(props.type)) {
260
- finalize = this.key(props);
261
- }
262
- else if (['keydown.arrow', 'keyup.arrow'].includes(props.type)) {
263
- finalize = this.key_arrow(props);
238
+ if (props.type.indexOf('window.') === 0) {
239
+ if (['window.keydown', 'window.keyup'].includes(props.type)) {
240
+ finalize = this.window_key(props);
241
+ }
242
+ else if (['window.keydown.arrow', 'window.keyup.arrow'].includes(props.type)) {
243
+ finalize = this.window_key_arrow(props);
244
+ }
245
+ else if (['window.keydown.wasd', 'window.keyup.wasd'].includes(props.type)) {
246
+ finalize = this.window_key_wasd(props);
247
+ }
248
+ else {
249
+ finalize = this.window_basic(props);
250
+ }
264
251
  }
265
- else if (['keydown.wasd', 'keyup.wasd'].includes(props.type)) {
266
- finalize = this.key_wasd(props);
252
+ else if (props.type.indexOf('document.') === 0) {
253
+ {
254
+ finalize = this.document_basic(props);
255
+ }
267
256
  }
268
257
  else {
269
- finalize = this.basic(props);
258
+ if (props.type === 'resize') {
259
+ finalize = this.element_resize(props);
260
+ }
261
+ else if (props.type === 'change') {
262
+ finalize = this.element_change(props);
263
+ }
264
+ else if (props.type === 'input') {
265
+ finalize = this.element_input(props);
266
+ }
267
+ else if (props.type === 'wheel') {
268
+ finalize = this.element_wheel(props);
269
+ }
270
+ else if (props.type === 'click') {
271
+ finalize = this.element_click(props);
272
+ }
273
+ else if (props.type === 'click.outside') {
274
+ finalize = this.element_click_outside(props);
275
+ }
276
+ else if (['pointerdown', 'pointermove', 'pointerup', 'pointerover', 'pointerout'].includes(props.type)) {
277
+ finalize = this.element_pointer(props);
278
+ }
279
+ else if (['pointerdown.outside', 'pointermove.outside', 'pointerup.outside'].includes(props.type)) {
280
+ finalize = this.element_pointer_outside(props);
281
+ }
282
+ else if (['mousedown', 'mousemove', 'mouseup', 'mouseover', 'mouseout'].includes(props.type)) {
283
+ finalize = this.element_mouse(props);
284
+ }
285
+ else if (['touchstart', 'touchmove', 'touchend', 'touchcancel'].includes(props.type)) {
286
+ finalize = this.element_touch(props);
287
+ }
288
+ else if (['dragstart', 'dragmove', 'dragend'].includes(props.type)) {
289
+ finalize = this.element_drag(props);
290
+ }
291
+ else {
292
+ finalize = this.element_basic(props);
293
+ }
270
294
  }
271
295
  this.map.set(props.type, props.listener, finalize);
272
296
  }
@@ -277,16 +301,12 @@
277
301
  this.map.delete(type, listener);
278
302
  }
279
303
  }
280
- basic(props) {
281
- const execute = (event) => {
304
+ element_basic(props) {
305
+ return addEvent(props.element, props.type, (event) => {
282
306
  props.listener({ event });
283
- };
284
- props.element.addEventListener(props.type, execute, props.options);
285
- return () => {
286
- props.element.removeEventListener(props.type, execute);
287
- };
307
+ }, props.options);
288
308
  }
289
- resize(props) {
309
+ element_resize(props) {
290
310
  const observer = new ResizeObserver((entries) => {
291
311
  for (const entry of entries) {
292
312
  props.listener({});
@@ -298,8 +318,8 @@
298
318
  observer.unobserve(props.element);
299
319
  };
300
320
  }
301
- change(props) {
302
- const execute = (event) => {
321
+ element_change(props) {
322
+ return addEvent(props.element, props.type, (event) => {
303
323
  let value = null;
304
324
  if (event.target.type === 'checkbox') {
305
325
  value = event.target.checked;
@@ -311,14 +331,10 @@
311
331
  value = event.target.value;
312
332
  }
313
333
  props.listener({ event, value });
314
- };
315
- props.element.addEventListener(props.type, execute, props.options);
316
- return () => {
317
- props.element.removeEventListener(props.type, execute);
318
- };
334
+ }, props.options);
319
335
  }
320
- input(props) {
321
- const execute = (event) => {
336
+ element_input(props) {
337
+ return addEvent(props.element, props.type, (event) => {
322
338
  let value = null;
323
339
  if (event.target.type === 'checkbox') {
324
340
  value = event.target.checked;
@@ -330,88 +346,56 @@
330
346
  value = event.target.value;
331
347
  }
332
348
  props.listener({ event, value });
333
- };
334
- props.element.addEventListener(props.type, execute, props.options);
335
- return () => {
336
- props.element.removeEventListener(props.type, execute);
337
- };
349
+ }, props.options);
338
350
  }
339
- click(props) {
340
- const execute = (event) => {
351
+ element_click(props) {
352
+ return addEvent(props.element, props.type, (event) => {
341
353
  props.listener({ event, position: pointer(props.element, event).position });
342
- };
343
- props.element.addEventListener(props.type, execute, props.options);
344
- return () => {
345
- props.element.removeEventListener(props.type, execute);
346
- };
354
+ }, props.options);
347
355
  }
348
- click_outside(props) {
349
- const execute = (event) => {
356
+ element_click_outside(props) {
357
+ return addEvent(document, props.type.split('.')[0], (event) => {
350
358
  if (props.element.contains(event.target) === false) {
351
359
  props.listener({ event, position: pointer(props.element, event).position });
352
360
  }
353
- };
354
- document.addEventListener(props.type.split('.')[0], execute, props.options);
355
- return () => {
356
- document.removeEventListener(props.type.split('.')[0], execute);
357
- };
361
+ }, props.options);
358
362
  }
359
- pointer(props) {
360
- const execute = (event) => {
363
+ element_pointer(props) {
364
+ return addEvent(props.element, props.type, (event) => {
361
365
  props.listener({ event, position: pointer(props.element, event).position });
362
- };
363
- props.element.addEventListener(props.type, execute, props.options);
364
- return () => {
365
- props.element.removeEventListener(props.type, execute);
366
- };
366
+ }, props.options);
367
367
  }
368
- mouse(props) {
369
- const execute = (event) => {
368
+ element_mouse(props) {
369
+ return addEvent(props.element, props.type, (event) => {
370
370
  props.listener({ event, position: pointer(props.element, event).position });
371
- };
372
- props.element.addEventListener(props.type, execute, props.options);
373
- return () => {
374
- props.element.removeEventListener(props.type, execute);
375
- };
371
+ }, props.options);
376
372
  }
377
- touch(props) {
378
- const execute = (event) => {
373
+ element_touch(props) {
374
+ return addEvent(props.element, props.type, (event) => {
379
375
  props.listener({ event, position: pointer(props.element, event).position });
380
- };
381
- props.element.addEventListener(props.type, execute, props.options);
382
- return () => {
383
- props.element.removeEventListener(props.type, execute);
384
- };
376
+ }, props.options);
385
377
  }
386
- pointer_outside(props) {
387
- const execute = (event) => {
378
+ element_pointer_outside(props) {
379
+ return addEvent(document, props.type.split('.')[0], (event) => {
388
380
  if (props.element.contains(event.target) === false) {
389
381
  props.listener({ event, position: pointer(props.element, event).position });
390
382
  }
391
- };
392
- document.addEventListener(props.type.split('.')[0], execute, props.options);
393
- return () => {
394
- document.removeEventListener(props.type.split('.')[0], execute);
395
- };
383
+ }, props.options);
396
384
  }
397
- wheel(props) {
398
- const execute = (event) => {
385
+ element_wheel(props) {
386
+ return addEvent(props.element, props.type, (event) => {
399
387
  props.listener({ event, delta: { x: event.wheelDeltaX, y: event.wheelDeltaY } });
400
- };
401
- props.element.addEventListener(props.type, execute, props.options);
402
- return () => {
403
- props.element.removeEventListener(props.type, execute);
404
- };
388
+ }, props.options);
405
389
  }
406
- drag(props) {
390
+ element_drag(props) {
407
391
  let pointermove = null;
408
392
  let pointerup = null;
409
393
  let pointercancel = null;
410
- const pointerdown = (event) => {
394
+ const pointerdown = addEvent(props.element, 'pointerdown', (event) => {
411
395
  const id = event.pointerId;
412
396
  const position = pointer(props.element, event).position;
413
397
  let previous = position;
414
- pointermove = (event) => {
398
+ pointermove = addEvent(window, 'pointermove', (event) => {
415
399
  if (event.pointerId === id) {
416
400
  const position = pointer(props.element, event).position;
417
401
  const delta = { x: position.x - previous.x, y: position.y - previous.y };
@@ -420,8 +404,8 @@
420
404
  }
421
405
  previous = position;
422
406
  }
423
- };
424
- pointerup = (event) => {
407
+ }, props.options);
408
+ pointerup = addEvent(window, 'pointerup', (event) => {
425
409
  if (event.pointerId === id) {
426
410
  const position = pointer(props.element, event).position;
427
411
  if (props.type === 'dragend') {
@@ -429,8 +413,8 @@
429
413
  }
430
414
  remove();
431
415
  }
432
- };
433
- pointercancel = (event) => {
416
+ }, props.options);
417
+ pointercancel = addEvent(window, 'pointercancel', (event) => {
434
418
  if (event.pointerId === id) {
435
419
  const position = pointer(props.element, event).position;
436
420
  if (props.type === 'dragend') {
@@ -438,168 +422,102 @@
438
422
  }
439
423
  remove();
440
424
  }
441
- };
442
- window.addEventListener('pointermove', pointermove);
443
- window.addEventListener('pointerup', pointerup);
444
- window.addEventListener('pointercancel', pointercancel);
425
+ }, props.options);
445
426
  if (props.type === 'dragstart') {
446
427
  props.listener({ event, position, delta: { x: 0, y: 0 } });
447
428
  }
448
- };
429
+ }, props.options);
449
430
  function remove() {
450
- if (pointermove)
451
- window.removeEventListener('pointermove', pointermove);
452
- if (pointerup)
453
- window.removeEventListener('pointerup', pointerup);
454
- if (pointercancel)
455
- window.removeEventListener('pointercancel', pointercancel);
431
+ pointermove === null || pointermove === void 0 ? void 0 : pointermove();
456
432
  pointermove = null;
433
+ pointerup === null || pointerup === void 0 ? void 0 : pointerup();
457
434
  pointerup = null;
435
+ pointercancel === null || pointercancel === void 0 ? void 0 : pointercancel();
458
436
  pointercancel = null;
459
437
  }
460
- props.element.addEventListener('pointerdown', pointerdown, props.options);
461
438
  return () => {
462
- props.element.removeEventListener('pointerdown', pointerdown);
439
+ pointerdown();
463
440
  remove();
464
441
  };
465
442
  }
466
- gesture(props) {
467
- let isActive = false;
468
- const map = new Map();
469
- const element = props.element;
470
- const options = props.options;
471
- const dragstart = ({ event, position }) => {
472
- map.set(event.pointerId, position);
473
- isActive = map.size === 2 ? true : false;
474
- if (isActive === true && props.type === 'gesturestart') {
475
- props.listener({ event });
476
- }
477
- };
478
- const dragmove = ({ event, position, delta }) => {
479
- if (map.size >= 2 && isActive === true) {
480
- const a = map.get(event.pointerId);
481
- const b = getOthers(event.pointerId)[0];
482
- let scale = 0.0;
483
- {
484
- const v = { x: a.x - b.x, y: a.y - b.y };
485
- const s = v.x * v.x + v.y * v.y;
486
- scale = 1 + (s > 0.0 ? (v.x * delta.x + v.y * delta.y) / s : 0);
487
- }
488
- // let rotate = 0.0;
489
- // {
490
- // const c = { x: a.x + delta.x, y: a.y + delta.y };
491
- // const v1 = { x: a.x - b.x, y: a.y - b.y };
492
- // const v2 = { x: c.x - b.x, y: c.y - b.y };
493
- // const l1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
494
- // const l2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
495
- // if (l1 > 0.0 && l2 > 0.0) {
496
- // const angle = Math.acos((v1.x * v2.x + v1.y * v2.y) / (l1 * l2));
497
- // const sign = v1.x * v2.y - v1.y * v2.x;
498
- // rotate = sign > 0.0 ? +angle : -angle;
499
- // }
500
- // }
501
- if (props.type === 'gesturemove') {
502
- props.listener({ event, scale });
503
- }
504
- }
505
- map.set(event.pointerId, position);
506
- };
507
- const dragend = ({ event }) => {
508
- map.delete(event.pointerId);
509
- if (isActive === true && props.type === 'gestureend') {
510
- props.listener({ event, scale: 1.0 });
511
- }
512
- isActive = false;
513
- };
514
- this.add(element, 'dragstart', dragstart, options);
515
- this.add(element, 'dragmove', dragmove, options);
516
- this.add(element, 'dragend', dragend, options);
517
- function getOthers(id) {
518
- const backup = map.get(id);
519
- map.delete(id);
520
- const others = [...map.values()];
521
- map.set(id, backup);
522
- return others;
523
- }
524
- return () => {
525
- this.remove('dragstart', dragstart);
526
- this.remove('dragmove', dragmove);
527
- this.remove('dragend', dragend);
528
- };
443
+ window_basic(props) {
444
+ const type = props.type.substring('window.'.length);
445
+ return addEvent(window, type, (event) => {
446
+ props.listener({ event });
447
+ }, props.options);
529
448
  }
530
- key(props) {
531
- const execute = (event) => {
532
- if (props.type === 'keydown' && event.repeat)
449
+ window_key(props) {
450
+ const type = props.type.substring(props.type.indexOf('.') + 1);
451
+ return addEvent(window, type, (event) => {
452
+ if (event.repeat)
533
453
  return;
534
454
  props.listener({ event, code: event.code });
535
- };
536
- window.addEventListener(props.type, execute, props.options);
537
- return () => {
538
- window.removeEventListener(props.type, execute);
539
- };
455
+ }, props.options);
540
456
  }
541
- key_arrow(props) {
457
+ window_key_arrow(props) {
542
458
  const keymap = {};
543
- const keydown = (event) => {
459
+ const keydown = addEvent(window, 'keydown', (event) => {
544
460
  if (event.repeat)
545
461
  return;
546
462
  keymap[event.code] = 1;
547
- if (props.type === 'keydown.arrow' && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
463
+ if (props.type === 'window.keydown.arrow' && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
548
464
  const vector = {
549
465
  x: (keymap['ArrowLeft'] ? -1 : 0) + (keymap['ArrowRight'] ? +1 : 0),
550
466
  y: (keymap['ArrowUp'] ? -1 : 0) + (keymap['ArrowDown'] ? +1 : 0)
551
467
  };
552
468
  props.listener({ event, code: event.code, vector });
553
469
  }
554
- };
555
- const keyup = (event) => {
470
+ }, props.options);
471
+ const keyup = addEvent(window, 'keyup', (event) => {
556
472
  keymap[event.code] = 0;
557
- if (props.type === 'keyup.arrow' && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
473
+ if (props.type === 'window.keyup.arrow' && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.code)) {
558
474
  const vector = {
559
475
  x: (keymap['ArrowLeft'] ? -1 : 0) + (keymap['ArrowRight'] ? +1 : 0),
560
476
  y: (keymap['ArrowUp'] ? -1 : 0) + (keymap['ArrowDown'] ? +1 : 0)
561
477
  };
562
478
  props.listener({ event, code: event.code, vector });
563
479
  }
564
- };
565
- window.addEventListener('keydown', keydown, props.options);
566
- window.addEventListener('keyup', keyup, props.options);
480
+ }, props.options);
567
481
  return () => {
568
- window.removeEventListener('keydown', keydown);
569
- window.removeEventListener('keyup', keyup);
482
+ keydown();
483
+ keyup();
570
484
  };
571
485
  }
572
- key_wasd(props) {
486
+ window_key_wasd(props) {
573
487
  const keymap = {};
574
- const keydown = (event) => {
488
+ const finalize1 = addEvent(window, 'keydown', (event) => {
575
489
  if (event.repeat)
576
490
  return;
577
491
  keymap[event.code] = 1;
578
- if (props.type === 'keydown.wasd' && ['KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(event.code)) {
492
+ if (props.type === 'window.keydown.wasd' && ['KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(event.code)) {
579
493
  const vector = {
580
494
  x: (keymap['KeyA'] ? -1 : 0) + (keymap['KeyD'] ? +1 : 0),
581
495
  y: (keymap['KeyW'] ? -1 : 0) + (keymap['KeyS'] ? +1 : 0)
582
496
  };
583
497
  props.listener({ event, code: event.code, vector });
584
498
  }
585
- };
586
- const keyup = (event) => {
499
+ }, props.options);
500
+ const finalize2 = addEvent(window, 'keyup', (event) => {
587
501
  keymap[event.code] = 0;
588
- if (props.type === 'keyup.wasd' && ['KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(event.code)) {
502
+ if (props.type === 'window.keyup.wasd' && ['KeyW', 'KeyA', 'KeyS', 'KeyD'].includes(event.code)) {
589
503
  const vector = {
590
504
  x: (keymap['KeyA'] ? -1 : 0) + (keymap['KeyD'] ? +1 : 0),
591
505
  y: (keymap['KeyW'] ? -1 : 0) + (keymap['KeyS'] ? +1 : 0)
592
506
  };
593
507
  props.listener({ event, code: event.code, vector });
594
508
  }
595
- };
596
- window.addEventListener('keydown', keydown, props.options);
597
- window.addEventListener('keyup', keyup, props.options);
509
+ }, props.options);
598
510
  return () => {
599
- window.removeEventListener('keydown', keydown);
600
- window.removeEventListener('keyup', keyup);
511
+ finalize1();
512
+ finalize2();
601
513
  };
602
514
  }
515
+ document_basic(props) {
516
+ const type = props.type.substring('document.'.length);
517
+ return addEvent(document, type, (event) => {
518
+ props.listener({ event });
519
+ }, props.options);
520
+ }
603
521
  }
604
522
  function pointer(element, event) {
605
523
  const rect = element.getBoundingClientRect();
@@ -719,7 +637,7 @@
719
637
  baseComponent = (unit) => { };
720
638
  }
721
639
  const baseContext = (_a = parent === null || parent === void 0 ? void 0 : parent._.currentContext) !== null && _a !== void 0 ? _a : { stack: null };
722
- this._ = { parent, target, baseElement, baseContext, baseComponent, props: props !== null && props !== void 0 ? props : {} };
640
+ this._ = { parent, target, baseElement, baseContext, baseComponent, props };
723
641
  parent === null || parent === void 0 ? void 0 : parent._.children.push(this);
724
642
  Unit.initialize(this, null);
725
643
  }
@@ -741,8 +659,10 @@
741
659
  }
742
660
  }
743
661
  reboot() {
744
- var _a, _b;
745
- const anchor = (_b = (_a = this._.elements[0]) === null || _a === void 0 ? void 0 : _a.nextElementSibling) !== null && _b !== void 0 ? _b : null;
662
+ let anchor = null;
663
+ if (this._.nestElements[0] && this._.nestElements[0].owned === true) {
664
+ anchor = this._.nestElements[0].element.nextElementSibling;
665
+ }
746
666
  Unit.stop(this);
747
667
  Unit.finalize(this);
748
668
  Unit.initialize(this, anchor);
@@ -760,7 +680,7 @@
760
680
  protected: false,
761
681
  ancestors: unit._.parent ? [unit._.parent, ...unit._.parent._.ancestors] : [],
762
682
  children: [],
763
- elements: [],
683
+ nestElements: [],
764
684
  promises: [],
765
685
  components: [],
766
686
  listeners: new MapMap(),
@@ -785,10 +705,12 @@
785
705
  [...unit._.systems.finalize].reverse().forEach(({ execute }) => execute());
786
706
  unit.off();
787
707
  unit._.components.forEach((component) => Unit.component2units.delete(component, unit));
788
- if (unit._.elements.length > 0) {
789
- unit._.baseElement.removeChild(unit._.elements[0]);
790
- unit._.currentElement = unit._.baseElement;
708
+ for (const { element, owned } of unit._.nestElements.reverse()) {
709
+ if (owned === true) {
710
+ element.remove();
711
+ }
791
712
  }
713
+ unit._.currentElement = unit._.baseElement;
792
714
  // reset defines
793
715
  Object.keys(unit._.defines).forEach((key) => {
794
716
  delete unit[key];
@@ -797,28 +719,35 @@
797
719
  unit._.state = 'finalized';
798
720
  }
799
721
  }
800
- static nest(unit, tag, textContent) {
801
- const match = tag.match(/<((\w+)[^>]*?)\/?>/);
802
- if (match !== null) {
803
- let element;
804
- if (unit._.anchor !== null) {
805
- unit._.anchor.insertAdjacentHTML('beforebegin', `<${match[1]}></${match[2]}>`);
806
- element = unit._.anchor.previousElementSibling;
807
- unit._.anchor = null;
722
+ static nest(unit, target, textContent) {
723
+ if (target instanceof HTMLElement || target instanceof SVGElement) {
724
+ unit._.nestElements.push({ element: target, owned: false });
725
+ unit._.currentElement = target;
726
+ return target;
727
+ }
728
+ else {
729
+ const match = target.match(/<((\w+)[^>]*?)\/?>/);
730
+ if (match !== null) {
731
+ let element;
732
+ if (unit._.anchor !== null) {
733
+ unit._.anchor.insertAdjacentHTML('beforebegin', `<${match[1]}></${match[2]}>`);
734
+ element = unit._.anchor.previousElementSibling;
735
+ unit._.anchor = null;
736
+ }
737
+ else {
738
+ unit._.currentElement.insertAdjacentHTML('beforeend', `<${match[1]}></${match[2]}>`);
739
+ element = unit._.currentElement.children[unit._.currentElement.children.length - 1];
740
+ }
741
+ unit._.currentElement = element;
742
+ if (textContent !== undefined) {
743
+ element.textContent = textContent.toString();
744
+ }
745
+ unit._.nestElements.push({ element, owned: true });
746
+ return element;
808
747
  }
809
748
  else {
810
- unit._.currentElement.insertAdjacentHTML('beforeend', `<${match[1]}></${match[2]}>`);
811
- element = unit._.currentElement.children[unit._.currentElement.children.length - 1];
749
+ throw new Error(`xnew.nest: invalid html string [${target}]`);
812
750
  }
813
- unit._.currentElement = element;
814
- if (textContent !== undefined) {
815
- element.textContent = textContent.toString();
816
- }
817
- unit._.elements.push(element);
818
- return element;
819
- }
820
- else {
821
- throw new Error(`xnew.nest: invalid html string [${tag}]`);
822
751
  }
823
752
  }
824
753
  static extend(unit, component, props) {
@@ -829,7 +758,7 @@
829
758
  else {
830
759
  const backupComponent = unit._.currentComponent;
831
760
  unit._.currentComponent = component;
832
- const defines = (_a = component(unit, props)) !== null && _a !== void 0 ? _a : {};
761
+ const defines = (_a = component(unit, props !== null && props !== void 0 ? props : {})) !== null && _a !== void 0 ? _a : {};
833
762
  unit._.currentComponent = backupComponent;
834
763
  Unit.component2units.add(component, unit);
835
764
  unit._.components.push(component);
@@ -1029,22 +958,22 @@
1029
958
  }, {
1030
959
  /**
1031
960
  * Creates a nested HTML/SVG element within the current component
1032
- * @param tag - HTML or SVG tag string (e.g., '<div class="my-class">', '<span style="color:red">', '<svg viewBox="0 0 24 24">')
961
+ * @param target - HTML or SVG tag string (e.g., '<div class="my-class">', '<span style="color:red">', '<svg viewBox="0 0 24 24">')
1033
962
  * @returns The created HTML/SVG element
1034
963
  * @throws Error if called after component initialization
1035
964
  * @example
1036
965
  * const div = xnew.nest('<div>')
1037
966
  * div.textContent = 'Hello'
1038
967
  */
1039
- nest(tag) {
968
+ nest(target) {
1040
969
  try {
1041
970
  if (Unit.currentUnit._.state !== 'invoked') {
1042
971
  throw new Error('xnew.nest can not be called after initialized.');
1043
972
  }
1044
- return Unit.nest(Unit.currentUnit, tag);
973
+ return Unit.nest(Unit.currentUnit, target);
1045
974
  }
1046
975
  catch (error) {
1047
- console.error('xnew.nest(tag: string): ', error);
976
+ console.error('xnew.nest(target: HTMLElement | SVGElement | string): ', error);
1048
977
  throw error;
1049
978
  }
1050
979
  },
@@ -1064,7 +993,8 @@
1064
993
  }
1065
994
  const defines = Unit.extend(Unit.currentUnit, component, props);
1066
995
  if (typeof component === 'function') {
1067
- return Unit.context(Unit.currentUnit, component, Unit.currentUnit);
996
+ Unit.context(Unit.currentUnit, component, Unit.currentUnit);
997
+ Unit.context(Unit.currentUnit._.parent, component, Unit.currentUnit);
1068
998
  }
1069
999
  return defines;
1070
1000
  }
@@ -1273,32 +1203,38 @@
1273
1203
  },
1274
1204
  });
1275
1205
 
1276
- function OpenAndClose(unit, { open = false }) {
1206
+ function OpenAndClose(unit, { open = true, transition = { duration: 200, easing: 'ease' } }) {
1277
1207
  let state = open ? 1.0 : 0.0;
1278
1208
  let sign = open ? +1 : -1;
1279
1209
  let timer = xnew$1.timeout(() => xnew$1.emit('-transition', { state }));
1280
1210
  return {
1281
- toggle(duration = 200, easing = 'ease') {
1282
- sign < 0 ? unit.open(duration, easing) : unit.close(duration, easing);
1211
+ toggle() {
1212
+ sign < 0 ? unit.open() : unit.close();
1283
1213
  },
1284
- open(duration = 200, easing = 'ease') {
1214
+ open() {
1215
+ var _a, _b;
1285
1216
  sign = +1;
1286
1217
  const d = 1 - state;
1218
+ const duration = ((_a = transition === null || transition === void 0 ? void 0 : transition.duration) !== null && _a !== void 0 ? _a : 200) * d;
1219
+ const easing = (_b = transition === null || transition === void 0 ? void 0 : transition.easing) !== null && _b !== void 0 ? _b : 'ease';
1287
1220
  timer === null || timer === void 0 ? void 0 : timer.clear();
1288
1221
  timer = xnew$1.transition((x) => {
1289
1222
  state = 1.0 - (x < 1.0 ? (1 - x) * d : 0.0);
1290
1223
  xnew$1.emit('-transition', { state });
1291
- }, duration * d, easing)
1224
+ }, duration, easing)
1292
1225
  .timeout(() => xnew$1.emit('-opened', { state }));
1293
1226
  },
1294
- close(duration = 200, easing = 'ease') {
1227
+ close() {
1228
+ var _a, _b;
1295
1229
  sign = -1;
1296
1230
  const d = state;
1231
+ const duration = ((_a = transition === null || transition === void 0 ? void 0 : transition.duration) !== null && _a !== void 0 ? _a : 200) * d;
1232
+ const easing = (_b = transition === null || transition === void 0 ? void 0 : transition.easing) !== null && _b !== void 0 ? _b : 'ease';
1297
1233
  timer === null || timer === void 0 ? void 0 : timer.clear();
1298
1234
  timer = xnew$1.transition((x) => {
1299
1235
  state = x < 1.0 ? (1 - x) * d : 0.0;
1300
1236
  xnew$1.emit('-transition', { state });
1301
- }, duration * d, easing)
1237
+ }, duration, easing)
1302
1238
  .timeout(() => xnew$1.emit('-closed', { state }));
1303
1239
  },
1304
1240
  };
@@ -1312,16 +1248,12 @@
1312
1248
  outer.style.opacity = state.toString();
1313
1249
  });
1314
1250
  }
1315
- function Modal(unit) {
1251
+ function Popup(unit) {
1316
1252
  const system = xnew$1.context(OpenAndClose);
1317
- system.open();
1318
1253
  system.on('-closed', () => unit.finalize());
1319
- xnew$1.nest('<div style="position: absolute; inset: 0; z-index: 1000; opacity: 0;">');
1320
- unit.on('click', ({ event }) => {
1321
- if (event.target === unit.element) {
1322
- system.close();
1323
- }
1324
- });
1254
+ system.open();
1255
+ xnew$1.nest('<div style="position: fixed; inset: 0; z-index: 1000; opacity: 0;">');
1256
+ unit.on('click', ({ event }) => event.target === unit.element && system.close());
1325
1257
  system.on('-transition', ({ state }) => {
1326
1258
  unit.element.style.opacity = state.toString();
1327
1259
  });
@@ -1375,7 +1307,7 @@
1375
1307
  target.element.style.left = `${vector.x * size / 4}px`;
1376
1308
  target.element.style.top = `${vector.y * size / 4}px`;
1377
1309
  const nexttype = { dragstart: '-down', dragmove: '-move' }[type];
1378
- xnew$1.emit(nexttype, { type: nexttype, vector });
1310
+ xnew$1.emit(nexttype, { vector });
1379
1311
  });
1380
1312
  unit.on('dragend', () => {
1381
1313
  const size = unit.element.clientWidth;
@@ -1383,7 +1315,7 @@
1383
1315
  target.element.style.filter = '';
1384
1316
  target.element.style.left = `${vector.x * size / 4}px`;
1385
1317
  target.element.style.top = `${vector.y * size / 4}px`;
1386
- xnew$1.emit('-up', { type: '-up', vector });
1318
+ xnew$1.emit('-up', { vector });
1387
1319
  });
1388
1320
  }
1389
1321
  function DPad(unit, { diagonal = true, stroke = 'currentColor', strokeOpacity = 0.8, strokeWidth = 1, strokeLinejoin = 'round', fill = '#FFF', fillOpacity = 0.8 } = {}) {
@@ -1448,6 +1380,8 @@
1448
1380
  });
1449
1381
  }
1450
1382
 
1383
+ const currentColorA = 'color-mix(in srgb, currentColor 70%, transparent)';
1384
+ const currentColorB = 'color-mix(in srgb, currentColor 10%, transparent)';
1451
1385
  function Panel(unit, { name, open = false, params }) {
1452
1386
  const object = params !== null && params !== void 0 ? params : {};
1453
1387
  xnew$1.extend(Group, { name, open });
@@ -1504,11 +1438,11 @@
1504
1438
  xnew$1.extend(Accordion);
1505
1439
  }
1506
1440
  function Button(unit, { key = '' }) {
1507
- xnew$1.nest('<button style="margin: 0.125rem; height: 2rem; border: 1px solid; border-radius: 0.25rem; cursor: pointer;">');
1441
+ xnew$1.nest('<button style="margin: 0.125rem 0; height: 2rem; border: 1px solid; border-radius: 0.25rem; cursor: pointer;">');
1508
1442
  unit.element.textContent = key;
1509
1443
  unit.on('pointerover', () => {
1510
- unit.element.style.background = 'color-mix(in srgb, currentColor 5%, transparent)';
1511
- unit.element.style.borderColor = 'color-mix(in srgb, currentColor 40%, transparent)';
1444
+ unit.element.style.background = currentColorB;
1445
+ unit.element.style.borderColor = currentColorA;
1512
1446
  });
1513
1447
  unit.on('pointerout', () => {
1514
1448
  unit.element.style.background = '';
@@ -1522,16 +1456,16 @@
1522
1456
  });
1523
1457
  }
1524
1458
  function Separator(unit) {
1525
- xnew$1.nest('<div style="margin: 0.5rem 0; border-top: 1px solid color-mix(in srgb, currentColor 40%, transparent);">');
1459
+ xnew$1.nest(`<div style="margin: 0.5rem 0; border-top: 1px solid ${currentColorA};">`);
1526
1460
  }
1527
1461
  function Range(unit, { key = '', value, min = 0, max = 100, step = 1 }) {
1528
1462
  value = value !== null && value !== void 0 ? value : min;
1529
1463
  xnew$1.nest(`<div style="position: relative; height: 2rem; margin: 0.125rem 0; cursor: pointer; user-select: none;">`);
1530
1464
  // fill bar
1531
1465
  const ratio = (value - min) / (max - min);
1532
- const fill = xnew$1(`<div style="position: absolute; top: 0; left: 0; bottom: 0; width: ${ratio * 100}%; background: color-mix(in srgb, currentColor 5%, transparent); border: 1px solid color-mix(in srgb, currentColor 40%, transparent); border-radius: 0.25rem; transition: width 0.05s;">`);
1466
+ 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;">`);
1533
1467
  // overlay labels
1534
- const status = xnew$1('<div style="position: absolute; inset: 0; padding: 0 0.25rem; display: flex; justify-content: space-between; align-items: center; pointer-events: none;">', (unit) => {
1468
+ 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) => {
1535
1469
  xnew$1('<div>', key);
1536
1470
  xnew$1('<div key="status">', value);
1537
1471
  });
@@ -1545,16 +1479,16 @@
1545
1479
  });
1546
1480
  }
1547
1481
  function Checkbox(unit, { key = '', value } = {}) {
1548
- xnew$1.nest(`<div style="position: relative; height: 2rem; margin: 0.125rem 0; padding: 0 0.25rem; display: flex; align-items: center; cursor: pointer; user-select: none;">`);
1482
+ 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;">`);
1549
1483
  xnew$1('<div style="flex: 1;">', key);
1550
- const box = xnew$1('<div style="width: 1.25rem; height: 1.25rem; border: 1px solid color-mix(in srgb, currentColor 40%, transparent); border-radius: 0.25rem; display: flex; align-items: center; justify-content: center; transition: background 0.1s;">', () => {
1551
- xnew$1('<svg viewBox="0 0 12 12" style="width: 1.25rem; height: 1.25rem; opacity: 0; transition: opacity 0.1s;" fill="none" stroke="color-mix(in srgb, currentColor 80%, transparent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">', () => {
1484
+ 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;">`, () => {
1485
+ 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">`, () => {
1552
1486
  xnew$1('<path d="M2 6 5 9 10 3" />');
1553
1487
  });
1554
1488
  });
1555
1489
  const check = box.element.querySelector('svg');
1556
1490
  const update = (checked) => {
1557
- box.element.style.background = checked ? 'color-mix(in srgb, currentColor 5%, transparent)' : '';
1491
+ box.element.style.background = checked ? currentColorB : '';
1558
1492
  check.style.opacity = checked ? '1' : '0';
1559
1493
  };
1560
1494
  update(!!value);
@@ -1563,13 +1497,60 @@
1563
1497
  update(value);
1564
1498
  });
1565
1499
  }
1566
- function Select(unit, { key = '', value, options = [] } = {}) {
1567
- xnew$1.nest(`<div style="height: 2rem; margin: 0.125rem 0; padding: 0 0.25rem; display: flex; align-items: center;">`);
1500
+ function Select(_, { key = '', value, options = [] } = {}) {
1501
+ var _a;
1502
+ const initial = (_a = value !== null && value !== void 0 ? value : options[0]) !== null && _a !== void 0 ? _a : '';
1503
+ xnew$1.nest(`<div style="position: relative; height: 2rem; margin: 0.125rem 0; padding: 0 0.5rem; display: flex; align-items: center;">`);
1568
1504
  xnew$1('<div style="flex: 1;">', key);
1569
- xnew$1.nest(`<select name="${key}" style="height: 2rem; padding: 0.25rem; border: 1px solid color-mix(in srgb, currentColor 40%, transparent); border-radius: 0.25rem; cursor: pointer; user-select: none;">`);
1570
- for (const option of options) {
1571
- xnew$1(`<option value="${option}" ${option === value ? 'selected' : ''}>`, option);
1572
- }
1505
+ const native = xnew$1(`<select name="${key}" style="display: none;">`, () => {
1506
+ for (const option of options) {
1507
+ xnew$1(`<option value="${option}" ${option === initial ? 'selected' : ''}>`, option);
1508
+ }
1509
+ });
1510
+ 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);
1511
+ 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">`, () => {
1512
+ xnew$1('<path d="M2 4 6 8 10 4" />');
1513
+ });
1514
+ button.on('click', () => {
1515
+ xnew$1((list) => {
1516
+ xnew$1.nest(`<div style="position: fixed; border: 1px solid ${currentColorA}; border-radius: 0.25rem; overflow: hidden; z-index: 1000;">`);
1517
+ const updatePosition = () => {
1518
+ const rect = button.element.getBoundingClientRect();
1519
+ list.element.style.right = (window.innerWidth - rect.right) + 'px';
1520
+ list.element.style.top = rect.bottom + 'px';
1521
+ list.element.style.minWidth = rect.width + 'px';
1522
+ };
1523
+ updatePosition();
1524
+ list.element.style.background = getEffectiveBg(button.element);
1525
+ window.addEventListener('scroll', updatePosition, true);
1526
+ list.on('finalize', () => window.removeEventListener('scroll', updatePosition, true));
1527
+ for (const option of options) {
1528
+ const item = xnew$1(`<div style="height: 2rem; padding: 0 0.5rem; display: flex; align-items: center; cursor: pointer; user-select: none;">`, option);
1529
+ item.on('pointerover', () => item.element.style.background = currentColorB);
1530
+ item.on('pointerout', () => item.element.style.background = '');
1531
+ item.on('click', () => {
1532
+ button.element.textContent = option;
1533
+ native.element.value = option;
1534
+ native.element.dispatchEvent(new Event('input', { bubbles: false }));
1535
+ list.finalize();
1536
+ });
1537
+ }
1538
+ list.on('click.outside', () => {
1539
+ list.finalize();
1540
+ });
1541
+ });
1542
+ });
1543
+ xnew$1.nest(native.element);
1544
+ }
1545
+ function getEffectiveBg(el) {
1546
+ let current = el.parentElement;
1547
+ while (current) {
1548
+ const bg = getComputedStyle(current).backgroundColor;
1549
+ if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent')
1550
+ return bg;
1551
+ current = current.parentElement;
1552
+ }
1553
+ return 'Canvas';
1573
1554
  }
1574
1555
 
1575
1556
  const context = new window.AudioContext();
@@ -1803,7 +1784,7 @@
1803
1784
  DPad,
1804
1785
  Panel,
1805
1786
  Accordion,
1806
- Modal,
1787
+ Popup,
1807
1788
  };
1808
1789
  const audio = {
1809
1790
  load(path) {