@pure-ds/core 0.7.19 → 0.7.20
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/.cursorrules +10 -0
- package/.github/copilot-instructions.md +10 -0
- package/custom-elements.json +232 -25
- package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-code.d.ts +19 -0
- package/dist/types/public/assets/pds/components/pds-code.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-icon.d.ts +24 -1
- package/dist/types/public/assets/pds/components/pds-icon.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-treeview.d.ts +271 -16
- package/dist/types/public/assets/pds/components/pds-treeview.d.ts.map +1 -1
- package/dist/types/src/js/components/pds-code.d.ts +19 -0
- package/dist/types/src/js/components/pds-code.d.ts.map +1 -0
- package/dist/types/src/js/external/shiki.d.ts +3 -0
- package/dist/types/src/js/external/shiki.d.ts.map +1 -0
- package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
- package/package.json +1 -1
- package/packages/pds-cli/bin/templates/bootstrap/public/assets/my/my-home.js +1 -1
- package/public/assets/js/app.js +1 -1
- package/public/assets/js/pds-manager.js +138 -148
- package/public/assets/pds/components/pds-calendar.js +504 -16
- package/public/assets/pds/components/pds-code.js +203 -0
- package/public/assets/pds/components/pds-icon.js +67 -16
- package/public/assets/pds/components/pds-live-importer.js +2 -2
- package/public/assets/pds/components/pds-scrollrow.js +27 -2
- package/public/assets/pds/components/pds-treeview.js +185 -0
- package/public/assets/pds/core/pds-manager.js +138 -148
- package/public/assets/pds/custom-elements.json +263 -18
- package/public/assets/pds/external/shiki.js +32 -0
- package/public/assets/pds/pds-css-complete.json +1 -1
- package/public/assets/pds/templates/feedback-ops-dashboard.html +1 -1
- package/public/assets/pds/templates/release-readiness-radar.html +2 -2
- package/public/assets/pds/templates/support-command-center.html +1 -1
- package/src/js/pds-core/pds-generator.js +142 -152
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const SHIKI_CDN_URL = 'https://esm.sh/shiki@1.0.0';
|
|
2
|
+
|
|
3
|
+
const SHIKI_LANGS = ['html', 'css', 'javascript', 'typescript', 'json', 'bash', 'shell'];
|
|
4
|
+
const SHIKI_THEMES = ['github-dark', 'github-light'];
|
|
5
|
+
|
|
6
|
+
let shikiModulePromise = null;
|
|
7
|
+
let highlighterPromise = null;
|
|
8
|
+
|
|
9
|
+
const escapeHtml = (text) => {
|
|
10
|
+
if (text == null) return '';
|
|
11
|
+
const value = typeof text === 'string' ? text : String(text);
|
|
12
|
+
return value
|
|
13
|
+
.replace(/&/g, '&')
|
|
14
|
+
.replace(/</g, '<')
|
|
15
|
+
.replace(/>/g, '>')
|
|
16
|
+
.replace(/\"/g, '"')
|
|
17
|
+
.replace(/'/g, ''');
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const resolveTheme = (preferredTheme) => {
|
|
21
|
+
if (preferredTheme === 'github-dark' || preferredTheme === 'github-light') {
|
|
22
|
+
return preferredTheme;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const docTheme = document.documentElement.getAttribute('data-theme')
|
|
26
|
+
|| document.body?.getAttribute('data-theme');
|
|
27
|
+
|
|
28
|
+
if (docTheme === 'dark') return 'github-dark';
|
|
29
|
+
if (docTheme === 'light') return 'github-light';
|
|
30
|
+
|
|
31
|
+
const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)')?.matches;
|
|
32
|
+
return prefersDark ? 'github-dark' : 'github-light';
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const resolveLang = (lang) => {
|
|
36
|
+
const next = String(lang || 'html').trim().toLowerCase();
|
|
37
|
+
if (next === 'js') return 'javascript';
|
|
38
|
+
if (next === 'ts') return 'typescript';
|
|
39
|
+
if (next === 'sh') return 'shell';
|
|
40
|
+
return next;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
async function loadShikiModule() {
|
|
44
|
+
if (shikiModulePromise) return shikiModulePromise;
|
|
45
|
+
|
|
46
|
+
shikiModulePromise = (async () => {
|
|
47
|
+
try {
|
|
48
|
+
return await import('#shiki');
|
|
49
|
+
} catch {
|
|
50
|
+
return import(SHIKI_CDN_URL);
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
|
|
54
|
+
return shikiModulePromise;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function loadHighlighter() {
|
|
58
|
+
if (highlighterPromise) return highlighterPromise;
|
|
59
|
+
|
|
60
|
+
highlighterPromise = (async () => {
|
|
61
|
+
try {
|
|
62
|
+
const shiki = await loadShikiModule();
|
|
63
|
+
if (typeof shiki?.getHighlighter !== 'function') return null;
|
|
64
|
+
return shiki.getHighlighter({ themes: SHIKI_THEMES, langs: SHIKI_LANGS });
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
69
|
+
|
|
70
|
+
return highlighterPromise;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class PDSCode extends HTMLElement {
|
|
74
|
+
static get observedAttributes() {
|
|
75
|
+
return ['code', 'lang', 'theme'];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
constructor() {
|
|
79
|
+
super();
|
|
80
|
+
this._code = null;
|
|
81
|
+
this._capturedInitialText = false;
|
|
82
|
+
this._renderToken = 0;
|
|
83
|
+
this._textObserver = null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
connectedCallback() {
|
|
87
|
+
this.captureInitialText();
|
|
88
|
+
this.startTextObserver();
|
|
89
|
+
this.render();
|
|
90
|
+
|
|
91
|
+
queueMicrotask(() => {
|
|
92
|
+
if (!this.isConnected || this.hasAttribute('code')) return;
|
|
93
|
+
if (this.ensureCodeFromContent()) {
|
|
94
|
+
this.render();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
disconnectedCallback() {
|
|
100
|
+
this.stopTextObserver();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
attributeChangedCallback() {
|
|
104
|
+
this.render();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
set code(value) {
|
|
108
|
+
this._code = value == null ? '' : String(value);
|
|
109
|
+
this.render();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
get code() {
|
|
113
|
+
if (this._code != null) return this._code;
|
|
114
|
+
if (this.hasAttribute('code')) return this.getAttribute('code') || '';
|
|
115
|
+
return '';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setCode(value, { lang, theme } = {}) {
|
|
119
|
+
this.code = value;
|
|
120
|
+
if (lang) this.setAttribute('lang', lang);
|
|
121
|
+
if (theme) this.setAttribute('theme', theme);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
captureInitialText() {
|
|
125
|
+
if (this._capturedInitialText || this._code != null || this.hasAttribute('code')) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this._code = this.textContent || '';
|
|
130
|
+
this._capturedInitialText = true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
ensureCodeFromContent() {
|
|
134
|
+
if (this.hasAttribute('code')) return false;
|
|
135
|
+
if (this._code && this._code.trim()) return false;
|
|
136
|
+
|
|
137
|
+
const text = this.textContent || '';
|
|
138
|
+
if (!text.trim()) return false;
|
|
139
|
+
|
|
140
|
+
this._code = text;
|
|
141
|
+
this._capturedInitialText = true;
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
startTextObserver() {
|
|
146
|
+
if (this._textObserver || this.hasAttribute('code')) return;
|
|
147
|
+
|
|
148
|
+
this._textObserver = new MutationObserver(() => {
|
|
149
|
+
if (!this.isConnected || this.hasAttribute('code')) return;
|
|
150
|
+
if (this.ensureCodeFromContent()) {
|
|
151
|
+
this.render();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
this._textObserver.observe(this, {
|
|
156
|
+
childList: true,
|
|
157
|
+
characterData: true,
|
|
158
|
+
subtree: true,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
stopTextObserver() {
|
|
163
|
+
if (!this._textObserver) return;
|
|
164
|
+
this._textObserver.disconnect();
|
|
165
|
+
this._textObserver = null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async render() {
|
|
169
|
+
const renderToken = ++this._renderToken;
|
|
170
|
+
if (this.ensureCodeFromContent()) {
|
|
171
|
+
this.stopTextObserver();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const sourceCode = this.code;
|
|
175
|
+
const lang = resolveLang(this.getAttribute('lang') || 'html');
|
|
176
|
+
const theme = resolveTheme(this.getAttribute('theme'));
|
|
177
|
+
|
|
178
|
+
if (!sourceCode) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const highlighter = await loadHighlighter();
|
|
183
|
+
|
|
184
|
+
if (renderToken !== this._renderToken) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (highlighter) {
|
|
189
|
+
try {
|
|
190
|
+
this.innerHTML = highlighter.codeToHtml(sourceCode, { lang, theme });
|
|
191
|
+
return;
|
|
192
|
+
} catch {
|
|
193
|
+
// Fall through to escaped pre/code fallback
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.innerHTML = `<pre><code>${escapeHtml(sourceCode)}</code></pre>`;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!customElements.get('pds-code')) {
|
|
202
|
+
customElements.define('pds-code', PDSCode);
|
|
203
|
+
}
|
|
@@ -48,9 +48,9 @@ export class SvgIcon extends HTMLElement {
|
|
|
48
48
|
static spritePromises = new Map();
|
|
49
49
|
static inlineSprites = new Map();
|
|
50
50
|
|
|
51
|
-
// Cache for externally fetched SVG icons (
|
|
51
|
+
// Cache for externally fetched SVG icons (path-aware key -> { content, viewBox, loaded, error })
|
|
52
52
|
static externalIconCache = new Map();
|
|
53
|
-
// Promises for in-flight external icon fetches
|
|
53
|
+
// Promises for in-flight external icon fetches (path-aware key)
|
|
54
54
|
static externalIconPromises = new Map();
|
|
55
55
|
|
|
56
56
|
static instances = new Set();
|
|
@@ -199,6 +199,8 @@ export class SvgIcon extends HTMLElement {
|
|
|
199
199
|
// Determine if we should use sprite or fallback
|
|
200
200
|
let useFallback = this.hasAttribute('no-sprite') || !this.spriteAvailable();
|
|
201
201
|
|
|
202
|
+
const externalIconBasePath = SvgIcon.normalizeExternalIconPath();
|
|
203
|
+
|
|
202
204
|
let effectiveHref = spriteHref ? `${spriteHref}#${icon}` : `#${icon}`;
|
|
203
205
|
let inlineSymbolContent = null;
|
|
204
206
|
let inlineSymbolViewBox = null;
|
|
@@ -241,7 +243,8 @@ export class SvgIcon extends HTMLElement {
|
|
|
241
243
|
// IMPORTANT: Don't try external icons while sprite is still loading
|
|
242
244
|
const hasFallback = SvgIcon.#fallbackIcons.hasOwnProperty(icon);
|
|
243
245
|
if (spriteIconNotFound && !hasFallback && !spriteStillLoading) {
|
|
244
|
-
const
|
|
246
|
+
const cacheKey = SvgIcon.getExternalIconCacheKey(icon, externalIconBasePath);
|
|
247
|
+
const cached = SvgIcon.externalIconCache.get(cacheKey);
|
|
245
248
|
if (cached) {
|
|
246
249
|
if (cached.loaded && cached.content) {
|
|
247
250
|
useExternalIcon = true;
|
|
@@ -252,7 +255,7 @@ export class SvgIcon extends HTMLElement {
|
|
|
252
255
|
}
|
|
253
256
|
} else {
|
|
254
257
|
// Trigger async fetch - will re-render when complete
|
|
255
|
-
SvgIcon.fetchExternalIcon(icon);
|
|
258
|
+
SvgIcon.fetchExternalIcon(icon, externalIconBasePath);
|
|
256
259
|
useFallback = true;
|
|
257
260
|
}
|
|
258
261
|
}
|
|
@@ -306,7 +309,8 @@ export class SvgIcon extends HTMLElement {
|
|
|
306
309
|
|
|
307
310
|
const hasFallbackLocal = SvgIcon.#fallbackIcons.hasOwnProperty(iconName);
|
|
308
311
|
if (spriteIconNotFoundLocal && !hasFallbackLocal && !spriteStillLoadingLocal) {
|
|
309
|
-
const
|
|
312
|
+
const cacheKey = SvgIcon.getExternalIconCacheKey(iconName, externalIconBasePath);
|
|
313
|
+
const cached = SvgIcon.externalIconCache.get(cacheKey);
|
|
310
314
|
if (cached) {
|
|
311
315
|
if (cached.loaded && cached.content) {
|
|
312
316
|
useExternalIconLocal = true;
|
|
@@ -315,7 +319,7 @@ export class SvgIcon extends HTMLElement {
|
|
|
315
319
|
useFallbackLocal = true;
|
|
316
320
|
}
|
|
317
321
|
} else {
|
|
318
|
-
SvgIcon.fetchExternalIcon(iconName);
|
|
322
|
+
SvgIcon.fetchExternalIcon(iconName, externalIconBasePath);
|
|
319
323
|
useFallbackLocal = true;
|
|
320
324
|
}
|
|
321
325
|
}
|
|
@@ -601,29 +605,76 @@ export class SvgIcon extends HTMLElement {
|
|
|
601
605
|
return '/assets/img/icons/';
|
|
602
606
|
}
|
|
603
607
|
|
|
608
|
+
/**
|
|
609
|
+
* Normalize the external icon path to make cache keys and URL joining stable.
|
|
610
|
+
* @private
|
|
611
|
+
* @param {string} [basePath]
|
|
612
|
+
* @returns {string}
|
|
613
|
+
*/
|
|
614
|
+
static normalizeExternalIconPath(basePath) {
|
|
615
|
+
const rawBasePath = typeof basePath === 'string' ? basePath : SvgIcon.getExternalIconPath();
|
|
616
|
+
const trimmed = (rawBasePath || '/assets/img/icons/').trim();
|
|
617
|
+
if (!trimmed) {
|
|
618
|
+
return '/assets/img/icons/';
|
|
619
|
+
}
|
|
620
|
+
return trimmed.endsWith('/') ? trimmed : `${trimmed}/`;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Build a deterministic cache key for external icon content.
|
|
625
|
+
* @private
|
|
626
|
+
* @param {string} iconName
|
|
627
|
+
* @param {string} basePath
|
|
628
|
+
* @returns {string}
|
|
629
|
+
*/
|
|
630
|
+
static getExternalIconCacheKey(iconName, basePath) {
|
|
631
|
+
return `${SvgIcon.normalizeExternalIconPath(basePath)}::${iconName}`;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Resolve an external icon URL from icon name + base path.
|
|
636
|
+
* @private
|
|
637
|
+
* @param {string} iconName
|
|
638
|
+
* @param {string} basePath
|
|
639
|
+
* @returns {string}
|
|
640
|
+
*/
|
|
641
|
+
static getExternalIconURL(iconName, basePath) {
|
|
642
|
+
const normalizedBasePath = SvgIcon.normalizeExternalIconPath(basePath);
|
|
643
|
+
try {
|
|
644
|
+
if (typeof window !== 'undefined' && window.location?.href) {
|
|
645
|
+
return new URL(`${iconName}.svg`, normalizedBasePath).href;
|
|
646
|
+
}
|
|
647
|
+
} catch (error) {
|
|
648
|
+
// Ignore URL construction errors and fall back to string concatenation.
|
|
649
|
+
}
|
|
650
|
+
return `${normalizedBasePath}${iconName}.svg`;
|
|
651
|
+
}
|
|
652
|
+
|
|
604
653
|
/**
|
|
605
654
|
* Fetch an external SVG icon and cache it
|
|
606
655
|
* @param {string} iconName - The icon name (without .svg extension)
|
|
607
656
|
* @returns {Promise<boolean>} True if successfully fetched
|
|
608
657
|
*/
|
|
609
|
-
static async fetchExternalIcon(iconName) {
|
|
658
|
+
static async fetchExternalIcon(iconName, basePath) {
|
|
610
659
|
if (!iconName || typeof document === 'undefined') {
|
|
611
660
|
return false;
|
|
612
661
|
}
|
|
613
662
|
|
|
663
|
+
const normalizedBasePath = SvgIcon.normalizeExternalIconPath(basePath);
|
|
664
|
+
const cacheKey = SvgIcon.getExternalIconCacheKey(iconName, normalizedBasePath);
|
|
665
|
+
|
|
614
666
|
// Check if already cached
|
|
615
|
-
const cached = SvgIcon.externalIconCache.get(
|
|
667
|
+
const cached = SvgIcon.externalIconCache.get(cacheKey);
|
|
616
668
|
if (cached) {
|
|
617
669
|
return cached.loaded;
|
|
618
670
|
}
|
|
619
671
|
|
|
620
672
|
// Check if fetch is already in progress
|
|
621
|
-
if (SvgIcon.externalIconPromises.has(
|
|
622
|
-
return SvgIcon.externalIconPromises.get(
|
|
673
|
+
if (SvgIcon.externalIconPromises.has(cacheKey)) {
|
|
674
|
+
return SvgIcon.externalIconPromises.get(cacheKey);
|
|
623
675
|
}
|
|
624
676
|
|
|
625
|
-
const
|
|
626
|
-
const iconUrl = `${basePath}${iconName}.svg`;
|
|
677
|
+
const iconUrl = SvgIcon.getExternalIconURL(iconName, normalizedBasePath);
|
|
627
678
|
|
|
628
679
|
const promise = fetch(iconUrl)
|
|
629
680
|
.then(async (response) => {
|
|
@@ -649,7 +700,7 @@ export class SvgIcon extends HTMLElement {
|
|
|
649
700
|
.map((node) => serializer.serializeToString(node))
|
|
650
701
|
.join('');
|
|
651
702
|
|
|
652
|
-
SvgIcon.externalIconCache.set(
|
|
703
|
+
SvgIcon.externalIconCache.set(cacheKey, {
|
|
653
704
|
loaded: true,
|
|
654
705
|
error: false,
|
|
655
706
|
content,
|
|
@@ -665,7 +716,7 @@ export class SvgIcon extends HTMLElement {
|
|
|
665
716
|
if (!error.message?.includes('404')) {
|
|
666
717
|
console.debug('[pds-icon] External icon not found:', iconName, error.message);
|
|
667
718
|
}
|
|
668
|
-
SvgIcon.externalIconCache.set(
|
|
719
|
+
SvgIcon.externalIconCache.set(cacheKey, {
|
|
669
720
|
loaded: false,
|
|
670
721
|
error: true,
|
|
671
722
|
content: null,
|
|
@@ -676,10 +727,10 @@ export class SvgIcon extends HTMLElement {
|
|
|
676
727
|
return false;
|
|
677
728
|
})
|
|
678
729
|
.finally(() => {
|
|
679
|
-
SvgIcon.externalIconPromises.delete(
|
|
730
|
+
SvgIcon.externalIconPromises.delete(cacheKey);
|
|
680
731
|
});
|
|
681
732
|
|
|
682
|
-
SvgIcon.externalIconPromises.set(
|
|
733
|
+
SvgIcon.externalIconPromises.set(cacheKey, promise);
|
|
683
734
|
return promise;
|
|
684
735
|
}
|
|
685
736
|
|
|
@@ -262,12 +262,12 @@ class PdsLiveImporter extends HTMLElement {
|
|
|
262
262
|
? entry.unknownTailwindTokens
|
|
263
263
|
: [];
|
|
264
264
|
const notesHtml = notes.length
|
|
265
|
-
? `<ul
|
|
265
|
+
? `<ul>${notes
|
|
266
266
|
.map((note) => `<li>${escapeHtml(note)}</li>`)
|
|
267
267
|
.join("")}</ul>`
|
|
268
268
|
: "none";
|
|
269
269
|
const issuesHtml = issueCount
|
|
270
|
-
? `<ul
|
|
270
|
+
? `<ul>${entry.issues
|
|
271
271
|
.map(
|
|
272
272
|
(issue) =>
|
|
273
273
|
`<li><strong>${escapeHtml(issue?.severity || "info")}</strong>: ${escapeHtml(issue?.message || "")}</li>`
|
|
@@ -348,8 +348,33 @@ class PdsScrollrow extends HTMLElement {
|
|
|
348
348
|
#updateControls() {
|
|
349
349
|
const el = this.#viewport;
|
|
350
350
|
if (!el) return;
|
|
351
|
-
|
|
352
|
-
const
|
|
351
|
+
|
|
352
|
+
const computed = getComputedStyle(el);
|
|
353
|
+
const padStart = parseFloat(computed.paddingInlineStart) || 0;
|
|
354
|
+
const padEnd = parseFloat(computed.paddingInlineEnd) || 0;
|
|
355
|
+
|
|
356
|
+
const maxScroll = Math.max(0, el.scrollWidth - el.clientWidth);
|
|
357
|
+
const dir = computed.direction;
|
|
358
|
+
let position = el.scrollLeft;
|
|
359
|
+
|
|
360
|
+
if (dir === "rtl") {
|
|
361
|
+
position = position < 0 ? -position : maxScroll - position;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
position = Math.min(maxScroll, Math.max(0, position));
|
|
365
|
+
|
|
366
|
+
const baseThreshold = 2;
|
|
367
|
+
const startThreshold = Math.max(baseThreshold, Math.ceil(padStart) + 1);
|
|
368
|
+
const endThreshold = Math.max(baseThreshold, Math.ceil(padEnd) + 1);
|
|
369
|
+
|
|
370
|
+
let atStart = position <= startThreshold;
|
|
371
|
+
let atEnd = maxScroll - position <= endThreshold;
|
|
372
|
+
|
|
373
|
+
if (maxScroll <= Math.max(startThreshold, endThreshold)) {
|
|
374
|
+
atStart = true;
|
|
375
|
+
atEnd = true;
|
|
376
|
+
}
|
|
377
|
+
|
|
353
378
|
this.classList.toggle("can-scroll-left", !atStart);
|
|
354
379
|
this.classList.toggle("can-scroll-right", !atEnd);
|
|
355
380
|
const buttons = this.shadowRoot.querySelectorAll(".control button");
|