@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.esm.js CHANGED
@@ -10286,6 +10286,15 @@ function restore2(element, state) {
10286
10286
  element.replaceChildren(...state.children);
10287
10287
  }
10288
10288
 
10289
+ // src/core/overlay-host.js
10290
+ var SCOPE_SELECTOR = ".zx-scope, [data-zx-theme], [data-zx-density], [data-zx-preset]";
10291
+ function overlayHost(source = null) {
10292
+ const context = source == null ? document.activeElement instanceof Element ? document.activeElement : null : resolveElement(source);
10293
+ const scope2 = context?.closest?.(SCOPE_SELECTOR) ?? null;
10294
+ if (!scope2 || scope2 === document.documentElement || scope2 === document.body) return document.body;
10295
+ return scope2;
10296
+ }
10297
+
10289
10298
  // src/components/modal/modal.js
10290
10299
  var Modal = class extends Component {
10291
10300
  static cssName = "modal";
@@ -10295,13 +10304,17 @@ var Modal = class extends Component {
10295
10304
  width: "auto",
10296
10305
  closable: true,
10297
10306
  lightDismiss: false,
10298
- destroyOnClose: false
10307
+ destroyOnClose: false,
10308
+ scope: null
10299
10309
  };
10300
- #pendingResult;
10301
- #hasPendingResult = false;
10310
+ /** @type {Map<number, {result: unknown}>} */
10311
+ #pendingResults = /* @__PURE__ */ new Map();
10302
10312
  #destroying = false;
10313
+ #presentation = 0;
10314
+ /** @type {number[]} */
10315
+ #nativeClosingPresentations = [];
10303
10316
  /**
10304
- * Creates a modal appended to `document.body`.
10317
+ * Creates a modal in the configured theme scope, or at document level when none is present.
10305
10318
  * @param {Element|string|null} [_target=null] Ignored; Modal always owns its root dialog.
10306
10319
  * @param {ModalOptions} [options={}] Modal options.
10307
10320
  */
@@ -10314,14 +10327,7 @@ var Modal = class extends Component {
10314
10327
  if (!this.options.closable || cancelEvent.defaultPrevented) event.preventDefault();
10315
10328
  });
