@zeyos/zx 4.2.0 → 4.3.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +5 -0
  3. package/dist/types/components/calendar/calendar.d.ts +19 -2
  4. package/dist/types/components/dialog/dialog.d.ts +6 -1
  5. package/dist/types/components/dropdown/dropdown.d.ts +5 -0
  6. package/dist/types/components/launcher/launcher.d.ts +5 -0
  7. package/dist/types/components/message/message.d.ts +9 -1
  8. package/dist/types/components/modal/modal.d.ts +44 -7
  9. package/dist/types/components/sheet/sheet.d.ts +22 -12
  10. package/dist/types/core/overlay-host.d.ts +12 -0
  11. package/dist/zx-zeyos.esm.js +138 -32
  12. package/dist/zx-zeyos.esm.min.js +1 -1
  13. package/dist/zx.css +259 -132
  14. package/dist/zx.esm.js +277 -82
  15. package/dist/zx.esm.min.js +3 -3
  16. package/dist/zx.global.js +277 -82
  17. package/dist/zx.global.min.js +3 -3
  18. package/dist/zx.min.css +1 -1
  19. package/docs/api.json +36 -0
  20. package/docs/llms.md +37 -18
  21. package/docs/llms.txt +1 -1
  22. package/package.json +1 -1
  23. package/src/components/account-menu/account-menu.css +1 -1
  24. package/src/components/app-rail/app-rail.css +1 -1
  25. package/src/components/calendar/calendar.css +66 -24
  26. package/src/components/calendar/calendar.js +114 -19
  27. package/src/components/context-menu/context-menu.css +2 -2
  28. package/src/components/context-menu/context-menu.js +5 -1
  29. package/src/components/date-picker/date-picker.css +18 -11
  30. package/src/components/datebox/date-range-box.css +4 -2
  31. package/src/components/datebox/datebox.css +4 -2
  32. package/src/components/dialog/dialog.css +4 -3
  33. package/src/components/dialog/dialog.js +2 -1
  34. package/src/components/dropdown/dropdown.css +4 -2
  35. package/src/components/dropdown/dropdown.js +7 -2
  36. package/src/components/launcher/launcher.css +10 -8
  37. package/src/components/launcher/launcher.js +9 -2
  38. package/src/components/menu-button/menu-button.css +2 -2
  39. package/src/components/message/message.css +7 -16
  40. package/src/components/message/message.js +17 -11
  41. package/src/components/modal/modal.css +6 -4
  42. package/src/components/modal/modal.js +99 -25
  43. package/src/components/select/select.css +9 -7
  44. package/src/components/sheet/sheet.css +25 -7
  45. package/src/components/sheet/sheet.js +43 -23
  46. package/src/components/table/table.css +5 -3
  47. package/src/components/tag-picker/tag-picker.css +5 -3
  48. package/src/components/tooltip/tooltip.css +4 -2
  49. package/src/components/tooltip/tooltip.js +4 -1
  50. package/src/core/overlay-host.js +23 -0
  51. package/styles/tokens/dark.css +4 -0
  52. package/styles/tokens/semantic.css +83 -8
