@pure-ds/core 0.6.9 → 0.6.11

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.
Files changed (90) hide show
  1. package/custom-elements.json +865 -35
  2. package/dist/types/pds.d.ts +31 -0
  3. package/dist/types/public/assets/js/pds-manager.d.ts +100 -2
  4. package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
  5. package/dist/types/public/assets/js/pds.d.ts.map +1 -1
  6. package/dist/types/public/assets/pds/components/pds-form.d.ts.map +1 -1
  7. package/dist/types/public/assets/pds/components/pds-live-converter.d.ts +8 -0
  8. package/dist/types/public/assets/pds/components/pds-live-converter.d.ts.map +1 -0
  9. package/dist/types/public/assets/pds/components/pds-live-edit.d.ts +1 -195
  10. package/dist/types/public/assets/pds/components/pds-live-edit.d.ts.map +1 -1
  11. package/dist/types/public/assets/pds/components/pds-live-importer.d.ts +2 -0
  12. package/dist/types/public/assets/pds/components/pds-live-importer.d.ts.map +1 -0
  13. package/dist/types/public/assets/pds/components/pds-live-template-canvas.d.ts +2 -0
  14. package/dist/types/public/assets/pds/components/pds-live-template-canvas.d.ts.map +1 -0
  15. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts +0 -2
  16. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
  17. package/dist/types/public/assets/pds/components/pds-scrollrow.d.ts +20 -0
  18. package/dist/types/public/assets/pds/components/pds-scrollrow.d.ts.map +1 -1
  19. package/dist/types/public/assets/pds/components/pds-toaster.d.ts +1 -1
  20. package/dist/types/public/assets/pds/components/pds-toaster.d.ts.map +1 -1
  21. package/dist/types/public/assets/pds/components/pds-treeview.d.ts +37 -0
  22. package/dist/types/public/assets/pds/components/pds-treeview.d.ts.map +1 -0
  23. package/dist/types/src/js/common/toast.d.ts +8 -0
  24. package/dist/types/src/js/common/toast.d.ts.map +1 -1
  25. package/dist/types/src/js/pds-core/pds-config.d.ts +1306 -13
  26. package/dist/types/src/js/pds-core/pds-config.d.ts.map +1 -1
  27. package/dist/types/src/js/pds-core/pds-enhancers-meta.d.ts.map +1 -1
  28. package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
  29. package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
  30. package/dist/types/src/js/pds-core/pds-live.d.ts +2 -1
  31. package/dist/types/src/js/pds-core/pds-live.d.ts.map +1 -1
  32. package/dist/types/src/js/pds-core/pds-ontology.d.ts.map +1 -1
  33. package/dist/types/src/js/pds-core/pds-start-helpers.d.ts +1 -4
  34. package/dist/types/src/js/pds-core/pds-start-helpers.d.ts.map +1 -1
  35. package/dist/types/src/js/pds-live-manager/conversion-service.d.ts +66 -0
  36. package/dist/types/src/js/pds-live-manager/conversion-service.d.ts.map +1 -0
  37. package/dist/types/src/js/pds-live-manager/import-contract.d.ts +15 -0
  38. package/dist/types/src/js/pds-live-manager/import-contract.d.ts.map +1 -0
  39. package/dist/types/src/js/pds-live-manager/import-history-service.d.ts +32 -0
  40. package/dist/types/src/js/pds-live-manager/import-history-service.d.ts.map +1 -0
  41. package/dist/types/src/js/pds-live-manager/import-service.d.ts +21 -0
  42. package/dist/types/src/js/pds-live-manager/import-service.d.ts.map +1 -0
  43. package/dist/types/src/js/pds-live-manager/template-service.d.ts +17 -0
  44. package/dist/types/src/js/pds-live-manager/template-service.d.ts.map +1 -0
  45. package/dist/types/src/js/pds-manager.d.ts +4 -0
  46. package/dist/types/src/js/pds.d.ts.map +1 -1
  47. package/package.json +7 -3
  48. package/packages/pds-cli/README.md +51 -0
  49. package/packages/pds-cli/bin/pds-import.js +176 -0
  50. package/packages/pds-cli/bin/pds-static.js +31 -1
  51. package/packages/pds-cli/bin/postinstall.mjs +17 -8
  52. package/public/assets/js/app.js +23 -147
  53. package/public/assets/js/pds-manager.js +481 -248
  54. package/public/assets/js/pds.js +16 -16
  55. package/public/assets/pds/components/pds-form.js +124 -27
  56. package/public/assets/pds/components/pds-live-converter.js +47 -0
  57. package/public/assets/pds/components/pds-live-edit.js +1626 -211
  58. package/public/assets/pds/components/pds-live-importer.js +772 -0
  59. package/public/assets/pds/components/pds-live-template-canvas.js +171 -0
  60. package/public/assets/pds/components/pds-omnibox.js +146 -20
  61. package/public/assets/pds/components/pds-scrollrow.js +56 -1
  62. package/public/assets/pds/components/pds-toaster.js +50 -5
  63. package/public/assets/pds/components/pds-treeview.js +972 -0
  64. package/public/assets/pds/custom-elements.json +865 -35
  65. package/public/assets/pds/pds-css-complete.json +7 -7
  66. package/public/assets/pds/pds.css-data.json +5 -35
  67. package/public/assets/pds/templates/commerce-scroll-explorer.html +115 -0
  68. package/public/assets/pds/templates/content-brand-showcase.html +110 -0
  69. package/public/assets/pds/templates/feedback-ops-dashboard.html +91 -0
  70. package/public/assets/pds/templates/release-readiness-radar.html +69 -0
  71. package/public/assets/pds/templates/support-command-center.html +92 -0
  72. package/public/assets/pds/templates/templates.json +53 -0
  73. package/public/assets/pds/templates/workspace-settings-lab.html +131 -0
  74. package/public/assets/pds/vscode-custom-data.json +54 -4
  75. package/readme.md +34 -0
  76. package/src/js/pds-core/pds-config.js +831 -40
  77. package/src/js/pds-core/pds-enhancers-meta.js +11 -0
  78. package/src/js/pds-core/pds-enhancers.js +259 -5
  79. package/src/js/pds-core/pds-generator.js +353 -52
  80. package/src/js/pds-core/pds-live.js +630 -15
  81. package/src/js/pds-core/pds-ontology.js +6 -0
  82. package/src/js/pds-core/pds-start-helpers.js +14 -6
  83. package/src/js/pds-live-manager/conversion-service.js +3136 -0
  84. package/src/js/pds-live-manager/import-contract.js +57 -0
  85. package/src/js/pds-live-manager/import-history-service.js +145 -0
  86. package/src/js/pds-live-manager/import-service.js +255 -0
  87. package/src/js/pds-live-manager/tailwind-conversion-rules.json +383 -0
  88. package/src/js/pds-live-manager/template-service.js +170 -0
  89. package/src/js/pds.d.ts +31 -0
  90. package/src/js/pds.js +71 -60
