@pure-ds/core 0.5.61 → 0.6.2
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/dist/types/packages/pds-configurator/src/pds-home-content.d.ts +375 -0
- package/dist/types/packages/pds-configurator/src/pds-home-content.d.ts.map +1 -0
- package/dist/types/packages/pds-configurator/src/pds-home.d.ts +2 -0
- package/dist/types/packages/pds-configurator/src/pds-home.d.ts.map +1 -0
- package/dist/types/pds.config.d.ts +2 -2
- package/dist/types/pds.config.d.ts.map +1 -1
- package/dist/types/pds.d.ts +3 -0
- package/dist/types/public/assets/js/pds-manager.d.ts +144 -429
- package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
- package/dist/types/public/assets/js/pds.d.ts +3 -4
- package/dist/types/public/assets/js/pds.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-live-edit.d.ts +150 -0
- package/dist/types/public/assets/pds/components/pds-live-edit.d.ts.map +1 -0
- package/dist/types/public/assets/pds/components/pds-omnibox.d.ts +2 -0
- package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-richtext.d.ts.map +1 -1
- package/dist/types/public/assets/pds/components/pds-theme.d.ts +5 -10
- package/dist/types/public/assets/pds/components/pds-theme.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-config.d.ts +3 -0
- package/dist/types/src/js/pds-core/pds-config.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-live.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-ontology.d.ts.map +1 -1
- package/dist/types/src/js/pds-core/pds-theme-utils.d.ts +6 -0
- package/dist/types/src/js/pds-core/pds-theme-utils.d.ts.map +1 -0
- package/dist/types/src/js/pds.d.ts.map +1 -1
- package/package.json +1 -4
- package/packages/pds-cli/bin/templates/bootstrap/pds.config.js +1 -1
- package/public/assets/js/app.js +106 -5636
- package/public/assets/js/pds-manager.js +137 -137
- package/public/assets/js/pds.js +7 -7
- package/public/assets/pds/components/pds-live-edit.js +1555 -0
- package/public/assets/pds/components/pds-omnibox.js +558 -369
- package/public/assets/pds/components/pds-richtext.js +57 -7
- package/public/assets/pds/components/pds-theme.js +59 -39
- package/readme.md +2 -2
- package/src/js/pds-core/pds-config.js +21 -3
- package/src/js/pds-core/pds-enhancers.js +61 -4
- package/src/js/pds-core/pds-live.js +180 -1
- package/src/js/pds-core/pds-ontology.js +8 -0
- package/src/js/pds-core/pds-theme-utils.js +33 -0
- package/src/js/pds.d.ts +3 -0
- package/src/js/pds.js +22 -0
|
@@ -341,7 +341,13 @@ export class RichText extends HTMLElement {
|
|
|
341
341
|
async #adoptStyles() {
|
|
342
342
|
// Component stylesheet (tokens + semantic vars)
|
|
343
343
|
const componentStyles = PDS.createStylesheet(/*css*/ `@layer richtext {
|
|
344
|
-
:host {
|
|
344
|
+
:host {
|
|
345
|
+
display: block;
|
|
346
|
+
color: var(--rt-fg, var(--color-text-primary));
|
|
347
|
+
font-family: var(--rt-font-family, var(--font-family-body));
|
|
348
|
+
font-size: var(--rt-font-size, var(--font-size-base));
|
|
349
|
+
line-height: var(--rt-line-height, var(--font-line-height-normal));
|
|
350
|
+
}
|
|
345
351
|
:host([disabled]) { opacity: .6; pointer-events: none; }
|
|
346
352
|
.box { border: 1px solid var(--rt-border, var(--color-border, currentColor)); border-radius: var(--radius-md,8px); background: var(--rt-bg, var(--color-input-bg)); }
|
|
347
353
|
.box:focus-within {
|
|
@@ -353,11 +359,15 @@ export class RichText extends HTMLElement {
|
|
|
353
359
|
}
|
|
354
360
|
}
|
|
355
361
|
|
|
356
|
-
.toolbar {background-color: var(--surface-subtle-bg);
|
|
357
|
-
.tbtn { transition: none; display:inline-flex; align-items:center; justify-content:center; width:22px; height:22px; border-radius: var(--radius-sm,6px); cursor:pointer; user-select:none; color: inherit; background: transparent; border:none;
|
|
362
|
+
.toolbar { background-color: var(--surface-subtle-bg); display: flex; gap: var(--spacing-0, 2px); align-items: center; padding: var(--spacing-0, 2px); border-bottom: 1px solid var(--rt-border, var(--color-border-muted)); border-radius: var(--radius-md,8px) var(--radius-md,8px) 0 0; width: 100%; max-width: 100%; flex-wrap: nowrap; box-sizing: border-box; overflow: hidden; }
|
|
363
|
+
.tbtn { transition: none; display:inline-flex; align-items:center; justify-content:center; width:22px; height:22px; border-radius: var(--radius-sm,6px); cursor:pointer; user-select:none; color: inherit; background: transparent; border:none;
|
|
364
|
+
|
|
365
|
+
padding: var(--spacing-0) var(--spacing-6, 8px);
|
|
366
|
+
|
|
367
|
+
}
|
|
358
368
|
.tbtn:hover { background: var(--color-surface-hover, color-mix(in oklab, CanvasText 12%, transparent)); }
|
|
359
369
|
.edwrap { position:relative; }
|
|
360
|
-
.ed { display: block; min-height:90px; max-height: 400px; overflow:auto; padding: var(--spacing-1, 0) var(--spacing-2, 0); outline:none; word-break:break-word; border-radius: 0 0 var(--radius-md,8px) var(--radius-md,8px); background: var(--rt-editor-bg, var(--color-input-bg)); }
|
|
370
|
+
.ed { display: block; font-weight: normal; min-height:90px; max-height: 400px; overflow:auto; padding: var(--spacing-1, 0) var(--spacing-2, 0); outline:none; word-break:break-word; border-radius: 0 0 var(--radius-md,8px) var(--radius-md,8px); background: var(--rt-editor-bg, var(--color-input-bg)); }
|
|
361
371
|
.ed[contenteditable="true"]:empty::before { content: attr(data-ph); color: var(--rt-muted, var(--color-text-muted)); pointer-events:none; }
|
|
362
372
|
.send { margin-left:auto; display:inline-flex; gap: var(--spacing-2,8px); align-items:center; }
|
|
363
373
|
button.icon { background:transparent; border:0; color:inherit; cursor:pointer; padding:6px; border-radius: var(--radius-sm,6px); }
|
|
@@ -589,9 +599,28 @@ export class RichText extends HTMLElement {
|
|
|
589
599
|
|
|
590
600
|
#toggleCode() {
|
|
591
601
|
// Wrap selection with <code> or unwrap if already in code
|
|
592
|
-
const
|
|
602
|
+
const root = this.shadowRoot;
|
|
603
|
+
const getSel = () =>
|
|
604
|
+
root && typeof root.getSelection === "function"
|
|
605
|
+
? root.getSelection()
|
|
606
|
+
: window.getSelection();
|
|
607
|
+
let sel = getSel();
|
|
593
608
|
if (!sel || sel.rangeCount === 0) return;
|
|
594
|
-
|
|
609
|
+
let range = sel.getRangeAt(0);
|
|
610
|
+
const inEditor = (r) => {
|
|
611
|
+
const node =
|
|
612
|
+
r.commonAncestorContainer.nodeType === 1
|
|
613
|
+
? r.commonAncestorContainer
|
|
614
|
+
: r.commonAncestorContainer.parentNode;
|
|
615
|
+
return !!(node && this.#editorDiv && this.#editorDiv.contains(node));
|
|
616
|
+
};
|
|
617
|
+
if (!inEditor(range)) {
|
|
618
|
+
this.#focus();
|
|
619
|
+
sel = getSel();
|
|
620
|
+
if (!sel || sel.rangeCount === 0) return;
|
|
621
|
+
range = sel.getRangeAt(0);
|
|
622
|
+
if (!inEditor(range)) return;
|
|
623
|
+
}
|
|
595
624
|
// Simple toggle: if ancestor <code>, unwrap; else wrap
|
|
596
625
|
const codeAncestor = this.#closestAncestor(
|
|
597
626
|
range.commonAncestorContainer,
|
|
@@ -604,7 +633,28 @@ export class RichText extends HTMLElement {
|
|
|
604
633
|
parent.removeChild(codeAncestor);
|
|
605
634
|
} else {
|
|
606
635
|
const wrapper = document.createElement("code");
|
|
607
|
-
range.
|
|
636
|
+
if (range.collapsed) {
|
|
637
|
+
const caret = document.createTextNode("\u200B");
|
|
638
|
+
wrapper.appendChild(caret);
|
|
639
|
+
range.insertNode(wrapper);
|
|
640
|
+
const next = document.createRange();
|
|
641
|
+
next.setStart(caret, 1);
|
|
642
|
+
next.collapse(true);
|
|
643
|
+
sel.removeAllRanges();
|
|
644
|
+
sel.addRange(next);
|
|
645
|
+
} else {
|
|
646
|
+
try {
|
|
647
|
+
range.surroundContents(wrapper);
|
|
648
|
+
} catch {
|
|
649
|
+
const contents = range.extractContents();
|
|
650
|
+
wrapper.appendChild(contents);
|
|
651
|
+
range.insertNode(wrapper);
|
|
652
|
+
}
|
|
653
|
+
const next = document.createRange();
|
|
654
|
+
next.selectNodeContents(wrapper);
|
|
655
|
+
sel.removeAllRanges();
|
|
656
|
+
sel.addRange(next);
|
|
657
|
+
}
|
|
608
658
|
}
|
|
609
659
|
}
|
|
610
660
|
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* keeps its UI in sync with programmatic theme changes.
|
|
4
4
|
*
|
|
5
5
|
* @element pds-theme
|
|
6
|
-
* @attr {string} label - Optional legend text (defaults to "Theme").
|
|
7
6
|
*/
|
|
8
7
|
const THEME_OPTIONS = [
|
|
9
8
|
{ value: "system", label: "System", icon: "moon-stars" },
|
|
@@ -11,14 +10,43 @@ const THEME_OPTIONS = [
|
|
|
11
10
|
{ value: "dark", label: "Dark", icon: "moon" },
|
|
12
11
|
];
|
|
13
12
|
|
|
14
|
-
const DEFAULT_LABEL = "Theme";
|
|
15
13
|
const LAYERS = ["tokens", "primitives", "components", "utilities"];
|
|
14
|
+
const DEFAULT_THEMES = ["light", "dark"];
|
|
15
|
+
const VALID_THEMES = new Set(DEFAULT_THEMES);
|
|
16
|
+
|
|
17
|
+
const normalizePresetThemes = (preset) => {
|
|
18
|
+
const themes = Array.isArray(preset?.themes)
|
|
19
|
+
? preset.themes.map((theme) => String(theme).toLowerCase())
|
|
20
|
+
: DEFAULT_THEMES;
|
|
21
|
+
const normalized = themes.filter((theme) => VALID_THEMES.has(theme));
|
|
22
|
+
return normalized.length ? normalized : DEFAULT_THEMES;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const resolveThemePreference = (preference) => {
|
|
26
|
+
const normalized = String(preference || "").toLowerCase();
|
|
27
|
+
if (VALID_THEMES.has(normalized)) return normalized;
|
|
28
|
+
|
|
29
|
+
if (typeof document !== "undefined") {
|
|
30
|
+
const applied = document.documentElement?.getAttribute("data-theme");
|
|
31
|
+
if (VALID_THEMES.has(applied)) return applied;
|
|
32
|
+
}
|
|
16
33
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return
|
|
34
|
+
if (typeof window !== "undefined" && window.matchMedia) {
|
|
35
|
+
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
36
|
+
return prefersDark ? "dark" : "light";
|
|
20
37
|
}
|
|
21
38
|
|
|
39
|
+
return "light";
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const isPresetThemeCompatible = (preset, themePreference) => {
|
|
43
|
+
const resolvedTheme = resolveThemePreference(themePreference);
|
|
44
|
+
const themes = normalizePresetThemes(preset);
|
|
45
|
+
return themes.includes(resolvedTheme);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
class PdsTheme extends HTMLElement {
|
|
49
|
+
|
|
22
50
|
#observer;
|
|
23
51
|
#listening = false;
|
|
24
52
|
|
|
@@ -32,7 +60,6 @@ class PdsTheme extends HTMLElement {
|
|
|
32
60
|
void this.#setup();
|
|
33
61
|
} else {
|
|
34
62
|
this.#attachObserver();
|
|
35
|
-
this.#syncLegend();
|
|
36
63
|
this.#syncCheckedState();
|
|
37
64
|
}
|
|
38
65
|
}
|
|
@@ -41,29 +68,6 @@ class PdsTheme extends HTMLElement {
|
|
|
41
68
|
this.#teardownObserver();
|
|
42
69
|
}
|
|
43
70
|
|
|
44
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
45
|
-
if (oldValue === newValue) return;
|
|
46
|
-
if (name === "label") {
|
|
47
|
-
this.#syncLegend();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Gets the legend/aria-label text to display.
|
|
53
|
-
* @returns {string}
|
|
54
|
-
*/
|
|
55
|
-
get label() {
|
|
56
|
-
return this.getAttribute("label")?.trim() || DEFAULT_LABEL;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
set label(value) {
|
|
60
|
-
if (value == null || value === "") {
|
|
61
|
-
this.removeAttribute("label");
|
|
62
|
-
} else {
|
|
63
|
-
this.setAttribute("label", value);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
71
|
async #setup() {
|
|
68
72
|
const componentStyles = PDS.createStylesheet(`
|
|
69
73
|
:host {
|
|
@@ -87,7 +91,6 @@ class PdsTheme extends HTMLElement {
|
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
this.#attachObserver();
|
|
90
|
-
this.#syncLegend();
|
|
91
94
|
this.#syncCheckedState();
|
|
92
95
|
}
|
|
93
96
|
|
|
@@ -105,8 +108,7 @@ class PdsTheme extends HTMLElement {
|
|
|
105
108
|
|
|
106
109
|
return /*html*/`
|
|
107
110
|
<form part="form">
|
|
108
|
-
<fieldset part="fieldset" role="radiogroup"
|
|
109
|
-
<legend part="legend">${DEFAULT_LABEL}</legend>
|
|
111
|
+
<fieldset part="fieldset" role="radiogroup" class="buttons">
|
|
110
112
|
${optionsMarkup}
|
|
111
113
|
</fieldset>
|
|
112
114
|
</form>`;
|
|
@@ -123,6 +125,21 @@ class PdsTheme extends HTMLElement {
|
|
|
123
125
|
return;
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
const currentPreset = PDS.currentConfig?.design || null;
|
|
129
|
+
if (currentPreset && !isPresetThemeCompatible(currentPreset, value)) {
|
|
130
|
+
const resolvedTheme = resolveThemePreference(value);
|
|
131
|
+
const presetName =
|
|
132
|
+
currentPreset?.name ||
|
|
133
|
+
PDS.currentPreset?.name ||
|
|
134
|
+
PDS.currentConfig?.preset ||
|
|
135
|
+
"current preset";
|
|
136
|
+
console.warn(
|
|
137
|
+
`PDS theme "${resolvedTheme}" not supported by preset "${presetName}".`
|
|
138
|
+
);
|
|
139
|
+
this.#syncCheckedState();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
126
143
|
if (PDS.theme !== value) {
|
|
127
144
|
PDS.theme = value;
|
|
128
145
|
}
|
|
@@ -148,19 +165,22 @@ class PdsTheme extends HTMLElement {
|
|
|
148
165
|
this.#observer = undefined;
|
|
149
166
|
}
|
|
150
167
|
|
|
151
|
-
#syncLegend() {
|
|
152
|
-
const legend = this.shadowRoot.querySelector("legend");
|
|
153
|
-
const fieldset = this.shadowRoot.querySelector("fieldset");
|
|
154
|
-
if (legend) legend.textContent = this.label;
|
|
155
|
-
if (fieldset) fieldset.setAttribute("aria-label", this.label);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
168
|
#syncCheckedState() {
|
|
159
169
|
const currentTheme = PDS.theme || "system";
|
|
170
|
+
const currentPreset = PDS.currentConfig?.design || null;
|
|
171
|
+
const supportedThemes = normalizePresetThemes(currentPreset);
|
|
160
172
|
this.shadowRoot
|
|
161
173
|
.querySelectorAll('input[type="radio"]')
|
|
162
174
|
.forEach((radio) => {
|
|
163
175
|
radio.checked = radio.value === currentTheme;
|
|
176
|
+
if (radio.value === "system") {
|
|
177
|
+
radio.disabled = false;
|
|
178
|
+
} else if (currentPreset) {
|
|
179
|
+
const resolved = resolveThemePreference(radio.value);
|
|
180
|
+
radio.disabled = !supportedThemes.includes(resolved);
|
|
181
|
+
} else {
|
|
182
|
+
radio.disabled = false;
|
|
183
|
+
}
|
|
164
184
|
});
|
|
165
185
|
}
|
|
166
186
|
}
|
package/readme.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](#license)
|
|
7
7
|
[](https://www.npmjs.com/package/@pure-ds/core)
|
|
8
8
|
|
|
9
|
-

|
|
10
10
|
|
|
11
11
|
## With Great Standards Comes Great Power
|
|
12
12
|
|
|
@@ -571,7 +571,7 @@ PDS.dispatchEvent(new CustomEvent('pds:toast', {
|
|
|
571
571
|
<script type="importmap">
|
|
572
572
|
{
|
|
573
573
|
"imports": {
|
|
574
|
-
"#showdown": "https://cdn.jsdelivr.net/npm/showdown@2.1.0
|
|
574
|
+
"#showdown": "https://cdn.jsdelivr.net/npm/showdown@2.1.0/+esm"
|
|
575
575
|
}
|
|
576
576
|
}
|
|
577
577
|
</script>
|
|
@@ -187,6 +187,7 @@ import { enums } from "./pds-enums.js";
|
|
|
187
187
|
* @property {PDSAutoDefineConfig} [autoDefine]
|
|
188
188
|
* @property {string} [managerURL]
|
|
189
189
|
* @property {any} [manager]
|
|
190
|
+
* @property {boolean} [liveEdit]
|
|
190
191
|
* @property {any} [log]
|
|
191
192
|
*/
|
|
192
193
|
|
|
@@ -565,16 +566,31 @@ const __INIT_CONFIG_SPEC__ = {
|
|
|
565
566
|
properties: {
|
|
566
567
|
mode: { type: "string" },
|
|
567
568
|
preset: { type: "string" },
|
|
568
|
-
design:
|
|
569
|
+
design: __DESIGN_CONFIG_SPEC__,
|
|
569
570
|
enhancers: { type: ["object", "array"] },
|
|
570
571
|
applyGlobalStyles: { type: "boolean" },
|
|
571
572
|
manageTheme: { type: "boolean" },
|
|
572
573
|
themeStorageKey: { type: "string" },
|
|
573
574
|
preloadStyles: { type: "boolean" },
|
|
574
575
|
criticalLayers: { type: "array", items: { type: "string" } },
|
|
575
|
-
autoDefine: {
|
|
576
|
+
autoDefine: {
|
|
577
|
+
type: "object",
|
|
578
|
+
allowUnknown: false,
|
|
579
|
+
properties: {
|
|
580
|
+
predefine: { type: "array", items: { type: "string" } },
|
|
581
|
+
mapper: { type: __ANY_TYPE__ },
|
|
582
|
+
enhancers: { type: ["object", "array"] },
|
|
583
|
+
scanExisting: { type: "boolean" },
|
|
584
|
+
observeShadows: { type: "boolean" },
|
|
585
|
+
patchAttachShadow: { type: "boolean" },
|
|
586
|
+
debounceMs: { type: "number" },
|
|
587
|
+
onError: { type: __ANY_TYPE__ },
|
|
588
|
+
baseURL: { type: "string" },
|
|
589
|
+
},
|
|
590
|
+
},
|
|
576
591
|
managerURL: { type: "string" },
|
|
577
592
|
manager: { type: __ANY_TYPE__ },
|
|
593
|
+
liveEdit: { type: "boolean" },
|
|
578
594
|
log: { type: __ANY_TYPE__ },
|
|
579
595
|
},
|
|
580
596
|
};
|
|
@@ -799,6 +815,7 @@ export const presets = {
|
|
|
799
815
|
id: "paper-and-ink",
|
|
800
816
|
name: "Paper & Ink",
|
|
801
817
|
tags: ["app", "featured"],
|
|
818
|
+
themes: ["light"], // Not optimized for dark mode
|
|
802
819
|
description: "Ultra-minimal design with focus on typography and whitespace",
|
|
803
820
|
colors: {
|
|
804
821
|
primary: "#171717",
|
|
@@ -1037,6 +1054,7 @@ export const presets = {
|
|
|
1037
1054
|
"pastel-play": {
|
|
1038
1055
|
id: "pastel-play",
|
|
1039
1056
|
name: "Pastel Play",
|
|
1057
|
+
themes: ["light"], // Not optimized for dark mode due to pastel contrast challenges
|
|
1040
1058
|
description:
|
|
1041
1059
|
"Playful pastels with soft surfaces and friendly rounded shapes",
|
|
1042
1060
|
colors: {
|
|
@@ -1081,7 +1099,7 @@ export const presets = {
|
|
|
1081
1099
|
accent: "#06b6d4", // cyan signal
|
|
1082
1100
|
background: "#f8fafc",
|
|
1083
1101
|
darkMode: {
|
|
1084
|
-
background: "#
|
|
1102
|
+
background: "#0c0c0c",
|
|
1085
1103
|
secondary: "#9ca3af",
|
|
1086
1104
|
// Set a chromatic primary in dark mode to ensure both:
|
|
1087
1105
|
// - outline/link contrast on dark surface, and
|
|
@@ -77,16 +77,55 @@ function enhanceDropdown(elem) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
const resolveDirection = () => {
|
|
80
|
-
const mode = (
|
|
80
|
+
const mode = (
|
|
81
|
+
elem.getAttribute("data-direction") ||
|
|
82
|
+
elem.getAttribute("data-dropdown-direction") ||
|
|
83
|
+
elem.getAttribute("data-mode") ||
|
|
84
|
+
"auto"
|
|
85
|
+
).toLowerCase();
|
|
81
86
|
if (mode === "up" || mode === "down") return mode;
|
|
82
87
|
const rect = elem.getBoundingClientRect();
|
|
88
|
+
const menuRect = menu?.getBoundingClientRect?.() || { height: 0 };
|
|
89
|
+
const menuHeight = Math.max(
|
|
90
|
+
menu?.offsetHeight || 0,
|
|
91
|
+
menu?.scrollHeight || 0,
|
|
92
|
+
menuRect.height || 0,
|
|
93
|
+
200
|
|
94
|
+
);
|
|
83
95
|
const spaceBelow = Math.max(0, window.innerHeight - rect.bottom);
|
|
84
96
|
const spaceAbove = Math.max(0, rect.top);
|
|
97
|
+
if (spaceBelow >= menuHeight) return "down";
|
|
98
|
+
if (spaceAbove >= menuHeight) return "up";
|
|
85
99
|
return spaceAbove > spaceBelow ? "up" : "down";
|
|
86
100
|
};
|
|
87
101
|
|
|
102
|
+
const resolveAlign = () => {
|
|
103
|
+
const align = (
|
|
104
|
+
elem.getAttribute("data-align") ||
|
|
105
|
+
elem.getAttribute("data-dropdown-align") ||
|
|
106
|
+
"auto"
|
|
107
|
+
).toLowerCase();
|
|
108
|
+
if (align === "left" || align === "right" || align === "start" || align === "end") {
|
|
109
|
+
return align === "start" ? "left" : align === "end" ? "right" : align;
|
|
110
|
+
}
|
|
111
|
+
const rect = elem.getBoundingClientRect();
|
|
112
|
+
const menuRect = menu?.getBoundingClientRect?.() || { width: 0 };
|
|
113
|
+
const menuWidth = Math.max(
|
|
114
|
+
menu?.offsetWidth || 0,
|
|
115
|
+
menu?.scrollWidth || 0,
|
|
116
|
+
menuRect.width || 0,
|
|
117
|
+
240
|
|
118
|
+
);
|
|
119
|
+
const spaceRight = Math.max(0, window.innerWidth - rect.left);
|
|
120
|
+
const spaceLeft = Math.max(0, rect.right);
|
|
121
|
+
if (spaceRight >= menuWidth) return "left";
|
|
122
|
+
if (spaceLeft >= menuWidth) return "right";
|
|
123
|
+
return spaceLeft > spaceRight ? "right" : "left";
|
|
124
|
+
};
|
|
125
|
+
|
|
88
126
|
const openMenu = () => {
|
|
89
127
|
elem.dataset.dropdownDirection = resolveDirection();
|
|
128
|
+
elem.dataset.dropdownAlign = resolveAlign();
|
|
90
129
|
menu.setAttribute("aria-hidden", "false");
|
|
91
130
|
trigger?.setAttribute("aria-expanded", "true");
|
|
92
131
|
};
|
|
@@ -264,16 +303,34 @@ function enhanceRequired(elem) {
|
|
|
264
303
|
elem.dataset.enhancedRequired = "true";
|
|
265
304
|
|
|
266
305
|
const enhanceRequiredField = (input) => {
|
|
267
|
-
|
|
268
|
-
|
|
306
|
+
let label;
|
|
307
|
+
if(input.closest("[role$=group]")) { // Handles both radiogroup and group
|
|
308
|
+
label = input.closest("[role$=group]").querySelector("legend");
|
|
309
|
+
}
|
|
310
|
+
else{
|
|
311
|
+
label = input.closest("label");
|
|
312
|
+
}
|
|
269
313
|
if (!label) return;
|
|
314
|
+
|
|
315
|
+
|
|
270
316
|
if (label.querySelector(".required-asterisk")) return;
|
|
271
317
|
|
|
272
318
|
const asterisk = document.createElement("span");
|
|
273
319
|
asterisk.classList.add("required-asterisk");
|
|
274
320
|
asterisk.textContent = "*";
|
|
275
321
|
asterisk.style.marginLeft = "4px";
|
|
276
|
-
|
|
322
|
+
|
|
323
|
+
const labelText = label.querySelector("span, [data-label]");
|
|
324
|
+
if (labelText) {
|
|
325
|
+
labelText.appendChild(asterisk);
|
|
326
|
+
} else {
|
|
327
|
+
const field = label.querySelector("input, select, textarea");
|
|
328
|
+
if (field) {
|
|
329
|
+
label.insertBefore(asterisk, field);
|
|
330
|
+
} else {
|
|
331
|
+
label.appendChild(asterisk);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
277
334
|
|
|
278
335
|
const form = input.closest("form");
|
|
279
336
|
if (form && !form.querySelector(".required-legend")) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Generator } from "./pds-generator.js";
|
|
6
6
|
import { applyStyles, adoptLayers, adoptPrimitives } from "./pds-runtime.js";
|
|
7
|
-
import { presets, defaultLog } from "./pds-config.js";
|
|
7
|
+
import { presets, defaultLog, PDS_CONFIG_RELATIONS } from "./pds-config.js";
|
|
8
8
|
import { defaultPDSEnhancers } from "./pds-enhancers.js";
|
|
9
9
|
import { defaultPDSEnhancerMetadata } from "./pds-enhancers-meta.js";
|
|
10
10
|
import { resolvePublicAssetURL } from "./pds-paths.js";
|
|
@@ -19,10 +19,77 @@ import {
|
|
|
19
19
|
setupAutoDefinerAndEnhancers,
|
|
20
20
|
stripFunctions,
|
|
21
21
|
} from "./pds-start-helpers.js";
|
|
22
|
+
import {
|
|
23
|
+
isPresetThemeCompatible,
|
|
24
|
+
resolveThemePreference,
|
|
25
|
+
} from "./pds-theme-utils.js";
|
|
22
26
|
|
|
23
27
|
let __liveApiReady = false;
|
|
24
28
|
let __queryClass = null;
|
|
25
29
|
|
|
30
|
+
function getStoredLiveConfig() {
|
|
31
|
+
if (typeof window === "undefined" || !window.localStorage) return null;
|
|
32
|
+
try {
|
|
33
|
+
const raw = window.localStorage.getItem("pure-ds-config");
|
|
34
|
+
if (!raw) return null;
|
|
35
|
+
const parsed = JSON.parse(raw);
|
|
36
|
+
if (parsed && ("preset" in parsed || "design" in parsed)) return parsed;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function deepMergeConfig(target = {}, source = {}) {
|
|
44
|
+
if (!source || typeof source !== "object") return target;
|
|
45
|
+
const out = Array.isArray(target) ? [...target] : { ...target };
|
|
46
|
+
for (const [key, value] of Object.entries(source)) {
|
|
47
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
48
|
+
out[key] = deepMergeConfig(
|
|
49
|
+
out[key] && typeof out[key] === "object" ? out[key] : {},
|
|
50
|
+
value
|
|
51
|
+
);
|
|
52
|
+
} else {
|
|
53
|
+
out[key] = value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function applyStoredConfigOverrides(baseConfig) {
|
|
60
|
+
const stored = getStoredLiveConfig();
|
|
61
|
+
if (!stored || !baseConfig || typeof baseConfig !== "object") return baseConfig;
|
|
62
|
+
|
|
63
|
+
const storedPreset = stored.preset;
|
|
64
|
+
const storedDesign =
|
|
65
|
+
stored.design && typeof stored.design === "object" ? stored.design : null;
|
|
66
|
+
|
|
67
|
+
if (!storedPreset && !storedDesign) return baseConfig;
|
|
68
|
+
|
|
69
|
+
const hasNewShape =
|
|
70
|
+
"preset" in baseConfig || "design" in baseConfig || "enhancers" in baseConfig;
|
|
71
|
+
|
|
72
|
+
let nextConfig = { ...baseConfig };
|
|
73
|
+
|
|
74
|
+
if (storedPreset) {
|
|
75
|
+
nextConfig.preset = storedPreset;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (storedDesign) {
|
|
79
|
+
if (hasNewShape) {
|
|
80
|
+
const baseDesign =
|
|
81
|
+
baseConfig.design && typeof baseConfig.design === "object"
|
|
82
|
+
? baseConfig.design
|
|
83
|
+
: {};
|
|
84
|
+
nextConfig.design = deepMergeConfig(baseDesign, storedDesign);
|
|
85
|
+
} else {
|
|
86
|
+
nextConfig = deepMergeConfig(baseConfig, storedDesign);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return nextConfig;
|
|
91
|
+
}
|
|
92
|
+
|
|
26
93
|
async function __attachLiveAPIs(PDS, { applyResolvedTheme, setupSystemListenerIfNeeded }) {
|
|
27
94
|
if (__liveApiReady) return;
|
|
28
95
|
|
|
@@ -45,6 +112,7 @@ async function __attachLiveAPIs(PDS, { applyResolvedTheme, setupSystemListenerIf
|
|
|
45
112
|
PDS.enums = enums;
|
|
46
113
|
PDS.common = commonModule || {};
|
|
47
114
|
PDS.presets = presets;
|
|
115
|
+
PDS.configRelations = PDS_CONFIG_RELATIONS;
|
|
48
116
|
PDS.enhancerMetadata = defaultPDSEnhancerMetadata;
|
|
49
117
|
PDS.applyStyles = function(generator) {
|
|
50
118
|
return applyStyles(generator || Generator.instance);
|
|
@@ -73,6 +141,91 @@ async function __attachLiveAPIs(PDS, { applyResolvedTheme, setupSystemListenerIf
|
|
|
73
141
|
return await queryEngine.search(question);
|
|
74
142
|
};
|
|
75
143
|
|
|
144
|
+
PDS.applyLivePreset = async function(presetId, options = {}) {
|
|
145
|
+
if (!presetId) return false;
|
|
146
|
+
if (!PDS.registry?.isLive) {
|
|
147
|
+
console.warn("PDS.applyLivePreset is only available in live mode.");
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const baseConfig = PDS.currentConfig || {};
|
|
152
|
+
const { design: _design, preset: _preset, ...rest } = baseConfig;
|
|
153
|
+
const inputConfig = {
|
|
154
|
+
...structuredClone(stripFunctions(rest)),
|
|
155
|
+
preset: presetId,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const normalized = normalizeInitConfig(inputConfig, {}, {
|
|
159
|
+
presets,
|
|
160
|
+
defaultLog,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const resolvedTheme = resolveThemePreference(PDS.theme);
|
|
164
|
+
if (!isPresetThemeCompatible(normalized.generatorConfig.design, resolvedTheme)) {
|
|
165
|
+
const presetName =
|
|
166
|
+
normalized.presetInfo?.name ||
|
|
167
|
+
normalized.generatorConfig?.design?.name ||
|
|
168
|
+
presetId;
|
|
169
|
+
console.warn(
|
|
170
|
+
`PDS theme "${resolvedTheme}" not supported by preset "${presetName}".`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (baseConfig.theme && !normalized.generatorConfig.theme) {
|
|
175
|
+
normalized.generatorConfig.theme = baseConfig.theme;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const generator = new Generator(normalized.generatorConfig);
|
|
179
|
+
|
|
180
|
+
if (normalized.generatorConfig.design?.typography) {
|
|
181
|
+
try {
|
|
182
|
+
await loadTypographyFonts(normalized.generatorConfig.design.typography);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
normalized.generatorConfig?.log?.(
|
|
185
|
+
"warn",
|
|
186
|
+
"Failed to load some fonts from Google Fonts:",
|
|
187
|
+
error,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await applyStyles(generator);
|
|
193
|
+
|
|
194
|
+
const presetInfo = normalized.presetInfo || { id: presetId, name: presetId };
|
|
195
|
+
PDS.currentPreset = presetInfo;
|
|
196
|
+
PDS.currentConfig = Object.freeze({
|
|
197
|
+
...baseConfig,
|
|
198
|
+
preset: normalized.generatorConfig.preset,
|
|
199
|
+
design: structuredClone(normalized.generatorConfig.design),
|
|
200
|
+
theme: normalized.generatorConfig.theme || baseConfig.theme,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const persist = options?.persist !== false;
|
|
204
|
+
if (persist && typeof window !== "undefined") {
|
|
205
|
+
const storageKey = "pure-ds-config";
|
|
206
|
+
try {
|
|
207
|
+
const storedRaw = localStorage.getItem(storageKey);
|
|
208
|
+
const storedParsed = storedRaw ? JSON.parse(storedRaw) : null;
|
|
209
|
+
const nextStored = {
|
|
210
|
+
...(storedParsed && typeof storedParsed === "object"
|
|
211
|
+
? storedParsed
|
|
212
|
+
: {}),
|
|
213
|
+
preset: presetInfo.id || presetId,
|
|
214
|
+
design: structuredClone(normalized.generatorConfig.design || {}),
|
|
215
|
+
};
|
|
216
|
+
localStorage.setItem(storageKey, JSON.stringify(nextStored));
|
|
217
|
+
} catch (error) {
|
|
218
|
+
normalized.generatorConfig?.log?.(
|
|
219
|
+
"warn",
|
|
220
|
+
"Failed to store preset:",
|
|
221
|
+
error,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return true;
|
|
227
|
+
};
|
|
228
|
+
|
|
76
229
|
// Live-only compiled getter
|
|
77
230
|
if (!Object.getOwnPropertyDescriptor(PDS, "compiled")) {
|
|
78
231
|
Object.defineProperty(PDS, "compiled", {
|
|
@@ -147,6 +300,8 @@ export async function startLive(PDS, config, { emitReady, applyResolvedTheme, se
|
|
|
147
300
|
);
|
|
148
301
|
}
|
|
149
302
|
|
|
303
|
+
config = applyStoredConfigOverrides(config);
|
|
304
|
+
|
|
150
305
|
// Attach live-only API surface (ontology, presets, query, etc.)
|
|
151
306
|
await __attachLiveAPIs(PDS, { applyResolvedTheme, setupSystemListenerIfNeeded });
|
|
152
307
|
attachFoucListener(PDS);
|
|
@@ -197,6 +352,16 @@ export async function startLive(PDS, config, { emitReady, applyResolvedTheme, se
|
|
|
197
352
|
|
|
198
353
|
// 2) Normalize first-arg API: support { preset, design, enhancers }
|
|
199
354
|
const normalized = normalizeInitConfig(config, {}, { presets, defaultLog });
|
|
355
|
+
if (manageTheme && !isPresetThemeCompatible(normalized.generatorConfig.design, resolvedTheme)) {
|
|
356
|
+
const presetName =
|
|
357
|
+
normalized.presetInfo?.name ||
|
|
358
|
+
normalized.generatorConfig?.design?.name ||
|
|
359
|
+
normalized.generatorConfig?.preset ||
|
|
360
|
+
"current preset";
|
|
361
|
+
console.warn(
|
|
362
|
+
`PDS theme "${resolvedTheme}" not supported by preset "${presetName}".`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
200
365
|
const userEnhancers = normalized.enhancers;
|
|
201
366
|
const { log: logFn, ...configToClone } = normalized.generatorConfig;
|
|
202
367
|
const generatorConfig = structuredClone(configToClone);
|
|
@@ -345,6 +510,20 @@ export async function startLive(PDS, config, { emitReady, applyResolvedTheme, se
|
|
|
345
510
|
enhancers: mergedEnhancers,
|
|
346
511
|
});
|
|
347
512
|
|
|
513
|
+
|
|
514
|
+
if (config?.liveEdit && typeof document !== "undefined") {
|
|
515
|
+
try {
|
|
516
|
+
if (!document.querySelector("pds-live-edit")) {
|
|
517
|
+
setTimeout(() => {
|
|
518
|
+
const liveEditor = document.createElement("pds-live-edit");
|
|
519
|
+
document.body.appendChild(liveEditor);
|
|
520
|
+
}, 1000);
|
|
521
|
+
}
|
|
522
|
+
} catch (error) {
|
|
523
|
+
generatorConfig?.log?.("warn", "Live editor failed to start:", error);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
348
527
|
emitReady({
|
|
349
528
|
mode: "live",
|
|
350
529
|
generator,
|
|
@@ -246,6 +246,14 @@ export const ontology = {
|
|
|
246
246
|
tags: ["form", "schema", "auto-generate"],
|
|
247
247
|
category: "form"
|
|
248
248
|
},
|
|
249
|
+
{
|
|
250
|
+
id: "pds-live-edit",
|
|
251
|
+
name: "Live Edit",
|
|
252
|
+
description: "Contextual live editing for PDS design settings",
|
|
253
|
+
selectors: ["pds-live-edit"],
|
|
254
|
+
tags: ["editor", "live", "config", "tooling"],
|
|
255
|
+
category: "tooling"
|
|
256
|
+
},
|
|
249
257
|
{
|
|
250
258
|
id: "pds-splitpanel",
|
|
251
259
|
name: "Split Panel",
|