@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.
- package/CHANGELOG.md +37 -0
- package/README.md +5 -0
- package/dist/types/components/calendar/calendar.d.ts +19 -2
- package/dist/types/components/dialog/dialog.d.ts +6 -1
- package/dist/types/components/dropdown/dropdown.d.ts +5 -0
- package/dist/types/components/launcher/launcher.d.ts +5 -0
- package/dist/types/components/message/message.d.ts +9 -1
- package/dist/types/components/modal/modal.d.ts +44 -7
- package/dist/types/components/sheet/sheet.d.ts +22 -12
- package/dist/types/core/overlay-host.d.ts +12 -0
- package/dist/zx-zeyos.esm.js +138 -32
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +259 -132
- package/dist/zx.esm.js +277 -82
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +277 -82
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +36 -0
- package/docs/llms.md +37 -18
- package/docs/llms.txt +1 -1
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +1 -1
- package/src/components/app-rail/app-rail.css +1 -1
- package/src/components/calendar/calendar.css +66 -24
- package/src/components/calendar/calendar.js +114 -19
- package/src/components/context-menu/context-menu.css +2 -2
- package/src/components/context-menu/context-menu.js +5 -1
- package/src/components/date-picker/date-picker.css +18 -11
- package/src/components/datebox/date-range-box.css +4 -2
- package/src/components/datebox/datebox.css +4 -2
- package/src/components/dialog/dialog.css +4 -3
- package/src/components/dialog/dialog.js +2 -1
- package/src/components/dropdown/dropdown.css +4 -2
- package/src/components/dropdown/dropdown.js +7 -2
- package/src/components/launcher/launcher.css +10 -8
- package/src/components/launcher/launcher.js +9 -2
- package/src/components/menu-button/menu-button.css +2 -2
- package/src/components/message/message.css +7 -16
- package/src/components/message/message.js +17 -11
- package/src/components/modal/modal.css +6 -4
- package/src/components/modal/modal.js +99 -25
- package/src/components/select/select.css +9 -7
- package/src/components/sheet/sheet.css +25 -7
- package/src/components/sheet/sheet.js +43 -23
- package/src/components/table/table.css +5 -3
- package/src/components/tag-picker/tag-picker.css +5 -3
- package/src/components/tooltip/tooltip.css +4 -2
- package/src/components/tooltip/tooltip.js +4 -1
- package/src/core/overlay-host.js +23 -0
- package/styles/tokens/dark.css +4 -0
- package/styles/tokens/semantic.css +83 -8
package/docs/api.json
CHANGED
|
@@ -2750,6 +2750,12 @@
|
|
|
2750
2750
|
"type": "Node|string|number|{toElement: () => Node|null}|null",
|
|
2751
2751
|
"default": "null",
|
|
2752
2752
|
"description": "Body content."
|
|
2753
|
+
},
|
|
2754
|
+
{
|
|
2755
|
+
"name": "scope",
|
|
2756
|
+
"type": "Element|string|null",
|
|
2757
|
+
"default": "null",
|
|
2758
|
+
"description": "Element whose nearest Zx theme scope owns the overlay; defaults to the opener."
|
|
2753
2759
|
}
|
|
2754
2760
|
],
|
|
2755
2761
|
"events": [
|
|
@@ -3082,6 +3088,12 @@
|
|
|
3082
3088
|
"type": "boolean",
|
|
3083
3089
|
"default": "false",
|
|
3084
3090
|
"description": "Whether a click within the panel closes it."
|
|
3091
|
+
},
|
|
3092
|
+
{
|
|
3093
|
+
"name": "scope",
|
|
3094
|
+
"type": "Element|string|null",
|
|
3095
|
+
"default": "null",
|
|
3096
|
+
"description": "Element whose nearest Zx theme scope owns the panel; defaults to the anchor."
|
|
3085
3097
|
}
|
|
3086
3098
|
],
|
|
3087
3099
|
"events": [
|
|
@@ -4587,6 +4599,12 @@
|
|
|
4587
4599
|
"type": "false|{move?: string, open?: string, close?: string}",
|
|
4588
4600
|
"default": null,
|
|
4589
4601
|
"description": "Visible keyboard hints, or false."
|
|
4602
|
+
},
|
|
4603
|
+
{
|
|
4604
|
+
"name": "scope",
|
|
4605
|
+
"type": "Element|string|null",
|
|
4606
|
+
"default": "null",
|
|
4607
|
+
"description": "Element whose nearest Zx theme scope owns an internally created launcher; defaults to the opener."
|
|
4590
4608
|
}
|
|
4591
4609
|
],
|
|
4592
4610
|
"events": [
|
|
@@ -4969,6 +4987,12 @@
|
|
|
4969
4987
|
"type": "boolean",
|
|
4970
4988
|
"default": "true",
|
|
4971
4989
|
"description": "Whether to render a close button."
|
|
4990
|
+
},
|
|
4991
|
+
{
|
|
4992
|
+
"name": "scope",
|
|
4993
|
+
"type": "Element|string|null",
|
|
4994
|
+
"default": "null",
|
|
4995
|
+
"description": "Element whose nearest Zx theme scope owns a floating toast."
|
|
4972
4996
|
}
|
|
4973
4997
|
],
|
|
4974
4998
|
"events": [],
|
|
@@ -5005,6 +5029,12 @@
|
|
|
5005
5029
|
"type": "boolean",
|
|
5006
5030
|
"default": "false",
|
|
5007
5031
|
"description": "Whether to destroy the modal after it closes."
|
|
5032
|
+
},
|
|
5033
|
+
{
|
|
5034
|
+
"name": "scope",
|
|
5035
|
+
"type": "Element|string|null",
|
|
5036
|
+
"default": "null",
|
|
5037
|
+
"description": "Element whose nearest Zx theme scope owns the overlay; defaults to the opener."
|
|
5008
5038
|
}
|
|
5009
5039
|
],
|
|
5010
5040
|
"events": [
|
|
@@ -6985,6 +7015,12 @@
|
|
|
6985
7015
|
"type": "boolean",
|
|
6986
7016
|
"default": "false",
|
|
6987
7017
|
"description": "Whether to destroy the sheet after it closes."
|
|
7018
|
+
},
|
|
7019
|
+
{
|
|
7020
|
+
"name": "scope",
|
|
7021
|
+
"type": "Element|string|null",
|
|
7022
|
+
"default": "null",
|
|
7023
|
+
"description": "Element whose nearest Zx theme scope owns a floating sheet; defaults to the opener."
|
|
6988
7024
|
}
|
|
6989
7025
|
],
|
|
6990
7026
|
"events": [
|
package/docs/llms.md
CHANGED
|
@@ -80,14 +80,19 @@ ambient canvas layer; material controls how surfaces respond to that light. The
|
|
|
80
80
|
applies to AppIcons, ordinary controls, persistent chrome, raised cards, and every transient overlay: Dropdown, ContextMenu,
|
|
81
81
|
MenuButton, AccountMenu, AppRail flyouts, Select, TagPicker, date pickers, tooltips, column menus,
|
|
82
82
|
and toasts use `--zx-color-overlay-surface`; Launcher, Modal, Dialog, and overlay Sheets use the
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
surfaces.
|
|
83
|
+
calmer `--zx-color-overlay-panel`. Both tiers combine a translucent base, responsive specular and
|
|
84
|
+
Aurora tint layers, backdrop filtering, and dedicated state/divider roles; the modal backdrop is a
|
|
85
|
+
separate material token so it does not erase the light the panel should refract. A docked Sheet is
|
|
86
|
+
layout rather than an overlay and becomes opaque, as do dense data surfaces. Glass and Deep glass
|
|
87
|
+
progressively raise translucency, blur, highlights, and elevation; reduced-transparency,
|
|
88
|
+
increased-contrast, forced-colour, and unsupported-filter environments restore solid surfaces.
|
|
89
|
+
Top-layer components remain DOM children of their nearest Zx theme scope, so a theme or material
|
|
90
|
+
set on an application region continues to inherit through its menus, dialogs, tooltips, and toasts.
|
|
87
91
|
|
|
88
92
|
Define a product theme by overriding **semantic** tokens under a `[data-zx-theme="name"]` selector:
|
|
89
93
|
`--zx-color-bg-page/surface/raised/control/hover/selected`, `--zx-color-glass-chrome/surface`,
|
|
90
|
-
`--zx-color-overlay-surface/panel/border`,
|
|
94
|
+
`--zx-color-overlay-surface/panel/backdrop/border/hover/selected/divider/scrim`,
|
|
95
|
+
`--zx-overlay-surface-image/toast-image/panel-image`, `--zx-color-border(-strong/-control)`,
|
|
91
96
|
`--zx-color-text(-muted/-placeholder)`, `--zx-color-accent(-hover)/on-accent`,
|
|
92
97
|
`--zx-color-danger/warning/success/info(+ -bg)`, `--zx-focus-ring`, `--zx-control-height/-radius`,
|
|
93
98
|
`--zx-space-*`, `--zx-radius-*`, `--zx-text-*`. Never use tier-1 palette tokens (`--zx-gray-*`,
|
|
@@ -456,11 +461,13 @@ A native-dialog launcher for applications, recent records, commands, and grouped
|
|
|
456
461
|
Zx owns ranking, focus, request cancellation, and presentation; ZeyOS owns the catalogue,
|
|
457
462
|
permissions, recent-history/cache policy, and the route or command ultimately invoked.
|
|
458
463
|
|
|
459
|
-
- **Constructor** — `new Launcher(null, options)` owns a
|
|
464
|
+
- **Constructor** — `new Launcher(null, options)` owns a top-layer dialog in the opener's nearest
|
|
465
|
+
Zx theme scope;
|
|
460
466
|
`new Launcher(existingDialog, options)` enhances and later restores an existing `<dialog>`.
|
|
461
467
|
- **Options** — `items`, abortable `sources: [{id, label?, minQuery?, when?, order?, load(query, {signal})}]`,
|
|
462
468
|
`query`, `debounce: 250`, `minQuery: 0`, `maxResults: 100`, `placeholder`, `label`, `emptyText`,
|
|
463
|
-
`loadingText`, `shortcut: 'mod+k'|false`,
|
|
469
|
+
`loadingText`, `shortcut: 'mod+k'|false`, visible `hints` labels (or `false`), and `scope` to
|
|
470
|
+
explicitly select an element whose nearest Zx theme scope owns the launcher.
|
|
464
471
|
- **Items** — `{id, label, description?, keywords?, group?, icon?, badge?, value?, href?, target?,
|
|
465
472
|
invoke?, pinned?, disabled?, kind?, current?, when?, groupOrder?, itemOrder?}`. `icon` accepts a
|
|
466
473
|
bundled icon name, Node, or factory. Links stay native so modified and middle clicks work;
|
|
@@ -787,8 +794,10 @@ The shared floating region is fixed to the upper-right logical corner by default
|
|
|
787
794
|
messages rather than the page, and uses the translucent glass surface tokens with a solid fallback.
|
|
788
795
|
|
|
789
796
|
- **Statics** — `Message.info/success/warning/error(msg, opts)`,
|
|
790
|
-
`Message.show(msg, { kind, timeout=4000, closable })` → `{ close() }`,
|
|
791
|
-
`Message.progress(text)` → `{ update(pct, text?), done(), fail(text?) }`.
|
|
797
|
+
`Message.show(msg, { kind, timeout=4000, closable, scope? })` → `{ close() }`,
|
|
798
|
+
`Message.progress(text, {scope?})` → `{ update(pct, text?), done(), fail(text?) }`. `scope` is
|
|
799
|
+
an element or selector whose nearest Zx theme scope owns that floating region; without it the
|
|
800
|
+
focused opener supplies the scope.
|
|
792
801
|
- **Per-message options** — `kind: 'info'`, `timeout`, `closable: true`, `maxVisible: 5` (extras
|
|
793
802
|
queue, and hovering pauses dismissal).
|
|
794
803
|
<!-- /doc -->
|
|
@@ -797,10 +806,10 @@ messages rather than the page, and uses the translucent glass surface tokens wit
|
|
|
797
806
|
### Modal
|
|
798
807
|
|
|
799
808
|
Thin overlay on the native `<dialog>` element. The surface uses the shared, reading-sized overlay
|
|
800
|
-
panel material; the backdrop uses `--zx-color-
|
|
809
|
+
panel material; the backdrop uses `--zx-color-overlay-backdrop`, and focus returns to the opener on close.
|
|
801
810
|
|
|
802
811
|
- **Options** — `content`, `width: 'auto'`, `closable: true`, `lightDismiss: false`,
|
|
803
|
-
`destroyOnClose: false
|
|
812
|
+
`destroyOnClose: false`, `scope: null` (the opener's nearest Zx theme scope by default).
|
|
804
813
|
- **Methods** — `open()`, `close(result?)`, `setContent()`, `isOpen()`.
|
|
805
814
|
- **Events** — `open`, `close {result}`, `cancel` (Esc; preventable with `event.preventDefault()`).
|
|
806
815
|
<!-- /doc -->
|
|
@@ -832,7 +841,7 @@ When a Dock adopts it, the Sheet becomes an opaque layout surface instead.
|
|
|
832
841
|
(`true | 'trap-focus' | false`), `backdrop: 'dim'` (`'dim' | 'blur' | 'none'`), `size: null`
|
|
833
842
|
(CSS length or px number, applied to whichever axis `side` implies), `closeButton: true`,
|
|
834
843
|
plus everything Dialog takes — `title`, `content`, `buttons`, `closable`, `lightDismiss`
|
|
835
|
-
(defaults to `true` here, unlike Modal), `destroyOnClose`.
|
|
844
|
+
(defaults to `true` here, unlike Modal), `destroyOnClose`, `scope`.
|
|
836
845
|
- **Methods** — `open()`, `close(result?)`, `setSide(side)`, `getSide()`, `setSize(size)`,
|
|
837
846
|
`isModal()`, `isDocked()`, `isOpen()`, `getSize()`, `snapTo(target)`, plus Dialog's `setTitle()`, `setContent()`,
|
|
838
847
|
`setButtons()`, `addView()`, `showView()`.
|
|
@@ -846,10 +855,12 @@ When a Dock adopts it, the Sheet becomes an opaque layout surface instead.
|
|
|
846
855
|
across a handoff: the element moves and reopens in place, so DOM state and listeners survive,
|
|
847
856
|
and no `open`/`close` is emitted for what is only a change of address.
|
|
848
857
|
- **Modality** — `true` delegates focus containment, page inertness, Escape, and the backdrop to
|
|
849
|
-
the browser through `showModal()`. `'trap-focus'` and `false`
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
`
|
|
858
|
+
the browser through `showModal()`. Floating `'trap-focus'` and `false` sheets use a manual
|
|
859
|
+
Popover top layer, so they stay viewport-anchored even inside a transformed or clipped theme
|
|
860
|
+
scope while the page keeps scrolling and stays interactive. Escape and outside-click are
|
|
861
|
+
re-implemented, and `'trap-focus'` adds `focusTrap()` on top. Their top-layer backdrop is
|
|
862
|
+
explicitly transparent and non-filtering; `backdrop` is meaningful only when `modal` is `true`.
|
|
863
|
+
A docked Sheet remains a native non-modal dialog in its Dock's flow.
|
|
853
864
|
- **Resizing and detents** — `resizable` makes the inner edge draggable between `min` and `max`.
|
|
854
865
|
`snap` gives it detents: a number at or below 1 is a fraction of the viewport along the sheet's
|
|
855
866
|
axis, anything larger is pixels, and a string is any CSS length (`'320px'`, `'90%'`). With
|
|
@@ -920,12 +931,14 @@ alone, `new SheetStack({…})`.
|
|
|
920
931
|
### Dropdown
|
|
921
932
|
|
|
922
933
|
Generic anchored popover in the top layer, constructed as `new Dropdown(anchor, options)`. It
|
|
923
|
-
light-dismisses on an outside click or Esc,
|
|
934
|
+
light-dismisses on an outside click or Esc, mirrors `aria-expanded` onto the anchor, and stays a
|
|
935
|
+
DOM child of the anchor's nearest Zx theme scope while the browser lifts it into the top layer.
|
|
924
936
|
|
|
925
937
|
- **Options** — `content`,
|
|
926
938
|
`placement: 'bottom-start'|'bottom-end'|'top-start'|'top-end'|'bottom'|'top'` plus the matching
|
|
927
939
|
`left*` and `right*` placements, `offset: 4`,
|
|
928
|
-
`matchWidth: false`, `openOn: 'click'|'manual'`, `closeOnSelect: false
|
|
940
|
+
`matchWidth: false`, `openOn: 'click'|'manual'`, `closeOnSelect: false`, `scope: null` (the
|
|
941
|
+
anchor's nearest Zx theme scope by default).
|
|
929
942
|
- **Methods** — `open()`, `close()`, `toggle()`, `isOpen()`, `setContent()`, `getPanel()`.
|
|
930
943
|
- **Events** — `open`, `close`.
|
|
931
944
|
<!-- /doc -->
|
|
@@ -1252,6 +1265,12 @@ windowed loader.
|
|
|
1252
1265
|
idempotent, version-guarded `detail.revert()` if persistence fails. With `optimistic: false`, the
|
|
1253
1266
|
component stays controlled: it emits the proposal without changing data, and the application
|
|
1254
1267
|
applies an accepted response through `updateEvent()` or `setEvents()`. Zx does not send requests.
|
|
1268
|
+
- **View layout** — agenda days keep every event in one vertically stacked content column. Month
|
|
1269
|
+
uses a continuous seven-day grid with full-height day boundaries and event lanes overlaid below
|
|
1270
|
+
the date labels; today is indicated with accent text instead of a decorative edge highlight.
|
|
1271
|
+
- **Pointer editing** — a timed move preserves the point where the event was grabbed and snaps the
|
|
1272
|
+
resulting start to `slotDuration`. The lower grip stretches the event in its original day column,
|
|
1273
|
+
while pointer capture keeps either interaction active if the pointer leaves the event surface.
|
|
1255
1274
|
- **Keyboard** — time slots and date grids use arrow-key navigation, Home/End, PageUp/PageDown,
|
|
1256
1275
|
Enter/Space activation, and roving focus. On an editable event, Space grabs it, arrows propose a
|
|
1257
1276
|
slot/day move (or change the focused resize grip), Space drops, and Escape cancels. Live status
|
package/docs/llms.txt
CHANGED
|
@@ -52,7 +52,7 @@ exports and as `window.zx.*`.
|
|
|
52
52
|
- Helpers: `formatNumber`/`formatCurrency`/`formatPercent`/`formatFileSize`/`formatRelativeTime` (locale-aware, cached `Intl`), `debounce`/`throttle`, `groupBy`/`sortBy`/`uniqueBy`, `escapeRegExp`/`highlightMatch`, `storage()` (namespaced JSON, degrades to memory), `toCsv`/`downloadBlob`/`copyToClipboard`, `truncate()`/`isTruncated()` (line clamp with the full value on hover).
|
|
53
53
|
|
|
54
54
|
## Theming
|
|
55
|
-
Load `zx.css` once; set `data-zx-theme` (`light`|`dark`|`auto`) and `data-zx-density` (`cozy`|`compact`) on any ancestor. Define a product theme by overriding semantic `--zx-color-*` / `--zx-control-*` tokens under `[data-zx-theme="name"]`. The theme studio controls accent, neutral tint, density, font, type size, radius, control height, and CSS-first glass material. Compact menus, pickers, tooltips, column menus, and toasts share
|
|
55
|
+
Load `zx.css` once; set `data-zx-theme` (`light`|`dark`|`auto`) and `data-zx-density` (`cozy`|`compact`) on any ancestor. Define a product theme by overriding semantic `--zx-color-*` / `--zx-control-*` tokens under `[data-zx-theme="name"]`. The theme studio controls accent, neutral tint, density, font, type size, radius, control height, and CSS-first glass material. Compact menus, pickers, tooltips, column menus, and toasts share responsive compact glass; Launcher, Modal, Dialog, and floating Sheets share a calmer reading panel. Both preserve scoped theme inheritance and refract Aurora/page content; docked Sheets and dense data surfaces remain opaque. Never use tier-1 palette tokens or raw color literals in application CSS.
|
|
56
56
|
|
|
57
57
|
## Talking to ZeyOS
|
|
58
58
|
Use the dedicated ZeyOS client library **`@zeyos/client`** (`npm install @zeyos/client`) for ZeyOS business data: `createZeyosClient({ platform, auth })`, typed `client.api.list/get/create/update<Resource>` over the full OpenAPI surface, and `normalizeListResult`. Zx accepts the client by injection and does not bundle it. See the `website/layouts/zeyos-invoices.layout.js` layout for a Zx-components-on-@zeyos/client reference. Zx also ships a minimal `Http`/`zeyosService` for ad-hoc in-app `remotecall`, but `@zeyos/client` is the recommended default.
|
package/package.json
CHANGED
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
min-inline-size: 220px;
|
|
61
61
|
gap: 2px;
|
|
62
62
|
margin: calc(-1 * var(--zx-space-1)) calc(-1 * var(--zx-space-1)) var(--zx-space-1);
|
|
63
|
-
border-block-end: 1px solid var(--zx-color-
|
|
63
|
+
border-block-end: 1px solid var(--zx-color-overlay-divider);
|
|
64
64
|
padding: var(--zx-space-3);
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
}
|
|
102
102
|
.zx-app-rail__flyout-item:hover,
|
|
103
103
|
.zx-app-rail__flyout-item:focus-visible,
|
|
104
|
-
.zx-app-rail__flyout-item[aria-current="page"] { background: var(--zx-color-
|
|
104
|
+
.zx-app-rail__flyout-item[aria-current="page"] { background: var(--zx-color-overlay-hover); color: var(--zx-color-accent); }
|
|
105
105
|
.zx-app-rail__flyout-item-icon { display: inline-flex; grid-column: 1; }
|
|
106
106
|
.zx-app-rail__flyout-item-label { grid-column: 2; }
|
|
107
107
|
.zx-app-rail__flyout-item-badge { grid-column: 3; color: var(--zx-color-text-muted); font-size: var(--zx-text-xs); }
|
|
@@ -261,6 +261,7 @@
|
|
|
261
261
|
inline-size: 100%;
|
|
262
262
|
block-size: 100%;
|
|
263
263
|
min-inline-size: 0;
|
|
264
|
+
min-block-size: 0;
|
|
264
265
|
overflow: hidden;
|
|
265
266
|
border: 1px solid color-mix(in srgb, var(--zx-calendar-event-color) 65%, var(--zx-color-border));
|
|
266
267
|
border-inline-start: 3px solid var(--zx-calendar-event-color);
|
|
@@ -310,8 +311,11 @@
|
|
|
310
311
|
z-index: 3;
|
|
311
312
|
display: grid;
|
|
312
313
|
block-size: 8px;
|
|
314
|
+
min-block-size: 0;
|
|
313
315
|
place-items: center;
|
|
316
|
+
appearance: none;
|
|
314
317
|
border: 0;
|
|
318
|
+
border-radius: 0;
|
|
315
319
|
background: transparent;
|
|
316
320
|
padding: 0;
|
|
317
321
|
cursor: ns-resize;
|
|
@@ -329,6 +333,7 @@
|
|
|
329
333
|
.zx-calendar__event[data-variant="span"] .zx-calendar__event-action,
|
|
330
334
|
.zx-calendar__event[data-variant="month"] .zx-calendar__event-action {
|
|
331
335
|
display: flex;
|
|
336
|
+
min-block-size: 0;
|
|
332
337
|
align-items: center;
|
|
333
338
|
gap: var(--zx-space-1);
|
|
334
339
|
padding-block: 2px;
|
|
@@ -358,10 +363,17 @@
|
|
|
358
363
|
}
|
|
359
364
|
|
|
360
365
|
.zx-calendar__agenda { display: grid; }
|
|
361
|
-
.zx-calendar__agenda-day {
|
|
366
|
+
.zx-calendar__agenda-day {
|
|
367
|
+
display: grid;
|
|
368
|
+
grid-template-columns: minmax(10rem, 22%) minmax(0, 1fr);
|
|
369
|
+
border-block-end: 1px solid var(--zx-color-border);
|
|
370
|
+
}
|
|
362
371
|
.zx-calendar__agenda-heading {
|
|
363
|
-
align-self:
|
|
372
|
+
align-self: stretch;
|
|
373
|
+
appearance: none;
|
|
364
374
|
border: 0;
|
|
375
|
+
border-inline-end: 1px solid var(--zx-color-border);
|
|
376
|
+
border-radius: 0;
|
|
365
377
|
background: var(--zx-color-bg-muted);
|
|
366
378
|
color: var(--zx-color-text);
|
|
367
379
|
padding: var(--zx-space-3);
|
|
@@ -371,9 +383,13 @@
|
|
|
371
383
|
cursor: pointer;
|
|
372
384
|
}
|
|
373
385
|
.zx-calendar__agenda-heading[aria-current="date"] { color: var(--zx-color-accent); }
|
|
386
|
+
.zx-calendar__agenda-events {
|
|
387
|
+
display: grid;
|
|
388
|
+
min-inline-size: 0;
|
|
389
|
+
}
|
|
374
390
|
.zx-calendar__agenda-empty { margin: 0; color: var(--zx-color-text-muted); padding: var(--zx-space-3); }
|
|
375
|
-
.zx-calendar__agenda-
|
|
376
|
-
.zx-calendar__agenda-
|
|
391
|
+
.zx-calendar__agenda-events > .zx-calendar__event { border-block-end: 1px solid var(--zx-color-border); }
|
|
392
|
+
.zx-calendar__agenda-events > .zx-calendar__event:last-child { border-block-end: 0; }
|
|
377
393
|
.zx-calendar__event[data-variant="agenda"] .zx-calendar__event-action {
|
|
378
394
|
grid-template-columns: minmax(6rem, 18%) minmax(0, 1fr);
|
|
379
395
|
grid-template-areas: "time title" "time location";
|
|
@@ -418,26 +434,13 @@
|
|
|
418
434
|
text-transform: uppercase;
|
|
419
435
|
letter-spacing: .04em;
|
|
420
436
|
}
|
|
421
|
-
.zx-calendar__month-weekdays .zx-calendar__month-weekday { grid-row: 1; }
|
|
422
|
-
.zx-calendar__month-weekdays .zx-calendar__month-weekday:nth-last-child(7) { grid-column: 2; }
|
|
423
|
-
.zx-calendar__month-weekdays .zx-calendar__month-weekday:nth-last-child(6) { grid-column: 3; }
|
|
424
|
-
.zx-calendar__month-weekdays .zx-calendar__month-weekday:nth-last-child(5) { grid-column: 4; }
|
|
425
|
-
.zx-calendar__month-weekdays .zx-calendar__month-weekday:nth-last-child(4) { grid-column: 5; }
|
|
426
|
-
.zx-calendar__month-weekdays .zx-calendar__month-weekday:nth-last-child(3) { grid-column: 6; }
|
|
427
|
-
.zx-calendar__month-weekdays .zx-calendar__month-weekday:nth-last-child(2) { grid-column: 7; }
|
|
428
|
-
.zx-calendar__month-weekdays .zx-calendar__month-weekday:nth-last-child(1) { grid-column: 8; }
|
|
429
|
-
.zx-calendar__month-weekdays:not(:has(.zx-calendar__month-week-gutter)) .zx-calendar__month-weekday:nth-last-child(7) { grid-column: 1; }
|
|
430
|
-
.zx-calendar__month-weekdays:not(:has(.zx-calendar__month-week-gutter)) .zx-calendar__month-weekday:nth-last-child(6) { grid-column: 2; }
|
|
431
|
-
.zx-calendar__month-weekdays:not(:has(.zx-calendar__month-week-gutter)) .zx-calendar__month-weekday:nth-last-child(5) { grid-column: 3; }
|
|
432
|
-
.zx-calendar__month-weekdays:not(:has(.zx-calendar__month-week-gutter)) .zx-calendar__month-weekday:nth-last-child(4) { grid-column: 4; }
|
|
433
|
-
.zx-calendar__month-weekdays:not(:has(.zx-calendar__month-week-gutter)) .zx-calendar__month-weekday:nth-last-child(3) { grid-column: 5; }
|
|
434
|
-
.zx-calendar__month-weekdays:not(:has(.zx-calendar__month-week-gutter)) .zx-calendar__month-weekday:nth-last-child(2) { grid-column: 6; }
|
|
435
|
-
.zx-calendar__month-weekdays:not(:has(.zx-calendar__month-week-gutter)) .zx-calendar__month-weekday:nth-last-child(1) { grid-column: 7; }
|
|
436
437
|
.zx-calendar__month-week { min-block-size: 7.5rem; border-block-end: 1px solid var(--zx-color-border); }
|
|
437
438
|
.zx-calendar__month-week-number {
|
|
438
|
-
grid-row: 1
|
|
439
|
+
grid-row: 1;
|
|
440
|
+
appearance: none;
|
|
439
441
|
border: 0;
|
|
440
442
|
border-inline-end: 1px solid var(--zx-color-border);
|
|
443
|
+
border-radius: 0;
|
|
441
444
|
background: var(--zx-color-bg-muted);
|
|
442
445
|
color: var(--zx-color-text-muted);
|
|
443
446
|
font: inherit;
|
|
@@ -452,11 +455,21 @@
|
|
|
452
455
|
}
|
|
453
456
|
.zx-calendar__month-week:not(:has(.zx-calendar__month-week-number)) .zx-calendar__month-days,
|
|
454
457
|
.zx-calendar__month-week:not(:has(.zx-calendar__month-week-number)) .zx-calendar__month-events { grid-column: 1; }
|
|
455
|
-
.zx-calendar__month-days {
|
|
458
|
+
.zx-calendar__month-days {
|
|
459
|
+
grid-row: 1;
|
|
460
|
+
min-block-size: 7.5rem;
|
|
461
|
+
align-items: stretch;
|
|
462
|
+
}
|
|
456
463
|
.zx-calendar__month-day {
|
|
464
|
+
display: flex;
|
|
465
|
+
block-size: 100%;
|
|
466
|
+
appearance: none;
|
|
467
|
+
align-items: flex-start;
|
|
468
|
+
justify-content: flex-end;
|
|
457
469
|
min-block-size: var(--zx-control-height);
|
|
458
470
|
border: 0;
|
|
459
471
|
border-inline-end: 1px solid var(--zx-color-border);
|
|
472
|
+
border-radius: 0;
|
|
460
473
|
background: var(--zx-color-bg-surface);
|
|
461
474
|
color: var(--zx-color-text);
|
|
462
475
|
padding: var(--zx-space-2);
|
|
@@ -464,12 +477,34 @@
|
|
|
464
477
|
text-align: end;
|
|
465
478
|
cursor: pointer;
|
|
466
479
|
}
|
|
480
|
+
.zx-calendar__month-day:last-child { border-inline-end: 0; }
|
|
467
481
|
.zx-calendar__month-day[data-other-month="true"] { color: var(--zx-color-text-muted); background: var(--zx-color-bg-muted); }
|
|
468
|
-
.zx-calendar__month-day[aria-current="date"] {
|
|
482
|
+
.zx-calendar__month-day[aria-current="date"] {
|
|
483
|
+
color: var(--zx-color-accent);
|
|
484
|
+
font-weight: 750;
|
|
485
|
+
}
|
|
469
486
|
.zx-calendar__month-day:hover:not(:disabled) { background: var(--zx-color-bg-hover); }
|
|
470
|
-
.zx-calendar__month-events {
|
|
487
|
+
.zx-calendar__month-events {
|
|
488
|
+
grid-row: 1;
|
|
489
|
+
z-index: 2;
|
|
490
|
+
grid-auto-rows: calc(var(--zx-control-height) - var(--zx-space-2));
|
|
491
|
+
align-content: start;
|
|
492
|
+
gap: 2px 0;
|
|
493
|
+
padding-block: calc(var(--zx-control-height) + var(--zx-space-1)) var(--zx-space-1);
|
|
494
|
+
pointer-events: none;
|
|
495
|
+
}
|
|
496
|
+
.zx-calendar__month-events > .zx-calendar__event,
|
|
497
|
+
.zx-calendar__month-events > .zx-calendar__more { pointer-events: auto; }
|
|
498
|
+
.zx-calendar__event[data-variant="month"] .zx-calendar__event-action {
|
|
499
|
+
border-color: color-mix(in srgb, var(--zx-calendar-event-color) 55%, var(--zx-color-border));
|
|
500
|
+
border-inline-start-width: 1px;
|
|
501
|
+
border-radius: 0;
|
|
502
|
+
background: color-mix(in srgb, var(--zx-calendar-event-color) 13%, var(--zx-color-bg-surface));
|
|
503
|
+
}
|
|
504
|
+
.zx-calendar__event[data-variant="month"][data-all-day="true"] .zx-calendar__event-time { display: none; }
|
|
471
505
|
.zx-calendar__more {
|
|
472
506
|
align-self: center;
|
|
507
|
+
min-block-size: 0;
|
|
473
508
|
overflow: hidden;
|
|
474
509
|
border: 0;
|
|
475
510
|
background: transparent;
|
|
@@ -546,7 +581,14 @@
|
|
|
546
581
|
.zx-calendar__view-button { flex: 1 0 auto; }
|
|
547
582
|
.zx-calendar__new span { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
548
583
|
.zx-calendar__agenda-day { grid-template-columns: 1fr; }
|
|
549
|
-
.zx-calendar__agenda-heading {
|
|
584
|
+
.zx-calendar__agenda-heading {
|
|
585
|
+
position: sticky;
|
|
586
|
+
inset-block-start: 0;
|
|
587
|
+
z-index: 3;
|
|
588
|
+
border-inline-end: 0;
|
|
589
|
+
border-block-end: 1px solid var(--zx-color-border);
|
|
590
|
+
}
|
|
591
|
+
.zx-calendar__agenda-events { grid-column: 1; }
|
|
550
592
|
.zx-calendar__event[data-variant="agenda"] .zx-calendar__event-action { grid-template-columns: 5.5rem minmax(0, 1fr); }
|
|
551
593
|
.zx-calendar__year { grid-template-columns: 1fr; padding: var(--zx-space-3); }
|
|
552
594
|
}
|
|
@@ -452,7 +452,11 @@ export class Calendar extends Component {
|
|
|
452
452
|
if (!calendarEventIntersects(event, day, next)) return false;
|
|
453
453
|
return event.start >= day || index === 0;
|
|
454
454
|
}).sort(compareEvents);
|
|
455
|
-
const row = h('section', {
|
|
455
|
+
const row = h('section', {
|
|
456
|
+
class: 'zx-calendar__agenda-day',
|
|
457
|
+
role: 'row',
|
|
458
|
+
dataset: { date: calendarDayKey(day) }
|
|
459
|
+
},
|
|
456
460
|
h('button', {
|
|
457
461
|
type: 'button',
|
|
458
462
|
class: 'zx-calendar__agenda-heading',
|
|
@@ -461,11 +465,13 @@ export class Calendar extends Component {
|
|
|
461
465
|
ariaCurrent: sameCalendarDay(day, this._now()) ? 'date' : null,
|
|
462
466
|
disabled: this.options.disabled
|
|
463
467
|
}, this._format(day, { weekday: 'long', day: 'numeric', month: 'long' })));
|
|
468
|
+
const entries = h('div', { class: 'zx-calendar__agenda-events', role: 'gridcell' });
|
|
464
469
|
if (!events.length) {
|
|
465
|
-
|
|
470
|
+
entries.append(h('p', { class: 'zx-calendar__agenda-empty' }, this.msg('noEvents')));
|
|
466
471
|
} else {
|
|
467
|
-
for (const event of events)
|
|
472
|
+
for (const event of events) entries.append(this._eventElement(event, { variant: 'agenda', day }));
|
|
468
473
|
}
|
|
474
|
+
row.append(entries);
|
|
469
475
|
grid.append(row);
|
|
470
476
|
}
|
|
471
477
|
return grid;
|
|
@@ -721,6 +727,7 @@ export class Calendar extends Component {
|
|
|
721
727
|
dataset: {
|
|
722
728
|
eventId: key,
|
|
723
729
|
variant: context.variant,
|
|
730
|
+
allDay: event.allDay ? 'true' : null,
|
|
724
731
|
draggable: this._canMove(event) ? 'true' : null
|
|
725
732
|
},
|
|
726
733
|
style: event.color ? { '--zx-calendar-event-color': event.color } : null
|
|
@@ -989,7 +996,9 @@ export class Calendar extends Component {
|
|
|
989
996
|
const item = this._eventById(control.dataset.eventId);
|
|
990
997
|
const action = control.dataset.eventAction === 'resize' ? 'resize' : 'move';
|
|
991
998
|
if (!item || (action === 'move' ? !this._canMove(item) : !this._canResize(item))) return;
|
|
992
|
-
|
|
999
|
+
event.preventDefault();
|
|
1000
|
+
this._capturePointer(event.pointerId);
|
|
1001
|
+
const offset = action === 'move' ? this._dragOffsetForEvent(item, event.clientX, event.clientY) : null;
|
|
993
1002
|
this._pointer = {
|
|
994
1003
|
type: 'event',
|
|
995
1004
|
pointerId: event.pointerId,
|
|
@@ -999,6 +1008,8 @@ export class Calendar extends Component {
|
|
|
999
1008
|
event: cloneCalendarEvent(item),
|
|
1000
1009
|
action,
|
|
1001
1010
|
control,
|
|
1011
|
+
timeOffsetMinutes: offset?.minutes ?? 0,
|
|
1012
|
+
dayOffset: offset?.days ?? 0,
|
|
1002
1013
|
moved: false
|
|
1003
1014
|
};
|
|
1004
1015
|
return;
|
|
@@ -1007,7 +1018,8 @@ export class Calendar extends Component {
|
|
|
1007
1018
|
if (!slot || !this.options.selectable) return;
|
|
1008
1019
|
const date = this._dateFromTarget(slot);
|
|
1009
1020
|
if (!date) return;
|
|
1010
|
-
|
|
1021
|
+
event.preventDefault();
|
|
1022
|
+
this._capturePointer(event.pointerId);
|
|
1011
1023
|
this._pointer = {
|
|
1012
1024
|
type: 'selection',
|
|
1013
1025
|
pointerId: event.pointerId,
|
|
@@ -1041,7 +1053,13 @@ export class Calendar extends Component {
|
|
|
1041
1053
|
const wrapper = pointer.control.closest('.zx-calendar__event');
|
|
1042
1054
|
if (wrapper) {
|
|
1043
1055
|
wrapper.dataset.state = 'dragging';
|
|
1044
|
-
|
|
1056
|
+
if (pointer.action === 'resize') {
|
|
1057
|
+
wrapper.style.transform = '';
|
|
1058
|
+
const height = this._timedPreviewHeight(proposed);
|
|
1059
|
+
if (height) wrapper.style.blockSize = height;
|
|
1060
|
+
} else {
|
|
1061
|
+
wrapper.style.transform = `translate(${event.clientX - pointer.startX}px, ${event.clientY - pointer.startY}px)`;
|
|
1062
|
+
}
|
|
1045
1063
|
wrapper.dataset.preview = this._eventTime(proposed, proposed.start);
|
|
1046
1064
|
}
|
|
1047
1065
|
this._announce(this.msg('editPreview', proposed.title,
|
|
@@ -1053,6 +1071,7 @@ export class Calendar extends Component {
|
|
|
1053
1071
|
const pointer = this._pointer;
|
|
1054
1072
|
if (!pointer || pointer.pointerId !== event.pointerId) return;
|
|
1055
1073
|
this._pointer = null;
|
|
1074
|
+
this._releasePointerCapture(pointer.pointerId);
|
|
1056
1075
|
this._clearSelectionPreview();
|
|
1057
1076
|
if (!pointer.moved) return;
|
|
1058
1077
|
event.preventDefault();
|
|
@@ -1071,6 +1090,7 @@ export class Calendar extends Component {
|
|
|
1071
1090
|
_cancelPointer(event) {
|
|
1072
1091
|
if (!this._pointer || this._pointer.pointerId !== event.pointerId) return;
|
|
1073
1092
|
const control = this._pointer.control;
|
|
1093
|
+
this._releasePointerCapture(this._pointer.pointerId);
|
|
1074
1094
|
this._pointer = null;
|
|
1075
1095
|
this._clearSelectionPreview();
|
|
1076
1096
|
control.closest?.('.zx-calendar__event')?.removeAttribute('data-state');
|
|
@@ -1079,13 +1099,14 @@ export class Calendar extends Component {
|
|
|
1079
1099
|
|
|
1080
1100
|
/** @param {CalendarEvent} event @param {'move'|'resize'} action @param {number} x @param {number} y @returns {CalendarEvent|null} */
|
|
1081
1101
|
_proposalAtPoint(event, action, x, y) {
|
|
1082
|
-
const
|
|
1102
|
+
const resizeTimed = action === 'resize' && !calendarEventSpansDays(event);
|
|
1103
|
+
const timed = this._timeAtPoint(x, y, resizeTimed ? event.start : null);
|
|
1083
1104
|
if (timed && !calendarEventSpansDays(event)) {
|
|
1084
1105
|
if (action === 'move') {
|
|
1085
1106
|
const duration = event.end.getTime() - event.start.getTime();
|
|
1086
1107
|
const next = cloneCalendarEvent(event);
|
|
1087
|
-
next.start = timed;
|
|
1088
|
-
next.end = new Date(
|
|
1108
|
+
next.start = addMinutes(timed, -Number(this._pointer?.timeOffsetMinutes ?? 0));
|
|
1109
|
+
next.end = new Date(next.start.getTime() + duration);
|
|
1089
1110
|
return next;
|
|
1090
1111
|
}
|
|
1091
1112
|
const next = cloneCalendarEvent(event);
|
|
@@ -1094,7 +1115,8 @@ export class Calendar extends Component {
|
|
|
1094
1115
|
}
|
|
1095
1116
|
const day = this._dayAtPoint(x, y);
|
|
1096
1117
|
if (!day) return null;
|
|
1097
|
-
const
|
|
1118
|
+
const targetDay = addCalendarDays(day, -Number(this._pointer?.dayOffset ?? 0));
|
|
1119
|
+
const difference = calendarDayDifference(startOfCalendarDay(event.start), targetDay);
|
|
1098
1120
|
if (action === 'move') return shiftCalendarEvent(event, { days: difference, minutes: 0 });
|
|
1099
1121
|
const next = cloneCalendarEvent(event);
|
|
1100
1122
|
const endDay = addCalendarDays(day, 1);
|
|
@@ -1102,15 +1124,23 @@ export class Calendar extends Component {
|
|
|
1102
1124
|
return next.end > next.start ? next : null;
|
|
1103
1125
|
}
|
|
1104
1126
|
|
|
1105
|
-
/** @param {number} x @param {number} y @returns {Date|null} */
|
|
1106
|
-
_timeAtPoint(x, y) {
|
|
1127
|
+
/** @param {number} x @param {number} y @param {Date|null} [fixedDay=null] @returns {Date|null} */
|
|
1128
|
+
_timeAtPoint(x, y, fixedDay = null) {
|
|
1107
1129
|
const slots = this.refs.body.querySelector('.zx-calendar__slots');
|
|
1108
1130
|
if (!slots) return null;
|
|
1109
1131
|
const rect = slots.getBoundingClientRect();
|
|
1110
|
-
|
|
1132
|
+
const scroller = slots.closest('.zx-calendar__time-scroller');
|
|
1133
|
+
const viewport = scroller?.getBoundingClientRect();
|
|
1134
|
+
const left = viewport ? Math.max(rect.left, viewport.left) : rect.left;
|
|
1135
|
+
const right = viewport ? Math.min(rect.right, viewport.right) : rect.right;
|
|
1136
|
+
const top = viewport ? Math.max(rect.top, viewport.top) : rect.top;
|
|
1137
|
+
const bottom = viewport ? Math.min(rect.bottom, viewport.bottom) : rect.bottom;
|
|
1138
|
+
if (x < left || x > right || y < top || y > bottom || rect.width <= 0 || rect.height <= 0) return null;
|
|
1111
1139
|
const columns = Number(slots.closest('[role="grid"]')?.getAttribute('aria-colcount')) || 1;
|
|
1112
1140
|
const rows = Number(slots.closest('[role="grid"]')?.getAttribute('aria-rowcount')) || 1;
|
|
1113
|
-
const
|
|
1141
|
+
const fixedColumn = fixedDay ? this._timeGridDayIndex(fixedDay) : -1;
|
|
1142
|
+
const column = fixedColumn >= 0 ? fixedColumn
|
|
1143
|
+
: Math.max(0, Math.min(columns - 1, Math.floor((x - rect.left) / rect.width * columns)));
|
|
1114
1144
|
const row = Math.max(0, Math.min(rows - 1, Math.floor((y - rect.top) / rect.height * rows)));
|
|
1115
1145
|
const target = slots.querySelector(`[data-row-index="${row}"][data-day-index="${column}"]`);
|
|
1116
1146
|
return target ? this._dateFromTarget(target) : null;
|
|
@@ -1118,11 +1148,6 @@ export class Calendar extends Component {
|
|
|
1118
1148
|
|
|
1119
1149
|
/** @param {number} x @param {number} y @returns {Date|null} */
|
|
1120
1150
|
_dayAtPoint(x, y) {
|
|
1121
|
-
const elements = typeof document.elementsFromPoint === 'function' ? document.elementsFromPoint(x, y) : [];
|
|
1122
|
-
for (const element of elements) {
|
|
1123
|
-
const date = element instanceof HTMLElement ? element.dataset.date : null;
|
|
1124
|
-
if (date) return parseDayKey(date);
|
|
1125
|
-
}
|
|
1126
1151
|
const allDay = this.refs.body.querySelector('.zx-calendar__all-day-grid');
|
|
1127
1152
|
if (allDay) {
|
|
1128
1153
|
const rect = allDay.getBoundingClientRect();
|
|
@@ -1132,9 +1157,79 @@ export class Calendar extends Component {
|
|
|
1132
1157
|
return parseDayKey(cells[index]?.dataset.date);
|
|
1133
1158
|
}
|
|
1134
1159
|
}
|
|
1160
|
+
for (const monthEvents of this.refs.body.querySelectorAll('.zx-calendar__month-events')) {
|
|
1161
|
+
const rect = monthEvents.getBoundingClientRect();
|
|
1162
|
+
if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom || rect.width <= 0) continue;
|
|
1163
|
+
const start = parseDayKey(monthEvents.closest('.zx-calendar__month-week')?.dataset.weekStart);
|
|
1164
|
+
if (!start) continue;
|
|
1165
|
+
const index = Math.max(0, Math.min(6, Math.floor((x - rect.left) / rect.width * 7)));
|
|
1166
|
+
return addCalendarDays(start, index);
|
|
1167
|
+
}
|
|
1168
|
+
for (const agendaDay of this.refs.body.querySelectorAll('.zx-calendar__agenda-day')) {
|
|
1169
|
+
const rect = agendaDay.getBoundingClientRect();
|
|
1170
|
+
if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) continue;
|
|
1171
|
+
return parseDayKey(agendaDay.dataset.date);
|
|
1172
|
+
}
|
|
1173
|
+
const elements = typeof document.elementsFromPoint === 'function' ? document.elementsFromPoint(x, y) : [];
|
|
1174
|
+
for (const element of elements) {
|
|
1175
|
+
const dateTarget = element instanceof HTMLElement ? element.closest('[data-date]') : null;
|
|
1176
|
+
const date = dateTarget instanceof HTMLElement ? dateTarget.dataset.date : null;
|
|
1177
|
+
if (date) return parseDayKey(date);
|
|
1178
|
+
}
|
|
1135
1179
|
return null;
|
|
1136
1180
|
}
|
|
1137
1181
|
|
|
1182
|
+
/** @param {CalendarEvent} event @param {number} x @param {number} y @returns {{minutes: number, days: number}} */
|
|
1183
|
+
_dragOffsetForEvent(event, x, y) {
|
|
1184
|
+
if (!calendarEventSpansDays(event)) {
|
|
1185
|
+
const timed = this._timeAtPoint(x, y);
|
|
1186
|
+
if (timed) {
|
|
1187
|
+
const slot = normalizeSlot(this.options.slotDuration);
|
|
1188
|
+
const duration = Math.max(0, Math.round((event.end.getTime() - event.start.getTime()) / 60_000));
|
|
1189
|
+
const maxOffset = Math.max(0, duration - slot);
|
|
1190
|
+
const minutes = Math.round((timed.getTime() - event.start.getTime()) / 60_000);
|
|
1191
|
+
return { minutes: Math.max(0, Math.min(maxOffset, minutes)), days: 0 };
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
const day = this._dayAtPoint(x, y);
|
|
1195
|
+
const days = day ? calendarDayDifference(startOfCalendarDay(event.start), day) : 0;
|
|
1196
|
+
return { minutes: 0, days: Math.max(0, days) };
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
/** @param {Date} day @returns {number} */
|
|
1200
|
+
_timeGridDayIndex(day) {
|
|
1201
|
+
const key = calendarDayKey(day);
|
|
1202
|
+
const headings = [...this.refs.body.querySelectorAll('.zx-calendar__day-heading')];
|
|
1203
|
+
return headings.findIndex((heading) => heading.dataset.date === key);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/** @param {CalendarEvent} event @returns {string|null} */
|
|
1207
|
+
_timedPreviewHeight(event) {
|
|
1208
|
+
const grid = this.refs.body.querySelector('.zx-calendar__time-grid');
|
|
1209
|
+
if (!grid || calendarEventSpansDays(event)) return null;
|
|
1210
|
+
const min = normalizeMinute(this.options.slotMinTime, 0);
|
|
1211
|
+
const max = normalizeMinute(this.options.slotMaxTime, 1440, true);
|
|
1212
|
+
const slot = normalizeSlot(this.options.slotDuration);
|
|
1213
|
+
const start = event.start.getHours() * 60 + event.start.getMinutes();
|
|
1214
|
+
const end = event.end.getHours() * 60 + event.end.getMinutes();
|
|
1215
|
+
const minutes = Math.max(slot, Math.min(max, end) - Math.max(min, start));
|
|
1216
|
+
return `${(minutes / (max - min)) * 100}%`;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/** @param {number} pointerId @returns {void} */
|
|
1220
|
+
_capturePointer(pointerId) {
|
|
1221
|
+
try {
|
|
1222
|
+
this.el.setPointerCapture?.(pointerId);
|
|
1223
|
+
} catch {
|
|
1224
|
+
// Synthetic PointerEvents used by smoke tests do not create an active pointer capture target.
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/** @param {number} pointerId @returns {void} */
|
|
1229
|
+
_releasePointerCapture(pointerId) {
|
|
1230
|
+
if (this.el.hasPointerCapture?.(pointerId)) this.el.releasePointerCapture?.(pointerId);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1138
1233
|
/** @param {Date} start @param {Date} end @returns {void} */
|
|
1139
1234
|
_previewSelection(start, end) {
|
|
1140
1235
|
const low = Math.min(start.getTime(), end.getTime());
|