@pure-ds/storybook 0.1.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/.storybook/addons/description/preview.js +15 -0
- package/.storybook/addons/description/register.js +60 -0
- package/.storybook/addons/html-preview/Panel.jsx +327 -0
- package/.storybook/addons/html-preview/constants.js +6 -0
- package/.storybook/addons/html-preview/preview.js +178 -0
- package/.storybook/addons/html-preview/register.js +16 -0
- package/.storybook/addons/pds-configurator/SearchTool.js +44 -0
- package/.storybook/addons/pds-configurator/Tool.js +30 -0
- package/.storybook/addons/pds-configurator/constants.js +9 -0
- package/.storybook/addons/pds-configurator/preview.js +159 -0
- package/.storybook/addons/pds-configurator/register.js +24 -0
- package/.storybook/docs.css +35 -0
- package/.storybook/htmlPreview.css +103 -0
- package/.storybook/htmlPreview.js +271 -0
- package/.storybook/main.js +160 -0
- package/.storybook/preview-body.html +48 -0
- package/.storybook/preview-head.html +11 -0
- package/.storybook/preview.js +1563 -0
- package/README.md +266 -0
- package/bin/index.js +40 -0
- package/dist/pds-reference.json +2101 -0
- package/package.json +45 -0
- package/pds.config.js +6 -0
- package/public/assets/css/app.css +1216 -0
- package/public/assets/data/auto-design-advanced.json +704 -0
- package/public/assets/data/auto-design-simple.json +123 -0
- package/public/assets/img/icon-512x512.png +0 -0
- package/public/assets/img/logo-trans.png +0 -0
- package/public/assets/img/logo.png +0 -0
- package/public/assets/js/app.js +15088 -0
- package/public/assets/js/app.js.map +7 -0
- package/public/assets/js/lit.js +1176 -0
- package/public/assets/js/lit.js.map +7 -0
- package/public/assets/js/pds.js +9801 -0
- package/public/assets/js/pds.js.map +7 -0
- package/public/assets/pds/components/pds-calendar.js +837 -0
- package/public/assets/pds/components/pds-drawer.js +857 -0
- package/public/assets/pds/components/pds-icon.js +338 -0
- package/public/assets/pds/components/pds-jsonform.js +1775 -0
- package/public/assets/pds/components/pds-richtext.js +1035 -0
- package/public/assets/pds/components/pds-scrollrow.js +331 -0
- package/public/assets/pds/components/pds-splitpanel.js +401 -0
- package/public/assets/pds/components/pds-tabstrip.js +251 -0
- package/public/assets/pds/components/pds-toaster.js +446 -0
- package/public/assets/pds/components/pds-upload.js +657 -0
- package/public/assets/pds/custom-elements.json +2003 -0
- package/public/assets/pds/icons/pds-icons.svg +498 -0
- package/public/assets/pds/pds-css-complete.json +1861 -0
- package/public/assets/pds/pds-runtime-config.json +11 -0
- package/public/assets/pds/pds.css-data.json +2152 -0
- package/public/assets/pds/styles/pds-components.css +1944 -0
- package/public/assets/pds/styles/pds-components.css.js +3895 -0
- package/public/assets/pds/styles/pds-primitives.css +352 -0
- package/public/assets/pds/styles/pds-primitives.css.js +711 -0
- package/public/assets/pds/styles/pds-styles.css +3761 -0
- package/public/assets/pds/styles/pds-styles.css.js +7529 -0
- package/public/assets/pds/styles/pds-tokens.css +699 -0
- package/public/assets/pds/styles/pds-tokens.css.js +1405 -0
- package/public/assets/pds/styles/pds-utilities.css +763 -0
- package/public/assets/pds/styles/pds-utilities.css.js +1533 -0
- package/public/assets/pds/vscode-custom-data.json +824 -0
- package/scripts/build-pds-reference.mjs +807 -0
- package/scripts/generate-stories.js +542 -0
- package/scripts/package-build.js +86 -0
- package/src/js/app.js +17 -0
- package/src/js/common/ask.js +208 -0
- package/src/js/common/common.js +20 -0
- package/src/js/common/font-loader.js +200 -0
- package/src/js/common/msg.js +90 -0
- package/src/js/lit.js +40 -0
- package/src/js/pds-core/pds-config.js +1162 -0
- package/src/js/pds-core/pds-enhancer-metadata.js +75 -0
- package/src/js/pds-core/pds-enhancers.js +357 -0
- package/src/js/pds-core/pds-enums.js +86 -0
- package/src/js/pds-core/pds-generator.js +5317 -0
- package/src/js/pds-core/pds-ontology.js +256 -0
- package/src/js/pds-core/pds-paths.js +109 -0
- package/src/js/pds-core/pds-query.js +571 -0
- package/src/js/pds-core/pds-registry.js +129 -0
- package/src/js/pds-core/pds.d.ts +129 -0
- package/src/js/pds.d.ts +408 -0
- package/src/js/pds.js +1579 -0
- package/src/pds-core/pds-api.js +105 -0
- package/stories/GettingStarted.md +96 -0
- package/stories/GettingStarted.stories.js +144 -0
- package/stories/WhatIsPDS.md +194 -0
- package/stories/WhatIsPDS.stories.js +144 -0
- package/stories/components/PdsCalendar.stories.js +263 -0
- package/stories/components/PdsDrawer.stories.js +623 -0
- package/stories/components/PdsIcon.stories.js +78 -0
- package/stories/components/PdsJsonform.stories.js +1444 -0
- package/stories/components/PdsRichtext.stories.js +367 -0
- package/stories/components/PdsScrollrow.stories.js +140 -0
- package/stories/components/PdsSplitpanel.stories.js +502 -0
- package/stories/components/PdsTabstrip.stories.js +442 -0
- package/stories/components/PdsToaster.stories.js +186 -0
- package/stories/components/PdsUpload.stories.js +66 -0
- package/stories/enhancements/Dropdowns.stories.js +185 -0
- package/stories/enhancements/InteractiveStates.stories.js +625 -0
- package/stories/enhancements/MeshGradients.stories.js +320 -0
- package/stories/enhancements/OpenGroups.stories.js +227 -0
- package/stories/enhancements/RangeSliders.stories.js +232 -0
- package/stories/enhancements/RequiredFields.stories.js +189 -0
- package/stories/enhancements/Toggles.stories.js +167 -0
- package/stories/foundations/Colors.stories.js +283 -0
- package/stories/foundations/Icons.stories.js +305 -0
- package/stories/foundations/SmartSurfaces.stories.js +367 -0
- package/stories/foundations/Spacing.stories.js +175 -0
- package/stories/foundations/Typography.stories.js +960 -0
- package/stories/foundations/ZIndex.stories.js +325 -0
- package/stories/patterns/BorderEffects.stories.js +72 -0
- package/stories/patterns/Layout.stories.js +99 -0
- package/stories/patterns/Utilities.stories.js +107 -0
- package/stories/primitives/Accordion.stories.js +359 -0
- package/stories/primitives/Alerts.stories.js +64 -0
- package/stories/primitives/Badges.stories.js +183 -0
- package/stories/primitives/Buttons.stories.js +229 -0
- package/stories/primitives/Cards.stories.js +353 -0
- package/stories/primitives/FormGroups.stories.js +569 -0
- package/stories/primitives/Forms.stories.js +131 -0
- package/stories/primitives/Media.stories.js +203 -0
- package/stories/primitives/Tables.stories.js +232 -0
- package/stories/reference/ReferenceCatalog.stories.js +28 -0
- package/stories/reference/reference-catalog.js +413 -0
- package/stories/reference/reference-docs.js +302 -0
- package/stories/reference/reference-helpers.js +310 -0
- package/stories/utilities/GridSystem.stories.js +208 -0
- package/stories/utils/PdsAsk.stories.js +420 -0
- package/stories/utils/toast-utils.js +148 -0
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
const PDS = window.PDS;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @element pds-drawer
|
|
5
|
+
* @fires toggle - Fired when the drawer opens or closes
|
|
6
|
+
*
|
|
7
|
+
* @slot drawer-header - Header content for the drawer
|
|
8
|
+
* @slot drawer-content - Main content of the drawer
|
|
9
|
+
*
|
|
10
|
+
* @cssprop --drawer-duration - Animation duration (default: var(--transition-normal))
|
|
11
|
+
* @cssprop --drawer-easing - Animation easing function (default: var(--easing-emphasized))
|
|
12
|
+
* @cssprop --drawer-max-height - Maximum height when position is top/bottom (default: 70vh)
|
|
13
|
+
* @cssprop --drawer-min-height - Minimum height when position is top/bottom (default: auto)
|
|
14
|
+
*
|
|
15
|
+
* @csspart backdrop - The semi-transparent backdrop overlay
|
|
16
|
+
* @csspart panel - The drawer panel container
|
|
17
|
+
* @csspart header - The drawer header section
|
|
18
|
+
* @csspart close-button - The close button
|
|
19
|
+
* @csspart grab-handle - The drag handle indicator
|
|
20
|
+
* @csspart content - The drawer content section
|
|
21
|
+
*/
|
|
22
|
+
customElements.define("pds-drawer", class extends HTMLElement {
|
|
23
|
+
#isDragging = false;
|
|
24
|
+
#startX = 0;
|
|
25
|
+
#startY = 0;
|
|
26
|
+
#lastX = 0;
|
|
27
|
+
#lastY = 0;
|
|
28
|
+
#lastTS = 0;
|
|
29
|
+
#velocity = 0; // px/ms along active axis
|
|
30
|
+
#startFraction = 0;
|
|
31
|
+
#aside = null;
|
|
32
|
+
#drawerHeight = 0;
|
|
33
|
+
#drawerWidth = 0;
|
|
34
|
+
#raf = 0;
|
|
35
|
+
#currentFraction = 0; // 0=open, 1=closed
|
|
36
|
+
#resizeObs = null;
|
|
37
|
+
#openAnimationController = null;
|
|
38
|
+
constructor() {
|
|
39
|
+
super();
|
|
40
|
+
this.attachShadow({ mode: "open" });
|
|
41
|
+
// default state
|
|
42
|
+
this._open = false;
|
|
43
|
+
this._position = "bottom"; // bottom | top | left | right
|
|
44
|
+
this._drag = "header"; // header | none
|
|
45
|
+
this._maxHeight = "";
|
|
46
|
+
this._minHeight = "";
|
|
47
|
+
this._showClose = false;
|
|
48
|
+
}
|
|
49
|
+
static get observedAttributes() {
|
|
50
|
+
return [
|
|
51
|
+
"open",
|
|
52
|
+
"position",
|
|
53
|
+
"drag",
|
|
54
|
+
"max-height",
|
|
55
|
+
"min-height",
|
|
56
|
+
"show-close",
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Attribute/property reflection
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Controls whether the drawer is open or closed
|
|
64
|
+
* @type {boolean}
|
|
65
|
+
* @attr open
|
|
66
|
+
*/
|
|
67
|
+
get open() {
|
|
68
|
+
return this._open;
|
|
69
|
+
}
|
|
70
|
+
set open(val) {
|
|
71
|
+
const bool = Boolean(val);
|
|
72
|
+
if (this._open === bool) return;
|
|
73
|
+
this._open = bool;
|
|
74
|
+
this.toggleAttribute("open", this._open);
|
|
75
|
+
if (this._open) {
|
|
76
|
+
queueMicrotask(() => this.#aside?.focus());
|
|
77
|
+
document.body.classList.add("drawer-open");
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
document.body.classList.remove("drawer-open");
|
|
81
|
+
}
|
|
82
|
+
this.dispatchEvent(new Event("toggle"));
|
|
83
|
+
this.#syncAria();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Position of the drawer relative to the viewport
|
|
88
|
+
* @type {"bottom" | "top" | "left" | "right"}
|
|
89
|
+
* @attr position
|
|
90
|
+
* @default "bottom"
|
|
91
|
+
*/
|
|
92
|
+
get position() {
|
|
93
|
+
return this._position;
|
|
94
|
+
}
|
|
95
|
+
set position(val) {
|
|
96
|
+
const v = String(val || "bottom");
|
|
97
|
+
if (this._position === v) return;
|
|
98
|
+
this._position = v;
|
|
99
|
+
this.setAttribute("position", v);
|
|
100
|
+
this.#applyFraction(this.#currentFraction, false);
|
|
101
|
+
this.#renderCloseButtonVisibility();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Controls drag interaction behavior
|
|
106
|
+
* @type {"header" | "none"}
|
|
107
|
+
* @attr drag
|
|
108
|
+
* @default "header"
|
|
109
|
+
*/
|
|
110
|
+
get drag() {
|
|
111
|
+
return this._drag;
|
|
112
|
+
}
|
|
113
|
+
set drag(val) {
|
|
114
|
+
const v = String(val || "header");
|
|
115
|
+
if (this._drag === v) return;
|
|
116
|
+
this._drag = v;
|
|
117
|
+
this.setAttribute("drag", v);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Maximum height for top/bottom positioned drawers (CSS value)
|
|
122
|
+
* @type {string}
|
|
123
|
+
* @attr max-height
|
|
124
|
+
* @default "70vh"
|
|
125
|
+
*/
|
|
126
|
+
get maxHeight() {
|
|
127
|
+
return this._maxHeight;
|
|
128
|
+
}
|
|
129
|
+
set maxHeight(val) {
|
|
130
|
+
this._maxHeight = val || "";
|
|
131
|
+
if (this.#aside) {
|
|
132
|
+
this.#aside.style.setProperty(
|
|
133
|
+
"--drawer-max-height",
|
|
134
|
+
this._maxHeight || "70vh"
|
|
135
|
+
);
|
|
136
|
+
this.#recalc();
|
|
137
|
+
}
|
|
138
|
+
if (this._maxHeight) this.setAttribute("max-height", this._maxHeight);
|
|
139
|
+
else this.removeAttribute("max-height");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Minimum height for top/bottom positioned drawers (CSS value)
|
|
144
|
+
* @type {string}
|
|
145
|
+
* @attr min-height
|
|
146
|
+
* @default "auto"
|
|
147
|
+
*/
|
|
148
|
+
get minHeight() {
|
|
149
|
+
return this._minHeight;
|
|
150
|
+
}
|
|
151
|
+
set minHeight(val) {
|
|
152
|
+
this._minHeight = val || "";
|
|
153
|
+
if (this.#aside) {
|
|
154
|
+
this.#aside.style.setProperty(
|
|
155
|
+
"--drawer-min-height",
|
|
156
|
+
this._minHeight || "auto"
|
|
157
|
+
);
|
|
158
|
+
this.#recalc();
|
|
159
|
+
}
|
|
160
|
+
if (this._minHeight) this.setAttribute("min-height", this._minHeight);
|
|
161
|
+
else this.removeAttribute("min-height");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Whether to show the close button in the header
|
|
166
|
+
* @type {boolean}
|
|
167
|
+
* @attr show-close
|
|
168
|
+
* @default false
|
|
169
|
+
*/
|
|
170
|
+
get showClose() {
|
|
171
|
+
return this._showClose;
|
|
172
|
+
}
|
|
173
|
+
set showClose(val) {
|
|
174
|
+
const bool = Boolean(val);
|
|
175
|
+
this._showClose = bool;
|
|
176
|
+
this.toggleAttribute("show-close", this._showClose);
|
|
177
|
+
this.#renderCloseButtonVisibility();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
attributeChangedCallback(name, _old, value) {
|
|
181
|
+
switch (name) {
|
|
182
|
+
case "open":
|
|
183
|
+
this._open = this.hasAttribute("open");
|
|
184
|
+
if (this._open) {
|
|
185
|
+
this.#queueOpenAnimation();
|
|
186
|
+
} else {
|
|
187
|
+
this.#cancelPendingOpenAnimation();
|
|
188
|
+
this.#animateTo(1);
|
|
189
|
+
}
|
|
190
|
+
this.#syncAria();
|
|
191
|
+
break;
|
|
192
|
+
case "position":
|
|
193
|
+
this._position = value || "bottom";
|
|
194
|
+
this.#applyFraction(this.#currentFraction, false);
|
|
195
|
+
this.#renderCloseButtonVisibility();
|
|
196
|
+
break;
|
|
197
|
+
case "drag":
|
|
198
|
+
this._drag = value || "header";
|
|
199
|
+
break;
|
|
200
|
+
case "max-height":
|
|
201
|
+
this._maxHeight = value || "";
|
|
202
|
+
if (this.#aside)
|
|
203
|
+
this.#aside.style.setProperty(
|
|
204
|
+
"--drawer-max-height",
|
|
205
|
+
this._maxHeight || "70vh"
|
|
206
|
+
);
|
|
207
|
+
break;
|
|
208
|
+
case "min-height":
|
|
209
|
+
this._minHeight = value || "";
|
|
210
|
+
if (this.#aside)
|
|
211
|
+
this.#aside.style.setProperty(
|
|
212
|
+
"--drawer-min-height",
|
|
213
|
+
this._minHeight || "auto"
|
|
214
|
+
);
|
|
215
|
+
break;
|
|
216
|
+
case "show-close":
|
|
217
|
+
this._showClose = this.hasAttribute("show-close");
|
|
218
|
+
this.#renderCloseButtonVisibility();
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async connectedCallback() {
|
|
224
|
+
if (!this.shadowRoot) this.attachShadow({ mode: "open" });
|
|
225
|
+
|
|
226
|
+
// Set default position attribute if not explicitly set
|
|
227
|
+
if (!this.hasAttribute('position')) {
|
|
228
|
+
this.setAttribute('position', 'bottom');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Compose shadow DOM
|
|
232
|
+
this.shadowRoot.innerHTML = /*html*/`
|
|
233
|
+
<div class="backdrop" part="backdrop"></div>
|
|
234
|
+
<div class="layer" id="layer" aria-hidden="true">
|
|
235
|
+
<aside part="panel" tabindex="-1">
|
|
236
|
+
<header part="header">
|
|
237
|
+
<button class="close-btn" part="close-button" aria-label="Close drawer" hidden>
|
|
238
|
+
<pds-icon icon="x" size="sm"></pds-icon>
|
|
239
|
+
</button>
|
|
240
|
+
<slot name="drawer-header"></slot>
|
|
241
|
+
<div class="grab-handle" part="grab-handle" aria-hidden="true"></div>
|
|
242
|
+
</header>
|
|
243
|
+
<div part="content">
|
|
244
|
+
<slot name="drawer-content"></slot>
|
|
245
|
+
</div>
|
|
246
|
+
</aside>
|
|
247
|
+
</div>
|
|
248
|
+
`;
|
|
249
|
+
|
|
250
|
+
// Adopt PDS layers + component stylesheet
|
|
251
|
+
const componentStyles = PDS.createStylesheet(/*css*/ `
|
|
252
|
+
@layer pds-drawer {
|
|
253
|
+
:host { position: fixed; inset: 0; display: contents; contain: layout style size; }
|
|
254
|
+
|
|
255
|
+
/* Timing tokens */
|
|
256
|
+
:host { --_dur: var(--drawer-duration, var(--transition-normal)); }
|
|
257
|
+
:host { --_easing: var(--drawer-easing, var(--easing-emphasized, cubic-bezier(0.25,1,0.5,1))); }
|
|
258
|
+
|
|
259
|
+
::slotted(*) {
|
|
260
|
+
padding: var(--spacing-4);
|
|
261
|
+
background-color: var(--color-surface-overlay);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Backdrop */
|
|
265
|
+
.backdrop {
|
|
266
|
+
position: fixed; inset: 0;
|
|
267
|
+
background: var(--backdrop-bg, var(--color-scrim, color-mix(in oklab, CanvasText 20%, Canvas 80%)));
|
|
268
|
+
backdrop-filter: var(--backdrop-filter, none);
|
|
269
|
+
opacity: 0; pointer-events: none; visibility: hidden;
|
|
270
|
+
transition: opacity var(--_dur) var(--_easing), visibility 0s var(--_dur);
|
|
271
|
+
z-index: var(--z-modal);
|
|
272
|
+
}
|
|
273
|
+
:host([open]) .backdrop { opacity: var(--backdrop-opacity); pointer-events: auto; visibility: visible; transition-delay: 0s; }
|
|
274
|
+
|
|
275
|
+
/* Layer container */
|
|
276
|
+
.layer {
|
|
277
|
+
position: fixed; left: 0; right: 0; width: 100%; max-width: 100%;
|
|
278
|
+
contain: layout paint style; will-change: transform;
|
|
279
|
+
z-index: var(--z-drawer);
|
|
280
|
+
display: flex; align-items: flex-end;
|
|
281
|
+
pointer-events: none; visibility: hidden;
|
|
282
|
+
transition: visibility 0s var(--_dur);
|
|
283
|
+
}
|
|
284
|
+
:host([open]) .layer { pointer-events: auto; visibility: visible; transition-delay: 0s; }
|
|
285
|
+
:host([position="bottom"]) .layer { bottom: 0; height: auto; }
|
|
286
|
+
:host([position="top"]) .layer { top: 0; height: auto; align-items: flex-start; }
|
|
287
|
+
|
|
288
|
+
/* Left/Right layout */
|
|
289
|
+
:host([position="left"]) .layer, :host([position="right"]) .layer {
|
|
290
|
+
top: 0; bottom: 0; translate: none;
|
|
291
|
+
width: var(--drawer-width, min(90vw, 28rem));
|
|
292
|
+
max-width: var(--drawer-width, min(90vw, 28rem));
|
|
293
|
+
}
|
|
294
|
+
:host([position="left"]) .layer { left: 0; right: auto; }
|
|
295
|
+
:host([position="right"]) .layer { right: 0; left: auto; }
|
|
296
|
+
|
|
297
|
+
/* Panel */
|
|
298
|
+
aside {
|
|
299
|
+
display: flex; flex-direction: column;
|
|
300
|
+
background: var(--drawer-bg, var(--color-surface-overlay, Canvas));
|
|
301
|
+
box-shadow: var(--drawer-shadow, var(--shadow-xl));
|
|
302
|
+
max-height: var(--drawer-max-height, 70vh);
|
|
303
|
+
min-height: var(--drawer-min-height, auto);
|
|
304
|
+
width: 100%; max-width: 100%;
|
|
305
|
+
margin: 0;
|
|
306
|
+
border-radius: var(--drawer-radius, var(--radius-lg));
|
|
307
|
+
overflow: clip; contain: layout paint style; will-change: transform;
|
|
308
|
+
touch-action: none;
|
|
309
|
+
outline: none;
|
|
310
|
+
}
|
|
311
|
+
:host([position="bottom"]) aside {
|
|
312
|
+
border-bottom-left-radius: 0; border-bottom-right-radius: 0;
|
|
313
|
+
}
|
|
314
|
+
:host([position="top"]) aside {
|
|
315
|
+
flex-direction: column-reverse;
|
|
316
|
+
border-top-left-radius: 0; border-top-right-radius: 0;
|
|
317
|
+
}
|
|
318
|
+
:host([position="left"]) aside {
|
|
319
|
+
border-top-left-radius: 0; border-bottom-left-radius: 0;
|
|
320
|
+
max-height: 100vh; height: 100%; width: 100%;
|
|
321
|
+
}
|
|
322
|
+
:host([position="right"]) aside {
|
|
323
|
+
border-top-right-radius: 0; border-bottom-right-radius: 0;
|
|
324
|
+
max-height: 100vh; height: 100%; width: 100%;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
header {
|
|
328
|
+
position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
329
|
+
min-block-size: var(--drawer-header-min-hit, var(--control-min-height, var(--spacing-10)));
|
|
330
|
+
}
|
|
331
|
+
.grab-handle {
|
|
332
|
+
order: -1; /* Put grab handle first (top) by default */
|
|
333
|
+
inline-size: var(--drawer-handle-width, var(--size-9, var(--spacing-9)));
|
|
334
|
+
block-size: var(--drawer-handle-height, var(--size-1, var(--spacing-1)));
|
|
335
|
+
border-radius: var(--drawer-handle-radius, var(--radius-full));
|
|
336
|
+
background: var(--drawer-handle-bg, var(--color-border));
|
|
337
|
+
opacity: 0.9; pointer-events: none; user-select: none;
|
|
338
|
+
}
|
|
339
|
+
:host([position="left"]) .grab-handle, :host([position="right"]) .grab-handle { display:none; }
|
|
340
|
+
:host([position="top"]) .grab-handle { order: 1; } /* Put grab handle last (bottom visually) for top position */
|
|
341
|
+
|
|
342
|
+
.close-btn {
|
|
343
|
+
position: absolute; right: var(--spacing-2); top: 50%; transform: translateY(-50%);
|
|
344
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
345
|
+
width: var(--size-8, var(--spacing-8)); height: var(--size-8, var(--spacing-8));
|
|
346
|
+
border-radius: var(--radius-sm);
|
|
347
|
+
border: none; background: transparent; color: inherit; cursor: pointer;
|
|
348
|
+
}
|
|
349
|
+
.close-btn:hover { opacity: 0.85; }
|
|
350
|
+
.close-btn:focus { outline: var(--focus-outline, none); }
|
|
351
|
+
::slotted([slot="drawer-header"]) { inline-size: 100%; display: block; min-block-size: var(--drawer-header-min-hit, var(--control-min-height, var(--spacing-10))); }
|
|
352
|
+
|
|
353
|
+
[part="content"] { flex: 1; min-height: 0; overflow: auto; -webkit-overflow-scrolling: touch; contain: layout paint style; }
|
|
354
|
+
|
|
355
|
+
main { overflow: auto; -webkit-overflow-scrolling: touch; contain: layout paint style; transition: height var(--_dur) var(--_easing); }
|
|
356
|
+
|
|
357
|
+
@media (min-width: 800px) {
|
|
358
|
+
aside { width: 100%; max-width: 800px; margin-inline: auto; border-radius: var(--drawer-radius, var(--radius-lg)); overflow: hidden; }
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
`);
|
|
362
|
+
|
|
363
|
+
await PDS.adoptLayers(this.shadowRoot, ["primitives", "components"], [componentStyles]);
|
|
364
|
+
|
|
365
|
+
// References
|
|
366
|
+
this.#aside = this.shadowRoot.querySelector("aside");
|
|
367
|
+
this.#applyFraction(this.open ? 0 : 1, false);
|
|
368
|
+
this.#syncAria();
|
|
369
|
+
this.#renderCloseButtonVisibility();
|
|
370
|
+
|
|
371
|
+
// Wire events
|
|
372
|
+
const backdrop = this.shadowRoot.querySelector('.backdrop');
|
|
373
|
+
backdrop?.addEventListener('click', this.#onBackdropClick);
|
|
374
|
+
|
|
375
|
+
const aside = this.#aside;
|
|
376
|
+
if (aside) aside.addEventListener('pointerdown', (e) => {
|
|
377
|
+
if (this._drag === 'none') return;
|
|
378
|
+
// Only allow drag from header when configured
|
|
379
|
+
if (this._drag === 'header') {
|
|
380
|
+
const header = this.shadowRoot.querySelector('header');
|
|
381
|
+
const path = e.composedPath();
|
|
382
|
+
if (!path.includes(header)) return;
|
|
383
|
+
}
|
|
384
|
+
this.#onPointerDown(e);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Global listeners
|
|
388
|
+
window.addEventListener("pointermove", this.#onPointerMove, { passive: false });
|
|
389
|
+
window.addEventListener("pointerup", this.#onPointerUp, { passive: true });
|
|
390
|
+
window.addEventListener("keydown", this.#onKeyDown);
|
|
391
|
+
|
|
392
|
+
// Resize observers
|
|
393
|
+
this.#resizeObs = new ResizeObserver(this.#recalc);
|
|
394
|
+
this.#resizeObs.observe(this.#aside);
|
|
395
|
+
window.addEventListener("resize", this.#recalc, { passive: true });
|
|
396
|
+
if (window.visualViewport) window.visualViewport.addEventListener("resize", this.#recalc, { passive: true });
|
|
397
|
+
|
|
398
|
+
this.#recalc();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
disconnectedCallback() {
|
|
402
|
+
// Clean up global listeners
|
|
403
|
+
window.removeEventListener("pointermove", this.#onPointerMove);
|
|
404
|
+
window.removeEventListener("pointerup", this.#onPointerUp);
|
|
405
|
+
window.removeEventListener("keydown", this.#onKeyDown);
|
|
406
|
+
if (window.visualViewport)
|
|
407
|
+
window.visualViewport.removeEventListener("resize", this.#recalc);
|
|
408
|
+
window.removeEventListener("resize", this.#recalc);
|
|
409
|
+
this.#resizeObs?.disconnect();
|
|
410
|
+
cancelAnimationFrame(this.#raf);
|
|
411
|
+
this.#cancelPendingOpenAnimation();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Public API
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Opens the drawer
|
|
418
|
+
* @method openDrawer
|
|
419
|
+
* @public
|
|
420
|
+
*/
|
|
421
|
+
openDrawer() {
|
|
422
|
+
this.open = true;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Closes the drawer
|
|
427
|
+
* @method closeDrawer
|
|
428
|
+
* @public
|
|
429
|
+
*/
|
|
430
|
+
closeDrawer() {
|
|
431
|
+
this.open = false;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Toggles the drawer open/closed state
|
|
436
|
+
* @method toggleDrawer
|
|
437
|
+
* @public
|
|
438
|
+
*/
|
|
439
|
+
toggleDrawer() {
|
|
440
|
+
this.open = !this.open;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Configure and open the drawer in one call
|
|
445
|
+
* @method show
|
|
446
|
+
* @public
|
|
447
|
+
* @param {any|HTMLElement|string} htmlContent - The main content to display
|
|
448
|
+
* @param {Object} [options] - Configuration options
|
|
449
|
+
* @param {any|HTMLElement|string} [options.header] - Header content
|
|
450
|
+
* @param {"bottom"|"top"|"left"|"right"} [options.position] - Drawer position
|
|
451
|
+
* @param {string} [options.maxHeight] - Maximum height (CSS value)
|
|
452
|
+
* @param {string} [options.minHeight] - Minimum height (CSS value)
|
|
453
|
+
* @param {boolean} [options.showClose] - Show close button
|
|
454
|
+
* @param {boolean} [options.waitForMedia=true] - Wait for images/videos to load
|
|
455
|
+
* @param {number} [options.mediaTimeout=500] - Media load timeout in ms
|
|
456
|
+
* @returns {Promise<this>} Resolves to the drawer element
|
|
457
|
+
*/
|
|
458
|
+
async show(htmlContent, options = {}) {
|
|
459
|
+
// Apply provided options to this instance
|
|
460
|
+
if (options.position) this.position = options.position;
|
|
461
|
+
if (options.maxHeight) this.maxHeight = options.maxHeight;
|
|
462
|
+
if (options.minHeight) this.minHeight = options.minHeight;
|
|
463
|
+
|
|
464
|
+
// Close button visibility
|
|
465
|
+
const pos = this.position || "bottom";
|
|
466
|
+
const defaultShowClose = pos === "left" || pos === "right";
|
|
467
|
+
const showClose = options.showClose === undefined ? defaultShowClose : !!options.showClose;
|
|
468
|
+
this.showClose = showClose;
|
|
469
|
+
|
|
470
|
+
// Render content (header/body)
|
|
471
|
+
await this.setContent(htmlContent, options.header);
|
|
472
|
+
|
|
473
|
+
// Wait for next frame so slots are distributed
|
|
474
|
+
await new Promise((r) => requestAnimationFrame(() => r()));
|
|
475
|
+
|
|
476
|
+
// Optionally wait for media to load (default: true)
|
|
477
|
+
const shouldWaitForMedia = options.waitForMedia !== false;
|
|
478
|
+
if (shouldWaitForMedia) {
|
|
479
|
+
const mediaTimeout = options.mediaTimeout || 500;
|
|
480
|
+
await this.#waitForMedia(mediaTimeout);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
this.openDrawer();
|
|
484
|
+
return this;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Set drawer content using slots
|
|
489
|
+
* @param {any|HTMLElement|string} bodyContent - Content for drawer body (HTMLElement or string; Lit templates supported if runtime available)
|
|
490
|
+
* @param {any|HTMLElement|string} headerContent - Optional content for drawer header
|
|
491
|
+
*/
|
|
492
|
+
/**
|
|
493
|
+
* Set the content of the drawer
|
|
494
|
+
* @method setContent
|
|
495
|
+
* @public
|
|
496
|
+
* @param {any|HTMLElement|string} bodyContent - Content for the drawer body
|
|
497
|
+
* @param {any|HTMLElement|string} [headerContent] - Optional header content
|
|
498
|
+
* @returns {Promise<void>}
|
|
499
|
+
*/
|
|
500
|
+
async setContent(bodyContent, headerContent = null) {
|
|
501
|
+
// Clear existing slotted content
|
|
502
|
+
this.querySelectorAll('[slot="drawer-content"], [slot="drawer-header"]').forEach(el => el.remove());
|
|
503
|
+
|
|
504
|
+
// Add new body content
|
|
505
|
+
if (bodyContent) {
|
|
506
|
+
const bodyWrapper = document.createElement('div');
|
|
507
|
+
bodyWrapper.setAttribute('slot', 'drawer-content');
|
|
508
|
+
//bodyWrapper.className = 'surface-overlay';
|
|
509
|
+
|
|
510
|
+
// Best-effort support for Lit templates only if lit renderer is available at runtime
|
|
511
|
+
if (bodyContent && bodyContent._$litType$) {
|
|
512
|
+
try {
|
|
513
|
+
const mod = await import("#pds/lit");
|
|
514
|
+
mod.render(bodyContent, bodyWrapper);
|
|
515
|
+
} catch {
|
|
516
|
+
// Fallback: attempt to set as text
|
|
517
|
+
bodyWrapper.textContent = String(bodyContent);
|
|
518
|
+
}
|
|
519
|
+
} else if (typeof bodyContent === 'string') {
|
|
520
|
+
bodyWrapper.innerHTML = bodyContent;
|
|
521
|
+
} else {
|
|
522
|
+
bodyWrapper.appendChild(bodyContent);
|
|
523
|
+
}
|
|
524
|
+
this.appendChild(bodyWrapper);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Add new header content
|
|
528
|
+
if (headerContent) {
|
|
529
|
+
const headerWrapper = document.createElement('div');
|
|
530
|
+
headerWrapper.setAttribute('slot', 'drawer-header');
|
|
531
|
+
//headerWrapper.className = 'surface-overlay';
|
|
532
|
+
|
|
533
|
+
if (headerContent && headerContent._$litType$) {
|
|
534
|
+
try {
|
|
535
|
+
const mod = await import("#pds/lit");
|
|
536
|
+
mod.render(headerContent, headerWrapper);
|
|
537
|
+
} catch {
|
|
538
|
+
headerWrapper.textContent = String(headerContent);
|
|
539
|
+
}
|
|
540
|
+
} else if (typeof headerContent === 'string') {
|
|
541
|
+
headerWrapper.innerHTML = headerContent;
|
|
542
|
+
} else {
|
|
543
|
+
headerWrapper.appendChild(headerContent);
|
|
544
|
+
}
|
|
545
|
+
this.appendChild(headerWrapper);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Recalculate height after content is rendered
|
|
549
|
+
// Use double RAF to ensure slots are fully processed
|
|
550
|
+
requestAnimationFrame(() => {
|
|
551
|
+
requestAnimationFrame(() => {
|
|
552
|
+
this.#recalc();
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Clear drawer content (removes all slotted content)
|
|
559
|
+
* @method clearContent
|
|
560
|
+
* @public
|
|
561
|
+
*/
|
|
562
|
+
clearContent() {
|
|
563
|
+
this.querySelectorAll('[slot="drawer-content"], [slot="drawer-header"]').forEach(el => el.remove());
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Events
|
|
567
|
+
#onBackdropClick = () => this.closeDrawer();
|
|
568
|
+
|
|
569
|
+
#onKeyDown = (e) => {
|
|
570
|
+
if (this.open && e.key === "Escape") this.closeDrawer();
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
#onPointerDown = (e) => {
|
|
574
|
+
if (this._drag === "none") return;
|
|
575
|
+
if (this._drag === "header") {
|
|
576
|
+
const header = this.shadowRoot.querySelector("header");
|
|
577
|
+
const path = e.composedPath();
|
|
578
|
+
if (!path.includes(header)) return;
|
|
579
|
+
}
|
|
580
|
+
const p = this.#getPoint(e);
|
|
581
|
+
this.#isDragging = true;
|
|
582
|
+
this.#startX = p.x;
|
|
583
|
+
this.#startY = p.y;
|
|
584
|
+
this.#lastX = p.x;
|
|
585
|
+
this.#lastY = p.y;
|
|
586
|
+
this.#lastTS = performance.now();
|
|
587
|
+
this.#velocity = 0;
|
|
588
|
+
this.#startFraction = this.#currentFraction;
|
|
589
|
+
|
|
590
|
+
// Capture pointer so dragging continues outside the element
|
|
591
|
+
if (e.target?.setPointerCapture && e.pointerId != null) {
|
|
592
|
+
try {
|
|
593
|
+
e.target.setPointerCapture(e.pointerId);
|
|
594
|
+
} catch { /* */}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
cancelAnimationFrame(this.#raf);
|
|
598
|
+
this.style.userSelect = "none";
|
|
599
|
+
document.documentElement.style.cursor = "grabbing";
|
|
600
|
+
this.shadowRoot.querySelector("main")?.style.setProperty("overflow", "hidden");
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
#onPointerMove = (e) => {
|
|
604
|
+
if (!this.#isDragging) return;
|
|
605
|
+
const p = this.#getPoint(e);
|
|
606
|
+
const isVertical = this.position === "bottom" || this.position === "top";
|
|
607
|
+
const dir = this.position === "bottom" || this.position === "right" ? 1 : -1;
|
|
608
|
+
const deltaFromStart = isVertical ? (p.y - this.#startY) : (p.x - this.#startX);
|
|
609
|
+
const extent = isVertical ? Math.max(1, this.#drawerHeight) : Math.max(1, this.#drawerWidth);
|
|
610
|
+
const next = this.#clamp(this.#startFraction + (dir * deltaFromStart) / extent, 0, 1);
|
|
611
|
+
this.#applyFraction(next, false);
|
|
612
|
+
|
|
613
|
+
// Velocity (px/ms), positive when moving down in screen coords
|
|
614
|
+
const now = performance.now();
|
|
615
|
+
const dt = Math.max(1, now - this.#lastTS);
|
|
616
|
+
const comp = isVertical ? p.y : p.x;
|
|
617
|
+
const lastComp = isVertical ? this.#lastY : this.#lastX;
|
|
618
|
+
this.#velocity = (comp - lastComp) / dt; // px/ms along active axis
|
|
619
|
+
if (isVertical) this.#lastY = p.y; else this.#lastX = p.x;
|
|
620
|
+
this.#lastTS = now;
|
|
621
|
+
|
|
622
|
+
if (e.cancelable) e.preventDefault();
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
#onPointerUp = (e) => {
|
|
626
|
+
if (!this.#isDragging) return;
|
|
627
|
+
this.#isDragging = false;
|
|
628
|
+
this.style.userSelect = "";
|
|
629
|
+
document.documentElement.style.cursor = "";
|
|
630
|
+
this.shadowRoot.querySelector("main")?.style.removeProperty("overflow");
|
|
631
|
+
|
|
632
|
+
//const isVertical = this.position === "bottom" || this.position === "top";
|
|
633
|
+
const dir = this.position === "bottom" || this.position === "right" ? 1 : -1;
|
|
634
|
+
//const throwCloseThreshold = (1.0 / 1000) * 1000; // keep var for clarity; we use 1.0 px/ms below
|
|
635
|
+
|
|
636
|
+
// Decide based on velocity first (positive in closing direction), else position threshold
|
|
637
|
+
const fastForward = this.#velocity * dir > 1.0; // closing direction
|
|
638
|
+
const fastBackward = this.#velocity * dir < -1.0; // opening direction
|
|
639
|
+
|
|
640
|
+
if (fastForward) {
|
|
641
|
+
this.#animateTo(1); // close
|
|
642
|
+
} else if (fastBackward) {
|
|
643
|
+
this.#animateTo(0); // open
|
|
644
|
+
} else {
|
|
645
|
+
const shouldClose = this.#currentFraction >= 0.5;
|
|
646
|
+
this.#animateTo(shouldClose ? 1 : 0);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Release pointer capture
|
|
650
|
+
if (e.target?.releasePointerCapture && e.pointerId != null) {
|
|
651
|
+
try {
|
|
652
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
653
|
+
} catch {/**/}
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
#recalc = () => {
|
|
658
|
+
if (!this.#aside) return;
|
|
659
|
+
const rect = this.#aside.getBoundingClientRect();
|
|
660
|
+
this.#drawerHeight = rect.height || 0;
|
|
661
|
+
this.#drawerWidth = rect.width || 0;
|
|
662
|
+
this.#applyFraction(this.#currentFraction, false);
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
// Helpers
|
|
666
|
+
#cancelPendingOpenAnimation() {
|
|
667
|
+
if (!this.#openAnimationController) return;
|
|
668
|
+
this.#openAnimationController.abort();
|
|
669
|
+
this.#openAnimationController = null;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
#queueOpenAnimation() {
|
|
673
|
+
const aside = this.#aside;
|
|
674
|
+
if (!aside) return;
|
|
675
|
+
|
|
676
|
+
this.#cancelPendingOpenAnimation();
|
|
677
|
+
const controller = new AbortController();
|
|
678
|
+
this.#openAnimationController = controller;
|
|
679
|
+
|
|
680
|
+
this.#applyFraction(1, false);
|
|
681
|
+
void aside.offsetHeight; // Force layout to register the closed state
|
|
682
|
+
|
|
683
|
+
this.#whenReadyForOpen(controller.signal)
|
|
684
|
+
.then(() => {
|
|
685
|
+
if (controller.signal.aborted) return;
|
|
686
|
+
this.#animateTo(0);
|
|
687
|
+
})
|
|
688
|
+
.finally(() => {
|
|
689
|
+
if (this.#openAnimationController === controller) {
|
|
690
|
+
this.#openAnimationController = null;
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
async #whenReadyForOpen(signal) {
|
|
696
|
+
await this.#nextFrame(signal);
|
|
697
|
+
await this.#nextFrame(signal);
|
|
698
|
+
await this.#waitForIdle(signal);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
#nextFrame(signal) {
|
|
702
|
+
if (signal?.aborted) return Promise.resolve();
|
|
703
|
+
return new Promise((resolve) => {
|
|
704
|
+
const id = requestAnimationFrame(() => resolve());
|
|
705
|
+
signal?.addEventListener("abort", () => {
|
|
706
|
+
cancelAnimationFrame(id);
|
|
707
|
+
resolve();
|
|
708
|
+
}, { once: true });
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
#waitForIdle(signal) {
|
|
713
|
+
if (signal?.aborted) return Promise.resolve();
|
|
714
|
+
if (typeof window.requestIdleCallback === "function") {
|
|
715
|
+
return new Promise((resolve) => {
|
|
716
|
+
const idleId = window.requestIdleCallback(() => resolve());
|
|
717
|
+
signal?.addEventListener("abort", () => {
|
|
718
|
+
if (typeof window.cancelIdleCallback === "function") {
|
|
719
|
+
window.cancelIdleCallback(idleId);
|
|
720
|
+
}
|
|
721
|
+
resolve();
|
|
722
|
+
}, { once: true });
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
// Fallback: wait for another frame as a lightweight idle approximation
|
|
726
|
+
return this.#nextFrame(signal);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async #waitForMedia(maxTimeout = 500) {
|
|
730
|
+
// Find media elements within the drawer (including slotted content)
|
|
731
|
+
const media = Array.from(this.querySelectorAll("img, video"));
|
|
732
|
+
if (media.length === 0) return;
|
|
733
|
+
|
|
734
|
+
const mediaPromises = media.map((el) => {
|
|
735
|
+
if (el.tagName === "IMG") {
|
|
736
|
+
const img = /** @type {HTMLImageElement} */ (el);
|
|
737
|
+
if (img.complete && img.naturalHeight !== 0) return Promise.resolve();
|
|
738
|
+
return new Promise((resolve) => {
|
|
739
|
+
img.addEventListener("load", resolve, { once: true });
|
|
740
|
+
img.addEventListener("error", resolve, { once: true });
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
if (el.tagName === "VIDEO") {
|
|
744
|
+
const vid = /** @type {HTMLVideoElement} */ (el);
|
|
745
|
+
if (vid.readyState > 0) return Promise.resolve();
|
|
746
|
+
return new Promise((resolve) => {
|
|
747
|
+
vid.addEventListener("loadedmetadata", resolve, { once: true });
|
|
748
|
+
vid.addEventListener("error", resolve, { once: true });
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
return Promise.resolve();
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
const timeout = new Promise((resolve) => setTimeout(resolve, maxTimeout));
|
|
755
|
+
await Promise.race([Promise.all(mediaPromises), timeout]);
|
|
756
|
+
}
|
|
757
|
+
#getPoint(e) {
|
|
758
|
+
if (e.touches && e.touches[0])
|
|
759
|
+
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
|
760
|
+
return { x: e.clientX ?? 0, y: e.clientY ?? 0 };
|
|
761
|
+
}
|
|
762
|
+
#clamp(v, lo, hi) {
|
|
763
|
+
return Math.min(hi, Math.max(lo, v));
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
#applyFraction(f, withTransition) {
|
|
767
|
+
this.#currentFraction = this.#clamp(f, 0, 1);
|
|
768
|
+
const t = withTransition ? `transform var(--_dur) var(--_easing)` : "none";
|
|
769
|
+
const aside = this.#aside;
|
|
770
|
+
if (!aside) return;
|
|
771
|
+
aside.style.transition = t;
|
|
772
|
+
if (this._position === "bottom" || this._position === "top") {
|
|
773
|
+
const yPct = this._position === "bottom" ? this.#currentFraction * 100 : -this.#currentFraction * 100;
|
|
774
|
+
aside.style.transform = `translateY(${yPct}%)`;
|
|
775
|
+
} else {
|
|
776
|
+
const xPct = this._position === "right" ? this.#currentFraction * 100 : -this.#currentFraction * 100;
|
|
777
|
+
aside.style.transform = `translateX(${xPct}%)`;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Whether to show the close icon button
|
|
782
|
+
#shouldShowClose() {
|
|
783
|
+
// Always show for side drawers; hide by default for top/bottom unless showClose flag is set
|
|
784
|
+
if (this._position === "left" || this._position === "right") return true;
|
|
785
|
+
if (this._position === "top" || this._position === "bottom") return !!this._showClose;
|
|
786
|
+
return !!this._showClose;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
#renderCloseButtonVisibility() {
|
|
790
|
+
const btn = this.shadowRoot?.querySelector('.close-btn');
|
|
791
|
+
if (!btn) return;
|
|
792
|
+
btn.hidden = !this.#shouldShowClose();
|
|
793
|
+
if (!btn._pdsBound) {
|
|
794
|
+
btn.addEventListener('click', () => this.closeDrawer());
|
|
795
|
+
btn._pdsBound = true;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
#animateTo(targetFraction) {
|
|
800
|
+
const aside = this.#aside;
|
|
801
|
+
if (!aside) return;
|
|
802
|
+
|
|
803
|
+
const clamped = this.#clamp(targetFraction, 0, 1);
|
|
804
|
+
const isOpening = clamped < this.#currentFraction;
|
|
805
|
+
|
|
806
|
+
// On mobile, ensure the browser recognizes the starting position before animating
|
|
807
|
+
// This fixes the missing transition when opening
|
|
808
|
+
if (isOpening) {
|
|
809
|
+
// First, ensure we're at the closed position without transition
|
|
810
|
+
aside.style.transition = 'none';
|
|
811
|
+
if (this._position === "bottom" || this._position === "top") {
|
|
812
|
+
const startPct = this._position === "bottom" ? this.#currentFraction * 100 : -this.#currentFraction * 100;
|
|
813
|
+
aside.style.transform = `translateY(${startPct}%)`;
|
|
814
|
+
} else {
|
|
815
|
+
const startPct = this._position === "right" ? this.#currentFraction * 100 : -this.#currentFraction * 100;
|
|
816
|
+
aside.style.transform = `translateX(${startPct}%)`;
|
|
817
|
+
}
|
|
818
|
+
// Force reflow to ensure starting position is applied
|
|
819
|
+
void aside.offsetHeight;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Now apply transition and animate to target
|
|
823
|
+
aside.style.transition = `transform var(--_dur) var(--_easing)`;
|
|
824
|
+
this.#currentFraction = clamped;
|
|
825
|
+
if (this._position === "bottom" || this._position === "top") {
|
|
826
|
+
const yPct = this._position === "bottom" ? clamped * 100 : -clamped * 100;
|
|
827
|
+
aside.style.transform = `translateY(${yPct}%)`;
|
|
828
|
+
} else {
|
|
829
|
+
const xPct = this._position === "right" ? clamped * 100 : -clamped * 100;
|
|
830
|
+
aside.style.transform = `translateX(${xPct}%)`;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Update the `open` property based on the target fraction
|
|
834
|
+
const isOpen = clamped === 0;
|
|
835
|
+
if (this._open !== isOpen) {
|
|
836
|
+
// Avoid recursion: just sync internal and attribute
|
|
837
|
+
this._open = isOpen;
|
|
838
|
+
this.toggleAttribute("open", isOpen);
|
|
839
|
+
this.#syncAria();
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
#syncAria() {
|
|
844
|
+
const layerEl = this.shadowRoot?.getElementById('layer');
|
|
845
|
+
const aside = this.#aside;
|
|
846
|
+
if (layerEl) layerEl.setAttribute('aria-hidden', String(!this._open));
|
|
847
|
+
if (aside) {
|
|
848
|
+
if (this._open) {
|
|
849
|
+
aside.setAttribute('role', 'dialog');
|
|
850
|
+
aside.setAttribute('aria-modal', 'true');
|
|
851
|
+
} else {
|
|
852
|
+
aside.removeAttribute('role');
|
|
853
|
+
aside.removeAttribute('aria-modal');
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
})
|