@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.
- package/NOTICE +19 -0
- package/README.md +2 -0
- package/_headers +2 -0
- package/all.js +24 -0
- package/clay-data.js +16 -0
- package/clay-dom.js +18 -0
- package/clay-events.js +28 -0
- package/clay-options.js +27 -0
- package/clay-ui.js +29 -0
- package/clay-utils.js +18 -0
- package/clay.js +17 -5
- package/package.json +25 -2
- package/sap.js +16 -0
- package/src/core/save.js +5 -4
- package/src/core/snapshot.js +12 -9
- package/src/dom/all.js +440 -0
- package/src/dom/dom-helpers.js +218 -0
- package/src/dom/form-data.js +62 -0
- package/src/dom/insert-style-tag.js +109 -0
- package/src/dom/nearest.js +97 -0
- package/src/dom/pipe.js +1 -0
- package/src/events/index.js +22 -0
- package/src/events/onclickaway.js +27 -0
- package/src/events/onclickchildren.js +12 -0
- package/src/events/onclone.js +54 -0
- package/src/events/onmutation.js +54 -0
- package/src/events/onpagemutation.js +37 -0
- package/src/events/onrender.js +32 -0
- package/src/lib/mutation.js +26 -9
- package/src/lib/on-load.js +9 -0
- package/src/loader-logic.js +2 -1
- package/src/loader.js +2 -0
- package/src/options/options.js +253 -0
- package/src/plugins/demo.js +124 -0
- package/src/sync/live-sync.js +61 -19
- package/src/sync/merge-tags.js +36 -0
- package/src/ui/dialogs.js +206 -0
- package/src/ui/index.js +38 -0
- package/src/ui/modal.js +884 -0
- package/src/ui/toast.js +434 -0
- package/src/utils/copy-to-clipboard.js +27 -0
- package/src/utils/debounce.js +23 -0
- package/src/utils/index.js +10 -0
- package/src/utils/slugify.js +20 -0
- 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();
|
package/src/ui/index.js
ADDED
|
@@ -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 };
|