package/dist/zx.global.js CHANGED
@@ -10527,6 +10527,15 @@ var zx = (() => {
10527
10527
  element.replaceChildren(...state.children);
10528
10528
  }
10529
10529
 
10530
+ // src/core/overlay-host.js
10531
+ var SCOPE_SELECTOR = ".zx-scope, [data-zx-theme], [data-zx-density], [data-zx-preset]";
10532
+ function overlayHost(source = null) {
10533
+ const context = source == null ? document.activeElement instanceof Element ? document.activeElement : null : resolveElement(source);
10534
+ const scope2 = context?.closest?.(SCOPE_SELECTOR) ?? null;
10535
+ if (!scope2 || scope2 === document.documentElement || scope2 === document.body) return document.body;
10536
+ return scope2;
10537
+ }
10538
+
10530
10539
  // src/components/modal/modal.js
10531
10540
  var Modal = class extends Component {
10532
10541
  static cssName = "modal";
@@ -10536,13 +10545,17 @@ var zx = (() => {
10536
10545
  width: "auto",
10537
10546
  closable: true,
10538
10547
  lightDismiss: false,
10539
- destroyOnClose: false
10548
+ destroyOnClose: false,
10549
+ scope: null
10540
10550
  };
10541
- #pendingResult;
10542
- #hasPendingResult = false;
10551
+ /** @type {Map<number, {result: unknown}>} */
10552
+ #pendingResults = /* @__PURE__ */ new Map();
10543
10553
  #destroying = false;
10554
+ #presentation = 0;
10555
+ /** @type {number[]} */
10556
+ #nativeClosingPresentations = [];
10544
10557
  /**
10545
- * Creates a modal appended to `document.body`.
10558
+ * Creates a modal in the configured theme scope, or at document level when none is present.
10546
10559
  * @param {Element|string|null} [_target=null] Ignored; Modal always owns its root dialog.
10547
10560
  * @param {ModalOptions} [options={}] Modal options.
10548
10561
  */
@@ -10555,14 +10568,7 @@ var zx = (() => {
10555
10568
  if (!this.options.closable || cancelEvent.defaultPrevented) event.preventDefault();
10556
10569
  });
10557
10570
  this.listen(this.el, "close", () => {
10558
- if (!this._isRealClose()) return;
10559
- this.el.dataset.state = "closed";
10560
- delete this.el.dataset.zxOverlayOrder;
10561
- const result = this.#hasPendingResult ? this.#pendingResult : this.el.returnValue || void 0;
10562
- this.#pendingResult = void 0;
10563
- this.#hasPendingResult = false;
10564
- this.emit("close", { result });
10565
- if (this.options.destroyOnClose && !this.#destroying) this.destroy();
10571
+ this._settleClose(this.#nativeClosingPresentations.shift());
10566
10572
  });
10567
10573
  this.listen(this.el, "click", (event) => {
10568
10574
  if (!this.options.lightDismiss || event.target !== this.el || !isBackdropClick(this.el, event)) return;
@@ -10578,7 +10584,7 @@ var zx = (() => {
10578
10584
  * @returns {Element}
10579
10585
  */
10580
10586
  mountTarget() {
10581
- return document.body;
10587
+ return overlayHost(this.options.scope);
10582
10588
  }
10583
10589
  /**
10584
10590
  * Puts the dialog on screen. Overridden by subclasses that open non-modally.
@@ -10587,17 +10593,78 @@ var zx = (() => {
10587
10593
  _show() {
10588
10594
  this.el.showModal();
10589
10595
  }
10596
+ /**
10597
+ * Removes the dialog from its current presentation layer.
10598
+ * Subclasses may replace native dialog presentation while preserving Modal lifecycle events.
10599
+ * @param {string} [result=''] Native string return value.
10600
+ * @param {number} [presentation=this.#presentation] Presentation generation being hidden.
10601
+ * @returns {void}
10602
+ */
10603
+ _hide(result = "", presentation = this.#presentation) {
10604
+ this.#nativeClosingPresentations.push(presentation);
10605
+ try {
10606
+ this.el.close(result);
10607
+ } catch (error2) {
10608
+ this.#nativeClosingPresentations.pop();
10609
+ throw error2;
10610
+ }
10611
+ }
10612
+ /**
10613
+ * Shows a new presentation and gives it a generation of its own. Close events are queued by the
10614
+ * platform, so the generation lets an event from an earlier presentation expire harmlessly if
10615
+ * the same overlay has already reopened.
10616
+ * @returns {void}
10617
+ */
10618
+ _present() {
10619
+ this._show();
10620
+ this.#presentation += 1;
10621
+ }
10622
+ /**
10623
+ * Hides the current presentation and records which generation its queued close event belongs to.
10624
+ * @param {string} [result=''] Native string return value.
10625
+ * @returns {void}
10626
+ */
10627
+ _dismiss(result = "") {
10628
+ this._hide(result, this.#presentation);
10629
+ }
10630
+ /**
10631
+ * Reports whether the current presentation layer is visible.
10632
+ * @returns {boolean}
10633
+ */
10634
+ _isShown() {
10635
+ return this.el.open;
10636
+ }
10590
10637
  /**
10591
10638
  * Whether an incoming `close` event is a real dismissal.
10592
10639
  *
10593
- * A dialog cannot move between the top layer and the page flow while it is open, so a subclass
10594
- * that re-hosts itself has to close and immediately reopen. `close` is dispatched in a queued
10595
- * task, not synchronously, so that stale event arrives once the dialog is already open again —
10596
- * late enough to clobber `data-state`, fire a `close` nobody asked for, and trip
10597
- * `destroyOnClose`. Returning false from an override drops it at the source instead.
10640
+ * `close` is dispatched in a queued task, not synchronously. By the time it arrives the overlay
10641
+ * may have reopened or crossed presentation layers, so only the event for the current hidden
10642
+ * generation may settle state, emit lifecycle events, or trigger `destroyOnClose`.
10643
+ * @param {number|undefined} closingPresentation Presentation that produced the event, if known.
10598
10644
  * @returns {boolean}
10599
10645
  */
10600
- _isRealClose() {
10646
+ _isRealClose(closingPresentation) {
10647
+ if (closingPresentation !== void 0 && closingPresentation !== this.#presentation) return false;
10648
+ return !this._isShown();
10649
+ }
10650
+ /**
10651
+ * Settles one presentation's close without allowing a queued event to invalidate a newer one.
10652
+ * Popover-based subclasses call this directly; native dialog events arrive through the listener.
10653
+ * @param {number|undefined} closingPresentation Presentation that was hidden, if known.
10654
+ * @returns {boolean} Whether this event closed the current presentation.
10655
+ */
10656
+ _settleClose(closingPresentation) {
10657
+ if (!this._isRealClose(closingPresentation)) {
10658
+ if (closingPresentation !== void 0) this.#pendingResults.delete(closingPresentation);
10659
+ return false;
10660
+ }
10661
+ this.el.dataset.state = "closed";
10662
+ delete this.el.dataset.zxOverlayOrder;
10663
+ const pending = closingPresentation === void 0 ? void 0 : this.#pendingResults.get(closingPresentation);
10664
+ if (closingPresentation !== void 0) this.#pendingResults.delete(closingPresentation);
10665
+ const result = pending ? pending.result : this.el.returnValue || void 0;
10666
+ this.emit("close", { result });
10667
+ if (this.options.destroyOnClose && !this.#destroying) this.destroy();
10601
10668
  return true;
10602
10669
  }
10603
10670
  /**
@@ -10622,10 +10689,12 @@ var zx = (() => {
10622
10689
  */
10623
10690
  open() {
10624
10691
  if (this.isOpen()) return this;
10692
+ const host = this.mountTarget();
10693
+ if (this.el.parentElement !== host) host.append(this.el);
10625
10694
  this.el.dataset.zxOverlayOrder = String(nextOverlayOrder());
10626
10695
  this.el.returnValue = "";
10627
10696
  try {
10628
- this._show();
10697
+ this._present();
10629
10698
  } catch (error2) {
10630
10699
  delete this.el.dataset.zxOverlayOrder;
10631
10700
  throw error2;
@@ -10641,9 +10710,14 @@ var zx = (() => {
10641
10710
  */
10642
10711
  close(result) {
10643
10712
  if (!this.isOpen()) return this;
10644
- this.#pendingResult = result;
10645
- this.#hasPendingResult = true;
10646
- this.el.close(typeof result === "string" ? result : "");
10713
+ const presentation = this.#presentation;
10714
+ this.#pendingResults.set(presentation, { result });
10715
+ try {
10716
+ this._dismiss(typeof result === "string" ? result : "");
10717
+ } catch (error2) {
10718
+ this.#pendingResults.delete(presentation);
10719
+ throw error2;
10720
+ }
10647
10721
  return this;
10648
10722
  }
10649
10723
  /**
@@ -10660,7 +10734,7 @@ var zx = (() => {
10660
10734
  * @returns {boolean}
10661
10735
  */
10662
10736
  isOpen() {
10663
- return this.el.open;
10737
+ return this._isShown();
10664
10738
  }
10665
10739
  /**
10666
10740
  * Closes and removes the owned dialog. Safe to call repeatedly.
@@ -10669,7 +10743,7 @@ var zx = (() => {
10669
10743
  destroy() {
10670
10744
  if (this.#destroying) return;
10671
10745
  this.#destroying = true;
10672
- if (this.isOpen()) this.el.close();
10746
+ if (this.isOpen()) this._dismiss();
10673
10747
  super.destroy();
10674
10748
  }
10675
10749
  /** @param {string|number} width @returns {void} */
@@ -10734,7 +10808,7 @@ var zx = (() => {
10734
10808
  /** @type {Element|null} */
10735
10809
  #opener = null;
10736
10810
  /**
10737
- * Creates a structured dialog appended to `document.body`.
10811
+ * Creates a structured dialog in the configured theme scope, or at document level when unscoped.
10738
10812
  * @param {Element|string|null} [_target=null] Ignored; Dialog always owns its root.
10739
10813
  * @param {DialogOptions} [options={}] Dialog options.
10740
10814
  */
@@ -15631,7 +15705,8 @@ var zx = (() => {
15631
15705
  offset: 4,
15632
15706
  matchWidth: false,
15633
15707
  openOn: "click",
15634
- closeOnSelect: false
15708
+ closeOnSelect: false,
15709
+ scope: null
15635
15710
  };
15636
15711
  /** @type {Element} */
15637
15712
  #anchor;
@@ -15650,6 +15725,7 @@ var zx = (() => {
15650
15725
  if (!resolved) throw new TypeError("Dropdown anchor could not be resolved");
15651
15726
  super(null, options);
15652
15727
  this.#anchor = resolved;
15728
+ overlayHost(this.options.scope ?? resolved).append(this.el);
15653
15729
  this.#rememberAnchorAttribute("aria-expanded");
15654
15730
  this.#rememberAnchorAttribute("aria-controls");
15655
15731
  this.#anchor.setAttribute("aria-expanded", "false");
@@ -15672,7 +15748,6 @@ var zx = (() => {
15672
15748
  popover: "manual"
15673
15749
  });
15674
15750
  panel.dataset.state = "closed";
15675
- document.body.append(panel);
15676
15751
  return panel;
15677
15752
  }
15678
15753
  /**
@@ -15682,6 +15757,8 @@ var zx = (() => {
15682
15757
  */
15683
15758
  open() {
15684
15759
  if (this.isOpen() || this.#destroyed) return this;
15760
+ const host = overlayHost(this.options.scope ?? this.#anchor);
15761
+ if (this.el.parentElement !== host) host.append(this.el);
15685
15762
  this.#position = position(this.#anchor, this.el, {
15686
15763
  placement: this.options.placement,
15687
15764
  offset: Number(this.options.offset),
@@ -20341,7 +20418,8 @@ var zx = (() => {
20341
20418
  emptyText: "No results",
20342
20419
  loadingText: "Searching\u2026",
20343
20420
  shortcut: "mod+k",
20344
- hints: { move: "Move", open: "Open", close: "Close" }
20421
+ hints: { move: "Move", open: "Open", close: "Close" },
20422
+ scope: null
20345
20423
  };
20346
20424
  /**
20347
20425
  * Creates an owned launcher dialog or enhances an existing `<dialog>` target.
@@ -20432,7 +20510,7 @@ var zx = (() => {
20432
20510
  )
20433
20511
  );
20434
20512
  dialog.dataset.state = "closed";
20435
- if (this._createdRoot) document.body.append(dialog);
20513
+ if (this._createdRoot) overlayHost(this.options.scope).append(dialog);
20436
20514
  this.listen(input, "input", () => {
20437
20515
  this._query = /** @type {HTMLInputElement} */
20438
20516
  input.value;
@@ -20483,6 +20561,10 @@ var zx = (() => {
20483
20561
  /** Opens the launcher and focuses/selects its query. @returns {this} @fires Launcher#open */
20484
20562
  open() {
20485
20563
  if (this.isOpen() || this._destroyed) return this;
20564
+ if (this._createdRoot) {
20565
+ const host = overlayHost(this.options.scope);
20566
+ if (this.el.parentElement !== host) host.append(this.el);
20567
+ }
20486
20568
  this.el.showModal();
20487
20569
  this.el.dataset.state = "open";
20488
20570
  this.refs.input.setAttribute("aria-expanded", "true");
@@ -21361,6 +21443,7 @@ var zx = (() => {
21361
21443
  if (!resolved) throw new TypeError("Tooltip anchor could not be resolved");
21362
21444
  super(null, options);
21363
21445
  this.#anchor = resolved;
21446
+ overlayHost(resolved).append(this.el);
21364
21447
  this.#content = this.options.content;
21365
21448
  this.#disabled = Boolean(this.options.disabled);
21366
21449
  this.#describedBy = resolved.getAttribute("aria-describedby");
@@ -21421,7 +21504,6 @@ var zx = (() => {
21421
21504
  popover: "manual"
21422
21505
  });
21423
21506
  panel.dataset.state = "closed";
21424
- document.body.append(panel);
21425
21507
  return panel;
21426
21508
  }
21427
21509
  /**
@@ -21433,6 +21515,8 @@ var zx = (() => {
21433
21515
  show() {
21434
21516
  if (this.#destroyed || this.#disabled || this.isOpen()) return this;
21435
21517
  this.#cancelTimers();
21518
+ const host = overlayHost(this.#anchor);
21519
+ if (this.el.parentElement !== host) host.append(this.el);
21436
21520
  const node = resolveContent(this.#content);
21437
21521
  if (node === null) return this;
21438
21522
  this.el.replaceChildren(node);
@@ -22879,8 +22963,8 @@ var zx = (() => {
22879
22963
  closable: true,
22880
22964
  maxVisible: 5
22881
22965
  };
22882
- /** @type {Message|null} */
22883
- static #floating = null;
22966
+ /** @type {WeakMap<Element, Message>} */
22967
+ static #floating = /* @__PURE__ */ new WeakMap();
22884
22968
  /** @returns {HTMLElement} */
22885
22969
  render() {
22886
22970
  const created = !this.el;
@@ -22959,15 +23043,16 @@ var zx = (() => {
22959
23043
  * @returns {MessageHandle}
22960
23044
  */
22961
23045
  static show(msg, options = {}) {
22962
- return this.#floatingRegion().show(msg, options);
23046
+ return this.#floatingRegion(options.scope).show(msg, options);
22963
23047
  }
22964
23048
  /**
22965
23049
  * Shows a floating progress status.
22966
23050
  * @param {string} text Initial status text.
23051
+ * @param {{scope?: Element|string|null}} [options={}] Floating-region scope.
22967
23052
  * @returns {ProgressHandle}
22968
23053
  */
22969
- static progress(text4) {
22970
- return this.#floatingRegion()._progress(text4);
23054
+ static progress(text4, options = {}) {
23055
+ return this.#floatingRegion(options.scope)._progress(text4);
22971
23056
  }
22972
23057
  /** @returns {void} */
22973
23058
  destroy() {
@@ -23145,19 +23230,22 @@ var zx = (() => {
23145
23230
  this._entries.delete(entry);
23146
23231
  this._queue.remove(entry);
23147
23232
  }
23148
- /** @returns {Message} */
23149
- static #floatingRegion() {
23150
- if (this.#floating) return this.#floating;
23233
+ /** @param {Element|string|null|undefined} scope @returns {Message} */
23234
+ static #floatingRegion(scope2) {
23235
+ const host = overlayHost(scope2 ?? null);
23236
+ const existing = this.#floating.get(host);
23237
+ if (existing) return existing;
23151
23238
  const region = h("div", {
23152
23239
  class: "zx-message-region",
23153
23240
  popover: "manual",
23154
23241
  role: "status",
23155
23242
  ariaLive: "polite"
23156
23243
  });
23157
- document.body.append(region);
23158
- this.#floating = new _Message(region);
23244
+ host.append(region);
23245
+ const floating = new _Message(region);
23246
+ this.#floating.set(host, floating);
23159
23247
  if (typeof region.showPopover === "function") region.showPopover();
23160
- return this.#floating;
23248
+ return floating;
23161
23249
  }
23162
23250
  };
23163
23251
  function normalizeKind3(value) {
@@ -23245,7 +23333,6 @@ var zx = (() => {
23245
23333
  constructor(_target = null, options = {}) {
23246
23334
  super(null, options);
23247
23335
  this._dock = null;
23248
- this._handoff = false;
23249
23336
  this.setSide(this.options.side);
23250
23337
  this.el.dataset.backdrop = BACKDROPS.has(this.options.backdrop) ? this.options.backdrop : "dim";
23251
23338
  this.el.dataset.modality = modality(this.options.modal);
@@ -23420,7 +23507,7 @@ var zx = (() => {
23420
23507
  * @returns {Element}
23421
23508
  */
23422
23509
  mountTarget() {
23423
- return this._dock?.el ?? document.body;
23510
+ return this._dock?.el ?? super.mountTarget();
23424
23511
  }
23425
23512
  /**
23426
23513
  * Reports whether a dock currently owns this sheet's positioning.
@@ -23446,35 +23533,23 @@ var zx = (() => {
23446
23533
  /**
23447
23534
  * Moves the dialog to its current mount and, when it was open, reopens it there.
23448
23535
  *
23449
- * The close is unavoidable — a dialog cannot cross between the top layer and the flow while
23450
- * open — but it is invisible: `_isRealClose()` drops the stale event, and reopening bypasses
23451
- * `open()` so no lifecycle event is emitted for what is only a change of address.
23536
+ * The close is unavoidable — a dialog cannot cross between presentation layers while open —
23537
+ * but it is invisible: Modal's presentation generation drops the stale event, and reopening
23538
+ * bypasses `open()` so no lifecycle event is emitted for what is only a change of address.
23452
23539
  * @returns {void}
23453
23540
  */
23454
23541
  _rehost() {
23455
23542
  const wasOpen = this.isOpen();
23456
- if (wasOpen) {
23457
- this._handoff = true;
23458
- this.el.close();
23459
- }
23543
+ if (wasOpen) this._dismiss();
23460
23544
  this.mountTarget().append(this.el);
23461
23545
  if (!wasOpen) return;
23462
23546
  this.el.returnValue = "";
23463
- this._show();
23547
+ this._present();
23464
23548
  this.el.dataset.state = "open";
23465
23549
  if (this._dock) delete /** @type {HTMLElement} */
23466
23550
  this.el.dataset.zxOverlayOrder;
23467
23551
  else this.el.dataset.zxOverlayOrder = String(nextOverlayOrder3());
23468
23552
  }
23469
- /**
23470
- * Drops the stale `close` a re-hosting handoff leaves behind.
23471
- * @returns {boolean}
23472
- */
23473
- _isRealClose() {
23474
- if (!this._handoff) return true;
23475
- this._handoff = false;
23476
- return false;
23477
- }
23478
23553
  /**
23479
23554
  * Opens the sheet, trapping focus first where the modality asks for it.
23480
23555
  * @returns {this}
@@ -23555,10 +23630,38 @@ var zx = (() => {
23555
23630
  */
23556
23631
  _show() {
23557
23632
  if (this.options.modal === true && !this._dock) {
23633
+ this.el.removeAttribute("popover");
23634
+ this.el.removeAttribute("aria-modal");
23558
23635
  super._show();
23559
23636
  return;
23560
23637
  }
23561
- this.el.show();
23638
+ this.el.setAttribute("aria-modal", "false");
23639
+ if (this._dock) {
23640
+ this.el.removeAttribute("popover");
23641
+ this.el.show();
23642
+ return;
23643
+ }
23644
+ this.el.setAttribute("popover", "manual");
23645
+ this.el.showPopover();
23646
+ }
23647
+ /**
23648
+ * Hides a native dialog or a non-modal Sheet promoted through the popover top layer.
23649
+ * @param {string} [result=''] Native string return value.
23650
+ * @param {number} [presentation] Presentation generation being hidden.
23651
+ * @returns {void}
23652
+ */
23653
+ _hide(result = "", presentation) {
23654
+ if (!this.el.matches(":popover-open")) {
23655
+ super._hide(result, presentation);
23656
+ return;
23657
+ }
23658
+ this.el.returnValue = result;
23659
+ this.el.hidePopover();
23660
+ queueMicrotask(() => this._settleClose(presentation));
23661
+ }
23662
+ /** @returns {boolean} */
23663
+ _isShown() {
23664
+ return this.el.matches(":popover-open") || super._isShown();
23562
23665
  }
23563
23666
  /**
23564
23667
  * Releases the focus trap and the owned dialog.
@@ -23883,7 +23986,11 @@ var zx = (() => {
23883
23986
  super(null, options);
23884
23987
  this.#target = resolved;
23885
23988
  document.body.append(this.el);
23886
- this.#dropdown = new Dropdown(this.el, { openOn: "manual", placement: "bottom-start" });
23989
+ this.#dropdown = new Dropdown(this.el, {
23990
+ openOn: "manual",
23991
+ placement: "bottom-start",
23992
+ scope: resolved
23993
+ });
23887
23994
  this.#panel = this.#dropdown.getPanel();
23888
23995
  this.#panel.classList.add("zx-context-menu");
23889
23996
  this.#panel.setAttribute("role", "menu");
@@ -24737,7 +24844,11 @@ var zx = (() => {
24737
24844
  }).sort(compareEvents);
24738
24845
  const row2 = h(
24739
24846
  "section",
24740
- { class: "zx-calendar__agenda-day", role: "row" },
24847
+ {
24848
+ class: "zx-calendar__agenda-day",
24849
+ role: "row",
24850
+ dataset: { date: calendarDayKey(day) }
24851
+ },
24741
24852
  h("button", {
24742
24853
  type: "button",
24743
24854
  class: "zx-calendar__agenda-heading",
@@ -24747,11 +24858,13 @@ var zx = (() => {
24747
24858
  disabled: this.options.disabled
24748
24859
  }, this._format(day, { weekday: "long", day: "numeric", month: "long" }))
24749
24860
  );
24861
+ const entries = h("div", { class: "zx-calendar__agenda-events", role: "gridcell" });
24750
24862
  if (!events.length) {
24751
- row2.append(h("p", { class: "zx-calendar__agenda-empty", role: "gridcell" }, this.msg("noEvents")));
24863
+ entries.append(h("p", { class: "zx-calendar__agenda-empty" }, this.msg("noEvents")));
24752
24864
  } else {
24753
- for (const event of events) row2.append(this._eventElement(event, { variant: "agenda", day }));
24865
+ for (const event of events) entries.append(this._eventElement(event, { variant: "agenda", day }));
24754
24866
  }
24867
+ row2.append(entries);
24755
24868
  grid2.append(row2);
24756
24869
  }
24757
24870
  return grid2;
@@ -25021,6 +25134,7 @@ var zx = (() => {
25021
25134
  dataset: {
25022
25135
  eventId: key,
25023
25136
  variant: context.variant,
25137
+ allDay: event.allDay ? "true" : null,
25024
25138
  draggable: this._canMove(event) ? "true" : null
25025
25139
  },
25026
25140
  style: event.color ? { "--zx-calendar-event-color": event.color } : null
@@ -25287,7 +25401,9 @@ var zx = (() => {
25287
25401
  const item = this._eventById(control.dataset.eventId);
25288
25402
  const action = control.dataset.eventAction === "resize" ? "resize" : "move";
25289
25403
  if (!item || (action === "move" ? !this._canMove(item) : !this._canResize(item))) return;
25290
- control.setPointerCapture?.(event.pointerId);
25404
+ event.preventDefault();
25405
+ this._capturePointer(event.pointerId);
25406
+ const offset = action === "move" ? this._dragOffsetForEvent(item, event.clientX, event.clientY) : null;
25291
25407
  this._pointer = {
25292
25408
  type: "event",
25293
25409
  pointerId: event.pointerId,
@@ -25297,6 +25413,8 @@ var zx = (() => {
25297
25413
  event: cloneCalendarEvent(item),
25298
25414
  action,
25299
25415
  control,
25416
+ timeOffsetMinutes: offset?.minutes ?? 0,
25417
+ dayOffset: offset?.days ?? 0,
25300
25418
  moved: false
25301
25419
  };
25302
25420
  return;
@@ -25305,7 +25423,8 @@ var zx = (() => {
25305
25423
  if (!slot || !this.options.selectable) return;
25306
25424
  const date = this._dateFromTarget(slot);
25307
25425
  if (!date) return;
25308
- slot.setPointerCapture?.(event.pointerId);
25426
+ event.preventDefault();
25427
+ this._capturePointer(event.pointerId);
25309
25428
  this._pointer = {
25310
25429
  type: "selection",
25311
25430
  pointerId: event.pointerId,
@@ -25338,7 +25457,13 @@ var zx = (() => {
25338
25457
  const wrapper = pointer.control.closest(".zx-calendar__event");
25339
25458
  if (wrapper) {
25340
25459
  wrapper.dataset.state = "dragging";
25341
- wrapper.style.transform = `translate(${event.clientX - pointer.startX}px, ${event.clientY - pointer.startY}px)`;
25460
+ if (pointer.action === "resize") {
25461
+ wrapper.style.transform = "";
25462
+ const height = this._timedPreviewHeight(proposed);
25463
+ if (height) wrapper.style.blockSize = height;
25464
+ } else {
25465
+ wrapper.style.transform = `translate(${event.clientX - pointer.startX}px, ${event.clientY - pointer.startY}px)`;
25466
+ }
25342
25467
  wrapper.dataset.preview = this._eventTime(proposed, proposed.start);
25343
25468
  }
25344
25469
  this._announce(this.msg(
@@ -25353,6 +25478,7 @@ var zx = (() => {
25353
25478
  const pointer = this._pointer;
25354
25479
  if (!pointer || pointer.pointerId !== event.pointerId) return;
25355
25480
  this._pointer = null;
25481
+ this._releasePointerCapture(pointer.pointerId);
25356
25482
  this._clearSelectionPreview();
25357
25483
  if (!pointer.moved) return;
25358
25484
  event.preventDefault();
@@ -25370,6 +25496,7 @@ var zx = (() => {
25370
25496
  _cancelPointer(event) {
25371
25497
  if (!this._pointer || this._pointer.pointerId !== event.pointerId) return;
25372
25498
  const control = this._pointer.control;
25499
+ this._releasePointerCapture(this._pointer.pointerId);
25373
25500
  this._pointer = null;
25374
25501
  this._clearSelectionPreview();
25375
25502
  control.closest?.(".zx-calendar__event")?.removeAttribute("data-state");
@@ -25377,13 +25504,14 @@ var zx = (() => {
25377
25504
  }
25378
25505
  /** @param {CalendarEvent} event @param {'move'|'resize'} action @param {number} x @param {number} y @returns {CalendarEvent|null} */
25379
25506
  _proposalAtPoint(event, action, x, y) {
25380
- const timed = this._timeAtPoint(x, y);
25507
+ const resizeTimed = action === "resize" && !calendarEventSpansDays(event);
25508
+ const timed = this._timeAtPoint(x, y, resizeTimed ? event.start : null);
25381
25509
  if (timed && !calendarEventSpansDays(event)) {
25382
25510
  if (action === "move") {
25383
25511
  const duration = event.end.getTime() - event.start.getTime();
25384
25512
  const next3 = cloneCalendarEvent(event);
25385
- next3.start = timed;
25386
- next3.end = new Date(timed.getTime() + duration);
25513
+ next3.start = addMinutes(timed, -Number(this._pointer?.timeOffsetMinutes ?? 0));
25514
+ next3.end = new Date(next3.start.getTime() + duration);
25387
25515
  return next3;
25388
25516
  }
25389
25517
  const next2 = cloneCalendarEvent(event);
@@ -25392,33 +25520,36 @@ var zx = (() => {
25392
25520
  }
25393
25521
  const day = this._dayAtPoint(x, y);
25394
25522
  if (!day) return null;
25395
- const difference = calendarDayDifference(startOfCalendarDay(event.start), day);
25523
+ const targetDay = addCalendarDays(day, -Number(this._pointer?.dayOffset ?? 0));
25524
+ const difference = calendarDayDifference(startOfCalendarDay(event.start), targetDay);
25396
25525
  if (action === "move") return shiftCalendarEvent(event, { days: difference, minutes: 0 });
25397
25526
  const next = cloneCalendarEvent(event);
25398
25527
  const endDay = addCalendarDays(day, 1);
25399
25528
  next.end = new Date(endDay.getTime());
25400
25529
  return next.end > next.start ? next : null;
25401
25530
  }
25402
- /** @param {number} x @param {number} y @returns {Date|null} */
25403
- _timeAtPoint(x, y) {
25531
+ /** @param {number} x @param {number} y @param {Date|null} [fixedDay=null] @returns {Date|null} */
25532
+ _timeAtPoint(x, y, fixedDay = null) {
25404
25533
  const slots = this.refs.body.querySelector(".zx-calendar__slots");
25405
25534
  if (!slots) return null;
25406
25535
  const rect = slots.getBoundingClientRect();
25407
- if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom || rect.width <= 0 || rect.height <= 0) return null;
25536
+ const scroller = slots.closest(".zx-calendar__time-scroller");
25537
+ const viewport = scroller?.getBoundingClientRect();
25538
+ const left = viewport ? Math.max(rect.left, viewport.left) : rect.left;
25539
+ const right = viewport ? Math.min(rect.right, viewport.right) : rect.right;
25540
+ const top = viewport ? Math.max(rect.top, viewport.top) : rect.top;
25541
+ const bottom = viewport ? Math.min(rect.bottom, viewport.bottom) : rect.bottom;
25542
+ if (x < left || x > right || y < top || y > bottom || rect.width <= 0 || rect.height <= 0) return null;
25408
25543
  const columns = Number(slots.closest('[role="grid"]')?.getAttribute("aria-colcount")) || 1;
25409
25544
  const rows = Number(slots.closest('[role="grid"]')?.getAttribute("aria-rowcount")) || 1;
25410
- const column = Math.max(0, Math.min(columns - 1, Math.floor((x - rect.left) / rect.width * columns)));
25545
+ const fixedColumn = fixedDay ? this._timeGridDayIndex(fixedDay) : -1;
25546
+ const column = fixedColumn >= 0 ? fixedColumn : Math.max(0, Math.min(columns - 1, Math.floor((x - rect.left) / rect.width * columns)));
25411
25547
  const row2 = Math.max(0, Math.min(rows - 1, Math.floor((y - rect.top) / rect.height * rows)));
25412
25548
  const target = slots.querySelector(`[data-row-index="${row2}"][data-day-index="${column}"]`);
25413
25549
  return target ? this._dateFromTarget(target) : null;
25414
25550
  }
25415
25551
  /** @param {number} x @param {number} y @returns {Date|null} */
25416
25552
  _dayAtPoint(x, y) {
25417
- const elements = typeof document.elementsFromPoint === "function" ? document.elementsFromPoint(x, y) : [];
25418
- for (const element of elements) {
25419
- const date = element instanceof HTMLElement ? element.dataset.date : null;
25420
- if (date) return parseDayKey(date);
25421
- }
25422
25553
  const allDay = this.refs.body.querySelector(".zx-calendar__all-day-grid");
25423
25554
  if (allDay) {
25424
25555
  const rect = allDay.getBoundingClientRect();
@@ -25428,8 +25559,72 @@ var zx = (() => {
25428
25559
  return parseDayKey(cells[index]?.dataset.date);
25429
25560
  }
25430
25561
  }
25562
+ for (const monthEvents of this.refs.body.querySelectorAll(".zx-calendar__month-events")) {
25563
+ const rect = monthEvents.getBoundingClientRect();
25564
+ if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom || rect.width <= 0) continue;
25565
+ const start = parseDayKey(monthEvents.closest(".zx-calendar__month-week")?.dataset.weekStart);
25566
+ if (!start) continue;
25567
+ const index = Math.max(0, Math.min(6, Math.floor((x - rect.left) / rect.width * 7)));
25568
+ return addCalendarDays(start, index);
25569
+ }
25570
+ for (const agendaDay of this.refs.body.querySelectorAll(".zx-calendar__agenda-day")) {
25571
+ const rect = agendaDay.getBoundingClientRect();
25572
+ if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) continue;
25573
+ return parseDayKey(agendaDay.dataset.date);
25574
+ }
25575
+ const elements = typeof document.elementsFromPoint === "function" ? document.elementsFromPoint(x, y) : [];
25576
+ for (const element of elements) {
25577
+ const dateTarget = element instanceof HTMLElement ? element.closest("[data-date]") : null;
25578
+ const date = dateTarget instanceof HTMLElement ? dateTarget.dataset.date : null;
25579
+ if (date) return parseDayKey(date);
25580
+ }
25431
25581
  return null;
25432
25582
  }
25583
+ /** @param {CalendarEvent} event @param {number} x @param {number} y @returns {{minutes: number, days: number}} */
25584
+ _dragOffsetForEvent(event, x, y) {
25585
+ if (!calendarEventSpansDays(event)) {
25586
+ const timed = this._timeAtPoint(x, y);
25587
+ if (timed) {
25588
+ const slot = normalizeSlot(this.options.slotDuration);
25589
+ const duration = Math.max(0, Math.round((event.end.getTime() - event.start.getTime()) / 6e4));
25590
+ const maxOffset = Math.max(0, duration - slot);
25591
+ const minutes = Math.round((timed.getTime() - event.start.getTime()) / 6e4);
25592
+ return { minutes: Math.max(0, Math.min(maxOffset, minutes)), days: 0 };
25593
+ }
25594
+ }
25595
+ const day = this._dayAtPoint(x, y);
25596
+ const days = day ? calendarDayDifference(startOfCalendarDay(event.start), day) : 0;
25597
+ return { minutes: 0, days: Math.max(0, days) };
25598
+ }
25599
+ /** @param {Date} day @returns {number} */
25600
+ _timeGridDayIndex(day) {
25601
+ const key = calendarDayKey(day);
25602
+ const headings = [...this.refs.body.querySelectorAll(".zx-calendar__day-heading")];
25603
+ return headings.findIndex((heading) => heading.dataset.date === key);
25604
+ }
25605
+ /** @param {CalendarEvent} event @returns {string|null} */
25606
+ _timedPreviewHeight(event) {
25607
+ const grid2 = this.refs.body.querySelector(".zx-calendar__time-grid");
25608
+ if (!grid2 || calendarEventSpansDays(event)) return null;
25609
+ const min = normalizeMinute(this.options.slotMinTime, 0);
25610
+ const max = normalizeMinute(this.options.slotMaxTime, 1440, true);
25611
+ const slot = normalizeSlot(this.options.slotDuration);
25612
+ const start = event.start.getHours() * 60 + event.start.getMinutes();
25613
+ const end = event.end.getHours() * 60 + event.end.getMinutes();
25614
+ const minutes = Math.max(slot, Math.min(max, end) - Math.max(min, start));
25615
+ return `${minutes / (max - min) * 100}%`;
25616
+ }
25617
+ /** @param {number} pointerId @returns {void} */
25618
+ _capturePointer(pointerId) {
25619
+ try {
25620
+ this.el.setPointerCapture?.(pointerId);
25621
+ } catch {
25622
+ }
25623
+ }
25624
+ /** @param {number} pointerId @returns {void} */
25625
+ _releasePointerCapture(pointerId) {
25626
+ if (this.el.hasPointerCapture?.(pointerId)) this.el.releasePointerCapture?.(pointerId);
25627
+ }
25433
25628
  /** @param {Date} start @param {Date} end @returns {void} */
25434
25629
  _previewSelection(start, end) {
25435
25630
  const low = Math.min(start.getTime(), end.getTime());