phoenix_duskmoon 9.8.0 → 9.9.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/README.md
CHANGED
|
@@ -141,7 +141,7 @@ Components that require hooks:
|
|
|
141
141
|
## Available Components
|
|
142
142
|
|
|
143
143
|
- **Action**: buttons, dropdowns, links, menus, toggles
|
|
144
|
-
- **Data Display**: accordion, avatar, badge, card, chip, collapse, flash, git repository, list, markdown, pagination, popover, progress, skeleton, stat, table, timeline, tooltip
|
|
144
|
+
- **Data Display**: accordion, avatar, badge, card, chip, collapse, flash, git repository, list, markdown, markdown body, pagination, popover, progress, skeleton, stat, table, timeline, tooltip
|
|
145
145
|
- **Data Entry**: autocomplete, cascader, checkbox, compact input, file upload, form, input, multi-select, OTP input, PIN input, radio, rating, segment control, select, slider, switch, textarea, time input, tree select
|
|
146
146
|
- **Feedback**: dialog, loading, snackbar, toast
|
|
147
147
|
- **Navigation**: actionbar, appbar, bottom nav, breadcrumb, left menu, navbar, nested menu, page footer, page header, stepper, steps, tabs
|
|
@@ -11,6 +11,11 @@ const DM_EVENTS = [
|
|
|
11
11
|
"dm-submit", "dm-select", "dm-close", "dm-open", "dm-toggle",
|
|
12
12
|
];
|
|
13
13
|
|
|
14
|
+
const COPY_SELECTOR = "[data-copy-value]";
|
|
15
|
+
const COPY_FEEDBACK_DURATION = 2000;
|
|
16
|
+
const COPY_LISTENER = Symbol.for("phoenix-duskmoon.copy-listener");
|
|
17
|
+
const copyFeedback = new WeakMap();
|
|
18
|
+
|
|
14
19
|
/**
|
|
15
20
|
* WebComponentHook - Universal LiveView ↔ Custom Element bridge
|
|
16
21
|
*
|
|
@@ -254,6 +259,108 @@ export const FormElementHook = {
|
|
|
254
259
|
},
|
|
255
260
|
};
|
|
256
261
|
|
|
262
|
+
/** Copy text with the Clipboard API, falling back only when it is unavailable. */
|
|
263
|
+
export async function copyTextToClipboard(value, environment = globalThis) {
|
|
264
|
+
const clipboard = environment.navigator?.clipboard;
|
|
265
|
+
|
|
266
|
+
if (typeof clipboard?.writeText === "function") {
|
|
267
|
+
await clipboard.writeText(value);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const document = environment.document;
|
|
272
|
+
|
|
273
|
+
if (!document?.body || typeof document.execCommand !== "function") {
|
|
274
|
+
throw new Error("Clipboard API is unavailable");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const textArea = document.createElement("textarea");
|
|
278
|
+
const activeElement = document.activeElement;
|
|
279
|
+
textArea.value = value;
|
|
280
|
+
textArea.setAttribute("readonly", "");
|
|
281
|
+
textArea.style.position = "fixed";
|
|
282
|
+
textArea.style.opacity = "0";
|
|
283
|
+
document.body.appendChild(textArea);
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
textArea.focus();
|
|
287
|
+
textArea.select();
|
|
288
|
+
|
|
289
|
+
if (!document.execCommand("copy")) {
|
|
290
|
+
throw new Error("Clipboard copy command failed");
|
|
291
|
+
}
|
|
292
|
+
} finally {
|
|
293
|
+
document.body.removeChild(textArea);
|
|
294
|
+
activeElement?.focus?.();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function copyStatus(button) {
|
|
299
|
+
const sibling = button.nextElementSibling;
|
|
300
|
+
|
|
301
|
+
if (sibling?.matches?.("[data-copy-status]")) {
|
|
302
|
+
return sibling;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return button.parentElement?.querySelector?.("[data-copy-status]") ?? null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function showCopyFeedback(button, message, state, options) {
|
|
309
|
+
const label = button.querySelector?.("[data-copy-label]");
|
|
310
|
+
const status = copyStatus(button);
|
|
311
|
+
const previous = copyFeedback.get(button);
|
|
312
|
+
const cancelReset = options.cancelReset ?? globalThis.clearTimeout;
|
|
313
|
+
const scheduleReset = options.scheduleReset ?? globalThis.setTimeout;
|
|
314
|
+
|
|
315
|
+
if (previous?.timer !== undefined) {
|
|
316
|
+
cancelReset(previous.timer);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const originalLabel = previous?.originalLabel ?? label?.textContent ?? "Copy";
|
|
320
|
+
|
|
321
|
+
if (label) label.textContent = message;
|
|
322
|
+
if (status) status.textContent = message;
|
|
323
|
+
button.dataset.copyState = state;
|
|
324
|
+
|
|
325
|
+
const timer = scheduleReset(() => {
|
|
326
|
+
if (label) label.textContent = originalLabel;
|
|
327
|
+
if (status) status.textContent = "";
|
|
328
|
+
delete button.dataset.copyState;
|
|
329
|
+
copyFeedback.delete(button);
|
|
330
|
+
}, COPY_FEEDBACK_DURATION);
|
|
331
|
+
|
|
332
|
+
copyFeedback.set(button, { originalLabel, timer });
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Handle clicks from any descendant of a generated copy control. */
|
|
336
|
+
export async function handleClipboardClick(event, options = {}) {
|
|
337
|
+
const button = event.target?.closest?.(COPY_SELECTOR);
|
|
338
|
+
|
|
339
|
+
if (!button || button.disabled || button.getAttribute?.("aria-disabled") === "true") {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const copy = options.copy ?? copyTextToClipboard;
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
await copy(button.dataset.copyValue);
|
|
347
|
+
showCopyFeedback(button, button.dataset.copySuccess ?? "Copied", "success", options);
|
|
348
|
+
} catch (_error) {
|
|
349
|
+
showCopyFeedback(button, button.dataset.copyFailure ?? "Copy failed", "error", options);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Install one delegated listener so LiveView-patched copy controls also work. */
|
|
354
|
+
export function installClipboardBehavior(root = globalThis.document, options = {}) {
|
|
355
|
+
if (!root?.addEventListener || root[COPY_LISTENER]) return;
|
|
356
|
+
|
|
357
|
+
const listener = (event) => handleClipboardClick(event, options);
|
|
358
|
+
root.addEventListener("click", listener);
|
|
359
|
+
root[COPY_LISTENER] = listener;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
installClipboardBehavior();
|
|
363
|
+
|
|
257
364
|
// Export to window for non-module usage
|
|
258
365
|
if (typeof window !== "undefined") {
|
|
259
366
|
window.__WebComponentHook__ = WebComponentHook;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phoenix_duskmoon",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.9.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./priv/static/assets/js/phoenix_duskmoon.js",
|
|
@@ -35,17 +35,17 @@
|
|
|
35
35
|
"registry": "https://registry.npmjs.org/"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@duskmoon-dev/art-elements": "1.5.
|
|
38
|
+
"@duskmoon-dev/art-elements": "1.5.6",
|
|
39
39
|
"@duskmoon-dev/core": "1.17.0",
|
|
40
40
|
"@duskmoon-dev/css-art": "1.17.0",
|
|
41
|
-
"@duskmoon-dev/elements": "1.5.
|
|
41
|
+
"@duskmoon-dev/elements": "1.5.6"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@mdi/svg": "7.4.47",
|
|
45
45
|
"bootstrap-icons": "1.13.1",
|
|
46
|
-
"postcss": "8.5.
|
|
46
|
+
"postcss": "8.5.19"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"tailwindcss": "4.3.
|
|
49
|
+
"tailwindcss": "4.3.3"
|
|
50
50
|
}
|
|
51
51
|
}
|