@@ -0,0 +1,171 @@
1
+ const COMPONENT_TAG = "pds-live-template-canvas";
2
+ const PDS = globalThis.PDS;
3
+
4
+ let managerPromise = null;
5
+
6
+ async function getManagerModule() {
7
+ if (managerPromise) return managerPromise;
8
+ managerPromise = (async () => {
9
+ const candidates = [
10
+ PDS?.currentConfig?.managerURL,
11
+ "../core/pds-manager.js",
12
+ "/assets/pds/core/pds-manager.js",
13
+ ].filter(Boolean);
14
+
15
+ const attempted = new Set();
16
+ for (const candidate of candidates) {
17
+ try {
18
+ const resolved = new URL(candidate, import.meta.url).href;
19
+ if (attempted.has(resolved)) continue;
20
+ attempted.add(resolved);
21
+ const mod = await import(resolved);
22
+ if (mod && typeof mod === "object") return mod;
23
+ } catch (e) {}
24
+ }
25
+
26
+ return null;
27
+ })();
28
+
29
+ return managerPromise;
30
+ }
31
+
32
+ class PdsLiveTemplateCanvas extends HTMLElement {
33
+ constructor() {
34
+ super();
35
+ this._templates = [];
36
+ this._selectedTemplateId = "";
37
+ }
38
+
39
+ connectedCallback() {
40
+ this._renderShell();
41
+ this._loadTemplates();
42
+ }
43
+
44
+ _renderShell() {
45
+ this.className = "stack-sm";
46
+ this.innerHTML = `
47
+ <label class="stack-xs">
48
+ <span>Template</span>
49
+ <div class="pds-live-template-omnibox"></div>
50
+ </label>
51
+ <p class="text-muted pds-live-template-description"></p>
52
+ `;
53
+
54
+ }
55
+
56
+ async _loadTemplates() {
57
+ const manager = await getManagerModule();
58
+ if (typeof manager?.listLiveTemplates !== "function") return;
59
+
60
+ this._templates = (await manager.listLiveTemplates()) || [];
61
+ this._selectedTemplateId = "";
62
+
63
+ const host = this.querySelector(".pds-live-template-omnibox");
64
+ if (!host) return;
65
+
66
+ host.replaceChildren();
67
+ try {
68
+ const omnibox = document.createElement("pds-omnibox");
69
+ omnibox.setAttribute("item-grid", "45px 1fr 0");
70
+ omnibox.setAttribute("placeholder", "Search canvas templates...");
71
+ omnibox.value = "";
72
+
73
+ const templates = this._templates;
74
+ omnibox.settings = {
75
+ hideCategory: true,
76
+ itemGrid: "45px 1fr 0",
77
+ categories: {
78
+ Templates: {
79
+ trigger: () => true,
80
+ getItems: (context = {}) => {
81
+ const query = String(context?.search || "").toLowerCase().trim();
82
+ return templates
83
+ .filter((item) => {
84
+ if (!query) return true;
85
+ const haystack = [item.name, item.description, ...(item.tags || [])]
86
+ .join(" ")
87
+ .toLowerCase();
88
+ return haystack.includes(query);
89
+ })
90
+ .map((item) => ({
91
+ id: item.id,
92
+ text: item.name,
93
+ description: item.description,
94
+ icon: item.icon || "grid-four",
95
+ }));
96
+ },
97
+ action: (selection) => {
98
+ this._selectedTemplateId = selection?.id || "";
99
+ this._updateDescription();
100
+ this._applyTemplate();
101
+ return selection?.id || "";
102
+ },
103
+ },
104
+ },
105
+ };
106
+
107
+ omnibox.addEventListener("result-selected", (event) => {
108
+ const selectedId =
109
+ event?.detail?.id ||
110
+ this._templates.find((item) => item.name === event?.detail?.text)?.id ||
111
+ this._selectedTemplateId;
112
+ if (selectedId) {
113
+ this._selectedTemplateId = selectedId;
114
+ }
115
+ const selected = this._templates.find((item) => item.id === this._selectedTemplateId);
116
+ if (selected?.name) {
117
+ omnibox.value = selected.name;
118
+ }
119
+ this._updateDescription();
120
+ });
121
+
122
+ host.appendChild(omnibox);
123
+ } catch (error) {
124
+ const fallback = document.createElement("select");
125
+ fallback.className = "pds-live-template-select";
126
+ this._templates.forEach((template) => {
127
+ const option = document.createElement("option");
128
+ option.value = template.id;
129
+ option.textContent = template.name;
130
+ fallback.appendChild(option);
131
+ });
132
+ fallback.addEventListener("change", () => {
133
+ this._selectedTemplateId = fallback.value;
134
+ this._updateDescription();
135
+ this._applyTemplate();
136
+ });
137
+ host.appendChild(fallback);
138
+ }
139
+
140
+ this._updateDescription();
141
+ }
142
+
143
+ _updateDescription() {
144
+ const description = this.querySelector(".pds-live-template-description");
145
+ if (!description) return;
146
+ const selected = this._templates.find((item) => item.id === this._selectedTemplateId);
147
+ description.textContent = selected?.description || "";
148
+ }
149
+
150
+ async _applyTemplate() {
151
+ if (!this._selectedTemplateId) return;
152
+
153
+ const manager = await getManagerModule();
154
+ if (typeof manager?.runLiveImport !== "function") return;
155
+
156
+ const result = await manager.runLiveImport({
157
+ sourceType: "template",
158
+ templateId: this._selectedTemplateId,
159
+ });
160
+
161
+ this.dispatchEvent(
162
+ new CustomEvent("pds:live-template:inject", {
163
+ bubbles: true,
164
+ composed: true,
165
+ detail: { result, templateId: this._selectedTemplateId },
166
+ })
167
+ );
168
+ }
169
+ }
170
+
171
+ customElements.define(COMPONENT_TAG, PdsLiveTemplateCanvas);
@@ -10,7 +10,6 @@
10
10
  * @attr {boolean} disabled - Disable the input
