phoenix_duskmoon 9.16.15 → 10.0.0
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/CHANGELOG.md +34 -1
- package/README.md +18 -3
- package/assets/js/phoenix_duskmoon.js +0 -105
- package/assets/js/react_chat/chat.js +16 -1
- package/package.json +1 -1
- package/priv/static/assets/css/app-49d183cdbcc6cccb6e9eadbec4fff01c.css +2 -0
- package/priv/static/assets/css/app-49d183cdbcc6cccb6e9eadbec4fff01c.css.gz +0 -0
- package/priv/static/assets/css/app.css +1 -1
- package/priv/static/assets/css/app.css.gz +0 -0
- package/priv/static/assets/css/manifest-771fd8eaae673c76a85c29e7b1106af0.json +1 -0
- package/priv/static/assets/css/manifest-771fd8eaae673c76a85c29e7b1106af0.json.gz +0 -0
- package/priv/static/assets/css/manifest.json.gz +0 -0
- package/priv/static/assets/js/manifest-b1cb13d0ed943a03c54da6dd727c4ea5.json +1 -0
- package/priv/static/assets/js/manifest-b1cb13d0ed943a03c54da6dd727c4ea5.json.gz +0 -0
- package/priv/static/assets/js/manifest.json +1 -1
- package/priv/static/assets/js/manifest.json.gz +0 -0
- package/priv/static/assets/js/phoenix_duskmoon-1d3f4951080405842d72f20baa38d4e9.js +1 -0
- package/priv/static/assets/js/phoenix_duskmoon-1d3f4951080405842d72f20baa38d4e9.js.gz +0 -0
- package/priv/static/assets/js/phoenix_duskmoon.js +1 -1
- package/priv/static/assets/js/phoenix_duskmoon.js.gz +0 -0
- package/priv/static/cache_manifest.json +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,33 @@
|
|
|
1
|
-
#
|
|
1
|
+
# [10.0.0](https://github.com/duskmoon-dev/phoenix-duskmoon-ui/compare/v9.16.16...v10.0.0) (2026-10-14)
|
|
2
|
+
|
|
3
|
+
### Breaking changes
|
|
4
|
+
|
|
5
|
+
- Move the eight build-toolchain applications, native sources, package staging
|
|
6
|
+
and toolchain release automation to the independent `duskmoon-bundler`
|
|
7
|
+
repository. Repository-local toolchain paths must migrate to the published
|
|
8
|
+
Hex packages or the explicit candidate integration helper. See
|
|
9
|
+
[the extraction guide](https://github.com/duskmoon-dev/phoenix-duskmoon-ui/blob/main/docs/bundler-extraction.md).
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Keep only the UI and Storybook applications in this umbrella. Ordinary UI
|
|
14
|
+
builds consume the verified, published Bundler 9.16.16 cohort with strict
|
|
15
|
+
precompiled native loading; UI JS, CSS and icons continue to build normally.
|
|
16
|
+
- Release the UI Hex/npm packages together as version 10.0.0, independently of
|
|
17
|
+
the toolchain version, with isolated package-consumer and runtime checks.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Preserve `mix version.sync` from both the umbrella root and UI application.
|
|
22
|
+
|
|
23
|
+
# Earlier UI changes
|
|
24
|
+
|
|
25
|
+
### Breaking changes
|
|
26
|
+
|
|
27
|
+
- Remove the six `dm_git_*` repository components, their automatic imports,
|
|
28
|
+
Storybook demos and delegated Git clipboard runtime. Git presentation is now
|
|
29
|
+
application-owned. There is no replacement published package; see
|
|
30
|
+
[the migration guide](guides/migrating-git-components.md) before upgrading.
|
|
2
31
|
|
|
3
32
|
### Added
|
|
4
33
|
|
|
@@ -36,6 +65,10 @@
|
|
|
36
65
|
|
|
37
66
|
### Fixed
|
|
38
67
|
|
|
68
|
+
- Hide the simple appbar's mobile toggle and navigation when no menu entries exist,
|
|
69
|
+
preserving its logo and user-profile slots.
|
|
70
|
+
- Generate React Chat submission IDs on HTTP LAN origins with a cryptographic
|
|
71
|
+
UUIDv4 fallback when the native `crypto.randomUUID()` API is unavailable.
|
|
39
72
|
- Forward React form field attributes to their interactive controls and preserve
|
|
40
73
|
application-disabled fields when the LiveView connection recovers.
|
|
41
74
|
- Preserve selected-value arrays for native checkbox groups named with `[]`,
|
package/README.md
CHANGED
|
@@ -8,19 +8,25 @@
|
|
|
8
8
|
|
|
9
9
|
Duskmoon UI component library for Phoenix LiveView applications.
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
**v10**: Keeps the `@duskmoon-dev/core` design system and DuskMoon custom elements,
|
|
12
|
+
with the build toolchain maintained and released independently in
|
|
13
|
+
[duskmoon-bundler](https://github.com/duskmoon-dev/duskmoon-bundler).
|
|
12
14
|
|
|
13
15
|
Requires Elixir `>= 1.18` and `tailwindcss >= 4.0`.
|
|
14
16
|
|
|
15
17
|
See the [docs](https://hexdocs.pm/phoenix_duskmoon/) for more information.
|
|
16
18
|
|
|
19
|
+
Applications using the removed `dm_git_*` helpers or the Git clipboard runtime
|
|
20
|
+
must migrate to application-owned components before upgrading; see the
|
|
21
|
+
[Git component migration guide](https://github.com/duskmoon-dev/phoenix-duskmoon-ui/blob/main/apps/phoenix_duskmoon/guides/migrating-git-components.md).
|
|
22
|
+
|
|
17
23
|
|
|
18
24
|
## Install
|
|
19
25
|
|
|
20
26
|
Add to `mix.exs`:
|
|
21
27
|
|
|
22
28
|
```elixir
|
|
23
|
-
{:phoenix_duskmoon, "~>
|
|
29
|
+
{:phoenix_duskmoon, "~> 10.0"},
|
|
24
30
|
```
|
|
25
31
|
|
|
26
32
|
Install frontend packages:
|
|
@@ -143,7 +149,7 @@ Components that require hooks:
|
|
|
143
149
|
## Available Components
|
|
144
150
|
|
|
145
151
|
- **Action**: buttons, dropdowns, floating actions, links, menus, swaps, toggles
|
|
146
|
-
- **Data Display**: accordion, avatar, badge, card, carousel, chat, chat scroll, chip, collapse, countdown, datetime, diff, flash,
|
|
152
|
+
- **Data Display**: accordion, avatar, badge, card, carousel, chat, chat scroll, chip, collapse, countdown, datetime, diff, flash, list, markdown, markdown body, pagination, popover, progress, radial progress, skeleton, stat, table, timeline, tooltip
|
|
147
153
|
- **Data Entry**: autocomplete, cascader, checkbox, compact input, file upload, filter group, form, input, multi-select, OTP input, PIN input, radio, rating, segment control, select, slider, switch, textarea, time input, tree select
|
|
148
154
|
- **Feedback**: dialog, loading, snackbar, toast
|
|
149
155
|
- **Navigation**: actionbar, appbar, bottom nav, breadcrumb, left menu, megamenu, navbar, page footer, page header, stepper, tabs
|
|
@@ -180,6 +186,15 @@ and events. `dm_loading_spinner` now follows Core's reduced-motion behavior, and
|
|
|
180
186
|
available in Storybook. Browser `File` objects from chat events require the
|
|
181
187
|
application's upload mechanism; they cannot be sent as LiveView JSON payloads.
|
|
182
188
|
|
|
189
|
+
## Repository development
|
|
190
|
+
|
|
191
|
+
This checkout contains the UI and Storybook. It consumes the published toolchain
|
|
192
|
+
cohort pinned in `BUNDLER_VERSION` and `mix.lock`; ordinary UI builds use verified
|
|
193
|
+
precompiled Duskmoon native artifacts. The toolchain source and its own releases
|
|
194
|
+
live in [duskmoon-bundler](https://github.com/duskmoon-dev/duskmoon-bundler).
|
|
195
|
+
See the [migration and development guide](docs/bundler-extraction.md) for setup,
|
|
196
|
+
candidate integration and independent release order.
|
|
197
|
+
|
|
183
198
|
## Live Storybook
|
|
184
199
|
|
|
185
200
|
[Live Storybook](https://duskmoon-storybook.gsmlg.dev)
|
|
@@ -11,13 +11,9 @@ 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
14
|
const DIALOG_SELECTOR = 'dialog[data-dm-confirm-dialog="true"]';
|
|
18
15
|
const DIALOG_CONFIRM_SELECTOR = '[data-dm-confirm-action="true"]';
|
|
19
16
|
const DIALOG_BEHAVIOR_LISTENERS = Symbol.for("phoenix-duskmoon.dialog-behavior-listeners");
|
|
20
|
-
const copyFeedback = new WeakMap();
|
|
21
17
|
|
|
22
18
|
/**
|
|
23
19
|
* WebComponentHook - Universal LiveView ↔ Custom Element bridge
|
|
@@ -262,106 +258,6 @@ export const FormElementHook = {
|
|
|
262
258
|
},
|
|
263
259
|
};
|
|
264
260
|
|
|
265
|
-
/** Copy text with the Clipboard API, falling back only when it is unavailable. */
|
|
266
|
-
export async function copyTextToClipboard(value, environment = globalThis) {
|
|
267
|
-
const clipboard = environment.navigator?.clipboard;
|
|
268
|
-
|
|
269
|
-
if (typeof clipboard?.writeText === "function") {
|
|
270
|
-
await clipboard.writeText(value);
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const document = environment.document;
|
|
275
|
-
|
|
276
|
-
if (!document?.body || typeof document.execCommand !== "function") {
|
|
277
|
-
throw new Error("Clipboard API is unavailable");
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const textArea = document.createElement("textarea");
|
|
281
|
-
const activeElement = document.activeElement;
|
|
282
|
-
textArea.value = value;
|
|
283
|
-
textArea.setAttribute("readonly", "");
|
|
284
|
-
textArea.style.position = "fixed";
|
|
285
|
-
textArea.style.opacity = "0";
|
|
286
|
-
document.body.appendChild(textArea);
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
textArea.focus();
|
|
290
|
-
textArea.select();
|
|
291
|
-
|
|
292
|
-
if (!document.execCommand("copy")) {
|
|
293
|
-
throw new Error("Clipboard copy command failed");
|
|
294
|
-
}
|
|
295
|
-
} finally {
|
|
296
|
-
document.body.removeChild(textArea);
|
|
297
|
-
activeElement?.focus?.();
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function copyStatus(button) {
|
|
302
|
-
const sibling = button.nextElementSibling;
|
|
303
|
-
|
|
304
|
-
if (sibling?.matches?.("[data-copy-status]")) {
|
|
305
|
-
return sibling;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return button.parentElement?.querySelector?.("[data-copy-status]") ?? null;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function showCopyFeedback(button, message, state, options) {
|
|
312
|
-
const label = button.querySelector?.("[data-copy-label]");
|
|
313
|
-
const status = copyStatus(button);
|
|
314
|
-
const previous = copyFeedback.get(button);
|
|
315
|
-
const cancelReset = options.cancelReset ?? globalThis.clearTimeout;
|
|
316
|
-
const scheduleReset = options.scheduleReset ?? globalThis.setTimeout;
|
|
317
|
-
|
|
318
|
-
if (previous?.timer !== undefined) {
|
|
319
|
-
cancelReset(previous.timer);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const originalLabel = previous?.originalLabel ?? label?.textContent ?? "Copy";
|
|
323
|
-
|
|
324
|
-
if (label) label.textContent = message;
|
|
325
|
-
if (status) status.textContent = message;
|
|
326
|
-
button.dataset.copyState = state;
|
|
327
|
-
|
|
328
|
-
const timer = scheduleReset(() => {
|
|
329
|
-
if (label) label.textContent = originalLabel;
|
|
330
|
-
if (status) status.textContent = "";
|
|
331
|
-
delete button.dataset.copyState;
|
|
332
|
-
copyFeedback.delete(button);
|
|
333
|
-
}, COPY_FEEDBACK_DURATION);
|
|
334
|
-
|
|
335
|
-
copyFeedback.set(button, { originalLabel, timer });
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/** Handle clicks from any descendant of a generated copy control. */
|
|
339
|
-
export async function handleClipboardClick(event, options = {}) {
|
|
340
|
-
const button = event.target?.closest?.(COPY_SELECTOR);
|
|
341
|
-
|
|
342
|
-
if (!button || button.disabled || button.getAttribute?.("aria-disabled") === "true") {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const copy = options.copy ?? copyTextToClipboard;
|
|
347
|
-
|
|
348
|
-
try {
|
|
349
|
-
await copy(button.dataset.copyValue);
|
|
350
|
-
showCopyFeedback(button, button.dataset.copySuccess ?? "Copied", "success", options);
|
|
351
|
-
} catch (_error) {
|
|
352
|
-
showCopyFeedback(button, button.dataset.copyFailure ?? "Copy failed", "error", options);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/** Install one delegated listener so LiveView-patched copy controls also work. */
|
|
357
|
-
export function installClipboardBehavior(root = globalThis.document, options = {}) {
|
|
358
|
-
if (!root?.addEventListener || root[COPY_LISTENER]) return;
|
|
359
|
-
|
|
360
|
-
const listener = (event) => handleClipboardClick(event, options);
|
|
361
|
-
root.addEventListener("click", listener);
|
|
362
|
-
root[COPY_LISTENER] = listener;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
261
|
/** Close the current confirm dialog before submit/reset/custom command default actions. */
|
|
366
262
|
export function handleDialogConfirmClick(event) {
|
|
367
263
|
const path = event.composedPath?.() ?? [];
|
|
@@ -379,7 +275,6 @@ export function installDialogBehavior(root = globalThis.document) {
|
|
|
379
275
|
root[DIALOG_BEHAVIOR_LISTENERS] = handleDialogConfirmClick;
|
|
380
276
|
}
|
|
381
277
|
|
|
382
|
-
installClipboardBehavior();
|
|
383
278
|
installDialogBehavior();
|
|
384
279
|
|
|
385
280
|
// Export to window for non-module usage
|
|
@@ -131,6 +131,21 @@ const Message = memo(function Message(props) {
|
|
|
131
131
|
);
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
+
function requestId() {
|
|
135
|
+
if (typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
136
|
+
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
137
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
138
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
139
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
|
|
140
|
+
return [
|
|
141
|
+
hex.slice(0, 4).join(""),
|
|
142
|
+
hex.slice(4, 6).join(""),
|
|
143
|
+
hex.slice(6, 8).join(""),
|
|
144
|
+
hex.slice(8, 10).join(""),
|
|
145
|
+
hex.slice(10).join(""),
|
|
146
|
+
].join("-");
|
|
147
|
+
}
|
|
148
|
+
|
|
134
149
|
// A composer owns its draft/attachments. This helper only owns admission and UI requests.
|
|
135
150
|
export function useChatSubmission({ bridge, canSubmit }) {
|
|
136
151
|
const [busy, setBusy] = useState(false);
|
|
@@ -151,7 +166,7 @@ export function useChatSubmission({ bridge, canSubmit }) {
|
|
|
151
166
|
setNotice("");
|
|
152
167
|
try {
|
|
153
168
|
return await bridge.send("send", {
|
|
154
|
-
request_id:
|
|
169
|
+
request_id: requestId(),
|
|
155
170
|
...payload,
|
|
156
171
|
});
|
|
157
172
|
} catch (error) {
|