@pure-ds/core 0.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/CSS-INTELLISENSE-LIMITATION.md +98 -0
- package/CSS-INTELLISENSE-QUICK-REF.md +238 -0
- package/INTELLISENSE.md +384 -0
- package/LICENSE +15 -0
- package/custom-elements-manifest.config.js +30 -0
- package/custom-elements.json +2003 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/packages/pds-configurator/src/figma-export.d.ts +13 -0
- package/dist/types/packages/pds-configurator/src/figma-export.d.ts.map +1 -0
- package/dist/types/packages/pds-configurator/src/pds-config-form.d.ts +2 -0
- package/dist/types/packages/pds-configurator/src/pds-config-form.d.ts.map +1 -0
- package/dist/types/packages/pds-configurator/src/pds-configurator.d.ts +2 -0
- package/dist/types/packages/pds-configurator/src/pds-configurator.d.ts.map +1 -0
- package/dist/types/packages/pds-configurator/src/pds-demo.d.ts +2 -0
- package/dist/types/packages/pds-configurator/src/pds-demo.d.ts.map +1 -0
- package/dist/types/pds.config.d.ts +13 -0
- package/dist/types/pds.config.d.ts.map +1 -0
- package/dist/types/pds.d.ts +408 -0
- package/dist/types/public/assets/js/app.d.ts +2 -0
- package/dist/types/public/assets/js/app.d.ts.map +1 -0
- package/dist/types/public/assets/js/pds.d.ts +23 -0
- package/dist/types/public/assets/js/pds.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-calendar.d.ts +23 -0
- package/dist/types/public/assets/pds/components/pds-calendar.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-drawer.d.ts +2 -0
- package/dist/types/public/assets/pds/components/pds-drawer.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-icon.d.ts +53 -0
- package/dist/types/public/assets/pds/components/pds-icon.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-jsonform.d.ts +104 -0
- package/dist/types/public/assets/pds/components/pds-jsonform.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-richtext.d.ts +121 -0
- package/dist/types/public/assets/pds/components/pds-richtext.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-scrollrow.d.ts +61 -0
- package/dist/types/public/assets/pds/components/pds-scrollrow.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-splitpanel.d.ts +1 -0
- package/dist/types/public/assets/pds/components/pds-splitpanel.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-tabstrip.d.ts +39 -0
- package/dist/types/public/assets/pds/components/pds-tabstrip.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-toaster.d.ts +111 -0
- package/dist/types/public/assets/pds/components/pds-toaster.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-upload.d.ts +83 -0
- package/dist/types/public/assets/pds/components/pds-upload.d.ts.map +1 -0
- package/dist/types/src/js/app.d.ts +2 -0
- package/dist/types/src/js/app.d.ts.map +1 -0
- package/dist/types/src/js/common/ask.d.ts +22 -0
- package/dist/types/src/js/common/ask.d.ts.map +1 -0
- package/dist/types/src/js/common/common.d.ts +3 -0
- package/dist/types/src/js/common/common.d.ts.map +1 -0
- package/dist/types/src/js/common/font-loader.d.ts +24 -0
- package/dist/types/src/js/common/font-loader.d.ts.map +1 -0
- package/dist/types/src/js/common/msg.d.ts +3 -0
- package/dist/types/src/js/common/msg.d.ts.map +1 -0
- package/dist/types/src/js/lit.d.ts +25 -0
- package/dist/types/src/js/lit.d.ts.map +1 -0
- package/dist/types/src/js/pds-configurator/figma-export.d.ts +13 -0
- package/dist/types/src/js/pds-configurator/figma-export.d.ts.map +1 -0
- package/dist/types/src/js/pds-configurator/pds-config-form.d.ts +2 -0
- package/dist/types/src/js/pds-configurator/pds-config-form.d.ts.map +1 -0
- package/dist/types/src/js/pds-configurator/pds-configurator.d.ts +2 -0
- package/dist/types/src/js/pds-configurator/pds-configurator.d.ts.map +1 -0
- package/dist/types/src/js/pds-configurator/pds-demo.d.ts +2 -0
- package/dist/types/src/js/pds-configurator/pds-demo.d.ts.map +1 -0
- package/dist/types/src/js/pds-core/pds-config.d.ts +758 -0
- package/dist/types/src/js/pds-core/pds-config.d.ts.map +1 -0
- package/dist/types/src/js/pds-core/pds-enhancer-metadata.d.ts +6 -0
- package/dist/types/src/js/pds-core/pds-enhancer-metadata.d.ts.map +1 -0
- package/dist/types/src/js/pds-core/pds-enhancers.d.ts +14 -0
- package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -0
- package/dist/types/src/js/pds-core/pds-enums.d.ts +87 -0
- package/dist/types/src/js/pds-core/pds-enums.d.ts.map +1 -0
- package/dist/types/src/js/pds-core/pds-generator.d.ts +741 -0
- package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -0
- package/dist/types/src/js/pds-core/pds-ontology.d.ts +48 -0
- package/dist/types/src/js/pds-core/pds-ontology.d.ts.map +1 -0
- package/dist/types/src/js/pds-core/pds-paths.d.ts +37 -0
- package/dist/types/src/js/pds-core/pds-paths.d.ts.map +1 -0
- package/dist/types/src/js/pds-core/pds-query.d.ts +102 -0
- package/dist/types/src/js/pds-core/pds-query.d.ts.map +1 -0
- package/dist/types/src/js/pds-core/pds-registry.d.ts +40 -0
- package/dist/types/src/js/pds-core/pds-registry.d.ts.map +1 -0
- package/dist/types/src/js/pds.d.ts +109 -0
- package/dist/types/src/js/pds.d.ts.map +1 -0
- package/dist/types/src/pds-core/pds-api.d.ts +31 -0
- package/dist/types/src/pds-core/pds-api.d.ts.map +1 -0
- package/package.json +104 -0
- package/packages/pds-cli/README.md +15 -0
- package/packages/pds-cli/bin/generate-css-data.js +565 -0
- package/packages/pds-cli/bin/generate-manifest.js +352 -0
- package/packages/pds-cli/bin/pds-build-icons.js +152 -0
- package/packages/pds-cli/bin/pds-dx.js +114 -0
- package/packages/pds-cli/bin/pds-static.js +556 -0
- package/packages/pds-cli/bin/pds.js +127 -0
- package/packages/pds-cli/bin/postinstall.js +380 -0
- package/packages/pds-cli/bin/sync-assets.js +252 -0
- package/packages/pds-cli/lib/asset-roots.js +47 -0
- package/packages/pds-cli/lib/fs-writer.js +75 -0
- package/pds.css-data.json +5 -0
- package/pds.html-data.json +5 -0
- package/public/assets/js/app.js +5719 -0
- package/public/assets/js/lit.js +131 -0
- package/public/assets/js/pds.js +3423 -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.css-data.json +2152 -0
- package/public/assets/pds/vscode-custom-data.json +824 -0
- package/readme.md +1870 -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
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @component pds-splitpanel
|
|
3
|
+
* @description A split panel component that supports horizontal and vertical layouts, resizable panels, and a responsive mobile view.
|
|
4
|
+
*
|
|
5
|
+
* @attr {String} layout - The layout direction of the panels. Can be "horizontal" or "vertical". Defaults to "horizontal".
|
|
6
|
+
* @attr {String} defaultsplit - The initial size of the primary (left/top) panel. Defaults to "450px".
|
|
7
|
+
* @attr {Number} breakpoint - The viewport width in pixels below which the component switches to mobile view. Defaults to 1024.
|
|
8
|
+
* @attr {Boolean} open - Controls the visibility of the primary panel in mobile view.
|
|
9
|
+
*
|
|
10
|
+
* @prop {String} layout - Gets or sets the layout direction.
|
|
11
|
+
* @prop {String} defaultSplit - Gets or sets the default split size.
|
|
12
|
+
* @prop {Number} breakpoint - Gets or sets the mobile breakpoint.
|
|
13
|
+
* @prop {Boolean} open - Gets or sets the open state of the mobile panel.
|
|
14
|
+
*
|
|
15
|
+
* @slot left - Content for the left (or top) panel.
|
|
16
|
+
* @slot right - Content for the right (or bottom) panel.
|
|
17
|
+
*
|
|
18
|
+
* @csspart toggle - The mobile toggle button.
|
|
19
|
+
*
|
|
20
|
+
* @cssprop --left-width - Width of the left panel in horizontal layout.
|
|
21
|
+
* @cssprop --color-border - Color of the splitter bar.
|
|
22
|
+
* @cssprop --color-surface-base - Background color of the left panel in mobile view.
|
|
23
|
+
* @cssprop --transition-fast - Transition duration for the mobile panel animation.
|
|
24
|
+
* @cssprop --spacing-4 - Positioning spacing for the mobile toggle button.
|
|
25
|
+
* @cssprop --spacing-1 - Padding for the mobile toggle button.
|
|
26
|
+
* @cssprop --spacing-2 - Padding for the mobile toggle button.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* <caption>Basic horizontal split</caption>
|
|
30
|
+
* <pds-splitpanel>
|
|
31
|
+
* <div slot="left">Left Panel Content</div>
|
|
32
|
+
* <div slot="right">Right Panel Content</div>
|
|
33
|
+
* </pds-splitpanel>
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* <caption>Vertical split with custom default size</caption>
|
|
37
|
+
* <pds-splitpanel layout="vertical" defaultsplit="200px">
|
|
38
|
+
* <div slot="left">Top Panel Content</div>
|
|
39
|
+
* <div slot="right">Bottom Panel Content</div>
|
|
40
|
+
* </pds-splitpanel>
|
|
41
|
+
*/
|
|
42
|
+
customElements.define(
|
|
43
|
+
"pds-splitpanel",
|
|
44
|
+
class extends HTMLElement {
|
|
45
|
+
static get observedAttributes() {
|
|
46
|
+
return ["layout", "defaultsplit", "breakpoint", "open"];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
constructor() {
|
|
50
|
+
super();
|
|
51
|
+
this.attachShadow({ mode: "open" });
|
|
52
|
+
|
|
53
|
+
// Defaults
|
|
54
|
+
this._layout = "horizontal";
|
|
55
|
+
this._defaultSplit = "450px";
|
|
56
|
+
this._breakpoint = 1024;
|
|
57
|
+
this._open = this.hasAttribute("open");
|
|
58
|
+
this.isDragging = false;
|
|
59
|
+
|
|
60
|
+
// Bound handlers for add/remove
|
|
61
|
+
this._onResize = () => this.updateLayout();
|
|
62
|
+
this._onMouseMove = (e) => this.drag(e);
|
|
63
|
+
this._onMouseUp = () => this.stopDragging();
|
|
64
|
+
|
|
65
|
+
this._render();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
connectedCallback() {
|
|
69
|
+
// Ensure defaults reflected if attributes missing
|
|
70
|
+
if (!this.hasAttribute("layout")) this.setAttribute("layout", this._layout);
|
|
71
|
+
if (!this.hasAttribute("defaultsplit")) this.setAttribute("defaultsplit", this._defaultSplit);
|
|
72
|
+
if (!this.hasAttribute("breakpoint")) this.setAttribute("breakpoint", String(this._breakpoint));
|
|
73
|
+
if (this._open) this.setAttribute("open", "");
|
|
74
|
+
|
|
75
|
+
// Adopt primitives + component stylesheet (fallback to <style> if PDS not present)
|
|
76
|
+
this._adoptStyles();
|
|
77
|
+
|
|
78
|
+
// Cache references
|
|
79
|
+
this.$leftWrap = this.shadowRoot.querySelector(".left-panel");
|
|
80
|
+
this.$rightWrap = this.shadowRoot.querySelector(".right-panel");
|
|
81
|
+
this.$splitter = this.shadowRoot.querySelector(".splitter");
|
|
82
|
+
this.$toggleBtn = this.shadowRoot.getElementById("mobile-toggle");
|
|
83
|
+
this.$overlay = this.shadowRoot.querySelector(".mobile-overlay");
|
|
84
|
+
this.$icon = this.shadowRoot.querySelector("pds-icon");
|
|
85
|
+
|
|
86
|
+
// Wire events
|
|
87
|
+
window.addEventListener("resize", this._onResize);
|
|
88
|
+
if (this.$splitter) this.$splitter.addEventListener("mousedown", (e) => this.startDragging(e));
|
|
89
|
+
document.addEventListener("mousemove", this._onMouseMove);
|
|
90
|
+
document.addEventListener("mouseup", this._onMouseUp);
|
|
91
|
+
if (this.$toggleBtn) this.$toggleBtn.addEventListener("click", () => this.toggleMobileView());
|
|
92
|
+
if (this.$overlay) this.$overlay.addEventListener("click", () => this.closeMobileView());
|
|
93
|
+
|
|
94
|
+
// Initial layout
|
|
95
|
+
this.updateLayout();
|
|
96
|
+
this._updateToggleButton();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
disconnectedCallback() {
|
|
100
|
+
window.removeEventListener("resize", this._onResize);
|
|
101
|
+
document.removeEventListener("mousemove", this._onMouseMove);
|
|
102
|
+
document.removeEventListener("mouseup", this._onMouseUp);
|
|
103
|
+
if (this.$splitter) this.$splitter.removeEventListener("mousedown", (e) => this.startDragging(e));
|
|
104
|
+
if (this.$toggleBtn) this.$toggleBtn.removeEventListener("click", () => this.toggleMobileView());
|
|
105
|
+
if (this.$overlay) this.$overlay.removeEventListener("click", () => this.closeMobileView());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
attributeChangedCallback(name, _oldVal, newVal) {
|
|
109
|
+
switch (name) {
|
|
110
|
+
case "layout":
|
|
111
|
+
this._layout = (newVal || "horizontal").toLowerCase();
|
|
112
|
+
this.updateLayout();
|
|
113
|
+
break;
|
|
114
|
+
case "defaultsplit":
|
|
115
|
+
this._defaultSplit = newVal || "450px";
|
|
116
|
+
this.updateLayout();
|
|
117
|
+
break;
|
|
118
|
+
case "breakpoint":
|
|
119
|
+
this._breakpoint = Number(newVal) || 1024;
|
|
120
|
+
this.updateLayout();
|
|
121
|
+
break;
|
|
122
|
+
case "open":
|
|
123
|
+
this._open = this.hasAttribute("open");
|
|
124
|
+
this._updateToggleButton();
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Properties
|
|
130
|
+
get layout() {
|
|
131
|
+
return this._layout;
|
|
132
|
+
}
|
|
133
|
+
set layout(v) {
|
|
134
|
+
this.setAttribute("layout", v);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get defaultSplit() {
|
|
138
|
+
return this._defaultSplit;
|
|
139
|
+
}
|
|
140
|
+
set defaultSplit(v) {
|
|
141
|
+
this.setAttribute("defaultsplit", v);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get breakpoint() {
|
|
145
|
+
return this._breakpoint;
|
|
146
|
+
}
|
|
147
|
+
set breakpoint(v) {
|
|
148
|
+
this.setAttribute("breakpoint", String(v));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
get open() {
|
|
152
|
+
return this._open;
|
|
153
|
+
}
|
|
154
|
+
set open(v) {
|
|
155
|
+
if (Boolean(v)) {
|
|
156
|
+
if (!this.hasAttribute("open")) this.setAttribute("open", "");
|
|
157
|
+
} else {
|
|
158
|
+
if (this.hasAttribute("open")) this.removeAttribute("open");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Rendering
|
|
163
|
+
_render() {
|
|
164
|
+
this.shadowRoot.innerHTML = `
|
|
165
|
+
<div class="left-panel">
|
|
166
|
+
<slot name="left"></slot>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="splitter"></div>
|
|
169
|
+
<div class="right-panel">
|
|
170
|
+
<slot name="right"></slot>
|
|
171
|
+
</div>
|
|
172
|
+
<button part="toggle"
|
|
173
|
+
id="mobile-toggle"
|
|
174
|
+
class="mobile-toggle btn btn-sm"
|
|
175
|
+
aria-label="Toggle panel"
|
|
176
|
+
aria-expanded="${this._open ? "true" : "false"}"
|
|
177
|
+
>
|
|
178
|
+
<pds-icon icon="${this._open ? "x" : "list"}" width="24" height="24"></pds-icon>
|
|
179
|
+
</button>
|
|
180
|
+
<div class="mobile-overlay"></div>
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async _adoptStyles() {
|
|
185
|
+
const cssText = `
|
|
186
|
+
:host {
|
|
187
|
+
display: flex;
|
|
188
|
+
position: relative;
|
|
189
|
+
height: 100%;
|
|
190
|
+
width: 100%;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
:host([layout="horizontal"]) {
|
|
194
|
+
flex-direction: row;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
:host([layout="vertical"]) {
|
|
198
|
+
flex-direction: column;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.left-panel,
|
|
202
|
+
.right-panel {
|
|
203
|
+
display: block;
|
|
204
|
+
min-width: 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.left-panel {
|
|
208
|
+
flex: 0 0 var(--left-width, 450px);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.right-panel {
|
|
212
|
+
flex: 1 1 auto;
|
|
213
|
+
display: flex;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.splitter {
|
|
217
|
+
background-color: var(--color-border);
|
|
218
|
+
cursor: col-resize;
|
|
219
|
+
position: relative;
|
|
220
|
+
z-index: 1;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
:host([layout="horizontal"]) .splitter {
|
|
224
|
+
width: 5px;
|
|
225
|
+
height: 100%;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
:host([layout="vertical"]) .splitter {
|
|
229
|
+
height: 5px;
|
|
230
|
+
width: 100%;
|
|
231
|
+
cursor: row-resize;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#mobile-toggle {
|
|
235
|
+
visibility: hidden;
|
|
236
|
+
position: fixed;
|
|
237
|
+
top: var(--spacing-4);
|
|
238
|
+
right: var(--spacing-4);
|
|
239
|
+
z-index: 1001;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
:host([mobile]) #mobile-toggle {
|
|
243
|
+
visibility: visible;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
:host([mobile]) .left-panel {
|
|
247
|
+
display: none;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
:host([mobile]) .right-panel {
|
|
251
|
+
flex: 1 1 100%;
|
|
252
|
+
width: 100%;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
:host([mobile][open]) .left-panel {
|
|
256
|
+
display: block;
|
|
257
|
+
position: absolute;
|
|
258
|
+
top: 0;
|
|
259
|
+
left: 0;
|
|
260
|
+
width: 100%;
|
|
261
|
+
height: 100%;
|
|
262
|
+
z-index: 1000;
|
|
263
|
+
background: var(--color-surface-base);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
:host([mobile]) .left-panel ::slotted([slot="left"]) {
|
|
267
|
+
display: block;
|
|
268
|
+
width: 100%;
|
|
269
|
+
height: 100%;
|
|
270
|
+
transform: translateX(-100%);
|
|
271
|
+
transition: transform var(--transition-fast) ease-in-out;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
:host([mobile][open]) .left-panel ::slotted([slot="left"]) {
|
|
275
|
+
transform: translateX(0);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.mobile-overlay {
|
|
279
|
+
display: none;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
:host([mobile][open]) .mobile-overlay {
|
|
283
|
+
display: block;
|
|
284
|
+
position: absolute;
|
|
285
|
+
top: 0;
|
|
286
|
+
left: 0;
|
|
287
|
+
width: 100%;
|
|
288
|
+
height: 100%;
|
|
289
|
+
background: rgba(0,0,0,0.5);
|
|
290
|
+
z-index: 999;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.mobile-toggle.btn {
|
|
294
|
+
padding: var(--spacing-1, 4px) var(--spacing-2, 6px);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.mobile-toggle.btn-sm {
|
|
298
|
+
padding: var(--spacing-1, 4px);
|
|
299
|
+
}
|
|
300
|
+
`;
|
|
301
|
+
|
|
302
|
+
// Prefer PDS layers if available
|
|
303
|
+
try {
|
|
304
|
+
if (window.PDS && typeof PDS.createStylesheet === "function" && typeof PDS.adoptLayers === "function") {
|
|
305
|
+
const componentStyles = PDS.createStylesheet(cssText);
|
|
306
|
+
await PDS.adoptLayers(this.shadowRoot, ["primitives", "components"], [componentStyles]);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
} catch (e) {
|
|
310
|
+
console.error("pds-splitpanel: adoptLayers failed", e);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Fallback: inject <style> into shadow root
|
|
314
|
+
const style = document.createElement("style");
|
|
315
|
+
style.textContent = cssText;
|
|
316
|
+
this.shadowRoot.prepend(style);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Updates the layout based on the current viewport width and breakpoint.
|
|
321
|
+
* Toggles mobile mode and adjusts panel styles.
|
|
322
|
+
*/
|
|
323
|
+
updateLayout() {
|
|
324
|
+
const isMobile = window.innerWidth < this._breakpoint;
|
|
325
|
+
this.toggleAttribute("mobile", isMobile);
|
|
326
|
+
|
|
327
|
+
if (isMobile) {
|
|
328
|
+
if (!this.hasAttribute("open")) this._open = false;
|
|
329
|
+
if (this.$splitter) this.$splitter.style.display = "none";
|
|
330
|
+
if (this.$rightWrap) this.$rightWrap.style.display = "block";
|
|
331
|
+
this.style.removeProperty("--left-width");
|
|
332
|
+
if (this.$leftWrap) this.$leftWrap.style.height = "";
|
|
333
|
+
if (this.$rightWrap) this.$rightWrap.style.flex = "1 1 auto";
|
|
334
|
+
} else {
|
|
335
|
+
if (this.$splitter) this.$splitter.style.display = "block";
|
|
336
|
+
this.style.setProperty("--left-width", this._defaultSplit);
|
|
337
|
+
if (this.$rightWrap)
|
|
338
|
+
this.$rightWrap.style.flex = `1 1 calc(100% - ${this._defaultSplit})`;
|
|
339
|
+
if (this._layout === "vertical") {
|
|
340
|
+
// For vertical layout, width var isn't used; ensure panels reset
|
|
341
|
+
if (this.$leftWrap) this.$leftWrap.style.height = "";
|
|
342
|
+
if (this.$rightWrap) this.$rightWrap.style.flex = "1 1 auto";
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
this._updateToggleButton();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
startDragging(event) {
|
|
349
|
+
if (this.hasAttribute("mobile")) return;
|
|
350
|
+
this.isDragging = true;
|
|
351
|
+
document.body.style.cursor =
|
|
352
|
+
this._layout === "horizontal" ? "col-resize" : "row-resize";
|
|
353
|
+
event.preventDefault();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
drag(event) {
|
|
357
|
+
if (!this.isDragging) return;
|
|
358
|
+
|
|
359
|
+
const newSize =
|
|
360
|
+
this._layout === "horizontal"
|
|
361
|
+
? Math.max(200, Math.min(event.clientX, window.innerWidth - 200))
|
|
362
|
+
: Math.max(200, Math.min(event.clientY, window.innerHeight - 200));
|
|
363
|
+
|
|
364
|
+
if (this._layout === "horizontal") {
|
|
365
|
+
this.style.setProperty("--left-width", `${newSize}px`);
|
|
366
|
+
if (this.$rightWrap)
|
|
367
|
+
this.$rightWrap.style.flex = `1 1 calc(100% - ${newSize}px)`;
|
|
368
|
+
} else {
|
|
369
|
+
if (this.$leftWrap) this.$leftWrap.style.height = `${newSize}px`;
|
|
370
|
+
if (this.$rightWrap)
|
|
371
|
+
this.$rightWrap.style.flex = `1 1 calc(100% - ${newSize}px)`;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
stopDragging() {
|
|
376
|
+
if (!this.isDragging) return;
|
|
377
|
+
this.isDragging = false;
|
|
378
|
+
document.body.style.cursor = "";
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Toggles the visibility of the primary panel in mobile view.
|
|
383
|
+
*/
|
|
384
|
+
toggleMobileView() {
|
|
385
|
+
this.open = !this._open;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Closes the primary panel in mobile view.
|
|
390
|
+
*/
|
|
391
|
+
closeMobileView() {
|
|
392
|
+
this.open = false;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
_updateToggleButton() {
|
|
396
|
+
if (!this.$toggleBtn || !this.$icon) return;
|
|
397
|
+
this.$toggleBtn.setAttribute("aria-expanded", this._open ? "true" : "false");
|
|
398
|
+
this.$icon.setAttribute("icon", this._open ? "x" : "list");
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
);
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @element pds-tabpanel
|
|
3
|
+
*
|
|
4
|
+
* @attr {string} label - Label for the tab button
|
|
5
|
+
* @attr {string} id - Unique identifier for the panel (auto-generated if not provided)
|
|
6
|
+
*
|
|
7
|
+
* @slot - Content of the tab panel
|
|
8
|
+
*/
|
|
9
|
+
class TabPanel extends HTMLElement {
|
|
10
|
+
connectedCallback() {
|
|
11
|
+
// ensure id + label
|
|
12
|
+
if (!this.id) this.id = `tab-${Math.random().toString(36).slice(2, 7)}`;
|
|
13
|
+
if (!this.hasAttribute("label")) this.setAttribute("label", this.id);
|
|
14
|
+
|
|
15
|
+
// Wrap light DOM into a <section> once (idempotent)
|
|
16
|
+
if (!this._section) {
|
|
17
|
+
const section = document.createElement("section");
|
|
18
|
+
section.setAttribute("role", "region");
|
|
19
|
+
section.id = this.id;
|
|
20
|
+
section.setAttribute("aria-label", this.getAttribute("label") || this.id);
|
|
21
|
+
section.dataset.tabpanel = "";
|
|
22
|
+
// Move children into section
|
|
23
|
+
while (this.firstChild) section.appendChild(this.firstChild);
|
|
24
|
+
this.appendChild(section);
|
|
25
|
+
this._section = section;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The inner `<section>` that exposes the panel region semantics.
|
|
30
|
+
* @returns {HTMLElement|null}
|
|
31
|
+
*/
|
|
32
|
+
get section() {
|
|
33
|
+
return this.querySelector("[data-tabpanel]");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
customElements.define("pds-tabpanel", TabPanel);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Tab navigation component that pairs anchors with `pds-tabpanel` children.
|
|
40
|
+
*
|
|
41
|
+
* @element pds-tabstrip
|
|
42
|
+
* @fires tabchange - Fired when the active tab changes. Detail: `{ oldTab: string|null, newTab: string }`
|
|
43
|
+
*
|
|
44
|
+
* @attr {string} label - Accessible label announced for the tablist
|
|
45
|
+
* @attr {string} selected - Identifier of the currently active panel (synced with the location hash)
|
|
46
|
+
*
|
|
47
|
+
* @slot - Collection of `pds-tabpanel` nodes representing individual tab panels
|
|
48
|
+
*
|
|
49
|
+
* @csspart tabs - Navigation container comprising the clickable tab buttons
|
|
50
|
+
* @cssprop --color-accent-400 - Color of the active tab indicator underline
|
|
51
|
+
*/
|
|
52
|
+
class TabStrip extends HTMLElement {
|
|
53
|
+
#shadow = this.attachShadow({ mode: "open" });
|
|
54
|
+
#inkbar;
|
|
55
|
+
#panels = [];
|
|
56
|
+
#mo;
|
|
57
|
+
#currentTab = null;
|
|
58
|
+
|
|
59
|
+
constructor() {
|
|
60
|
+
super();
|
|
61
|
+
this.#shadow.innerHTML = /*html*/ `
|
|
62
|
+
<style>
|
|
63
|
+
:host{display:block}
|
|
64
|
+
nav{
|
|
65
|
+
position:relative; display:inline-flex; gap:.5rem; align-items:flex-end;
|
|
66
|
+
|
|
67
|
+
--pad-x:.5rem; --pad-y:.25rem;
|
|
68
|
+
}
|
|
69
|
+
nav a{
|
|
70
|
+
color: currentColor;
|
|
71
|
+
display:inline-block; padding:var(--pad-y) var(--pad-x);
|
|
72
|
+
text-decoration:none; line-height:1.2; border-bottom:2px solid transparent;
|
|
73
|
+
cursor:pointer;
|
|
74
|
+
}
|
|
75
|
+
nav a[aria-current="page"]{ font-weight:600; }
|
|
76
|
+
nav a:focus-visible{ outline:auto; outline-offset:2px; }
|
|
77
|
+
.inkbar{
|
|
78
|
+
position:absolute; inset-inline-start:0; bottom:-1px; height:2px; width:0;
|
|
79
|
+
transform:translateX(0); transition:transform .25s ease, width .25s ease;
|
|
80
|
+
background-color: var(--color-accent-400); pointer-events:none;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
83
|
+
<nav part="tabs"></nav>
|
|
84
|
+
<slot></slot>
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Attach event listeners and observe panels once connected.
|
|
90
|
+
*/
|
|
91
|
+
connectedCallback() {
|
|
92
|
+
// Set nav aria-label based on attribute or default
|
|
93
|
+
const nav = this.#shadow.querySelector("nav");
|
|
94
|
+
nav.setAttribute("aria-label", this.getAttribute("label") || "Tabs");
|
|
95
|
+
|
|
96
|
+
// Build once panels are in the light DOM
|
|
97
|
+
queueMicrotask(() => {
|
|
98
|
+
this.#collectPanels();
|
|
99
|
+
this.#renderTabs();
|
|
100
|
+
this.#syncFromUrl(true);
|
|
101
|
+
this.#positionInkbar();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Observe changes to children/attributes that affect tabs
|
|
105
|
+
this.#mo = new MutationObserver(() => {
|
|
106
|
+
this.#collectPanels();
|
|
107
|
+
this.#renderTabs();
|
|
108
|
+
this.#syncFromUrl(false);
|
|
109
|
+
this.#positionInkbar();
|
|
110
|
+
});
|
|
111
|
+
this.#mo.observe(this, {
|
|
112
|
+
subtree: true,
|
|
113
|
+
childList: true,
|
|
114
|
+
attributes: true,
|
|
115
|
+
attributeFilter: ["id", "label"],
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Respond to URL + layout changes
|
|
119
|
+
addEventListener("hashchange", this.#onUrlChange, { passive: true });
|
|
120
|
+
addEventListener("popstate", this.#onUrlChange, { passive: true });
|
|
121
|
+
addEventListener("resize", this.#positionInkbar, { passive: true });
|
|
122
|
+
|
|
123
|
+
// Handle clicks/keys in the shadow nav
|
|
124
|
+
nav.addEventListener("click", this.#onNavClick);
|
|
125
|
+
nav.addEventListener("keydown", this.#onNavKeydown);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// --- helpers ---
|
|
129
|
+
#collectPanels() {
|
|
130
|
+
// Direct children <pds-tabpanel> only (your structure)
|
|
131
|
+
this.#panels = Array.from(this.querySelectorAll(":scope > pds-tabpanel"));
|
|
132
|
+
// Ensure each has an id + section
|
|
133
|
+
this.#panels.forEach((p, i) => {
|
|
134
|
+
if (!p.id) p.id = `tab-${i + 1}`;
|
|
135
|
+
p.connectedCallback?.(); // make sure its section exists
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#renderTabs() {
|
|
140
|
+
const nav = this.#shadow.querySelector("nav");
|
|
141
|
+
nav.innerHTML =
|
|
142
|
+
this.#panels
|
|
143
|
+
.map((p) => {
|
|
144
|
+
const id = p.id;
|
|
145
|
+
const label = p.getAttribute("label") || id;
|
|
146
|
+
return `<a href="#${id}" aria-controls="${id}">${label}</a>`;
|
|
147
|
+
})
|
|
148
|
+
.join("") + `<span class="inkbar" aria-hidden="true"></span>`;
|
|
149
|
+
this.#inkbar = nav.querySelector(".inkbar");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#syncFromUrl(initial = false) {
|
|
153
|
+
if (!this.#panels.length) return;
|
|
154
|
+
const hashId = (location.hash || "").slice(1);
|
|
155
|
+
const exists = this.#panels.some((p) => p.id === hashId);
|
|
156
|
+
const next = exists
|
|
157
|
+
? hashId
|
|
158
|
+
: this.getAttribute("selected") || this.#panels[0].id;
|
|
159
|
+
|
|
160
|
+
// Track previous tab for event
|
|
161
|
+
const oldTab = this.#currentTab;
|
|
162
|
+
const changed = oldTab !== null && oldTab !== next;
|
|
163
|
+
|
|
164
|
+
// Update selected attribute (optional external reflection)
|
|
165
|
+
this.setAttribute("selected", next);
|
|
166
|
+
this.#currentTab = next;
|
|
167
|
+
|
|
168
|
+
// Show/hide panels
|
|
169
|
+
for (const p of this.#panels) {
|
|
170
|
+
const sec = p.section || p.querySelector("[data-tabpanel]");
|
|
171
|
+
if (!sec) continue;
|
|
172
|
+
const active = p.id === next;
|
|
173
|
+
sec.hidden = !active; // semantic
|
|
174
|
+
sec.setAttribute("aria-hidden", String(!active));
|
|
175
|
+
sec.style.display = active ? "" : "none"; // guaranteed visual hide
|
|
176
|
+
if (active) sec.setAttribute("tabindex", "0");
|
|
177
|
+
else sec.removeAttribute("tabindex");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Mark active link
|
|
181
|
+
const links = this.#shadow.querySelectorAll('nav a[href^="#"]');
|
|
182
|
+
links.forEach((a) => {
|
|
183
|
+
const target = a.getAttribute("href").slice(1);
|
|
184
|
+
if (target === next) a.setAttribute("aria-current", "page");
|
|
185
|
+
else a.removeAttribute("aria-current");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
this.#positionInkbar();
|
|
189
|
+
|
|
190
|
+
// Stabilize URL on first paint if needed
|
|
191
|
+
if (initial && (!hashId || !exists)) {
|
|
192
|
+
history.replaceState(null, "", `#${next}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Emit tabchange event (skip initial load)
|
|
196
|
+
if (changed) {
|
|
197
|
+
this.dispatchEvent(
|
|
198
|
+
new CustomEvent("tabchange", {
|
|
199
|
+
bubbles: true,
|
|
200
|
+
composed: true,
|
|
201
|
+
detail: { oldTab, newTab: next }
|
|
202
|
+
})
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#positionInkbar = () => {
|
|
208
|
+
const nav = this.#shadow.querySelector("nav");
|
|
209
|
+
if (!nav) return;
|
|
210
|
+
const active = nav.querySelector('a[aria-current="page"]');
|
|
211
|
+
if (!active) return;
|
|
212
|
+
const nb = nav.getBoundingClientRect();
|
|
213
|
+
const ab = active.getBoundingClientRect();
|
|
214
|
+
const w = Math.max(0, ab.width);
|
|
215
|
+
const x = Math.max(0, ab.left - nb.left);
|
|
216
|
+
this.#inkbar.style.width = `${w}px`;
|
|
217
|
+
this.#inkbar.style.transform = `translateX(${x}px)`;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
#onUrlChange = () => this.#syncFromUrl(false);
|
|
221
|
+
|
|
222
|
+
#onNavClick = (e) => {
|
|
223
|
+
const a = e
|
|
224
|
+
.composedPath()
|
|
225
|
+
.find(
|
|
226
|
+
(el) => el?.tagName === "A" && el.getAttribute("href")?.startsWith("#")
|
|
227
|
+
);
|
|
228
|
+
if (!a) return;
|
|
229
|
+
e.preventDefault(); // prevent anchor scroll/jump
|
|
230
|
+
const id = a.getAttribute("href").slice(1);
|
|
231
|
+
if (!id) return;
|
|
232
|
+
if (location.hash.slice(1) !== id) history.pushState(null, "", `#${id}`);
|
|
233
|
+
this.#syncFromUrl(false);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
#onNavKeydown = (e) => {
|
|
237
|
+
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
|
|
238
|
+
const links = Array.from(this.#shadow.querySelectorAll('nav a[href^="#"]'));
|
|
239
|
+
const i = links.indexOf(
|
|
240
|
+
this.#shadow.activeElement || document.activeElement
|
|
241
|
+
);
|
|
242
|
+
if (i === -1) return;
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
const next =
|
|
245
|
+
e.key === "ArrowRight"
|
|
246
|
+
? links[(i + 1) % links.length]
|
|
247
|
+
: links[(i - 1 + links.length) % links.length];
|
|
248
|
+
next?.focus();
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
customElements.define("pds-tabstrip", TabStrip);
|