11
11
  * @attr {boolean} required - Mark the input as required
12
12
  * @attr {string} autocomplete - Native autocomplete attribute (default: off)
13
- * @attr {string} item-grid - Grid template columns for suggestion items
14
13
  *
15
14
  * @property {Object} settings - AutoComplete settings object (required by consumer)
16
15
  */
@@ -30,7 +29,6 @@ export class PdsOmnibox extends HTMLElement {
30
29
  "required",
31
30
  "autocomplete",
32
31
  "icon",
33
- "item-grid",
34
32
  ];
35
33
  }
36
34
 
@@ -165,15 +163,6 @@ export class PdsOmnibox extends HTMLElement {
165
163
  else this.setAttribute("icon", value);
166
164
  }
167
165
 
168
- get itemGrid() {
169
- return this.getAttribute("item-grid") || "";
170
- }
171
-
172
- set itemGrid(value) {
173
- if (value == null || value === "") this.removeAttribute("item-grid");
174
- else this.setAttribute("item-grid", value);
175
- }
176
-
177
166
  formAssociatedCallback() {}
178
167
 
179
168
  formDisabledCallback(disabled) {
@@ -251,7 +240,7 @@ export class PdsOmnibox extends HTMLElement {
251
240
  --ac-margin: var(--spacing-0);
252
241
  --icon-size: var(--spacing-6);
253
242
  --ac-itm-height-default: 5rem;
254
- --ac-max-height-default: 300px;
243
+ --ac-max-height-default: 300px;
255
244
  --ac-viewport-gap: var(--spacing-4);
256
245
  --ac-suggest-offset: var(--spacing-1);
257
246
  }
@@ -535,9 +524,6 @@ export class PdsOmnibox extends HTMLElement {
535
524
  if (this.required) this.#input.setAttribute("required", "");
536
525
  else this.#input.removeAttribute("required");
537
526
 
538
- if (this.itemGrid) this.style.setProperty("--ac-grid", this.itemGrid);
539
- else this.style.removeProperty("--ac-grid");
540
-
541
527
  this.#updateFormValue(this.#input.value);
542
528
  }
543
529
 
@@ -579,7 +565,8 @@ export class PdsOmnibox extends HTMLElement {
579
565
  container.style.removeProperty("--ac-color-default");
580
566
  container.style.removeProperty("--ac-accent-color");
581
567
 
582
- const gridOverride = this.itemGrid;
568
+ const gridOverride =
569
+ typeof settings.itemGrid === "string" ? settings.itemGrid.trim() : "";
583
570
  if (gridOverride) {
584
571
  container.style.setProperty("--ac-grid", gridOverride);
585
572
  } else if (settings.hideCategory === true) {
@@ -661,6 +648,8 @@ export class PdsOmnibox extends HTMLElement {
661
648
  if (!suggestion) return;
662
649
  this.#suggestionsObserver = new MutationObserver(() => {
663
650
  if (!suggestion.classList.contains("ac-active")) {
651
+ this.removeAttribute("data-suggestions-open");
652
+ this.#clearSuggestionOverlayStyles(suggestion);
664
653
  this.#resetIconToDefault();
665
654
  }
666
655
  });
@@ -695,6 +684,106 @@ export class PdsOmnibox extends HTMLElement {
695
684
  this.#autoCompleteViewportHandler = null;
696
685
  }
697
686
 
687
+ #clearSuggestionOverlayStyles(suggestion) {
688
+ if (!suggestion || !suggestion.style) return;
689
+ suggestion.style.removeProperty("position");
690
+ suggestion.style.removeProperty("left");
691
+ suggestion.style.removeProperty("right");
692
+ suggestion.style.removeProperty("top");
693
+ suggestion.style.removeProperty("bottom");
694
+ suggestion.style.removeProperty("width");
695
+ suggestion.style.removeProperty("max-width");
696
+ }
697
+
698
+ #getComposedParent(node) {
699
+ if (!node) return null;
700
+ if (node.parentElement) return node.parentElement;
701
+ const root = node.getRootNode?.();
702
+ if (root && root.host) return root.host;
703
+ return null;
704
+ }
705
+
706
+ #hasTransformedAncestor(startNode) {
707
+ let current = startNode;
708
+ let safety = 0;
709
+ while (current && safety < 80) {
710
+ if (current instanceof Element) {
711
+ const style = getComputedStyle(current);
712
+ if (
713
+ style.transform !== "none" ||
714
+ style.perspective !== "none" ||
715
+ style.filter !== "none" ||
716
+ style.backdropFilter !== "none"
717
+ ) {
718
+ return true;
719
+ }
720
+ }
721
+ current = this.#getComposedParent(current);
722
+ safety += 1;
723
+ }
724
+ return false;
725
+ }
726
+
727
+ #shouldUseFixedSuggestionOverlay(container) {
728
+ if (!container) return false;
729
+ if (this.closest("pds-drawer, dialog")) return false;
730
+ return !this.#hasTransformedAncestor(this);
731
+ }
732
+
733
+ #positionSuggestionInline({ container, suggestion, rect, direction, offset, maxHeight }) {
734
+ if (!container || !suggestion) return;
735
+ this.#clearSuggestionOverlayStyles(suggestion);
736
+ if (direction === "up") {
737
+ suggestion.style.top = "auto";
738
+ suggestion.style.bottom = `${Math.round(rect.height + offset)}px`;
739
+ } else {
740
+ suggestion.style.bottom = "auto";
741
+ suggestion.style.top = `${Math.round(rect.height + offset)}px`;
742
+ }
743
+ container.setAttribute("data-direction", direction);
744
+ suggestion.setAttribute("data-direction", direction);
745
+ container.style.setProperty("--ac-max-height", `${maxHeight}px`);
746
+ }
747
+
748
+ #positionSuggestionOverlay({
749
+ container,
750
+ suggestion,
751
+ rect,
752
+ viewportHeight,
753
+ viewportWidth,
754
+ direction,
755
+ maxHeight,
756
+ gap,
757
+ offset,
758
+ }) {
759
+ if (!container || !suggestion || !suggestion.style) return;
760
+ if (suggestion.classList.contains("full-mobile")) {
761
+ this.#clearSuggestionOverlayStyles(suggestion);
762
+ return;
763
+ }
764
+
765
+ const clampedLeft = Math.max(gap, Math.min(rect.left, viewportWidth - rect.width - gap));
766
+ const clampedWidth = Math.max(0, Math.min(rect.width, viewportWidth - gap * 2));
767
+
768
+ suggestion.style.position = "fixed";
769
+ suggestion.style.left = `${Math.round(clampedLeft)}px`;
770
+ suggestion.style.width = `${Math.round(clampedWidth)}px`;
771
+ suggestion.style.maxWidth = `${Math.round(Math.max(0, viewportWidth - gap * 2))}px`;
772
+ suggestion.style.right = "auto";
773
+
774
+ if (direction === "up") {
775
+ suggestion.style.top = "auto";
776
+ suggestion.style.bottom = `${Math.round(viewportHeight - rect.top + offset)}px`;
777
+ } else {
778
+ suggestion.style.bottom = "auto";
779
+ suggestion.style.top = `${Math.round(rect.bottom + offset)}px`;
780
+ }
781
+
782
+ container.setAttribute("data-direction", direction);
783
+ suggestion.setAttribute("data-direction", direction);
784
+ container.style.setProperty("--ac-max-height", `${maxHeight}px`);
785
+ }
786
+
698
787
  #updateSuggestionMaxHeight() {
