@panphora/clayjs 0.1.0 → 0.2.1

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 (45) hide show
  1. package/NOTICE +19 -0
  2. package/README.md +2 -0
  3. package/_headers +2 -0
  4. package/all.js +24 -0
  5. package/clay-data.js +16 -0
  6. package/clay-dom.js +18 -0
  7. package/clay-events.js +28 -0
  8. package/clay-options.js +27 -0
  9. package/clay-ui.js +29 -0
  10. package/clay-utils.js +18 -0
  11. package/clay.js +17 -5
  12. package/package.json +25 -2
  13. package/sap.js +16 -0
  14. package/src/core/save.js +5 -4
  15. package/src/core/snapshot.js +12 -9
  16. package/src/dom/all.js +440 -0
  17. package/src/dom/dom-helpers.js +218 -0
  18. package/src/dom/form-data.js +62 -0
  19. package/src/dom/insert-style-tag.js +109 -0
  20. package/src/dom/nearest.js +97 -0
  21. package/src/dom/pipe.js +1 -0
  22. package/src/events/index.js +22 -0
  23. package/src/events/onclickaway.js +27 -0
  24. package/src/events/onclickchildren.js +12 -0
  25. package/src/events/onclone.js +54 -0
  26. package/src/events/onmutation.js +54 -0
  27. package/src/events/onpagemutation.js +37 -0
  28. package/src/events/onrender.js +32 -0
  29. package/src/lib/mutation.js +26 -9
  30. package/src/lib/on-load.js +9 -0
  31. package/src/loader-logic.js +2 -1
  32. package/src/loader.js +2 -0
  33. package/src/options/options.js +253 -0
  34. package/src/plugins/demo.js +124 -0
  35. package/src/sync/live-sync.js +61 -19
  36. package/src/sync/merge-tags.js +36 -0
  37. package/src/ui/dialogs.js +206 -0
  38. package/src/ui/index.js +38 -0
  39. package/src/ui/modal.js +884 -0
  40. package/src/ui/toast.js +434 -0
  41. package/src/utils/copy-to-clipboard.js +27 -0
  42. package/src/utils/debounce.js +23 -0
  43. package/src/utils/index.js +10 -0
  44. package/src/utils/slugify.js +20 -0
  45. package/src/vendor/hyper-morph.vendor.js +2 -1
