@panphora/clayjs 1.5.3 → 1.6.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/README.md +5 -3
- package/THIRD-PARTY-NOTICES.md +6 -0
- package/dist/clay.standalone.js +14965 -10714
- package/package.json +9 -4
- package/src/core/conflict-download.js +91 -0
- package/src/core/conflict-notice.js +720 -0
- package/src/core/conflict-presentation.js +301 -0
- package/src/core/save-conflict-notice.js +2 -192
- package/src/core/save-feedback.js +6 -0
- package/src/core/save.js +53 -1
- package/src/core/snapshot.js +21 -0
- package/src/core/stale-host-notice.js +21 -24
- package/src/core/unsaved-warning.js +35 -10
- package/src/lib/content-dom.js +4 -2
- package/src/lib/dirty-gate.js +9 -0
- package/src/lib/unsaved-state.js +55 -0
- package/src/loader-logic.js +26 -15
- package/src/loader.js +4 -3
- package/src/plugins/ai-edit.js +489 -179
- package/src/plugins/cms.js +12 -0
- package/src/plugins/indicator.js +35 -16
- package/src/plugins/quickcrop.js +103 -0
- package/src/plugins/richclay.js +21 -0
- package/src/plugins/source.js +13 -6
- package/src/plugins/wire.js +4 -0
- package/src/sync/conflict-revert.js +1138 -0
- package/src/sync/conflicts.js +159 -0
- package/src/sync/live-sync.js +130 -109
- package/src/sync/presence.js +42 -36
- package/src/sync/section-notice.js +53 -28
- package/src/ui/bevel-controls.js +277 -0
- package/src/ui/bevel-dialog.js +112 -0
- package/src/ui/bevel.js +247 -0
- package/src/ui/dialogs.js +63 -86
- package/src/ui/index.js +22 -11
- package/src/ui/modal.js +80 -387
- package/src/ui/skins/cms-toggle.js +15 -0
- package/src/ui/skins/cms.js +4 -0
- package/src/ui/skins/quickcrop.js +4 -0
- package/src/ui/skins/richclay.js +4 -0
- package/src/ui/toast.js +116 -401
- package/src/ui/vendor-skin.js +65 -0
- package/src/vendor/hyper-morph.vendor.js +4 -4
- package/src/vendor/quickcrop.vendor.js +14 -8
- package/src/vendor/richclay.vendor.js +22 -41
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// GENERATED from src/vendor/richclay.vendor.js and bevel/integrations/richclay.css by scripts/build-vendor-skins.mjs. Run `npm run build:skins`.
|
|
2
|
+
export const SOURCE_SHA256 = "91b0339a9c534d4aa94d5b7179e5e9174fa61383f57fa2406cdac56b3719581c";
|
|
3
|
+
export const ROOTS = [".richclay-toolbar",".richclay-float",".richclay-dialog"];
|
|
4
|
+
export const CSS = "@layer clay-skin{\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog),:is(.richclay-toolbar, .richclay-float, .richclay-dialog) *{--richclay-surface:light-dark(#FFFCF6, #11131E) !important;--richclay-surface-alt:light-dark(#F4ECDF, #1A1D2C) !important;--richclay-text:light-dark(#2B241B, #ECEAF2) !important;--richclay-muted:light-dark(#6B5942, #8A90AB) !important;--richclay-border:light-dark(#D8C8AF, #353B52) !important;--richclay-accent:light-dark(#8F6310, #D9A445) !important;--richclay-accent-ink:light-dark(#FDF8F0, #0B0C13) !important;--richclay-focus:light-dark(#8F6310, #D9A445) !important;--richclay-danger:light-dark(#A83A2C, #FF5566) !important;--richclay-radius:0px !important;--richclay-control-size:32px !important;--richclay-shadow:0 1px 2px rgba(0,0,0,.12),0 10px 28px -12px rgba(0,0,0,.35) !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog){font-family:system-ui,-apple-system,\"Segoe UI\",sans-serif !important}\n:where(.richclay-float, .richclay-dialog):is(.richclay-toolbar, .richclay-float, .richclay-dialog){color:light-dark(#2B241B, #ECEAF2) !important;background:light-dark(#FFFCF6, #11131E) !important;border:1px solid light-dark(#D8C8AF, #353B52) !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-toolbar{gap:3px !important;margin:0 !important;padding:8px !important;border:1px solid light-dark(#D8C8AF, #353B52) !important;border-radius:0 !important;background:light-dark(#F4ECDF, #1A1D2C) !important;color:light-dark(#2B241B, #ECEAF2) !important}\n.richclay-toolbar:is(.richclay-toolbar, .richclay-float, .richclay-dialog){gap:3px !important;padding:8px !important;border:1px solid light-dark(#D8C8AF, #353B52) !important;border-radius:0 !important;background:light-dark(#F4ECDF, #1A1D2C) !important;color:light-dark(#2B241B, #ECEAF2) !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-button{min-width:32px !important;height:32px !important;padding:0 7px !important;gap:6px !important;border:2px solid !important;border-color:light-dark(#FFF9ED, #3C4260) light-dark(#D6C3A5, #1B2033) light-dark(#D6C3A5, #1B2033) light-dark(#FFF9ED, #3C4260) !important;border-radius:0 !important;background:light-dark(#F1E7D4, #22273E) !important;font:14px system-ui,-apple-system,\"Segoe UI\",sans-serif !important;cursor:pointer !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-button:is([aria-pressed=\"true\"], [aria-expanded=\"true\"]){background:light-dark(#E3D0B3, #28304D) !important;border-color:light-dark(#B89F7B, #1F2339) light-dark(#F0E1C9, #4D587A) light-dark(#F0E1C9, #4D587A) light-dark(#B89F7B, #1F2339) !important;box-shadow:none !important;color:light-dark(#2B241B, #ECEAF2) !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-button[aria-pressed=\"true\"] > svg{translate:1px 1px !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-button:disabled{opacity:.45 !important;cursor:not-allowed !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-button:hover:not([aria-pressed=\"true\"], [aria-expanded=\"true\"], :disabled){border-color:light-dark(#FFF9ED, #3C4260) light-dark(#D6C3A5, #1B2033) light-dark(#D6C3A5, #1B2033) light-dark(#FFF9ED, #3C4260) !important;background:color-mix(in srgb, light-dark(#F1E7D4, #22273E), light-dark(#FFF9ED, #3C4260) 45%) !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-separator{align-self:center !important;height:22px !important;margin:0 5px !important;background:light-dark(#D8C8AF, #353B52) !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-menu{inset-block-start:38px !important;min-width:210px !important;padding:5px !important;border-radius:0 !important;box-shadow:0 1px 2px rgba(0,0,0,.12),0 10px 28px -12px rgba(0,0,0,.35) !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-menu-item{padding:8px 10px !important;border:0 !important;border-radius:0 !important;font:14px system-ui,-apple-system,\"Segoe UI\",sans-serif !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-menu-item[aria-checked=\"true\"]{background:light-dark(#F4ECDF, #1A1D2C) !important;box-shadow:inset 2px 0 light-dark(#8F6310, #D9A445) !important}\n.richclay-dialog:is(.richclay-toolbar, .richclay-float, .richclay-dialog){width:min(100%, 360px) !important;padding:14px 16px 12px !important;border:1px solid light-dark(#D8C8AF, #353B52) !important;border-radius:0 !important;background:light-dark(#FFFCF6, #11131E) !important;color:light-dark(#2B241B, #ECEAF2) !important;box-shadow:0 1px 2px rgba(0,0,0,.12),0 10px 28px -12px rgba(0,0,0,.35) !important}\n.richclay-dialog:is(.richclay-toolbar, .richclay-float, .richclay-dialog)::before{content:\"\" !important;position:absolute !important;left:calc(var(--richclay-dialog-arrow, 28px) - 6px) !important;top:-7px !important;width:12px !important;height:12px !important;background:light-dark(#FFFCF6, #11131E) !important;border-top:1px solid light-dark(#D8C8AF, #353B52) !important;border-left:1px solid light-dark(#D8C8AF, #353B52) !important;transform:rotate(45deg) !important}\n.richclay-dialog:is(.richclay-toolbar, .richclay-float, .richclay-dialog)[data-richclay-side=\"above\"]::before{top:auto !important;bottom:-7px !important;border:0 !important;border-right:1px solid light-dark(#D8C8AF, #353B52) !important;border-bottom:1px solid light-dark(#D8C8AF, #353B52) !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-dialog-title{margin-bottom:2px !important;font:500 10px ui-monospace,SFMono-Regular,Menlo,Consolas,monospace !important;text-transform:uppercase !important;letter-spacing:.12em !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-field{gap:5px !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-field > span{color:light-dark(#4A4033, #C9CDE0) !important;font-size:12px !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-input{min-height:30px !important;padding:4px 8px !important;border:2px solid !important;border-color:light-dark(#B89F7B, #1F2339) light-dark(#F0E1C9, #4D587A) light-dark(#F0E1C9, #4D587A) light-dark(#B89F7B, #1F2339) !important;border-radius:0 !important;background:light-dark(#F4ECDF, #1A1D2C) !important;color:light-dark(#2B241B, #ECEAF2) !important;font:13px system-ui,-apple-system,\"Segoe UI\",sans-serif !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-dialog-actions{margin-top:4px !important;padding-top:12px !important;border-top:1px solid light-dark(#E8DCC6, #262B42) !important;flex-wrap:wrap !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) :is(.richclay-primary, .richclay-secondary){min-height:30px !important;padding:4px 10px !important;border:2px solid !important;border-color:light-dark(#FFF9ED, #3C4260) light-dark(#D6C3A5, #1B2033) light-dark(#D6C3A5, #1B2033) light-dark(#FFF9ED, #3C4260) !important;border-radius:0 !important;background:light-dark(#F1E7D4, #22273E) !important;color:light-dark(#2B241B, #ECEAF2) !important;font:13px system-ui,-apple-system,\"Segoe UI\",sans-serif !important}\n:is(.richclay-toolbar, .richclay-float, .richclay-dialog) .richclay-primary{background:light-dark(#2B241B, #ECEAF2) !important;color:light-dark(#FDF8F0, #0B0C13) !important;border-color:color-mix(in srgb, light-dark(#2B241B, #ECEAF2), white 24%) color-mix(in srgb, light-dark(#2B241B, #ECEAF2), black 32%) color-mix(in srgb, light-dark(#2B241B, #ECEAF2), black 32%) color-mix(in srgb, light-dark(#2B241B, #ECEAF2), white 24%) !important}\n.richclay-float:is(.richclay-toolbar, .richclay-float, .richclay-dialog){border-radius:0 !important;box-shadow:0 1px 2px rgba(0,0,0,.12),0 10px 28px -12px rgba(0,0,0,.35) !important}\n.richclay-float:is(.richclay-toolbar, .richclay-float, .richclay-dialog) > .richclay-toolbar{padding:5px !important}\n.richclay-toolbar{align-items:center !important;background:var(--richclay-surface-alt) !important;border:1px solid var(--richclay-border) !important;border-radius:var(--richclay-radius) !important;color:var(--richclay-text) !important;display:flex !important;flex-wrap:wrap !important;gap:2px !important;margin-block:0 6px !important;padding:4px !important}\n.richclay-toolbar:has(+ .richclay-editor){margin-block-end:0 !important;border-bottom-left-radius:0 !important;border-bottom-right-radius:0 !important}\n.richclay-button{align-items:center !important;appearance:none !important;background:transparent !important;border:1px solid transparent !important;border-radius:5px !important;color:currentColor !important;display:inline-flex !important;height:var(--richclay-control-size) !important;justify-content:center !important;margin:0 !important;min-width:var(--richclay-control-size) !important;padding:0 !important;position:relative !important}\n.richclay-button:hover,.richclay-button[aria-expanded=\"true\"],.richclay-menu-item:hover{background:var(--richclay-surface) !important;border-color:var(--richclay-border) !important}\n.richclay-button.is-active,.richclay-button[aria-pressed=\"true\"],.richclay-menu-item.is-active{background:rgb(0 0 0 / 0.13) !important;border-color:rgb(0 0 0 / 0.22) !important;color:var(--richclay-text) !important;box-shadow:inset 0 1px 3px rgb(0 0 0 / 0.26) !important}\n.richclay-button:focus-visible,.richclay-menu-item:focus-visible,.richclay-input:focus-visible,.richclay-primary:focus-visible,.richclay-secondary:focus-visible{outline:3px solid var(--richclay-focus) !important;outline-offset:2px !important}\n.richclay-button:disabled{color:var(--richclay-muted) !important;cursor:not-allowed !important;opacity:0.55 !important}\n.richclay-separator{align-self:stretch !important;background:var(--richclay-border) !important;display:inline-block !important;margin:5px 4px !important;width:1px !important}\n.richclay-menu-wrap{display:inline-flex !important;position:relative !important}\n.richclay-menu{background:var(--richclay-surface) !important;border:1px solid var(--richclay-border) !important;border-radius:var(--richclay-radius) !important;box-shadow:var(--richclay-shadow) !important;color:var(--richclay-text) !important;display:grid !important;gap:2px !important;inset-block-start:calc(100% + 4px) !important;inset-inline-start:0 !important;min-width:160px !important;padding:4px !important;position:absolute !important;z-index:1000 !important}\n.richclay-menu[hidden]{display:none !important}\n.richclay-menu-item{appearance:none !important;background:transparent !important;border:1px solid transparent !important;border-radius:4px !important;color:inherit !important;font:inherit !important;min-height:32px !important;padding:5px 9px !important;text-align:start !important}\n.richclay-float{left:0 !important;position:fixed !important;top:0 !important;will-change:transform !important;z-index:99999 !important}\n.richclay-float .richclay-toolbar{box-shadow:var(--richclay-shadow) !important;margin-block:0 !important}\n.richclay-float-rail .richclay-toolbar{--richclay-control-size:28px !important;flex-direction:column !important;flex-wrap:nowrap !important}\n.richclay-float-rail .richclay-toolbar svg{height:15px !important;width:15px !important}\n.richclay-float-rail .richclay-separator{align-self:stretch !important;height:1px !important;margin:4px 5px !important;width:auto !important}\n.richclay-float-rail .richclay-menu{inset-block-start:0 !important;inset-inline-start:calc(100% + 4px) !important}\n.richclay-dialog{background:var(--richclay-surface) !important;border:1px solid var(--richclay-border) !important;border-radius:var(--richclay-radius) !important;box-shadow:var(--richclay-shadow) !important;color:var(--richclay-text) !important;display:grid !important;gap:10px !important;box-sizing:border-box !important;width:min(420px, calc(100vw - 16px)) !important;z-index:99999 !important;padding:12px !important}\n.richclay-dialog-title{font-weight:650 !important}\n.richclay-field{display:grid !important;gap:4px !important}\n.richclay-field span{color:var(--richclay-muted) !important;font-size:0.875rem !important}\n.richclay-input{background:var(--richclay-surface) !important;border:1px solid var(--richclay-border) !important;border-radius:4px !important;color:var(--richclay-text) !important;font:inherit !important;min-height:36px !important;padding:6px 8px !important}\n.richclay-dialog-actions{display:flex !important;gap:8px !important;justify-content:flex-end !important}\n.richclay-primary,.richclay-secondary{appearance:none !important;border:1px solid var(--richclay-border) !important;border-radius:4px !important;font:inherit !important;min-height:34px !important;padding:5px 10px !important}\n.richclay-primary{background:var(--richclay-accent) !important;border-color:var(--richclay-accent) !important;color:var(--richclay-accent-ink) !important}\n.richclay-secondary{background:var(--richclay-surface-alt) !important;color:var(--richclay-text) !important}\n.richclay-sr-only{border:0 !important;clip:rect(0 0 0 0) !important;clip-path:inset(50%) !important;height:1px !important;margin:-1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:1px !important}\n@media (forced-colors: active){\n.richclay-toolbar,.richclay-menu,.richclay-input{border-color:CanvasText !important}\n.richclay-dialog{border-color:CanvasText !important}\n.richclay-button.is-active,.richclay-button[aria-pressed=\"true\"],.richclay-menu-item.is-active,.richclay-primary{background:Highlight !important;color:HighlightText !important}\n.richclay-button:focus-visible,.richclay-menu-item:focus-visible,.richclay-input:focus-visible{outline-color:Highlight !important}\n}\n.richclay-toolbar[hidden]{display:none !important}\n.richclay-button[hidden]{display:none !important}\n.richclay-separator[hidden]{display:none !important}\n.richclay-menu-wrap[hidden]{display:none !important}\n.richclay-menu[hidden]{display:none !important}\n.richclay-dialog[hidden]{display:none !important}\n.richclay-field[hidden]{display:none !important}\n.richclay-dialog-actions[hidden]{display:none !important}\n}";
|
package/src/ui/toast.js
CHANGED
|
@@ -1,434 +1,149 @@
|
|
|
1
1
|
// a nice, simple alert
|
|
2
2
|
// ❗️ don't use too much text!
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
success: `
|
|
24
|
-
<div class="toast hide success noise-texture">
|
|
25
|
-
<div class="toast-icon">{icon}</div>
|
|
26
|
-
<div class="toast-message">{message}</div>
|
|
27
|
-
</div>
|
|
28
|
-
`,
|
|
29
|
-
error: `
|
|
30
|
-
<div class="toast hide error noise-texture">
|
|
31
|
-
<div class="toast-icon">{icon}</div>
|
|
32
|
-
<div class="toast-message">{message}</div>
|
|
33
|
-
</div>
|
|
34
|
-
`,
|
|
35
|
-
warning: `
|
|
36
|
-
<div class="toast hide warning noise-texture">
|
|
37
|
-
<div class="toast-icon">{icon}</div>
|
|
38
|
-
<div class="toast-message">{message}</div>
|
|
39
|
-
</div>
|
|
40
|
-
`,
|
|
41
|
-
info: `
|
|
42
|
-
<div class="toast hide info noise-texture">
|
|
43
|
-
<div class="toast-icon">{icon}</div>
|
|
44
|
-
<div class="toast-message">{message}</div>
|
|
45
|
-
</div>
|
|
46
|
-
`
|
|
47
|
-
}
|
|
3
|
+
//
|
|
4
|
+
// The Hyperclay dashboard's toast (Bevel's React toast with the dashboard's overrides),
|
|
5
|
+
// drawn inline: a 3px tone edge, a 16px Phosphor glyph, 13.5px semibold text, a quiet close.
|
|
6
|
+
import { set } from "../lib/hostile-css.js";
|
|
7
|
+
import { bevelBox, bevelSurface, bevelText, pageScheme, protectIcon } from "./bevel-controls.js";
|
|
8
|
+
import { TOKENS, RULES } from "./bevel.js";
|
|
9
|
+
|
|
10
|
+
const icon = (d) =>
|
|
11
|
+
`<svg viewBox="0 0 256 256" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="${d}"/></svg>`;
|
|
12
|
+
|
|
13
|
+
const CHECK = "M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z";
|
|
14
|
+
const WARNING = "M236.8,188.09,149.35,36.22h0a24.76,24.76,0,0,0-42.7,0L19.2,188.09a23.51,23.51,0,0,0,0,23.72A24.35,24.35,0,0,0,40.55,224h174.9a24.35,24.35,0,0,0,21.33-12.19A23.51,23.51,0,0,0,236.8,188.09ZM222.93,203.8a8.5,8.5,0,0,1-7.48,4.2H40.55a8.5,8.5,0,0,1-7.48-4.2,7.59,7.59,0,0,1,0-7.72L120.52,44.21a8.75,8.75,0,0,1,15,0l87.45,151.87A7.59,7.59,0,0,1,222.93,203.8ZM120,144V104a8,8,0,0,1,16,0v40a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,180Z";
|
|
15
|
+
const INFO = "M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z";
|
|
16
|
+
const X = "M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z";
|
|
17
|
+
|
|
18
|
+
const TONES = {
|
|
19
|
+
success: { edge: TOKENS.teal, icon: icon(CHECK) },
|
|
20
|
+
error: { edge: TOKENS.ox, icon: icon(WARNING) },
|
|
21
|
+
warning: { edge: TOKENS.brass, icon: icon(WARNING) },
|
|
22
|
+
info: { edge: TOKENS.brass, icon: icon(INFO) },
|
|
48
23
|
};
|
|
49
24
|
|
|
50
|
-
|
|
51
|
-
export const hyperclayTemplates = {
|
|
52
|
-
toast: {
|
|
53
|
-
success: `
|
|
54
|
-
<div class="toast hide success">
|
|
55
|
-
{icon}
|
|
56
|
-
<span class="message">{message}</span>
|
|
57
|
-
</div>
|
|
58
|
-
`,
|
|
59
|
-
error: `
|
|
60
|
-
<div class="toast hide error">
|
|
61
|
-
{icon}
|
|
62
|
-
<span class="message">{message}</span>
|
|
63
|
-
</div>
|
|
64
|
-
`,
|
|
65
|
-
warning: `
|
|
66
|
-
<div class="toast hide warning">
|
|
67
|
-
{icon}
|
|
68
|
-
<span class="message">{message}</span>
|
|
69
|
-
</div>
|
|
70
|
-
`,
|
|
71
|
-
info: `
|
|
72
|
-
<div class="toast hide info">
|
|
73
|
-
{icon}
|
|
74
|
-
<span class="message">{message}</span>
|
|
75
|
-
</div>
|
|
76
|
-
`
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// Modern styles (scoped by data-toast-theme="modern")
|
|
81
|
-
const modernStyles = `
|
|
82
|
-
[data-toast-theme="modern"].toast-container {
|
|
83
|
-
z-index: 9999;
|
|
84
|
-
position: fixed;
|
|
85
|
-
top: 20px;
|
|
86
|
-
right: 20px;
|
|
87
|
-
display: flex;
|
|
88
|
-
flex-direction: column;
|
|
89
|
-
align-items: end;
|
|
90
|
-
gap: 18px;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
[data-toast-theme="modern"] .toast {
|
|
94
|
-
position: relative;
|
|
95
|
-
right: 0;
|
|
96
|
-
cursor: pointer;
|
|
97
|
-
transition: right 0.5s ease-in-out, opacity 0.5s ease-in-out;
|
|
98
|
-
display: flex;
|
|
99
|
-
align-items: center;
|
|
100
|
-
gap: 12px;
|
|
101
|
-
padding: 16px 24px 16px 16px;
|
|
102
|
-
font-size: var(--hyperclay-toast-font-size, 16px);
|
|
103
|
-
font-family: var(--hyperclay-toast-font-family, monospace);
|
|
104
|
-
font-weight: bold;
|
|
105
|
-
color: white;
|
|
106
|
-
border-width: 1px;
|
|
107
|
-
border-style: solid;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
[data-toast-theme="modern"] .toast.hide {
|
|
111
|
-
right: -400px;
|
|
112
|
-
opacity: 0;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
[data-toast-theme="modern"] .toast.success {
|
|
116
|
-
border-color: #358234;
|
|
117
|
-
background: radial-gradient(85.86% 68.42% at 50% 68.42%, #142419 0%, #1D3927 100%);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
[data-toast-theme="modern"] .toast.error {
|
|
121
|
-
border-color: #992930;
|
|
122
|
-
background: radial-gradient(85.86% 68.42% at 50% 68.42%, #240A13 0%, #481826 100%);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
[data-toast-theme="modern"] .toast.warning {
|
|
126
|
-
border-color: #B8860B;
|
|
127
|
-
background: radial-gradient(85.86% 68.42% at 50% 68.42%, #2A2010 0%, #3D3018 100%);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
[data-toast-theme="modern"] .toast.info {
|
|
131
|
-
border-color: #2E6B9E;
|
|
132
|
-
background: radial-gradient(85.86% 68.42% at 50% 68.42%, #0A1A24 0%, #162D3D 100%);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
[data-toast-theme="modern"] .toast-icon {
|
|
136
|
-
position: relative;
|
|
137
|
-
top: -1px;
|
|
138
|
-
}
|
|
25
|
+
const CLOSE_ICON = icon(X);
|
|
139
26
|
|
|
140
|
-
|
|
141
|
-
display: block;
|
|
142
|
-
width: 22px;
|
|
143
|
-
height: 22px;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
[data-toast-theme="modern"] .toast.info .toast-icon svg {
|
|
147
|
-
width: 24px;
|
|
148
|
-
height: 24px;
|
|
149
|
-
}
|
|
27
|
+
const HIDDEN = ["opacity:0", "transform:translateX(24px)"];
|
|
150
28
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
[data-toast-theme="modern"] .noise-texture {
|
|
157
|
-
position: relative;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
[data-toast-theme="modern"] .noise-texture::before {
|
|
161
|
-
content: "";
|
|
162
|
-
position: absolute;
|
|
163
|
-
top: 0;
|
|
164
|
-
left: 0;
|
|
165
|
-
width: 100%;
|
|
166
|
-
height: 100%;
|
|
167
|
-
opacity: 0.12;
|
|
168
|
-
pointer-events: none;
|
|
169
|
-
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.2' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
|
|
170
|
-
background-repeat: repeat;
|
|
171
|
-
}
|
|
172
|
-
`;
|
|
173
|
-
|
|
174
|
-
// Hyperclay styles (scoped by data-toast-theme="hyperclay")
|
|
175
|
-
export const hyperclayStyles = `
|
|
176
|
-
[data-toast-theme="hyperclay"].toast-container {
|
|
177
|
-
z-index: 9999;
|
|
178
|
-
position: fixed;
|
|
179
|
-
top: 20px;
|
|
180
|
-
right: 20px;
|
|
181
|
-
display: flex;
|
|
182
|
-
flex-direction: column;
|
|
183
|
-
align-items: end;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
[data-toast-theme="hyperclay"].toast-container > * + * {
|
|
187
|
-
margin-top: 18px;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
[data-toast-theme="hyperclay"] .toast {
|
|
191
|
-
position: relative;
|
|
192
|
-
right: 0;
|
|
193
|
-
display: flex;
|
|
194
|
-
align-items: center;
|
|
195
|
-
padding: 10px 19px 11px 17px;
|
|
196
|
-
cursor: pointer;
|
|
197
|
-
color: rgba(255,255,255,.8);
|
|
198
|
-
background-color: #0B0C12;
|
|
199
|
-
border: 2px dashed rgba(255,255,255,.6);
|
|
200
|
-
transition: right 0.5s ease-in-out;
|
|
201
|
-
font-size: var(--hyperclay-toast-font-size, inherit);
|
|
202
|
-
font-family: var(--hyperclay-toast-font-family, inherit);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
[data-toast-theme="hyperclay"] .toast svg {
|
|
206
|
-
position: relative;
|
|
207
|
-
top: -1px;
|
|
208
|
-
width: 17px;
|
|
209
|
-
height: 17px;
|
|
210
|
-
margin-right: 13px;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
[data-toast-theme="hyperclay"] .toast.hide {
|
|
214
|
-
right: -300px;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
[data-toast-theme="hyperclay"] .toast.success {
|
|
218
|
-
color: #76C824;
|
|
219
|
-
border: 2px dashed #589E11;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
[data-toast-theme="hyperclay"] .toast.error {
|
|
223
|
-
color: #DD304F;
|
|
224
|
-
border: 2px dashed #CD2140;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
[data-toast-theme="hyperclay"] .toast.warning {
|
|
228
|
-
color: #F5A623;
|
|
229
|
-
border: 2px dashed #D4900E;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
[data-toast-theme="hyperclay"] .toast.info {
|
|
233
|
-
color: #4A90D9;
|
|
234
|
-
border: 2px dashed #3A7BBF;
|
|
235
|
-
}
|
|
236
|
-
`;
|
|
237
|
-
|
|
238
|
-
// Global toast configuration (can be overridden by toast-hyperclay module)
|
|
239
|
-
let toastConfig = {
|
|
240
|
-
styles: modernStyles,
|
|
241
|
-
templates: defaultTemplates,
|
|
242
|
-
icons: defaultIcons,
|
|
243
|
-
theme: 'modern'
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
// Allow other modules (like toast-hyperclay) to override default toast styling
|
|
247
|
-
export function setToastTheme(config) {
|
|
248
|
-
Object.assign(toastConfig, config);
|
|
29
|
+
function motion() {
|
|
30
|
+
const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
31
|
+
return reduced ? "none" : "opacity .5s ease-in-out, transform .5s ease-in-out";
|
|
249
32
|
}
|
|
250
33
|
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
34
|
+
// Looked up on every call, never cached: a live-sync morph or a caller can empty the
|
|
35
|
+
// body, and a detached stack would swallow every toast after it.
|
|
36
|
+
function stack() {
|
|
37
|
+
const existing = document.querySelector("[data-clay-toasts]");
|
|
38
|
+
if (existing) return existing;
|
|
39
|
+
const box = bevelBox("div", [
|
|
40
|
+
"position:fixed", "top:18px", "right:18px", "z-index:9999",
|
|
41
|
+
"display:flex", "flex-direction:column", "align-items:flex-end", "gap:10px",
|
|
42
|
+
"width:min(330px, calc(100vw - 36px))", "pointer-events:none",
|
|
43
|
+
]);
|
|
44
|
+
box.setAttribute("data-clay-toasts", "");
|
|
45
|
+
box.setAttribute("aria-live", "polite");
|
|
46
|
+
document.body.append(box);
|
|
47
|
+
return box;
|
|
260
48
|
}
|
|
261
49
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
toastContainer.setAttribute('clay', 'no-save no-snapshot');
|
|
275
|
-
document.body.append(toastContainer);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Create toast element
|
|
279
|
-
const icon = icons[messageType] || icons.success;
|
|
280
|
-
const template = templates.toast[messageType] || templates.toast.success;
|
|
281
|
-
const toastHtml = template
|
|
282
|
-
.replace('{icon}', icon)
|
|
283
|
-
.replace('{message}', '');
|
|
284
|
-
|
|
285
|
-
const tempDiv = document.createElement('div');
|
|
286
|
-
tempDiv.innerHTML = toastHtml.trim();
|
|
287
|
-
const toastElement = tempDiv.firstElementChild;
|
|
50
|
+
function build(message, type) {
|
|
51
|
+
const tone = TONES[type];
|
|
52
|
+
const el = bevelSurface("div", [
|
|
53
|
+
"display:flex", "align-items:flex-start", ...RULES.toast, "width:100%",
|
|
54
|
+
`border-inline-start:3px solid ${tone.edge}`,
|
|
55
|
+
"cursor:pointer", "pointer-events:auto", `color-scheme:${pageScheme()}`,
|
|
56
|
+
...HIDDEN, `transition:${motion()}`,
|
|
57
|
+
]);
|
|
58
|
+
el.setAttribute("data-clay-toast", type);
|
|
59
|
+
const glyph = bevelText("span", ["display:inline-grid", "flex:none", "padding-top:1px", `color:${tone.edge}`]);
|
|
60
|
+
glyph.innerHTML = tone.icon;
|
|
61
|
+
protectIcon(glyph.firstElementChild, 16);
|
|
288
62
|
// A message can carry a filename, and a filename is whatever whoever wrote the
|
|
289
|
-
// file chose.
|
|
290
|
-
|
|
63
|
+
// file chose. It goes in as text so markup in a name can never become markup here.
|
|
64
|
+
const text = bevelText("span", ["flex:1 1 auto", "min-width:120px", ...RULES.toastTitle, "overflow-wrap:anywhere"], message);
|
|
65
|
+
el.append(glyph, text);
|
|
66
|
+
return el;
|
|
67
|
+
}
|
|
291
68
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
69
|
+
// The dashboard's toast close: a bare 24px square in the muted ink, the sunk ground on hover.
|
|
70
|
+
function closeButton(onClick) {
|
|
71
|
+
const b = bevelBox("button", [
|
|
72
|
+
"flex:none", "display:inline-grid", "place-items:center", ...RULES.toastClose,
|
|
73
|
+
"cursor:pointer",
|
|
74
|
+
]);
|
|
75
|
+
b.type = "button";
|
|
76
|
+
b.setAttribute("aria-label", "Dismiss");
|
|
77
|
+
b.title = "Dismiss";
|
|
78
|
+
b.innerHTML = CLOSE_ICON;
|
|
79
|
+
protectIcon(b.firstElementChild, 14);
|
|
80
|
+
const paint = (lit) => {
|
|
81
|
+
set(b, "color", lit ? TOKENS.ink : TOKENS.muted);
|
|
82
|
+
set(b, "background", lit ? TOKENS.sunk : "transparent");
|
|
83
|
+
};
|
|
84
|
+
b.addEventListener("pointerenter", () => paint(true));
|
|
85
|
+
b.addEventListener("pointerleave", () => paint(false));
|
|
86
|
+
b.addEventListener("focus", () => { paint(true); set(b, "outline", `2px solid ${TOKENS.brass}`); set(b, "outline-offset", "1px"); });
|
|
87
|
+
b.addEventListener("blur", () => { paint(false); set(b, "outline", "none"); });
|
|
88
|
+
b.addEventListener("click", onClick);
|
|
89
|
+
return b;
|
|
90
|
+
}
|
|
297
91
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
setTimeout(() =>
|
|
92
|
+
function reveal(el) {
|
|
93
|
+
stack().append(el);
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
set(el, "opacity", "1");
|
|
96
|
+
set(el, "transform", "none");
|
|
97
|
+
}, 10);
|
|
98
|
+
}
|
|
301
99
|
|
|
302
|
-
|
|
100
|
+
function dismiss(el, after) {
|
|
101
|
+
set(el, "opacity", "0");
|
|
102
|
+
set(el, "transform", "translateX(24px)");
|
|
303
103
|
setTimeout(() => {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
},
|
|
104
|
+
el.remove();
|
|
105
|
+
after?.();
|
|
106
|
+
}, 500);
|
|
307
107
|
}
|
|
308
108
|
|
|
309
|
-
// Main toast function - uses configured theme (default: modern, or hyperclay if toast-hyperclay loaded)
|
|
310
109
|
function toast(message, messageType = "success") {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
theme: toastConfig.theme
|
|
316
|
-
});
|
|
110
|
+
const el = build(message, TONES[messageType] ? messageType : "success");
|
|
111
|
+
el.addEventListener("click", () => dismiss(el));
|
|
112
|
+
reveal(el);
|
|
113
|
+
setTimeout(() => dismiss(el), 6600);
|
|
317
114
|
}
|
|
318
115
|
|
|
319
|
-
// Close button (button with pixel art X icon inside)
|
|
320
|
-
const CLOSE_BUTTON_HTML = `<button class="toast-close" type="button" aria-label="Close"><svg viewBox="0 0 558 558" xmlns="http://www.w3.org/2000/svg"><path d="M0,0h62.02v62.02h-62.02Z M62,0h62.02v62.02h-62.02Z M434,0h62.02v62.02h-62.02Z M496,0h62.02v62.02h-62.02Z M0,62h62.02v62.02h-62.02Z M62,62h62.02v62.02h-62.02Z M124,62h62.02v62.02h-62.02Z M372,62h62.02v62.02h-62.02Z M434,62h62.02v62.02h-62.02Z M496,62h62.02v62.02h-62.02Z M62,124h62.02v62.02h-62.02Z M124,124h62.02v62.02h-62.02Z M186,124h62.02v62.02h-62.02Z M310,124h62.02v62.02h-62.02Z M372,124h62.02v62.02h-62.02Z M434,124h62.02v62.02h-62.02Z M124,186h62.02v62.02h-62.02Z M186,186h62.02v62.02h-62.02Z M248,186h62.02v62.02h-62.02Z M310,186h62.02v62.02h-62.02Z M372,186h62.02v62.02h-62.02Z M186,248h62.02v62.02h-62.02Z M248,248h62.02v62.02h-62.02Z M310,248h62.02v62.02h-62.02Z M124,310h62.02v62.02h-62.02Z M186,310h62.02v62.02h-62.02Z M248,310h62.02v62.02h-62.02Z M310,310h62.02v62.02h-62.02Z M372,310h62.02v62.02h-62.02Z M62,372h62.02v62.02h-62.02Z M124,372h62.02v62.02h-62.02Z M186,372h62.02v62.02h-62.02Z M310,372h62.02v62.02h-62.02Z M372,372h62.02v62.02h-62.02Z M434,372h62.02v62.02h-62.02Z M0,434h62.02v62.02h-62.02Z M62,434h62.02v62.02h-62.02Z M124,434h62.02v62.02h-62.02Z M372,434h62.02v62.02h-62.02Z M434,434h62.02v62.02h-62.02Z M496,434h62.02v62.02h-62.02Z M0,496h62.02v62.02h-62.02Z M62,496h62.02v62.02h-62.02Z M434,496h62.02v62.02h-62.02Z M496,496h62.02v62.02h-62.02Z"/></svg></button>`;
|
|
321
|
-
|
|
322
|
-
// Persistent toast styles (added once)
|
|
323
|
-
const persistentToastStyles = `
|
|
324
|
-
.toast.toast-persistent {
|
|
325
|
-
position: relative;
|
|
326
|
-
align-items: center;
|
|
327
|
-
padding-right: 18px;
|
|
328
|
-
}
|
|
329
|
-
.toast-persistent .toast-close {
|
|
330
|
-
background: none;
|
|
331
|
-
border: none;
|
|
332
|
-
padding: 0;
|
|
333
|
-
margin-left: 3px;
|
|
334
|
-
position: relative;
|
|
335
|
-
top: -2px;
|
|
336
|
-
cursor: pointer;
|
|
337
|
-
opacity: 0.7;
|
|
338
|
-
transition: opacity 0.2s;
|
|
339
|
-
display: flex;
|
|
340
|
-
align-items: center;
|
|
341
|
-
justify-content: center;
|
|
342
|
-
color: white;
|
|
343
|
-
width: 22px;
|
|
344
|
-
height: 22px;
|
|
345
|
-
}
|
|
346
|
-
.toast-persistent .toast-close:hover {
|
|
347
|
-
opacity: 1;
|
|
348
|
-
}
|
|
349
|
-
.toast-persistent .toast-close svg {
|
|
350
|
-
width: 11px;
|
|
351
|
-
height: 11px;
|
|
352
|
-
fill: currentColor;
|
|
353
|
-
}
|
|
354
|
-
`;
|
|
355
|
-
|
|
356
116
|
// Track active persistent toasts by message
|
|
357
117
|
const activePersistentToasts = new Map();
|
|
358
118
|
|
|
359
119
|
// Persistent toast function - doesn't auto-dismiss, requires click to close
|
|
360
120
|
function toastPersistent(message, messageType = "warning") {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (!document.querySelector('style.toast-styles-persistent')) {
|
|
365
|
-
const styleSheet = document.createElement('style');
|
|
366
|
-
styleSheet.className = 'toast-styles-persistent';
|
|
367
|
-
styleSheet.setAttribute('clay', 'no-save no-snapshot');
|
|
368
|
-
styleSheet.textContent = persistentToastStyles;
|
|
369
|
-
document.head.appendChild(styleSheet);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const templates = toastConfig.templates;
|
|
373
|
-
const icons = toastConfig.icons;
|
|
374
|
-
const theme = toastConfig.theme;
|
|
375
|
-
|
|
376
|
-
// Get or create container for this theme
|
|
377
|
-
let toastContainer = document.querySelector(`.toast-container[data-toast-theme="${theme}"]`);
|
|
378
|
-
if (!toastContainer) {
|
|
379
|
-
toastContainer = document.createElement('div');
|
|
380
|
-
toastContainer.className = 'toast-container';
|
|
381
|
-
toastContainer.setAttribute('data-toast-theme', theme);
|
|
382
|
-
toastContainer.setAttribute('clay', 'no-save no-snapshot');
|
|
383
|
-
document.body.append(toastContainer);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// If same message already showing, animate it out first
|
|
387
|
-
const existingToast = activePersistentToasts.get(message);
|
|
388
|
-
if (existingToast) {
|
|
389
|
-
existingToast.classList.add('hide');
|
|
390
|
-
setTimeout(() => existingToast.remove(), 500);
|
|
121
|
+
const existing = activePersistentToasts.get(message);
|
|
122
|
+
if (existing) {
|
|
123
|
+
dismiss(existing);
|
|
391
124
|
activePersistentToasts.delete(message);
|
|
392
125
|
}
|
|
393
126
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
.
|
|
399
|
-
.
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
tempDiv.innerHTML = toastHtml.trim();
|
|
403
|
-
const toastElement = tempDiv.firstElementChild;
|
|
404
|
-
// A message can carry a filename, and a filename is whatever whoever wrote the
|
|
405
|
-
// file chose. Assign it as text so markup in a name can never become markup here.
|
|
406
|
-
toastElement.querySelector('.toast-message, .message').textContent = message;
|
|
407
|
-
toastElement.classList.add('toast-persistent');
|
|
408
|
-
|
|
409
|
-
// Add close button as sibling (not inside message)
|
|
410
|
-
toastElement.insertAdjacentHTML('beforeend', CLOSE_BUTTON_HTML);
|
|
411
|
-
|
|
412
|
-
// Close handler - only on close button click
|
|
413
|
-
const closeBtn = toastElement.querySelector('.toast-close');
|
|
414
|
-
if (closeBtn) {
|
|
415
|
-
closeBtn.addEventListener('click', (e) => {
|
|
416
|
-
e.stopPropagation();
|
|
417
|
-
toastElement.classList.add('hide');
|
|
418
|
-
setTimeout(() => {
|
|
419
|
-
toastElement.remove();
|
|
420
|
-
activePersistentToasts.delete(message);
|
|
421
|
-
}, 500);
|
|
422
|
-
});
|
|
423
|
-
}
|
|
127
|
+
const el = build(message, TONES[messageType] ? messageType : "warning");
|
|
128
|
+
set(el, "cursor", "default");
|
|
129
|
+
const close = closeButton((e) => {
|
|
130
|
+
e.stopPropagation();
|
|
131
|
+
// A newer toast with the same message may have taken this entry since.
|
|
132
|
+
dismiss(el, () => { if (activePersistentToasts.get(message) === el) activePersistentToasts.delete(message); });
|
|
133
|
+
});
|
|
134
|
+
el.append(close);
|
|
424
135
|
|
|
425
|
-
|
|
426
|
-
|
|
136
|
+
activePersistentToasts.set(message, el);
|
|
137
|
+
reveal(el);
|
|
138
|
+
}
|
|
427
139
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
140
|
+
// Takes down the persistent toast showing this message, if one is up.
|
|
141
|
+
function dismissPersistent(message) {
|
|
142
|
+
const el = activePersistentToasts.get(message);
|
|
143
|
+
if (!el) return;
|
|
144
|
+
activePersistentToasts.delete(message);
|
|
145
|
+
dismiss(el);
|
|
431
146
|
}
|
|
432
147
|
|
|
433
|
-
export { toastPersistent };
|
|
148
|
+
export { toastPersistent, dismissPersistent };
|
|
434
149
|
export default toast;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Puts a generated vendor skin (src/ui/skins/*.js) on the page.
|
|
2
|
+
//
|
|
3
|
+
// A skin is one `@layer clay-skin { ... }` block whose declarations are all
|
|
4
|
+
// !important. Important declarations in the earliest cascade layer beat the host
|
|
5
|
+
// page's unlayered !important rules and every later layer, and a layer's position is
|
|
6
|
+
// fixed where its name first appears, so the skin goes in ahead of every other
|
|
7
|
+
// stylesheet in <head>. It is runtime-only: never saved, never watched, never sent to
|
|
8
|
+
// a peer, and a second call for the same name reuses the element.
|
|
9
|
+
import { RUNTIME_ONLY, pageScheme } from './bevel-controls.js';
|
|
10
|
+
|
|
11
|
+
const isSkin = (node) => node.nodeType === 1 && node.hasAttribute('data-clay-skin');
|
|
12
|
+
|
|
13
|
+
function firstNonSkin(head) {
|
|
14
|
+
for (const node of head.childNodes) if (!isSkin(node)) return node;
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function placed(head, el) {
|
|
19
|
+
if (el.parentNode !== head) return false;
|
|
20
|
+
for (let node = el.previousSibling; node; node = node.previousSibling) if (!isSkin(node)) return false;
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const installed = new Map();
|
|
25
|
+
|
|
26
|
+
// A vendor that sets no color-scheme of its own inherits the page's, and a page that
|
|
27
|
+
// declares none computes `normal`, which resolves every light-dark() pin to light.
|
|
28
|
+
// ClayJS's own surfaces follow the reader's preference there (pageScheme), so a skin
|
|
29
|
+
// installed with its roots gives them the same scheme.
|
|
30
|
+
function schemeRule(roots) {
|
|
31
|
+
return `\n@layer clay-skin{:is(${roots.join(', ')}){color-scheme:${pageScheme()} !important}}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Install or refresh one skin.
|
|
36
|
+
* @param {string} name one element per name
|
|
37
|
+
* @param {string} css the skin module's CSS export
|
|
38
|
+
* @param {{ roots?: string[] }} [options] roots to give the page's scheme
|
|
39
|
+
* @returns {HTMLStyleElement}
|
|
40
|
+
*/
|
|
41
|
+
export function installSkin(name, css, { roots } = {}) {
|
|
42
|
+
installed.set(name, { css, roots });
|
|
43
|
+
const head = document.head || document.documentElement;
|
|
44
|
+
let el = null;
|
|
45
|
+
for (const node of head.querySelectorAll('style[data-clay-skin]')) {
|
|
46
|
+
if (node.getAttribute('data-clay-skin') === name) el = node;
|
|
47
|
+
}
|
|
48
|
+
if (!el) {
|
|
49
|
+
el = document.createElement('style');
|
|
50
|
+
el.setAttribute('clay', RUNTIME_ONLY);
|
|
51
|
+
el.setAttribute('data-clay-skin', name);
|
|
52
|
+
}
|
|
53
|
+
const text = roots ? css + schemeRule(roots) : css;
|
|
54
|
+
if (el.textContent !== text) el.textContent = text;
|
|
55
|
+
if (!placed(head, el)) head.insertBefore(el, firstNonSkin(head));
|
|
56
|
+
return el;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// A live-sync merge can put a peer's stylesheet ahead of a skin, and a page can change
|
|
60
|
+
// its scheme; after every applied frame each skin goes back in front, re-read.
|
|
61
|
+
if (typeof document !== 'undefined') {
|
|
62
|
+
document.addEventListener('clay:sync-applied', () => {
|
|
63
|
+
for (const [name, { css, roots }] of installed) installSkin(name, css, { roots });
|
|
64
|
+
});
|
|
65
|
+
}
|