699
788
  if (!this.#input) return;
700
789
  const container = this.#input.parentElement;
@@ -709,6 +798,7 @@ export class PdsOmnibox extends HTMLElement {
709
798
  suggestion?.getAttribute("data-direction") ||
710
799
  container.getAttribute("data-direction") ||
711
800
  "down";
801
+ const offset = this.#readSpacingToken(container, "--ac-suggest-offset") || 0;
712
802
 
713
803
  const availableDown = viewportHeight - rect.bottom - gap;
714
804
  const availableUp = rect.top - gap;
@@ -718,8 +808,6 @@ export class PdsOmnibox extends HTMLElement {
718
808
  container.setAttribute("data-direction", direction);
719
809
  if (suggestion) suggestion.setAttribute("data-direction", direction);
720
810
  if (suggestion) {
721
- const offset =
722
- this.#readSpacingToken(container, "--ac-suggest-offset") || 0;
723
811
  if (direction === "up") {
724
812
  suggestion.style.top = "auto";
725
813
  suggestion.style.bottom = `${rect.height + offset}px`;
@@ -732,8 +820,46 @@ export class PdsOmnibox extends HTMLElement {
732
820
 
733
821
  const available = direction === "up" ? availableUp : availableDown;
734
822
 
735
- const maxHeight = Math.max(0, Math.floor(available));
823
+ const configuredMaxHeight =
824
+ this.#readSpacingToken(container, "--ac-max-height-default") || 200;
825
+ const maxHeight = Math.max(
826
+ 0,
827
+ Math.floor(Math.min(available, configuredMaxHeight)),
828
+ );
736
829
  container.style.setProperty("--ac-max-height", `${maxHeight}px`);
830
+
831
+ const isSuggestionActive = suggestion?.classList?.contains("ac-active");
832
+ this.toggleAttribute("data-suggestions-open", Boolean(isSuggestionActive));
833
+
834
+ if (!suggestion || !isSuggestionActive || suggestion.classList.contains("full-mobile")) {
835
+ this.#clearSuggestionOverlayStyles(suggestion);
836
+ return;
837
+ }
838
+
839
+ if (!this.#shouldUseFixedSuggestionOverlay(container)) {
840
+ this.#positionSuggestionInline({
841
+ container,
842
+ suggestion,
843
+ rect,
844
+ direction,
845
+ offset,
846
+ maxHeight,
847
+ });
848
+ return;
849
+ }
850
+
851
+ const viewportWidth = window.visualViewport?.width || window.innerWidth;
852
+ this.#positionSuggestionOverlay({
853
+ container,
854
+ suggestion,
855
+ rect,
856
+ viewportHeight,
857
+ viewportWidth,
858
+ direction,
859
+ maxHeight,
860
+ gap,
861
+ offset,
862
+ });
737
863
  }
738
864
 
739
865
  #resolveSuggestionDirection(container) {
@@ -743,7 +869,7 @@ export class PdsOmnibox extends HTMLElement {
743
869
  const availableDown = viewportHeight - rect.bottom - gap;
744
870
  const availableUp = rect.top - gap;
745
871
  const maxDefault =
746
- this.#readSpacingToken(container, "--ac-max-height-default") || 300;
872
+ this.#readSpacingToken(container, "--ac-max-height-default") || 200;
747
873
  const minDown = Math.min(maxDefault, 180);
748
874
 
749
875
  return availableDown >= minDown || availableDown >= availableUp
@@ -11,6 +11,8 @@
11
11
  *
12
12
  * @attr {string} label - Accessible label for the scroll region; also used as fallback heading copy
13
13
  * @attr {"start"|"center"} snap - Snap alignment for tiles when scrolling (default: start)
14
+ * @attr {string} tile-min - Minimum tile width (CSS length, e.g. "250px")
15
+ * @attr {string} tile-max - Maximum tile width (CSS length, e.g. "360px")
14
16
  */
15
17
  class PdsScrollrow extends HTMLElement {
16
18
  #viewport;
@@ -19,7 +21,7 @@ class PdsScrollrow extends HTMLElement {
19
21
  #adopted = false;
20
22
 
21
23
  static get observedAttributes() {
22
- return ["label", "snap"];
24
+ return ["label", "snap", "tile-min", "tile-max"];
23
25
  }
24
26
 
25
27
  constructor() {
@@ -128,6 +130,36 @@ class PdsScrollrow extends HTMLElement {
128
130
  else this.setAttribute("snap", String(val));
129
131
  }
130
132
 
133
+ /**
134
+ * Minimum tile size applied to slotted content.
135
+ * @returns {string|null}
136
+ */
137
+ get tileMin() {
138
+ return this.getAttribute("tile-min");
139
+ }
140
+ /**
141
+ * @param {string|null} val
142
+ */
143
+ set tileMin(val) {
144
+ if (val == null) this.removeAttribute("tile-min");
145
+ else this.setAttribute("tile-min", String(val));
146
+ }
147
+
148
+ /**
149
+ * Maximum tile size applied to slotted content.
150
+ * @returns {string|null}
151
+ */
152
+ get tileMax() {
153
+ return this.getAttribute("tile-max");
154
+ }
155
+ /**
156
+ * @param {string|null} val
157
+ */
158
+ set tileMax(val) {
159
+ if (val == null) this.removeAttribute("tile-max");
160
+ else this.setAttribute("tile-max", String(val));
161
+ }
162
+
131
163
  /**
132
164
  * Lifecycle hook called when the element is inserted into the document.
133
165
  */
@@ -189,6 +221,11 @@ class PdsScrollrow extends HTMLElement {
189
221
  this.#applySnap();
190
222
  break;
191
223
  }
224
+ case "tile-min":
225
+ case "tile-max": {
226
+ this.#applyTileSizing();
227
+ break;
228
+ }
192
229
  }
193
230
  }
194
231
 
@@ -256,6 +293,7 @@ class PdsScrollrow extends HTMLElement {
256
293
 
257
294
  // Apply initial snap alignment
258
295
  this.#applySnap();
296
+ this.#applyTileSizing();
259
297
 
260
298
  // Observe size changes to refresh controls
261
299
  this.#ro = new ResizeObserver(() => this.#updateControls());
@@ -274,6 +312,23 @@ class PdsScrollrow extends HTMLElement {
274
312
  }
275
313
  }
276
314
 
315
+ #applyTileSizing() {
316
+ const tileMin = this.tileMin;
317
+ const tileMax = this.tileMax;
318
+
319
+ if (tileMin && String(tileMin).trim()) {
320
+ this.style.setProperty("--tile-min", String(tileMin).trim());
321
+ } else {
322
+ this.style.removeProperty("--tile-min");
323
+ }
324
+
325
+ if (tileMax && String(tileMax).trim()) {
326
+ this.style.setProperty("--tile-max", String(tileMax).trim());
327
+ } else {
328
+ this.style.removeProperty("--tile-max");
329
+ }
330
+ }
331
+
277
332
  /**
278
333
  * Scroll the viewport by roughly one page in the indicated direction.
279
334
  * @param {Event} e
@@ -124,6 +124,14 @@ export class AppToaster extends HTMLElement {
124
124
  white-space: pre-line;
125
125
  }
126
126
 
127
+ aside.toast .toast-content {
128
+ width: 100%;
129
+ }
130
+
131
+ aside.toast .toast-action {
132
+ margin-top: var(--spacing-2);
133
+ }
134
+
127
135
  /* Mobile responsive toast positioning */
128
136
  @_media (max-width: 640px) {
129
137
  :host {
@@ -182,19 +190,23 @@ export class AppToaster extends HTMLElement {
182
190
  duration: null, // auto-calculated based on reading time
183
191
  closable: true,
184
192
  persistent: false, // if true, doesn't auto-dismiss
193
+ html: false,
194
+ action: null,
185
195
  };
186
196
 
187
197
  const config = { ...defaults, ...options };
188
198
 
189
199
  // Calculate reading time (average 200 words per minute)
190
- const wordCount = message.split(/\s+/).length;
200
+ const messageText = String(message || "");
201
+ const readingText = config.html ? messageText.replace(/<[^>]*>/g, " ") : messageText;
202
+ const wordCount = readingText.split(/\s+/).filter(Boolean).length;
191
203
  const baseReadingTime = Math.max(2000, (wordCount / 200) * 60 * 1000); // minimum 2 seconds
192
204
 
193
205
  // Extend time for errors (people need more time to process error messages)
194
206
  const multiplier = config.type === "error" ? 1.5 : 1;
195
207
  const duration = config.duration || baseReadingTime * multiplier;
196
208
 
197
- return this.#showToast(message, config, duration);
209
+ return this.#showToast(messageText, config, duration);
198
210
  }
199
211
 
200
212
  /*
@@ -213,7 +225,9 @@ export class AppToaster extends HTMLElement {
213
225
  config.type,
214
226
  config.closable,
215
227
  duration,
216
- config.persistent
228
+ config.persistent,
229
+ config.html,
230
+ config.action
217
231
  );
218
232
 
219
233
  // Add to DOM
@@ -247,7 +261,7 @@ export class AppToaster extends HTMLElement {
247
261
  * @param {boolean} persistent
248
262
  * @returns {HTMLElement}
249
263
  */
250
- createToastElement(id, message, type, closable, duration, persistent) {
264
+ createToastElement(id, message, type, closable, duration, persistent, html = false, action = null) {
251
265
  const toast = document.createElement("aside");
252
266
  toast.className = `toast callout ${this.#getAlertClass(type)}`;
253
267
  toast.setAttribute("data-toast-id", id);
@@ -260,6 +274,7 @@ export class AppToaster extends HTMLElement {
260
274
  icon.setAttribute("size", "lg");
261
275
 
262
276
  const content = document.createElement("div");
277
+ content.className = "toast-content";
263
278
 
264
279
  const title = document.createElement("div");
265
280
  title.className = "callout-title";
@@ -267,11 +282,41 @@ export class AppToaster extends HTMLElement {
267
282
 
268
283
  const text = document.createElement("p");
269
284
  text.style.margin = "0";
270
- text.textContent = message;
285
+ if (html) {
286
+ text.innerHTML = String(message || "");
287
+ } else {
288
+ text.textContent = String(message || "");
289
+ }
271
290
 
272
291
  content.appendChild(title);
273
292
  content.appendChild(text);
274
293
 
294
+ if (action && typeof action === "object" && action.label) {
295
+ const actionButton = document.createElement("button");
296
+ actionButton.className = "btn-outline btn-sm toast-action";
297
+ actionButton.type = "button";
298
+ const actionLabel = document.createElement("span");
299
+ actionLabel.textContent = String(action.label);
300
+
301
+ if (action.icon) {
302
+ const icon = document.createElement("pds-icon");
303
+ icon.setAttribute("icon", String(action.icon));
304
+ icon.setAttribute("size", "sm");
305
+ actionButton.appendChild(icon);
306
+ }
307
+
308
+ actionButton.appendChild(actionLabel);
309
+ actionButton.addEventListener("click", () => {
310
+ if (typeof action.onClick === "function") {
311
+ action.onClick();
312
+ }
313
+ if (action.dismissOnClick !== false) {
314
+ this.dismissToast(id);
315
+ }
316
+ });
317
+ content.appendChild(actionButton);
318
+ }
319
+
275
320
  toast.appendChild(icon);
276
321
  toast.appendChild(content);
277
322