@@ -0,0 +1,206 @@
1
+ import themodal from "./modal.js";
2
+ import onDomReady from "../lib/dom-ready.js";
3
+ import toast from "./toast.js";
4
+ import copyToClipboard from "../utils/copy-to-clipboard.js";
5
+
6
+ const CLOSE_BUTTON_SVG = `<svg viewBox="0 0 134 134" fill="none" xmlns="http://www.w3.org/2000/svg"><path class="micromodal__close-bg" d="M132 132.5 1 1.5h131v131Z" /><path class="micromodal__close-x" fill-rule="evenodd" clip-rule="evenodd" d="M0 0h3v1.5h1.5V3H6v1.5h1.5V6H9v1.5h1.5V9H12v1.5h1.5V12H15v1.5h1.5V15H18v1.5h1.5V18H21v1.5h1.5V21H24v1.5h1.5V24H27v1.5h1.5V27H30v1.5h1.5V30H33v1.5h1.5V33H36v1.5h1.5V36H39v1.5h1.5V39H42v1.5h1.5V42H45v1.5h1.5V45H48v1.5h1.5V48H51v1.5h1.5V51H54v1.5h1.5V54H57v1.5h1.5V57H60v1.5h1.5V60H63v1.5h1.5V63H66v1.5h1.5V66H69v1.5h1.5V69H72v1.5h1.5V72H75v1.5h1.5V75H78v1.5h1.5V78H81v1.5h1.5V81H84v1.5h1.5V84H87v1.5h1.5V87H90v1.5h1.5V90H93v1.5h1.5V93H96v1.5h1.5V96H99v1.5h1.5V99h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v1.5h1.5v3h-3V132H129v-1.5h-1.5V129H126v-1.5h-1.5V126H123v-1.5h-1.5V123H120v-1.5h-1.5V120H117v-1.5h-1.5V117H114v-1.5h-1.5V114H111v-1.5h-1.5V111H108v-1.5h-1.5V108H105v-1.5h-1.5V105H102v-1.5h-1.5V102H99v-1.5h-1.5V99H96v-1.5h-1.5V96H93v-1.5h-1.5V93H90v-1.5h-1.5V90H87v-1.5h-1.5V87H84v-1.5h-1.5V84H81v-1.5h-1.5V81H78v-1.5h-1.5V78H75v-1.5h-1.5V75H72v-1.5h-1.5V72H69v-1.5h-1.5V69H66v-1.5h-1.5V66H63v-1.5h-1.5V63H60v-1.5h-1.5V60H57v-1.5h-1.5V57H54v-1.5h-1.5V54H51v-1.5h-1.5V51H48v-1.5h-1.5V48H45v-1.5h-1.5V45H42v-1.5h-1.5V42H39v-1.5h-1.5V39H36v-1.5h-1.5V36H33v-1.5h-1.5V33H30v-1.5h-1.5V30H27v-1.5h-1.5V27H24v-1.5h-1.5V24H21v-1.5h-1.5V21H18v-1.5h-1.5V18H15v-1.5h-1.5V15H12v-1.5h-1.5V12H9v-1.5H7.5V9H6V7.5H4.5V6H3V4.5H1.5V3H0V0ZM108.8 22h5.2v5.1h-2.6v2.6H109v2.6h-2.6v2.6h-2.6v2.5h-2.6v5.2h2.6V45h2.6v2.6h2.6v2.6h2.5v2.6h2.6V58h-5.1v-2.6h-2.6V53h-2.6v-2.6h-2.6v-2.6h-2.5v-2.6h-5.2v2.6H91v2.6h-2.6v2.6h-2.6v2.5h-2.6V58H78v-5.1h2.6v-2.6H83v-2.6h2.6v-2.6h2.6v-2.5h2.6v-5.2h-2.6V35h-2.6v-2.6h-2.6v-2.6h-2.5v-2.6H78V22h5.2v2.6h2.5V27h2.6v2.6h2.6v2.6h2.5v2.6h5.2v-2.6h2.5v-2.6h2.6v-2.6h2.6v-2.5h2.5V22Z" /></svg>`;
7
+ const CONFIRM_BUTTON_SVG = `<div style="width: 28px;"><svg viewBox="0 0 60 33" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M34.5 0.75H43.5V5.25H49V9.75H54.5V14.25H60V18.75H54.5V23.25H49V27.75H43.5V32.25H34.5V27.75H40V23.25H45.5V18.75H0V14.25H45.5V9.75H40V5.25H34.5V0.75Z" fill="white"/></svg></div>`;
8
+
9
+ function createModal(promptText, yesCallback, extraContent = "", includeInput = false, defaultValue = "") {
10
+ const inputHtml = includeInput
11
+ ? `<div><input class="micromodal__input" type="text" value="${defaultValue}" required></div>`
12
+ : "";
13
+
14
+ themodal.html = `<div>
15
+ <div class="micromodal__heading">${promptText}</div>
16
+ ${inputHtml}
17
+ ${extraContent}
18
+ </div>`;
19
+ themodal.closeHtml = CLOSE_BUTTON_SVG;
20
+ themodal.yes = CONFIRM_BUTTON_SVG;
21
+
22
+ const promise = new Promise((resolve, reject) => {
23
+ themodal.onYes(() => {
24
+ let promptResult;
25
+ if (includeInput) {
26
+ promptResult = document.querySelector(".micromodal__input").value;
27
+ if (!promptResult) return false; // keep modal open on empty input
28
+ }
29
+ // Run the validation callback synchronously so a throw can keep the
30
+ // modal open (callers rely on this — e.g. delete-site confirms by
31
+ // throwing when the typed name doesn't match).
32
+ if (yesCallback) {
33
+ try {
34
+ yesCallback(promptResult);
35
+ } catch (err) {
36
+ toast(err.message || 'An error occurred', 'error');
37
+ return false; // keep modal open, user can retry
38
+ }
39
+ }
40
+ // Defer resolve so downstream .then() handlers don't fire inside this
41
+ // modal's onYes loop — themodal is a singleton, and chained ask()/
42
+ // consent() calls need a clean themodal to set up their state.
43
+ setTimeout(() => resolve(promptResult), 0);
44
+ return true; // allow modal to close
45
+ });
46
+
47
+ themodal.onNo = () => {
48
+ setTimeout(reject, 0);
49
+ };
50
+ });
51
+
52
+ themodal.open();
53
+
54
+ setTimeout(() => {
55
+ const modalContainer = document.querySelector('.micromodal-parent');
56
+ if (modalContainer) {
57
+ modalContainer.addEventListener('click', (event) => {
58
+ const btn = event.target.closest('[data-copy]');
59
+ if (btn) {
60
+ copyToClipboard(btn.dataset.copy);
61
+ toast('Copied', 'success');
62
+ }
63
+ });
64
+ }
65
+ }, 0);
66
+
67
+ // Fire-and-forget callers (e.g. consent(msg, cb) with no await) don't consume
68
+ // the reject path; dismissal now rejects, so swallow it here to avoid an
69
+ // unhandled rejection. Awaiters still observe the rejection via their await.
70
+ promise.catch(() => {});
71
+
72
+ return promise;
73
+ }
74
+
75
+ // Public API functions
76
+ export function ask(promptText, yesCallback, defaultValue = "", extraContent = "") {
77
+ return createModal(promptText, yesCallback, extraContent, true, defaultValue);
78
+ }
79
+
80
+ export function consent(promptText, yesCallback, extraContent = "") {
81
+ return createModal(promptText, yesCallback, extraContent, false);
82
+ }
83
+
84
+ /**
85
+ * Display an informational modal with a title and optional content paragraphs
86
+ * @param {string} promptText - The title/heading text
87
+ * @param {...string} content - Additional content paragraphs (variadic)
88
+ * @returns {Promise} Resolves when user confirms, rejects on close
89
+ */
90
+ export function tell(promptText, ...content) {
91
+ const contentHtml = content.length > 0
92
+ ? content.map(c => `<div class="micromodal__tell-content">${c}</div>`).join("")
93
+ : "";
94
+
95
+ themodal.html = `<div class="micromodal__tell">
96
+ <div class="micromodal__tell-title">${promptText}</div>
97
+ ${contentHtml}
98
+ </div>`;
99
+ themodal.closeHtml = CLOSE_BUTTON_SVG;
100
+ themodal.yes = CONFIRM_BUTTON_SVG;
101
+
102
+ const promise = new Promise((resolve, reject) => {
103
+ themodal.onYes(() => {
104
+ setTimeout(resolve, 0);
105
+ return true;
106
+ });
107
+
108
+ themodal.onNo = () => {
109
+ setTimeout(reject, 0);
110
+ };
111
+ });
112
+
113
+ themodal.open();
114
+
115
+ // See createModal: swallow the reject for fire-and-forget tell() callers;
116
+ // awaiters still observe it via their await.
117
+ promise.catch(() => {});
118
+
119
+ return promise;
120
+ }
121
+
122
+ /**
123
+ * Display a modal with a code snippet and copy functionality
124
+ * @param {string} title - The modal heading
125
+ * @param {string} content - The code to display
126
+ * @param {string} extraContent - Optional raw HTML rendered below the copy button.
127
+ * Callers style their own container; use `<div class="snippet-warning">…</div>`
128
+ * for the standard yellow-bordered warning box.
129
+ */
130
+ export function snippet(title, content, extraContent = '') {
131
+
132
+ // Create the modal content with copy button
133
+ const modalContent = `
134
+ <div class="snippet-code-block">
135
+ <pre>${content}</pre>
136
+ </div>
137
+
138
+ <button type="button" class="micromodal__secondary-btn copy-snippet-btn" style="margin-bottom: 14px;">copy</button>
139
+
140
+ ${extraContent || ''}
141
+ `;
142
+
143
+ // Use the existing modal system
144
+ themodal.html = `<div>
145
+ <div class="micromodal__heading">${title}</div>
146
+ ${modalContent}
147
+ </div>`;
148
+
149
+ themodal.closeHtml = CLOSE_BUTTON_SVG;
150
+ themodal.yes = '';
151
+
152
+ const promise = new Promise((resolve) => {
153
+ // Local copy function
154
+ const handleCopy = function(event) {
155
+ if (event.target.closest('.copy-snippet-btn')) {
156
+ copyToClipboard(content);
157
+ toast('Copied to clipboard!', 'success');
158
+ }
159
+ };
160
+
161
+ // Add event listener to the modal container after it opens
162
+ setTimeout(() => {
163
+ const modalContainer = document.querySelector('.micromodal-parent');
164
+ if (modalContainer) {
165
+ modalContainer.addEventListener('click', handleCopy);
166
+ }
167
+ }, 0);
168
+
169
+ themodal.onYes(() => {
170
+ // Clean up the event listener synchronously — the DOM may be torn down
171
+ // before our deferred resolve fires otherwise.
172
+ const modalContainer = document.querySelector('.micromodal-parent');
173
+ if (modalContainer) {
174
+ modalContainer.removeEventListener('click', handleCopy);
175
+ }
176
+ setTimeout(resolve, 0);
177
+ return true;
178
+ });
179
+
180
+ themodal.onNo = () => {
181
+ const modalContainer = document.querySelector('.micromodal-parent');
182
+ if (modalContainer) {
183
+ modalContainer.removeEventListener('click', handleCopy);
184
+ }
185
+ setTimeout(resolve, 0);
186
+ };
187
+ });
188
+
189
+ themodal.open();
190
+
191
+ return promise;
192
+ }
193
+
194
+ // Auto-initialize - cleanup any leftover modal elements
195
+ export function init() {
196
+ onDomReady(() => {
197
+ const micromodalParentElem = document.querySelector(".micromodal-parent");
198
+ if (micromodalParentElem) {
199
+ micromodalParentElem.remove();
200
+ document.body.style.overflow = "";
201
+ }
202
+ });
203
+ }
204
+
205
+ // Auto-init when module is imported
206
+ init();
@@ -0,0 +1,38 @@
1
+ import toast, { toastPersistent } from "./toast.js";
2
+ import themodal from "./modal.js";
3
+ import { ask, consent, tell, snippet } from "./dialogs.js";
4
+
5
+ // Attach the public surface explicitly (§2.3). We do NOT rely on toast.js /
6
+ // dialogs.js evaluation side effects — their window auto-exports are stripped.
7
+ const clay = (window.clay = window.clay || {});
8
+ clay.toast = toast;
9
+ clay.toastPersistent = toastPersistent;
10
+ clay.ask = ask;
11
+ clay.confirm = consent; // hyperclayjs `consent` → clayjs `clay.confirm`
12
+ clay.tell = tell;
13
+ clay.snippet = snippet;
14
+ clay.modal = themodal;
15
+
16
+ // Toast globals are a carve-out because core's live-sync soft-reads exactly these
17
+ // names. Only set them when absent, honoring "already have a toast library? keep yours".
18
+ if (typeof window.toast === "undefined") window.toast = toast;
19
+ if (typeof window.toastPersistent === "undefined") window.toastPersistent = toastPersistent;
20
+
21
+ // vendor-compat mirror: sap reads window.hyperclay.consent (the legacy name for
22
+ // clay.confirm). Never CREATE window.hyperclay here: sap's mutation bridge treats
23
+ // any truthy window.hyperclay as "a hub is coming" and waits forever for it. Core
24
+ // creates the object (with Mutation) and fires clay:mutation-ready; attach then.
25
+ function mirrorConsent() {
26
+ if (window.hyperclay && !window.hyperclay.consent) window.hyperclay.consent = consent;
27
+ }
28
+ if (window.hyperclay) mirrorConsent();
29
+ else document.addEventListener("clay:mutation-ready", mirrorConsent, { once: true });
30
+
31
+ // Automatic save feedback. clay:save-saving stays deliberately silent (a toast for
32
+ // a sub-second transient is noise). Keep your own toast lib? The events are public.
33
+ document.addEventListener("clay:save-saved", (e) =>
34
+ toast(e.detail?.msg || "Saved", e.detail?.msgType === "warning" ? "warning" : "success"));
35
+ document.addEventListener("clay:save-error", () => toastPersistent("Couldn't save", "error"));
36
+ document.addEventListener("clay:save-offline", () => toastPersistent("Offline, not saved", "warning"));
37
+
38
+ export { toast, toastPersistent, ask, consent, tell, snippet, themodal };