10316
10329
  this.listen(this.el, "close", () => {
10317
- if (!this._isRealClose()) return;
10318
- this.el.dataset.state = "closed";
10319
- delete this.el.dataset.zxOverlayOrder;
10320
- const result = this.#hasPendingResult ? this.#pendingResult : this.el.returnValue || void 0;
10321
- this.#pendingResult = void 0;
10322
- this.#hasPendingResult = false;
10323
- this.emit("close", { result });
10324
- if (this.options.destroyOnClose && !this.#destroying) this.destroy();
10330
+ this._settleClose(this.#nativeClosingPresentations.shift());
10325
10331
  });
10326
10332
  this.listen(this.el, "click", (event) => {
10327
10333
  if (!this.options.lightDismiss || event.target !== this.el || !isBackdropClick(this.el, event)) return;
@@ -10337,7 +10343,7 @@ var Modal = class extends Component {
10337
10343
  * @returns {Element}
10338
10344
  */
10339
10345
  mountTarget() {
10340
- return document.body;
10346
+ return overlayHost(this.options.scope);
10341
10347
  }
10342
10348
  /**
10343
10349
  * Puts the dialog on screen. Overridden by subclasses that open non-modally.
@@ -10346,17 +10352,78 @@ var Modal = class extends Component {
10346
10352
  _show() {
10347
10353
  this.el.showModal();
10348
10354
  }
10355
+ /**
10356
+ * Removes the dialog from its current presentation layer.
10357
+ * Subclasses may replace native dialog presentation while preserving Modal lifecycle events.
10358
+ * @param {string} [result=''] Native string return value.
10359
+ * @param {number} [presentation=this.#presentation] Presentation generation being hidden.
10360
+ * @returns {void}
10361
+ */
10362
+ _hide(result = "", presentation = this.#presentation) {
10363
+ this.#nativeClosingPresentations.push(presentation);
10364
+ try {
10365
+ this.el.close(result);
10366
+ } catch (error2) {
10367
+ this.#nativeClosingPresentations.pop();
10368
+ throw error2;
10369
+ }
10370
+ }
10371
+ /**
10372
+ * Shows a new presentation and gives it a generation of its own. Close events are queued by the
10373
+ * platform, so the generation lets an event from an earlier presentation expire harmlessly if
10374
+ * the same overlay has already reopened.
10375
+ * @returns {void}
10376
+ */
10377
+ _present() {
10378
+ this._show();
10379
+ this.#presentation += 1;
10380
+ }
10381
+ /**
10382
+ * Hides the current presentation and records which generation its queued close event belongs to.
10383
+ * @param {string} [result=''] Native string return value.
10384
+ * @returns {void}
10385
+ */
10386
+ _dismiss(result = "") {
10387
+ this._hide(result, this.#presentation);
10388
+ }
10389
+ /**
10390
+ * Reports whether the current presentation layer is visible.
10391
+ * @returns {boolean}
10392
+ */
10393
+ _isShown() {
10394
+ return this.el.open;
10395
+ }
10349
10396
  /**
10350
10397
  * Whether an incoming `close` event is a real dismissal.
10351
10398
  *
10352
- * A dialog cannot move between the top layer and the page flow while it is open, so a subclass
10353
- * that re-hosts itself has to close and immediately reopen. `close` is dispatched in a queued
10354
- * task, not synchronously, so that stale event arrives once the dialog is already open again —
10355
- * late enough to clobber `data-state`, fire a `close` nobody asked for, and trip
10356
- * `destroyOnClose`. Returning false from an override drops it at the source instead.
10399
+ * `close` is dispatched in a queued task, not synchronously. By the time it arrives the overlay
10400
+ * may have reopened or crossed presentation layers, so only the event for the current hidden
10401
+ * generation may settle state, emit lifecycle events, or trigger `destroyOnClose`.
10402
+ * @param {number|undefined} closingPresentation Presentation that produced the event, if known.
10357
10403
  * @returns {boolean}
10358
10404
  */
10359
- _isRealClose() {
10405
+ _isRealClose(closingPresentation) {
10406
+ if (closingPresentation !== void 0 && closingPresentation !== this.#presentation) return false;
10407
+ return !this._isShown();
10408
+ }
10409
+ /**
10410
+ * Settles one presentation's close without allowing a queued event to invalidate a newer one.
10411
+ * Popover-based subclasses call this directly; native dialog events arrive through the listener.
10412
+ * @param {number|undefined} closingPresentation Presentation that was hidden, if known.
10413
+ * @returns {boolean} Whether this event closed the current presentation.
10414
+ */
10415
+ _settleClose(closingPresentation) {
10416
+ if (!this._isRealClose(closingPresentation)) {
10417
+ if (closingPresentation !== void 0) this.#pendingResults.delete(closingPresentation);
10418
+ return false;
10419
+ }
10420
+ this.el.dataset.state = "closed";
10421
+ delete this.el.dataset.zxOverlayOrder;
10422
+ const pending = closingPresentation === void 0 ? void 0 : this.#pendingResults.get(closingPresentation);
10423
+ if (closingPresentation !== void 0) this.#pendingResults.delete(closingPresentation);
10424
+ const result = pending ? pending.result : this.el.returnValue || void 0;
10425
+ this.emit("close", { result });
10426
+ if (this.options.destroyOnClose && !this.#destroying) this.destroy();
10360
10427
  return true;
10361
10428
  }
10362
10429
  /**
@@ -10381,10 +10448,12 @@ var Modal = class extends Component {
10381
10448
  */
10382
10449
  open() {
10383
10450
  if (this.isOpen()) return this;
10451
+ const host = this.mountTarget();
10452
+ if (this.el.parentElement !== host) host.append(this.el);
10384
10453
  this.el.dataset.zxOverlayOrder = String(nextOverlayOrder());
10385
10454
  this.el.returnValue = "";
10386
10455
  try {
10387
- this._show();
10456
+ this._present();
10388
10457
  } catch (error2) {
10389
10458
  delete this.el.dataset.zxOverlayOrder;
10390
10459
  throw error2;
@@ -10400,9 +10469,14 @@ var Modal = class extends Component {
10400
10469
  */
10401
10470
  close(result) {
10402
10471
  if (!this.isOpen()) return this;
10403
- this.#pendingResult = result;
10404
- this.#hasPendingResult = true;
10405
- this.el.close(typeof result === "string" ? result : "");
10472
+ const presentation = this.#presentation;
10473
+ this.#pendingResults.set(presentation, { result });
10474
+ try {
10475
+ this._dismiss(typeof result === "string" ? result : "");
10476
+ } catch (error2) {
10477
+ this.#pendingResults.delete(presentation);
10478
+ throw error2;
10479
+ }
10406
10480
  return this;
10407
10481
  }
10408
10482
  /**
@@ -10419,7 +10493,7 @@ var Modal = class extends Component {
10419
10493
  * @returns {boolean}
10420
10494
  */
10421
10495
  isOpen() {
10422
- return this.el.open;
10496
+ return this._isShown();
10423
10497
  }
10424
10498
  /**
10425
10499
  * Closes and removes the owned dialog. Safe to call repeatedly.
@@ -10428,7 +10502,7 @@ var Modal = class extends Component {
10428
10502
  destroy() {
10429
10503
  if (this.#destroying) return;
10430
10504
  this.#destroying = true;
10431
- if (this.isOpen()) this.el.close();
10505
+ if (this.isOpen()) this._dismiss();
10432
10506
  super.destroy();
10433
10507
  }
10434
10508
  /** @param {string|number} width @returns {void} */
@@ -10493,7 +10567,7 @@ var Dialog = class _Dialog extends Modal {
10493
10567
  /** @type {Element|null} */
10494
10568
  #opener = null;
10495
10569
  /**
10496
- * Creates a structured dialog appended to `document.body`.
10570
+ * Creates a structured dialog in the configured theme scope, or at document level when unscoped.
10497
10571
  * @param {Element|string|null} [_target=null] Ignored; Dialog always owns its root.
10498
10572
  * @param {DialogOptions} [options={}] Dialog options.
10499
10573
  */
@@ -15390,7 +15464,8 @@ var Dropdown = class extends Component {
15390
15464
  offset: 4,
15391
15465
  matchWidth: false,
15392
15466
  openOn: "click",
15393
- closeOnSelect: false
15467
+ closeOnSelect: false,
15468
+ scope: null
15394
15469
  };
15395
15470
  /** @type {Element} */
15396
15471
  #anchor;
@@ -15409,6 +15484,7 @@ var Dropdown = class extends Component {
15409
15484
  if (!resolved) throw new TypeError("Dropdown anchor could not be resolved");
15410
15485
  super(null, options);
15411
15486
  this.#anchor = resolved;
15487
+ overlayHost(this.options.scope ?? resolved).append(this.el);
15412
15488
  this.#rememberAnchorAttribute("aria-expanded");
15413
15489
  this.#rememberAnchorAttribute("aria-controls");
15414
15490
  this.#anchor.setAttribute("aria-expanded", "false");
@@ -15431,7 +15507,6 @@ var Dropdown = class extends Component {
15431
15507
  popover: "manual"
15432
15508
  });
15433
15509
  panel.dataset.state = "closed";
15434
- document.body.append(panel);
15435
15510
  return panel;
15436
15511
  }
15437
15512
  /**
@@ -15441,6 +15516,8 @@ var Dropdown = class extends Component {
15441
15516
  */
15442
15517
  open() {
15443
15518
  if (this.isOpen() || this.#destroyed) return this;
15519
+ const host = overlayHost(this.options.scope ?? this.#anchor);
15520
+ if (this.el.parentElement !== host) host.append(this.el);
15444
15521
  this.#position = position(this.#anchor, this.el, {
15445
15522
  placement: this.options.placement,
15446
15523
  offset: Number(this.options.offset),
@@ -20100,7 +20177,8 @@ var Launcher = class extends Component {
20100
20177
  emptyText: "No results",
20101
20178
  loadingText: "Searching\u2026",
20102
20179
  shortcut: "mod+k",
20103
- hints: { move: "Move", open: "Open", close: "Close" }
20180
+ hints: { move: "Move", open: "Open", close: "Close" },
20181
+ scope: null
20104
20182
  };
20105
20183
  /**
20106
20184
  * Creates an owned launcher dialog or enhances an existing `<dialog>` target.
@@ -20191,7 +20269,7 @@ var Launcher = class extends Component {
20191
20269
  )
20192
20270
  );
20193
20271
  dialog.dataset.state = "closed";
20194
- if (this._createdRoot) document.body.append(dialog);
20272
+ if (this._createdRoot) overlayHost(this.options.scope).append(dialog);
20195
20273
  this.listen(input, "input", () => {
20196
20274
  this._query = /** @type {HTMLInputElement} */
20197
20275
  input.value;
@@ -20242,6 +20320,10 @@ var Launcher = class extends Component {
20242
20320
  /** Opens the launcher and focuses/selects its query. @returns {this} @fires Launcher#open */
20243
20321
  open() {
20244
20322
  if (this.isOpen() || this._destroyed) return this;
20323
+ if (this._createdRoot) {
20324
+ const host = overlayHost(this.options.scope);
20325
+ if (this.el.parentElement !== host) host.append(this.el);
20326
+ }
20245
20327
  this.el.showModal();
20246
20328
  this.el.dataset.state = "open";
20247
20329
  this.refs.input.setAttribute("aria-expanded", "true");
@@ -21120,6 +21202,7 @@ var Tooltip = class extends Component {
21120
21202
  if (!resolved) throw new TypeError("Tooltip anchor could not be resolved");
21121
21203
  super(null, options);
21122
21204
  this.#anchor = resolved;
21205
+ overlayHost(resolved).append(this.el);
21123
21206
  this.#content = this.options.content;
21124
21207
  this.#disabled = Boolean(this.options.disabled);
21125
21208
  this.#describedBy = resolved.getAttribute("aria-describedby");
@@ -21180,7 +21263,6 @@ var Tooltip = class extends Component {
21180
21263
  popover: "manual"
21181
21264
  });
21182
21265
  panel.dataset.state = "closed";
21183
- document.body.append(panel);
21184
21266
  return panel;
21185
21267
  }
21186
21268
  /**
@@ -21192,6 +21274,8 @@ var Tooltip = class extends Component {
21192
21274
  show() {
21193
21275
  if (this.#destroyed || this.#disabled || this.isOpen()) return this;
21194
21276
  this.#cancelTimers();
21277
+ const host = overlayHost(this.#anchor);
21278
+ if (this.el.parentElement !== host) host.append(this.el);
21195
21279
  const node = resolveContent(this.#content);
21196
21280
  if (node === null) return this;
21197
21281
  this.el.replaceChildren(node);
@@ -22638,8 +22722,8 @@ var Message = class _Message extends Component {
22638
22722
  closable: true,
22639
22723
  maxVisible: 5
22640
22724
  };
22641
- /** @type {Message|null} */
22642
- static #floating = null;
22725
+ /** @type {WeakMap<Element, Message>} */
22726
+ static #floating = /* @__PURE__ */ new WeakMap();
22643
22727
  /** @returns {HTMLElement} */
22644
22728
  render() {
22645
22729
  const created = !this.el;
@@ -22718,15 +22802,16 @@ var Message = class _Message extends Component {
22718
22802
  * @returns {MessageHandle}
22719
22803
  */
22720
22804
  static show(msg, options = {}) {
22721
- return this.#floatingRegion().show(msg, options);
22805
+ return this.#floatingRegion(options.scope).show(msg, options);
22722
22806
  }
22723
22807
  /**
22724
22808
  * Shows a floating progress status.
22725
22809
  * @param {string} text Initial status text.
22810
+ * @param {{scope?: Element|string|null}} [options={}] Floating-region scope.
22726
22811
  * @returns {ProgressHandle}
22727
22812
  */
22728
- static progress(text4) {
22729
- return this.#floatingRegion()._progress(text4);
22813
+ static progress(text4, options = {}) {
22814
+ return this.#floatingRegion(options.scope)._progress(text4);
22730
22815
  }
22731
22816
  /** @returns {void} */
22732
22817
  destroy() {
@@ -22904,19 +22989,22 @@ var Message = class _Message extends Component {
22904
22989
  this._entries.delete(entry);
22905
22990
  this._queue.remove(entry);
22906
22991
  }
22907
- /** @returns {Message} */
22908
- static #floatingRegion() {
22909
- if (this.#floating) return this.#floating;
22992
+ /** @param {Element|string|null|undefined} scope @returns {Message} */
22993
+ static #floatingRegion(scope2) {
22994
+ const host = overlayHost(scope2 ?? null);
22995
+ const existing = this.#floating.get(host);
22996
+ if (existing) return existing;
22910
22997
  const region = h("div", {
22911
22998
  class: "zx-message-region",
22912
22999
  popover: "manual",
22913
23000
  role: "status",
22914
23001
  ariaLive: "polite"
22915
23002
  });
22916
- document.body.append(region);
22917
- this.#floating = new _Message(region);
23003
+ host.append(region);
23004
+ const floating = new _Message(region);
23005
+ this.#floating.set(host, floating);
22918
23006
  if (typeof region.showPopover === "function") region.showPopover();
22919
- return this.#floating;
23007
+ return floating;
22920
23008
  }
22921
23009
  };
22922
23010
  function normalizeKind3(value) {
@@ -23004,7 +23092,6 @@ var Sheet = class extends Dialog {
23004
23092
  constructor(_target = null, options = {}) {
23005
23093
  super(null, options);
23006
23094
  this._dock = null;
23007
- this._handoff = false;
23008
23095
  this.setSide(this.options.side);
23009
23096
  this.el.dataset.backdrop = BACKDROPS.has(this.options.backdrop) ? this.options.backdrop : "dim";
23010
23097
  this.el.dataset.modality = modality(this.options.modal);
@@ -23179,7 +23266,7 @@ var Sheet = class extends Dialog {
23179
23266
  * @returns {Element}
23180
23267
  */
23181
23268
  mountTarget() {
23182
- return this._dock?.el ?? document.body;
23269
+ return this._dock?.el ?? super.mountTarget();
23183
23270
  }
23184
23271
  /**
23185
23272
  * Reports whether a dock currently owns this sheet's positioning.
@@ -23205,35 +23292,23 @@ var Sheet = class extends Dialog {
23205
23292
  /**
23206
23293
  * Moves the dialog to its current mount and, when it was open, reopens it there.
23207
23294
  *
23208
- * The close is unavoidable — a dialog cannot cross between the top layer and the flow while
23209
- * open — but it is invisible: `_isRealClose()` drops the stale event, and reopening bypasses
23210
- * `open()` so no lifecycle event is emitted for what is only a change of address.
23295
+ * The close is unavoidable — a dialog cannot cross between presentation layers while open —
23296
+ * but it is invisible: Modal's presentation generation drops the stale event, and reopening
23297
+ * bypasses `open()` so no lifecycle event is emitted for what is only a change of address.
23211
23298
  * @returns {void}
23212
23299
  */
23213
23300
  _rehost() {
23214
23301
  const wasOpen = this.isOpen();
23215
- if (wasOpen) {
23216
- this._handoff = true;
23217
- this.el.close();
23218
- }
23302
+ if (wasOpen) this._dismiss();
23219
23303
  this.mountTarget().append(this.el);
23220
23304
  if (!wasOpen) return;
23221
23305
  this.el.returnValue = "";
23222
- this._show();
23306
+ this._present();
23223
23307
  this.el.dataset.state = "open";
23224
23308
  if (this._dock) delete /** @type {HTMLElement} */
23225
23309
  this.el.dataset.zxOverlayOrder;
23226
23310
  else this.el.dataset.zxOverlayOrder = String(nextOverlayOrder3());
23227
23311
  }
23228
- /**
23229
- * Drops the stale `close` a re-hosting handoff leaves behind.
23230
- * @returns {boolean}
23231
- */
23232
- _isRealClose() {
23233
- if (!this._handoff) return true;
23234
- this._handoff = false;
23235
- return false;
23236
- }
23237
23312
  /**
23238
23313
  * Opens the sheet, trapping focus first where the modality asks for it.
23239
23314
  * @returns {this}
@@ -23314,10 +23389,38 @@ var Sheet = class extends Dialog {
23314
23389
  */
23315
23390
  _show() {
23316
23391
  if (this.options.modal === true && !this._dock) {
23392
+ this.el.removeAttribute("popover");
23393
+ this.el.removeAttribute("aria-modal");
23317
23394
  super._show();
23318
23395
  return;
23319
23396
  }
23320
- this.el.show();
23397
+ this.el.setAttribute("aria-modal", "false");
23398
+ if (this._dock) {
23399
+ this.el.removeAttribute("popover");
23400
+ this.el.show();
23401
+ return;
23402
+ }
23403
+ this.el.setAttribute("popover", "manual");
23404
+ this.el.showPopover();
23405
+ }
23406
+ /**
23407
+ * Hides a native dialog or a non-modal Sheet promoted through the popover top layer.
23408
+ * @param {string} [result=''] Native string return value.
23409
+ * @param {number} [presentation] Presentation generation being hidden.
23410
+ * @returns {void}
23411
+ */
23412
+ _hide(result = "", presentation) {
23413
+ if (!this.el.matches(":popover-open")) {
23414
+ super._hide(result, presentation);
23415
+ return;
23416
+ }
23417
+ this.el.returnValue = result;
23418
+ this.el.hidePopover();
23419
+ queueMicrotask(() => this._settleClose(presentation));
23420
+ }
23421
+ /** @returns {boolean} */
23422
+ _isShown() {
23423
+ return this.el.matches(":popover-open") || super._isShown();
23321
23424
  }
23322
23425
  /**
23323
23426
  * Releases the focus trap and the owned dialog.
@@ -23642,7 +23745,11 @@ var ContextMenu = class extends Component {
23642
23745
  super(null, options);
23643
23746
  this.#target = resolved;
23644
23747
  document.body.append(this.el);
23645
- this.#dropdown = new Dropdown(this.el, { openOn: "manual", placement: "bottom-start" });
23748
+ this.#dropdown = new Dropdown(this.el, {
23749
+ openOn: "manual",
23750
+ placement: "bottom-start",
23751
+ scope: resolved
23752
+ });
23646
23753
  this.#panel = this.#dropdown.getPanel();
23647
23754
  this.#panel.classList.add("zx-context-menu");
23648
23755
  this.#panel.setAttribute("role", "menu");
@@ -24496,7 +24603,11 @@ var Calendar = class extends Component {
24496
24603
  }).sort(compareEvents);
24497
24604
  const row2 = h(
24498
24605
  "section",
24499
- { class: "zx-calendar__agenda-day", role: "row" },
24606
+ {
24607
+ class: "zx-calendar__agenda-day",
24608
+ role: "row",
24609
+ dataset: { date: calendarDayKey(day) }
24610
+ },
24500
24611
  h("button", {
24501
24612
  type: "button",
24502
24613
  class: "zx-calendar__agenda-heading",
@@ -24506,11 +24617,13 @@ var Calendar = class extends Component {
24506
24617
  disabled: this.options.disabled
24507
24618
  }, this._format(day, { weekday: "long", day: "numeric", month: "long" }))
24508
24619
  );
24620
+ const entries = h("div", { class: "zx-calendar__agenda-events", role: "gridcell" });
24509
24621
  if (!events.length) {
24510
- row2.append(h("p", { class: "zx-calendar__agenda-empty", role: "gridcell" }, this.msg("noEvents")));
24622
+ entries.append(h("p", { class: "zx-calendar__agenda-empty" }, this.msg("noEvents")));
24511
24623
  } else {
24512
- for (const event of events) row2.append(this._eventElement(event, { variant: "agenda", day }));
24624
+ for (const event of events) entries.append(this._eventElement(event, { variant: "agenda", day }));
24513
24625
  }
24626
+ row2.append(entries);
24514
24627
  grid2.append(row2);
24515
24628
  }
24516
24629
  return grid2;
@@ -24780,6 +24893,7 @@ var Calendar = class extends Component {
24780
24893
  dataset: {
24781
24894
  eventId: key,
24782
24895
  variant: context.variant,
24896
+ allDay: event.allDay ? "true" : null,
24783
24897
  draggable: this._canMove(event) ? "true" : null
24784
24898
  },
24785
24899
  style: event.color ? { "--zx-calendar-event-color": event.color } : null
@@ -25046,7 +25160,9 @@ var Calendar = class extends Component {
25046
25160
  const item = this._eventById(control.dataset.eventId);
25047
25161
  const action = control.dataset.eventAction === "resize" ? "resize" : "move";
25048
25162
  if (!item || (action === "move" ? !this._canMove(item) : !this._canResize(item))) return;
25049
- control.setPointerCapture?.(event.pointerId);
25163
+ event.preventDefault();
25164
+ this._capturePointer(event.pointerId);
25165
+ const offset = action === "move" ? this._dragOffsetForEvent(item, event.clientX, event.clientY) : null;
25050
25166
  this._pointer = {
25051
25167
  type: "event",
25052
25168
  pointerId: event.pointerId,
@@ -25056,6 +25172,8 @@ var Calendar = class extends Component {
25056
25172
  event: cloneCalendarEvent(item),
25057
25173
  action,
25058
25174
  control,
25175
+ timeOffsetMinutes: offset?.minutes ?? 0,
25176
+ dayOffset: offset?.days ?? 0,
25059
25177
  moved: false
25060
25178
  };
25061
25179
  return;
@@ -25064,7 +25182,8 @@ var Calendar = class extends Component {
25064
25182
  if (!slot || !this.options.selectable) return;
25065
25183
  const date = this._dateFromTarget(slot);
25066
25184
  if (!date) return;
25067
- slot.setPointerCapture?.(event.pointerId);
25185
+ event.preventDefault();
25186
+ this._capturePointer(event.pointerId);
25068
25187
  this._pointer = {
25069
25188
  type: "selection",
25070
25189
  pointerId: event.pointerId,
@@ -25097,7 +25216,13 @@ var Calendar = class extends Component {
25097
25216
  const wrapper = pointer.control.closest(".zx-calendar__event");
25098
25217
  if (wrapper) {
25099
25218
  wrapper.dataset.state = "dragging";
25100
- wrapper.style.transform = `translate(${event.clientX - pointer.startX}px, ${event.clientY - pointer.startY}px)`;
25219
+ if (pointer.action === "resize") {
25220
+ wrapper.style.transform = "";
25221
+ const height = this._timedPreviewHeight(proposed);
25222
+ if (height) wrapper.style.blockSize = height;
25223
+ } else {
25224
+ wrapper.style.transform = `translate(${event.clientX - pointer.startX}px, ${event.clientY - pointer.startY}px)`;
25225
+ }
25101
25226
  wrapper.dataset.preview = this._eventTime(proposed, proposed.start);
25102
25227
  }
25103
25228
  this._announce(this.msg(
@@ -25112,6 +25237,7 @@ var Calendar = class extends Component {
25112
25237
  const pointer = this._pointer;
25113
25238
  if (!pointer || pointer.pointerId !== event.pointerId) return;
25114
25239
  this._pointer = null;
25240
+ this._releasePointerCapture(pointer.pointerId);
25115
25241
  this._clearSelectionPreview();
25116
25242
  if (!pointer.moved) return;
25117
25243
  event.preventDefault();
@@ -25129,6 +25255,7 @@ var Calendar = class extends Component {
25129
25255
  _cancelPointer(event) {
25130
25256
  if (!this._pointer || this._pointer.pointerId !== event.pointerId) return;
25131
25257
  const control = this._pointer.control;
25258
+ this._releasePointerCapture(this._pointer.pointerId);
25132
25259
  this._pointer = null;
25133
25260
  this._clearSelectionPreview();
25134
25261
  control.closest?.(".zx-calendar__event")?.removeAttribute("data-state");
@@ -25136,13 +25263,14 @@ var Calendar = class extends Component {
25136
25263
  }
25137
25264
  /** @param {CalendarEvent} event @param {'move'|'resize'} action @param {number} x @param {number} y @returns {CalendarEvent|null} */
25138
25265
  _proposalAtPoint(event, action, x, y) {
25139
- const timed = this._timeAtPoint(x, y);
25266
+ const resizeTimed = action === "resize" && !calendarEventSpansDays(event);
25267
+ const timed = this._timeAtPoint(x, y, resizeTimed ? event.start : null);
25140
25268
  if (timed && !calendarEventSpansDays(event)) {
25141
25269
  if (action === "move") {
25142
25270
  const duration = event.end.getTime() - event.start.getTime();
25143
25271
  const next3 = cloneCalendarEvent(event);
25144
- next3.start = timed;
25145
- next3.end = new Date(timed.getTime() + duration);
25272
+ next3.start = addMinutes(timed, -Number(this._pointer?.timeOffsetMinutes ?? 0));
25273
+ next3.end = new Date(next3.start.getTime() + duration);
25146
25274
  return next3;
25147
25275
  }
25148
25276
  const next2 = cloneCalendarEvent(event);
@@ -25151,33 +25279,36 @@ var Calendar = class extends Component {
25151
25279
  }
25152
25280
  const day = this._dayAtPoint(x, y);
25153
25281
  if (!day) return null;
25154
- const difference = calendarDayDifference(startOfCalendarDay(event.start), day);
25282
+ const targetDay = addCalendarDays(day, -Number(this._pointer?.dayOffset ?? 0));
25283
+ const difference = calendarDayDifference(startOfCalendarDay(event.start), targetDay);
25155
25284
  if (action === "move") return shiftCalendarEvent(event, { days: difference, minutes: 0 });
25156
25285
  const next = cloneCalendarEvent(event);
25157
25286
  const endDay = addCalendarDays(day, 1);
25158
25287
  next.end = new Date(endDay.getTime());
25159
25288
  return next.end > next.start ? next : null;
25160
25289
  }
25161
- /** @param {number} x @param {number} y @returns {Date|null} */
25162
- _timeAtPoint(x, y) {
25290
+ /** @param {number} x @param {number} y @param {Date|null} [fixedDay=null] @returns {Date|null} */
25291
+ _timeAtPoint(x, y, fixedDay = null) {
25163
25292
  const slots = this.refs.body.querySelector(".zx-calendar__slots");
25164
25293
  if (!slots) return null;
25165
25294
  const rect = slots.getBoundingClientRect();
25166
- if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom || rect.width <= 0 || rect.height <= 0) return null;
25295
+ const scroller = slots.closest(".zx-calendar__time-scroller");
25296
+ const viewport = scroller?.getBoundingClientRect();
25297
+ const left = viewport ? Math.max(rect.left, viewport.left) : rect.left;
25298
+ const right = viewport ? Math.min(rect.right, viewport.right) : rect.right;
25299
+ const top = viewport ? Math.max(rect.top, viewport.top) : rect.top;
25300
+ const bottom = viewport ? Math.min(rect.bottom, viewport.bottom) : rect.bottom;
25301
+ if (x < left || x > right || y < top || y > bottom || rect.width <= 0 || rect.height <= 0) return null;
25167
25302
  const columns = Number(slots.closest('[role="grid"]')?.getAttribute("aria-colcount")) || 1;
25168
25303
  const rows = Number(slots.closest('[role="grid"]')?.getAttribute("aria-rowcount")) || 1;
25169
- const column = Math.max(0, Math.min(columns - 1, Math.floor((x - rect.left) / rect.width * columns)));
25304
+ const fixedColumn = fixedDay ? this._timeGridDayIndex(fixedDay) : -1;
25305
+ const column = fixedColumn >= 0 ? fixedColumn : Math.max(0, Math.min(columns - 1, Math.floor((x - rect.left) / rect.width * columns)));
25170
25306
  const row2 = Math.max(0, Math.min(rows - 1, Math.floor((y - rect.top) / rect.height * rows)));
25171
25307
  const target = slots.querySelector(`[data-row-index="${row2}"][data-day-index="${column}"]`);
25172
25308
  return target ? this._dateFromTarget(target) : null;
25173
25309
  }
25174
25310
  /** @param {number} x @param {number} y @returns {Date|null} */
25175
25311
  _dayAtPoint(x, y) {
25176
- const elements = typeof document.elementsFromPoint === "function" ? document.elementsFromPoint(x, y) : [];
25177
- for (const element of elements) {
25178
- const date = element instanceof HTMLElement ? element.dataset.date : null;
25179
- if (date) return parseDayKey(date);
25180
- }
25181
25312
  const allDay = this.refs.body.querySelector(".zx-calendar__all-day-grid");
25182
25313
  if (allDay) {
25183
25314
  const rect = allDay.getBoundingClientRect();
@@ -25187,8 +25318,72 @@ var Calendar = class extends Component {
25187
25318
  return parseDayKey(cells[index]?.dataset.date);
25188
25319
  }
25189
25320
  }
25321
+ for (const monthEvents of this.refs.body.querySelectorAll(".zx-calendar__month-events")) {
25322
+ const rect = monthEvents.getBoundingClientRect();
25323
+ if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom || rect.width <= 0) continue;
25324
+ const start = parseDayKey(monthEvents.closest(".zx-calendar__month-week")?.dataset.weekStart);
25325
+ if (!start) continue;
25326
+ const index = Math.max(0, Math.min(6, Math.floor((x - rect.left) / rect.width * 7)));
25327
+ return addCalendarDays(start, index);
25328
+ }
25329
+ for (const agendaDay of this.refs.body.querySelectorAll(".zx-calendar__agenda-day")) {
25330
+ const rect = agendaDay.getBoundingClientRect();
25331
+ if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) continue;
25332
+ return parseDayKey(agendaDay.dataset.date);
25333
+ }
25334
+ const elements = typeof document.elementsFromPoint === "function" ? document.elementsFromPoint(x, y) : [];
25335
+ for (const element of elements) {
25336
+ const dateTarget = element instanceof HTMLElement ? element.closest("[data-date]") : null;
25337
+ const date = dateTarget instanceof HTMLElement ? dateTarget.dataset.date : null;
25338
+ if (date) return parseDayKey(date);
25339
+ }
25190
25340
  return null;
25191
25341
  }
25342
+ /** @param {CalendarEvent} event @param {number} x @param {number} y @returns {{minutes: number, days: number}} */
25343
+ _dragOffsetForEvent(event, x, y) {
25344
+ if (!calendarEventSpansDays(event)) {
25345
+ const timed = this._timeAtPoint(x, y);
25346
+ if (timed) {
25347
+ const slot = normalizeSlot(this.options.slotDuration);
25348
+ const duration = Math.max(0, Math.round((event.end.getTime() - event.start.getTime()) / 6e4));
25349
+ const maxOffset = Math.max(0, duration - slot);
25350
+ const minutes = Math.round((timed.getTime() - event.start.getTime()) / 6e4);
25351
+ return { minutes: Math.max(0, Math.min(maxOffset, minutes)), days: 0 };
25352
+ }
25353
+ }
25354
+ const day = this._dayAtPoint(x, y);
25355
+ const days = day ? calendarDayDifference(startOfCalendarDay(event.start), day) : 0;
25356
+ return { minutes: 0, days: Math.max(0, days) };
25357
+ }
25358
+ /** @param {Date} day @returns {number} */
25359
+ _timeGridDayIndex(day) {
25360
+ const key = calendarDayKey(day);
25361
+ const headings = [...this.refs.body.querySelectorAll(".zx-calendar__day-heading")];
25362
+ return headings.findIndex((heading) => heading.dataset.date === key);
25363
+ }
25364
+ /** @param {CalendarEvent} event @returns {string|null} */
25365
+ _timedPreviewHeight(event) {
25366
+ const grid2 = this.refs.body.querySelector(".zx-calendar__time-grid");
25367
+ if (!grid2 || calendarEventSpansDays(event)) return null;
25368
+ const min = normalizeMinute(this.options.slotMinTime, 0);
25369
+ const max = normalizeMinute(this.options.slotMaxTime, 1440, true);
25370
+ const slot = normalizeSlot(this.options.slotDuration);
25371
+ const start = event.start.getHours() * 60 + event.start.getMinutes();
25372
+ const end = event.end.getHours() * 60 + event.end.getMinutes();
25373
+ const minutes = Math.max(slot, Math.min(max, end) - Math.max(min, start));
25374
+ return `${minutes / (max - min) * 100}%`;
25375
+ }
25376
+ /** @param {number} pointerId @returns {void} */
25377
+ _capturePointer(pointerId) {
25378
+ try {
25379
+ this.el.setPointerCapture?.(pointerId);
25380
+ } catch {
25381
+ }
25382
+ }
25383
+ /** @param {number} pointerId @returns {void} */
25384
+ _releasePointerCapture(pointerId) {
25385
+ if (this.el.hasPointerCapture?.(pointerId)) this.el.releasePointerCapture?.(pointerId);
25386
+ }
25192
25387
  /** @param {Date} start @param {Date} end @returns {void} */
25193
25388
  _previewSelection(start, end) {
25194
25389
  const low = Math.min(start.getTime(), end.getTime());