@devframes/hub-ui-onboard 1.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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-PRESENT Anthony Fu <https://github.com/antfu>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,104 @@
1
+ //#region src/client/.generated/css.ts
2
+ var e = import.meta.url, t = new URL("./", e), n = 1e3, r = "devframes-onboard-hidden", i = (e, n) => fetch(new URL(`__onboard/${e}`, t), n);
3
+ async function a() {
4
+ return (await i("status")).json();
5
+ }
6
+ async function o() {
7
+ await import(
8
+ /* @vite-ignore */
9
+ new URL(`embedded.js?onboard=${Date.now()}`, t).href
10
+ );
11
+ }
12
+ function s() {
13
+ let e = localStorage.getItem("devframes-color-scheme");
14
+ return e === "dark" || e === "light" ? e === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
15
+ }
16
+ function c(e) {
17
+ let t = e.logo;
18
+ return !t || typeof t == "string" ? t : s() ? t.dark ?? t.light : t.light;
19
+ }
20
+ function l(e, t) {
21
+ let n = document.createElement("span");
22
+ n.className = `${t} flex items-center justify-center`;
23
+ let r = c(e);
24
+ if (r) {
25
+ let e = document.createElement("img");
26
+ e.src = r, e.alt = "", e.draggable = !1, e.className = "w-full h-full object-contain", n.append(e);
27
+ } else n.innerHTML = "<svg viewBox=\"0 0 500 500\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-label=\"Devframe logo\"><path d=\"M238.464 40H66V236.007H104.577C220.688 236.007 232.79 158.437 232.79 121.733C232.79 108.867 243.38 108.867 243.38 121.733C251.318 236.701 356.35 245.457 365.527 246.223L365.542 246.224C374.619 246.981 382.561 251.521 365.542 254.548C253.592 264.765 243.758 364.282 243.38 375.634C243.002 386.986 235.438 399.851 232.79 375.634C222.957 274.603 144.289 257.954 104.577 257.954H66V460.772H238.464C358.356 460.772 452.152 371.472 452.152 251.521C452.152 131.571 356.465 40 238.464 40Z\" fill=\"url(#devframes-onboard-logo-gradient)\" stroke=\"#86997F\" stroke-opacity=\"0.5\" stroke-width=\"1.33156\"/><defs><radialGradient id=\"devframes-onboard-logo-gradient\" cx=\"0\" cy=\"0\" r=\"1\" gradientTransform=\"matrix(399.011 555.102 -568.423 408.982 -11.6221 -34.9218)\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#ADC77F\"/><stop offset=\"0.71903\" stop-color=\"#517158\"/><stop offset=\"1\" stop-color=\"#486954\"/></radialGradient></defs></svg>";
28
+ return n;
29
+ }
30
+ function u(e, t, n) {
31
+ let r = document.createElement("button");
32
+ return r.type = "button", r.className = e, r.textContent = t, r.addEventListener("click", n), r;
33
+ }
34
+ function d(e) {
35
+ let t = document.createElement("devframes-onboard"), c = t.attachShadow({ mode: "open" }), d = document.createElement("style");
36
+ d.textContent = "*,:before,:after{box-sizing:border-box;border-style:solid;border-width:0;border-color:var(--un-onboard-default-border-color,#e5e7eb)}:before,:after{--un-onboard-content:\"\"}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}body{line-height:inherit;margin:0}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-feature-settings:normal;font-variation-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-feature-settings:inherit;font-variation-settings:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:#0000;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{margin:0;padding:0;list-style:none}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.devframes-onboard-root:hover .devframes-onboard-glow{opacity:.6}.devframes-onboard-pill{z-index:2147483644;--un-onboard-border-opacity:.13;border-width:1px;border-color:rgb(153 153 153/var(--un-onboard-border-opacity));--un-onboard-backdrop-blur:blur(7px);width:1.5rem;height:1.5rem;backdrop-filter:var(--un-onboard-backdrop-blur) var(--un-onboard-backdrop-brightness) var(--un-onboard-backdrop-contrast) var(--un-onboard-backdrop-grayscale) var(--un-onboard-backdrop-hue-rotate) var(--un-onboard-backdrop-invert) var(--un-onboard-backdrop-opacity) var(--un-onboard-backdrop-saturate) var(--un-onboard-backdrop-sepia);--un-onboard-shadow:var(--un-onboard-shadow-inset) 0 1px 3px 0 var(--un-onboard-shadow-color,#0000001a),var(--un-onboard-shadow-inset) 0 1px 2px -1px var(--un-onboard-shadow-color,#0000001a);box-shadow:var(--un-onboard-ring-offset-shadow), var(--un-onboard-ring-shadow), var(--un-onboard-shadow);cursor:pointer;-webkit-user-select:none;user-select:none;outline-offset:2px;opacity:.25;--un-onboard-ring-opacity:1;--un-onboard-ring-color:rgb(117 166 126/var(--un-onboard-ring-opacity));background-color:#ffffffa6;border-radius:9999px;outline:2px solid #0000;justify-content:center;align-items:center;padding:0;transition-property:all;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex;position:fixed;bottom:1rem;left:1rem}.devframes-onboard-pill:hover{--un-onboard-scale-x:1.05;--un-onboard-scale-y:1.05;transform:translateX(var(--un-onboard-translate-x)) translateY(var(--un-onboard-translate-y)) translateZ(var(--un-onboard-translate-z)) rotate(var(--un-onboard-rotate)) rotateX(var(--un-onboard-rotate-x)) rotateY(var(--un-onboard-rotate-y)) rotateZ(var(--un-onboard-rotate-z)) skewX(var(--un-onboard-skew-x)) skewY(var(--un-onboard-skew-y)) scaleX(var(--un-onboard-scale-x)) scaleY(var(--un-onboard-scale-y)) scaleZ(var(--un-onboard-scale-z));opacity:1}.devframes-onboard-pill:focus-visible{opacity:1;--un-onboard-ring-width:2px;--un-onboard-ring-offset-shadow:var(--un-onboard-ring-inset) 0 0 0 var(--un-onboard-ring-offset-width) var(--un-onboard-ring-offset-color);--un-onboard-ring-shadow:var(--un-onboard-ring-inset) 0 0 0 calc(var(--un-onboard-ring-width) + var(--un-onboard-ring-offset-width)) var(--un-onboard-ring-color);box-shadow:var(--un-onboard-ring-offset-shadow), var(--un-onboard-ring-shadow), var(--un-onboard-shadow)}.dark .devframes-onboard-pill{background-color:#111c}.devframes-onboard-pill[aria-expanded=true]{opacity:1}.devframes-onboard-glow{pointer-events:none;opacity:0;filter:blur(60px);background-image:linear-gradient(45deg, var(--colors-primary-300,#adc77f), var(--colors-primary-500,#517158), var(--colors-primary-DEFAULT,#486954));border-radius:9999px;width:10rem;height:10rem;transition-property:opacity;transition-duration:1s;transition-timing-function:cubic-bezier(0,0,.2,1);position:absolute;bottom:-6.5rem;left:-1rem;transform:translate(-50%,-50%)}.devframes-onboard-panel{z-index:2147483644;--un-onboard-border-opacity:.13;border-width:1px;border-color:rgb(153 153 153/var(--un-onboard-border-opacity));--un-onboard-bg-opacity:1;background-color:rgb(255 255 255/var(--un-onboard-bg-opacity));--un-onboard-text-opacity:1;width:20rem;max-width:calc(100vw - 2rem);color:rgb(38 38 38/var(--un-onboard-text-opacity));--un-onboard-shadow:var(--un-onboard-shadow-inset) 0 10px 15px -3px var(--un-onboard-shadow-color,#0000001a),var(--un-onboard-shadow-inset) 0 4px 6px -4px var(--un-onboard-shadow-color,#0000001a);box-shadow:var(--un-onboard-ring-offset-shadow), var(--un-onboard-ring-shadow), var(--un-onboard-shadow);transform-origin:0 100%;border-radius:.5rem;flex-direction:column;gap:.75rem;padding:1rem;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-size:.875rem;line-height:1.5;transition-property:all;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex;position:fixed;bottom:3rem;left:1rem}.dark .devframes-onboard-panel{--un-onboard-bg-opacity:1;background-color:rgb(17 17 17/var(--un-onboard-bg-opacity));--un-onboard-text-opacity:1;color:rgb(229 229 229/var(--un-onboard-text-opacity))}.devframes-onboard-panel[hidden]{pointer-events:none;--un-onboard-scale-x:.95;--un-onboard-scale-y:.95;transform:translateX(var(--un-onboard-translate-x)) translateY(var(--un-onboard-translate-y)) translateZ(var(--un-onboard-translate-z)) rotate(var(--un-onboard-rotate)) rotateX(var(--un-onboard-rotate-x)) rotateY(var(--un-onboard-rotate-y)) rotateZ(var(--un-onboard-rotate-z)) skewX(var(--un-onboard-skew-x)) skewY(var(--un-onboard-skew-y)) scaleX(var(--un-onboard-scale-x)) scaleY(var(--un-onboard-scale-y)) scaleZ(var(--un-onboard-scale-z));opacity:0;display:block}.devframes-onboard-command{-webkit-user-select:all;user-select:all;word-break:break-all;--un-onboard-bg-opacity:1;background-color:rgb(246 246 246/var(--un-onboard-bg-opacity));--un-onboard-text-opacity:1;color:rgb(82 82 82/var(--un-onboard-text-opacity));border-radius:.25rem;padding:.375rem .5rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.75rem;line-height:1rem;display:block}.dark .devframes-onboard-command{--un-onboard-bg-opacity:1;background-color:rgb(16 16 16/var(--un-onboard-bg-opacity));--un-onboard-text-opacity:1;color:rgb(163 163 163/var(--un-onboard-text-opacity))}@media print{.devframes-onboard-pill,.devframes-onboard-panel{display:none}}@media (prefers-reduced-motion:reduce){.devframes-onboard-pill,.devframes-onboard-panel,.devframes-onboard-glow{transition:none}}*,:before,:after,::backdrop{--un-onboard-rotate:0;--un-onboard-rotate-x:0;--un-onboard-rotate-y:0;--un-onboard-rotate-z:0;--un-onboard-scale-x:1;--un-onboard-scale-y:1;--un-onboard-scale-z:1;--un-onboard-skew-x:0;--un-onboard-skew-y:0;--un-onboard-translate-x:0;--un-onboard-translate-y:0;--un-onboard-translate-z:0;--un-onboard-pan-x: ;--un-onboard-pan-y: ;--un-onboard-pinch-zoom: ;--un-onboard-scroll-snap-strictness:proximity;--un-onboard-ordinal: ;--un-onboard-slashed-zero: ;--un-onboard-numeric-figure: ;--un-onboard-numeric-spacing: ;--un-onboard-numeric-fraction: ;--un-onboard-border-spacing-x:0;--un-onboard-border-spacing-y:0;--un-onboard-ring-offset-shadow:0 0 #0000;--un-onboard-ring-shadow:0 0 #0000;--un-onboard-shadow-inset: ;--un-onboard-shadow:0 0 #0000;--un-onboard-ring-inset: ;--un-onboard-ring-offset-width:0px;--un-onboard-ring-offset-color:#fff;--un-onboard-ring-width:0px;--un-onboard-ring-color:#93c5fd80;--un-onboard-blur: ;--un-onboard-brightness: ;--un-onboard-contrast: ;--un-onboard-drop-shadow: ;--un-onboard-grayscale: ;--un-onboard-hue-rotate: ;--un-onboard-invert: ;--un-onboard-saturate: ;--un-onboard-sepia: ;--un-onboard-backdrop-blur: ;--un-onboard-backdrop-brightness: ;--un-onboard-backdrop-contrast: ;--un-onboard-backdrop-grayscale: ;--un-onboard-backdrop-hue-rotate: ;--un-onboard-backdrop-invert: ;--un-onboard-backdrop-opacity: ;--un-onboard-backdrop-saturate: ;--un-onboard-backdrop-sepia: }.btn-action:disabled{pointer-events:none;opacity:.3!important}.btn-primary:disabled{pointer-events:none;opacity:.5}.btn-action{--un-onboard-border-opacity:.13;border-width:1px;border-color:rgb(153 153 153/var(--un-onboard-border-opacity));opacity:.75;outline-offset:2px;border-radius:.25rem;outline:2px solid #0000;align-items:center;gap:.5rem;padding:.25rem .5rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.btn-primary{--un-onboard-bg-opacity:1;background-color:rgb(from var(--colors-primary-500,#578861) r g b / var(--un-onboard-bg-opacity));--un-onboard-text-opacity:1;color:rgb(255 255 255/var(--un-onboard-text-opacity));outline-offset:2px;border-width:1px;border-color:#0000;border-radius:.25rem;outline:2px solid #0000;align-items:center;gap:.5rem;padding:.25rem .5rem;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);display:flex}.btn-action:hover{--un-onboard-bg-opacity:.08;background-color:rgb(153 153 153/var(--un-onboard-bg-opacity));opacity:1}.btn-primary:hover{--un-onboard-bg-opacity:1;background-color:rgb(from var(--colors-primary-600,#40704b) r g b / var(--un-onboard-bg-opacity))}.btn-action:focus-visible,.btn-primary:focus-visible{--un-onboard-ring-width:2px;--un-onboard-ring-offset-shadow:var(--un-onboard-ring-inset) 0 0 0 var(--un-onboard-ring-offset-width) var(--un-onboard-ring-offset-color);--un-onboard-ring-shadow:var(--un-onboard-ring-inset) 0 0 0 calc(var(--un-onboard-ring-width) + var(--un-onboard-ring-offset-width)) var(--un-onboard-ring-color);box-shadow:var(--un-onboard-ring-offset-shadow), var(--un-onboard-ring-shadow), var(--un-onboard-shadow);--un-onboard-ring-color:rgb(from var(--colors-primary-500,#578861) r g b / .4)}@property --scroll-fade-t{syntax:\"<length-percentage>\";inherits:false;initial-value:0}@property --scroll-fade-b{syntax:\"<length-percentage>\";inherits:false;initial-value:0}@property --scroll-fade-s{syntax:\"<length-percentage>\";inherits:false;initial-value:0}@property --scroll-fade-e{syntax:\"<length-percentage>\";inherits:false;initial-value:0}@property --scroll-fade-mask{syntax:\"*\";inherits:false}@property --shimmer-angle{syntax:\"<angle>\";inherits:true;initial-value:20deg}@property --shimmer-image{syntax:\"*\";inherits:false}@property --shimmer-text-fill{syntax:\"*\";inherits:false}.grid{display:grid}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.m-0{margin:0}.contents{display:contents}.h-3{height:.75rem}.h-5{height:1.25rem}.h-full{height:100%}.w-3{width:.75rem}.w-5{width:1.25rem}.w-full{width:100%}.flex{display:flex}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.object-contain{object-fit:contain}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.dark .dark\\:text-red-400{--un-onboard-text-opacity:1;color:rgb(248 113 113/var(--un-onboard-text-opacity))}.text-red-600{--un-onboard-text-opacity:1;color:rgb(220 38 38/var(--un-onboard-text-opacity))}.font-medium{font-weight:500}*,:before,:after,::backdrop{--un-onboard-rotate:0;--un-onboard-rotate-x:0;--un-onboard-rotate-y:0;--un-onboard-rotate-z:0;--un-onboard-scale-x:1;--un-onboard-scale-y:1;--un-onboard-scale-z:1;--un-onboard-skew-x:0;--un-onboard-skew-y:0;--un-onboard-translate-x:0;--un-onboard-translate-y:0;--un-onboard-translate-z:0;--un-onboard-pan-x: ;--un-onboard-pan-y: ;--un-onboard-pinch-zoom: ;--un-onboard-scroll-snap-strictness:proximity;--un-onboard-ordinal: ;--un-onboard-slashed-zero: ;--un-onboard-numeric-figure: ;--un-onboard-numeric-spacing: ;--un-onboard-numeric-fraction: ;--un-onboard-border-spacing-x:0;--un-onboard-border-spacing-y:0;--un-onboard-ring-offset-shadow:0 0 #0000;--un-onboard-ring-shadow:0 0 #0000;--un-onboard-shadow-inset: ;--un-onboard-shadow:0 0 #0000;--un-onboard-ring-inset: ;--un-onboard-ring-offset-width:0px;--un-onboard-ring-offset-color:#fff;--un-onboard-ring-width:0px;--un-onboard-ring-color:#93c5fd80;--un-onboard-blur: ;--un-onboard-brightness: ;--un-onboard-contrast: ;--un-onboard-drop-shadow: ;--un-onboard-grayscale: ;--un-onboard-hue-rotate: ;--un-onboard-invert: ;--un-onboard-saturate: ;--un-onboard-sepia: ;--un-onboard-backdrop-blur: ;--un-onboard-backdrop-brightness: ;--un-onboard-backdrop-contrast: ;--un-onboard-backdrop-grayscale: ;--un-onboard-backdrop-hue-rotate: ;--un-onboard-backdrop-invert: ;--un-onboard-backdrop-opacity: ;--un-onboard-backdrop-saturate: ;--un-onboard-backdrop-sepia: }*,:before,:after{border-color:#8882}.border-base{--un-onboard-border-opacity:.13;border-color:rgb(153 153 153/var(--un-onboard-border-opacity))}.bg-active{--un-onboard-bg-opacity:.13;background-color:rgb(153 153 153/var(--un-onboard-bg-opacity))}.bg-base{--un-onboard-bg-opacity:1;background-color:rgb(255 255 255/var(--un-onboard-bg-opacity))}.dark .bg-base{--un-onboard-bg-opacity:1;background-color:rgb(17 17 17/var(--un-onboard-bg-opacity))}.bg-hover{--un-onboard-bg-opacity:.08;background-color:rgb(153 153 153/var(--un-onboard-bg-opacity))}.bg-secondary{--un-onboard-bg-opacity:1;background-color:rgb(246 246 246/var(--un-onboard-bg-opacity))}.dark .bg-secondary{--un-onboard-bg-opacity:1;background-color:rgb(16 16 16/var(--un-onboard-bg-opacity))}.color-active{--un-onboard-text-opacity:1;color:rgb(from var(--colors-primary-600,#40704b) r g b / var(--un-onboard-text-opacity))}.dark .color-active{--un-onboard-text-opacity:1;color:rgb(from var(--colors-primary-300,#93c69d) r g b / var(--un-onboard-text-opacity))}.color-base{--un-onboard-text-opacity:1;color:rgb(38 38 38/var(--un-onboard-text-opacity))}.dark .color-base{--un-onboard-text-opacity:1;color:rgb(229 229 229/var(--un-onboard-text-opacity))}.color-faint,.dark .color-faint{--un-onboard-text-opacity:1;color:rgb(115 115 115/var(--un-onboard-text-opacity))}.color-muted{--un-onboard-text-opacity:1;color:rgb(82 82 82/var(--un-onboard-text-opacity))}.dark .color-muted{--un-onboard-text-opacity:1;color:rgb(163 163 163/var(--un-onboard-text-opacity))}:root,:host,.devframes-accent-scope{--devframe-primary-DEFAULT:var(--devframe-primary);--devframe-primary-600:color-mix(in oklab, var(--devframe-primary), white 9%);--devframe-primary-500:color-mix(in oklab, var(--devframe-primary), white 18%);--devframe-primary-400:color-mix(in oklab, var(--devframe-primary), white 34%);--devframe-primary-300:color-mix(in oklab, var(--devframe-primary), white 54%);--colors-primary-DEFAULT:var(--devframe-primary-DEFAULT,#3a6a45);--colors-primary-600:var(--devframe-primary-600,#40704b);--colors-primary-500:var(--devframe-primary-500,#578861);--colors-primary-400:var(--devframe-primary-400,#75a67e);--colors-primary-300:var(--devframe-primary-300,#93c69d)}";
37
+ let f = document.createElement("div");
38
+ f.className = [s() ? "dark" : "light", "devframes-onboard-root"].join(" "), f.style.display = "contents", c.append(d, f), e.branding.primaryColor && t.style.setProperty("--devframe-primary", e.branding.primaryColor);
39
+ let { messages: p, branding: m } = e, h = document.createElement("button");
40
+ h.type = "button", h.className = "devframes-onboard-pill", h.title = p.title, h.setAttribute("aria-haspopup", "dialog"), h.setAttribute("aria-expanded", "false");
41
+ let g = document.createElement("span");
42
+ g.className = "devframes-onboard-glow", h.append(l(m, "w-3 h-3"));
43
+ let _ = document.createElement("div");
44
+ _.className = "devframes-onboard-panel", _.setAttribute("role", "dialog"), _.hidden = !0;
45
+ let v = document.createElement("div");
46
+ v.className = "flex items-center gap-2 font-medium";
47
+ let y = document.createElement("span");
48
+ y.textContent = p.title, v.append(l(m, "w-5 h-5"), y);
49
+ let b = document.createElement("p");
50
+ b.className = "color-muted m-0", b.textContent = p.description;
51
+ let x = document.createElement("code");
52
+ x.className = "devframes-onboard-command", x.textContent = e.command;
53
+ let S = document.createElement("p");
54
+ S.className = "m-0 text-xs", S.hidden = !0;
55
+ let C = u("btn-primary text-sm justify-center", p.install, () => void M()), w = u("btn-action justify-center text-sm color-muted", p.hide, () => {
56
+ sessionStorage.setItem(r, "1"), t.remove();
57
+ }), T = u("btn-action justify-center text-sm color-muted", p.disable, () => void N()), E = document.createElement("div");
58
+ E.className = "grid grid-cols-2 gap-1", E.append(w, T);
59
+ let D = document.createElement("div");
60
+ D.className = "flex flex-col gap-2", D.append(E, C), _.append(v, b, x, S, D), f.append(g, h, _);
61
+ let O = !1, k = (e) => {
62
+ O = e, _.hidden = !O, h.setAttribute("aria-expanded", String(O));
63
+ };
64
+ h.addEventListener("click", () => k(!O)), document.addEventListener("keydown", (e) => {
65
+ e.key === "Escape" && O && k(!1);
66
+ }), document.addEventListener("pointerdown", (e) => {
67
+ O && !e.composedPath().includes(t) && k(!1);
68
+ });
69
+ function A(e) {
70
+ let { state: t } = e;
71
+ C.disabled = t === "installing", C.textContent = t === "error" ? p.retry : p.install, C.hidden = t === "installed", S.hidden = t !== "installing" && t !== "installed" && t !== "error", S.className = `m-0 text-xs ${t === "error" ? "text-red-600 dark:text-red-400" : "color-muted"}`, S.textContent = t === "installing" ? p.installing : t === "installed" ? p.restart : t === "error" ? `${e.error?.code}: ${e.error?.message}` : "";
72
+ }
73
+ async function j() {
74
+ let e = await a();
75
+ if (e.state === "ready") {
76
+ t.remove(), await o();
77
+ return;
78
+ }
79
+ A(e), e.state === "installing" && setTimeout(() => void j(), n);
80
+ }
81
+ async function M() {
82
+ A({
83
+ ...e,
84
+ state: "installing"
85
+ }), await i("install", { method: "POST" }), await j();
86
+ }
87
+ async function N() {
88
+ (await i("disable", { method: "POST" })).ok && t.remove();
89
+ }
90
+ A(e), e.state === "installing" && j(), document.body.append(t);
91
+ }
92
+ async function f() {
93
+ if (window.parent !== window || sessionStorage.getItem(r)) return;
94
+ let e = await a();
95
+ if (e.state !== "disabled" && e.state !== "installed") {
96
+ if (e.state === "ready") {
97
+ await o();
98
+ return;
99
+ }
100
+ d(e);
101
+ }
102
+ }
103
+ f();
104
+ //#endregion
@@ -0,0 +1,92 @@
1
+ //#region src/types.d.ts
2
+ /** A logo URL, or one per color scheme. Same shape as hub-ui's `BrandingLogo`. */
3
+ type OnboardingLogo = string | {
4
+ light: string;
5
+ dark?: string;
6
+ };
7
+ /** The subset of hub-ui's `DevframeBranding` the two-button panel renders. */
8
+ interface OnboardingBranding {
9
+ /** Product name shown in the panel and used in the default strings. Default: `Devframes`. */
10
+ productName?: string;
11
+ logo?: OnboardingLogo;
12
+ /** Primary accent as a CSS color; retints the pill glow and the primary button. */
13
+ primaryColor?: string;
14
+ }
15
+ /** Every string the panel renders. Defaults derive from `branding.productName`. */
16
+ interface OnboardingMessages {
17
+ /** Pill tooltip and panel heading. */
18
+ title: string;
19
+ /** Short sentence under the heading. */
20
+ description: string;
21
+ install: string;
22
+ /** Hides the button for this browser tab (`sessionStorage`). */
23
+ hide: string;
24
+ /** Writes the state file; the host stops injecting the button. */
25
+ disable: string;
26
+ installing: string;
27
+ /** Shown when the install finished and no hot swap happened. */
28
+ restart: string;
29
+ retry: string;
30
+ }
31
+ /** A web-standard request handler the hub exposes once installed. */
32
+ type OnboardingHandler = (request: Request) => Response | Promise<Response>;
33
+ interface CreateOnboardingOptions {
34
+ /** Packages to install when the user clicks Install. Package specs as the package manager accepts them. */
35
+ packages: string[];
36
+ /** Mount base. Default: the hub default `/__devframes/`. */
37
+ base?: string;
38
+ /** Project directory the install runs in. Default: `process.cwd()`. */
39
+ cwd?: string;
40
+ /** Directory of the `hub-ui-onboard.json` state file. Default: `<cwd>/node_modules/.devframe`. */
41
+ stateDir?: string;
42
+ /** Install as a devDependency. Default: `true`. */
43
+ dev?: boolean;
44
+ branding?: OnboardingBranding;
45
+ messages?: Partial<OnboardingMessages>;
46
+ /**
47
+ * Called once the packages are installed and verified. Return a handler
48
+ * to take over every request at `base` in this process; the floating
49
+ * button then loads `<base>embedded.js` from it. Return nothing to ask the
50
+ * user to restart the dev server.
51
+ */
52
+ onInstalled?: () => void | OnboardingHandler | Promise<void | OnboardingHandler>;
53
+ }
54
+ type OnboardingState = 'idle' | 'installing' | 'installed' | 'ready' | 'error' | 'disabled';
55
+ /** The `GET <base>__onboard/status` body. */
56
+ interface OnboardingStatus {
57
+ state: OnboardingState;
58
+ /** The exact command Install runs, for the panel's preview line. */
59
+ command: string;
60
+ branding: OnboardingBranding;
61
+ messages: OnboardingMessages;
62
+ error?: {
63
+ code: string;
64
+ message: string;
65
+ };
66
+ }
67
+ interface Onboarding {
68
+ /** Web-standard handler for every request under `base`. */
69
+ handler: OnboardingHandler;
70
+ /** Connect-style bridge for Node servers (Vite, Express, Fastify with middie). */
71
+ nodeMiddleware: (req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse, next?: (err?: unknown) => void) => void;
72
+ /** `true` when the user disabled DevTools in an earlier session; skip injecting `scriptSrc`. */
73
+ disabled: boolean;
74
+ /**
75
+ * `true` when every named package was already in `node_modules` at
76
+ * creation. `onInstalled` then runs on the first request, which already
77
+ * reaches its handler; the button never shows.
78
+ */
79
+ installed: boolean;
80
+ /** `<base>embedded.js`: the `<script type="module">` URL to inject. */
81
+ scriptSrc: string;
82
+ }
83
+ //#endregion
84
+ //#region src/index.d.ts
85
+ /**
86
+ * Create the stand-in for a hub UI that is not installed yet: a request
87
+ * handler that serves the floating button at `<base>embedded.js` and the
88
+ * `__onboard/*` routes it talks to.
89
+ */
90
+ export declare function createOnboarding(options: CreateOnboardingOptions): Onboarding;
91
+ //#endregion
92
+ export type { CreateOnboardingOptions, Onboarding, OnboardingBranding, OnboardingHandler, OnboardingLogo, OnboardingMessages, OnboardingState, OnboardingStatus };
package/dist/index.mjs ADDED
@@ -0,0 +1,307 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { readFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import process from "node:process";
6
+ import { Readable } from "node:stream";
7
+ import { pipeline } from "node:stream/promises";
8
+ import { fileURLToPath } from "node:url";
9
+ import { Diagnostic, createConsoleReporter, defineDiagnostics } from "nostics";
10
+ import { detect, resolveCommand } from "package-manager-detector";
11
+ import { exec } from "tinyexec";
12
+ //#region src/diagnostics.ts
13
+ /**
14
+ * `DF90xx` is this package's range (see `.agents/08-diagnostics.md`). It
15
+ * uses `nostics` directly: the package must stay free of `devframe` so a
16
+ * host can ship it while devframe itself is not installed.
17
+ */
18
+ const diagnostics = defineDiagnostics({
19
+ docsBase: "https://devfra.me/errors",
20
+ reporters: [createConsoleReporter()],
21
+ codes: {
22
+ DF9000: {
23
+ why: "`createOnboarding()` received no packages to install.",
24
+ fix: "Pass at least one package spec in `packages`, for example `packages: ['@nuxt/devtools']`."
25
+ },
26
+ DF9001: {
27
+ why: (p) => `\`${p.command}\` exited with code ${p.exitCode ?? "unknown"}.${p.stderr ? `\n${p.stderr}` : ""}`,
28
+ fix: "Run the command in a terminal to see the package manager's full output, then click Install again."
29
+ },
30
+ DF9002: {
31
+ why: (p) => `The install finished, but "${p.name}" is not in ${p.cwd}/node_modules.`,
32
+ fix: "Make sure that `cwd` points at the project that should receive the dependency (in a workspace, the package that runs the dev server)."
33
+ },
34
+ DF9003: {
35
+ why: (p) => `\`onInstalled\` threw after the packages were installed: ${p.reason}`,
36
+ fix: "The packages are installed. Fix the error in your `onInstalled` callback, or restart the dev server to load them."
37
+ },
38
+ DF9004: {
39
+ why: (p) => `The onboarding state file ${p.file} could not be read or written.`,
40
+ fix: "Make sure that `stateDir` is writable, or point it at another directory."
41
+ }
42
+ }
43
+ });
44
+ //#endregion
45
+ //#region src/install.ts
46
+ /** The `add` command for the project's package manager, `npm` when none is detected. */
47
+ async function resolveInstallCommand(plan) {
48
+ const agent = (await detect({ cwd: plan.cwd }))?.agent ?? "npm";
49
+ const args = [...plan.dev ? ["-D"] : [], ...plan.packages];
50
+ return resolveCommand(agent, "add", args);
51
+ }
52
+ function formatCommand(command) {
53
+ return [command.command, ...command.args].join(" ");
54
+ }
55
+ /**
56
+ * Run the install and make sure that each named package landed in
57
+ * `<cwd>/node_modules`. Throws a `DF9001` / `DF9002` diagnostic on failure.
58
+ */
59
+ async function runInstall(plan, command) {
60
+ const result = await exec(command.command, command.args, { nodeOptions: {
61
+ cwd: plan.cwd,
62
+ env: {
63
+ ...process.env,
64
+ CI: "1"
65
+ }
66
+ } });
67
+ if (result.exitCode !== 0) throw diagnostics.DF9001({
68
+ command: formatCommand(command),
69
+ exitCode: result.exitCode,
70
+ stderr: result.stderr.trim().slice(-2048)
71
+ });
72
+ for (const name of namedPackages(plan)) if (!isInstalled(plan.cwd, name)) throw diagnostics.DF9002({
73
+ name,
74
+ cwd: plan.cwd
75
+ });
76
+ }
77
+ /**
78
+ * `true` when every named package is already in `<cwd>/node_modules`, so the
79
+ * onboarding has nothing to install. Path, URL and alias specs cannot be
80
+ * checked by name, so a plan made only of those is never "already installed".
81
+ */
82
+ function packagesInstalled(plan) {
83
+ const names = namedPackages(plan);
84
+ return names.length > 0 && names.every((name) => isInstalled(plan.cwd, name));
85
+ }
86
+ function isInstalled(cwd, name) {
87
+ return existsSync(join(cwd, "node_modules", name));
88
+ }
89
+ function namedPackages(plan) {
90
+ return plan.packages.map(packageName).filter((name) => name !== void 0);
91
+ }
92
+ /**
93
+ * The package name of a bare spec (`foo`, `@scope/foo@^1`), or `undefined`
94
+ * for a path, URL or alias spec whose installed name is not in the spec.
95
+ */
96
+ function packageName(spec) {
97
+ return /^((?:@[\w.~-]+\/)?[\w.~-]+)(?:@[^/]*)?$/.exec(spec)?.[1];
98
+ }
99
+ //#endregion
100
+ //#region src/index.ts
101
+ const STATE_FILE = "hub-ui-onboard.json";
102
+ /** `devframes` → `/devframes/`, same rule as `normalizeHubBase` in `@devframes/hub`. */
103
+ function normalizeBase(base) {
104
+ return `/${base}/`.replace(/\/{2,}/g, "/");
105
+ }
106
+ /**
107
+ * The built button lives next to the built entry (`dist/index.mjs` →
108
+ * `dist/client/embedded.js`). Running from source (tests), fall back to
109
+ * the package's `dist/`.
110
+ */
111
+ function clientFile() {
112
+ const here = fileURLToPath(new URL(".", import.meta.url));
113
+ const sibling = join(here, "client/embedded.js");
114
+ return existsSync(sibling) ? sibling : join(here, "../dist/client/embedded.js");
115
+ }
116
+ function defaultMessages(productName) {
117
+ return {
118
+ title: productName,
119
+ description: `${productName} enables development features in your project. Install it with:`,
120
+ install: `Install ${productName}`,
121
+ hide: "Hide for now",
122
+ disable: "Disable entirely",
123
+ installing: "Installing...",
124
+ restart: `Installed. Restart your dev server to open ${productName}.`,
125
+ retry: "Retry"
126
+ };
127
+ }
128
+ function json(body, status = 200) {
129
+ return Response.json(body, {
130
+ status,
131
+ headers: { "Cache-Control": "no-store" }
132
+ });
133
+ }
134
+ /**
135
+ * A browser sends `Origin` on every POST. Any other page on the same machine
136
+ * must not be able to start an install, so the origin has to match the
137
+ * request's own host (port included, which `Sec-Fetch-Site: same-site` ignores).
138
+ */
139
+ function isSameOrigin(request) {
140
+ const origin = request.headers.get("origin");
141
+ if (origin) return new URL(origin).host === new URL(request.url).host;
142
+ const site = request.headers.get("sec-fetch-site");
143
+ return !site || site === "same-origin" || site === "none";
144
+ }
145
+ function readDisabled(file) {
146
+ if (!existsSync(file)) return false;
147
+ try {
148
+ return JSON.parse(readFileSync(file, "utf8")).disabled === true;
149
+ } catch (cause) {
150
+ diagnostics.DF9004({
151
+ file,
152
+ cause
153
+ });
154
+ return false;
155
+ }
156
+ }
157
+ async function toRequest(req) {
158
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
159
+ const headers = new Headers();
160
+ for (const [name, value] of Object.entries(req.headers)) for (const item of Array.isArray(value) ? value : [value]) if (item !== void 0) headers.append(name, item);
161
+ const method = req.method ?? "GET";
162
+ const body = method === "GET" || method === "HEAD" ? void 0 : Buffer.concat(await req.toArray());
163
+ return new Request(url, {
164
+ method,
165
+ headers,
166
+ body
167
+ });
168
+ }
169
+ async function sendResponse(res, response) {
170
+ res.statusCode = response.status;
171
+ response.headers.forEach((value, name) => res.setHeader(name, value));
172
+ if (!response.body) {
173
+ res.end();
174
+ return;
175
+ }
176
+ await pipeline(Readable.fromWeb(response.body), res).catch((cause) => {
177
+ if (!(cause instanceof Error && "code" in cause && cause.code === "ERR_STREAM_PREMATURE_CLOSE")) throw cause;
178
+ });
179
+ }
180
+ /**
181
+ * Create the stand-in for a hub UI that is not installed yet: a request
182
+ * handler that serves the floating button at `<base>embedded.js` and the
183
+ * `__onboard/*` routes it talks to.
184
+ */
185
+ function createOnboarding(options) {
186
+ if (options.packages.length === 0) throw diagnostics.DF9000();
187
+ const base = normalizeBase(options.base ?? "/__devframes/");
188
+ const cwd = options.cwd ?? process.cwd();
189
+ const stateFile = join(options.stateDir ?? join(cwd, "node_modules/.devframe"), STATE_FILE);
190
+ const plan = {
191
+ cwd,
192
+ packages: options.packages,
193
+ dev: options.dev ?? true
194
+ };
195
+ const branding = options.branding ?? {};
196
+ const messages = {
197
+ ...defaultMessages(branding.productName?.trim() || "Devframes"),
198
+ ...options.messages
199
+ };
200
+ const disabled = readDisabled(stateFile);
201
+ const installed = !disabled && packagesInstalled(plan);
202
+ let state = disabled ? "disabled" : installed ? "installed" : "idle";
203
+ let error;
204
+ let delegate;
205
+ let command;
206
+ const getCommand = () => command ??= resolveInstallCommand(plan);
207
+ function fail(cause, fallback) {
208
+ const diagnostic = cause instanceof Diagnostic ? cause : fallback();
209
+ error = {
210
+ code: diagnostic.code,
211
+ message: diagnostic.message
212
+ };
213
+ state = "error";
214
+ }
215
+ /** Hand the base to the host's hub, if it offers one. */
216
+ async function activate() {
217
+ try {
218
+ const next = await options.onInstalled?.();
219
+ delegate = typeof next === "function" ? next : void 0;
220
+ state = delegate ? "ready" : "installed";
221
+ } catch (cause) {
222
+ fail(cause, () => diagnostics.DF9003({
223
+ reason: cause instanceof Error ? cause.message : String(cause),
224
+ cause
225
+ }));
226
+ }
227
+ }
228
+ async function install() {
229
+ state = "installing";
230
+ error = void 0;
231
+ let resolved;
232
+ try {
233
+ resolved = await getCommand();
234
+ await runInstall(plan, resolved);
235
+ } catch (cause) {
236
+ const command = resolved ? formatCommand(resolved) : plan.packages.join(" ");
237
+ fail(cause, () => diagnostics.DF9001({
238
+ command,
239
+ exitCode: void 0,
240
+ stderr: String(cause)
241
+ }));
242
+ return;
243
+ }
244
+ await activate();
245
+ }
246
+ let activated;
247
+ function disable() {
248
+ try {
249
+ mkdirSync(join(stateFile, ".."), { recursive: true });
250
+ writeFileSync(stateFile, JSON.stringify({ disabled: true }));
251
+ } catch (cause) {
252
+ const diagnostic = diagnostics.DF9004({
253
+ file: stateFile,
254
+ cause
255
+ });
256
+ return json({
257
+ code: diagnostic.code,
258
+ message: diagnostic.message
259
+ }, 500);
260
+ }
261
+ state = "disabled";
262
+ return new Response(null, { status: 204 });
263
+ }
264
+ async function status() {
265
+ return {
266
+ state,
267
+ command: formatCommand(await getCommand()),
268
+ branding,
269
+ messages,
270
+ error
271
+ };
272
+ }
273
+ const handler = async (request) => {
274
+ if (installed) await (activated ??= activate());
275
+ if (delegate) return delegate(request);
276
+ const { pathname } = new URL(request.url);
277
+ if (!pathname.startsWith(base)) return new Response(null, { status: 404 });
278
+ const route = `${request.method} ${pathname.slice(base.length)}`;
279
+ if (route === "GET embedded.js") return new Response(await readFile(clientFile()), { headers: {
280
+ "Content-Type": "text/javascript; charset=utf-8",
281
+ "Cache-Control": "no-store"
282
+ } });
283
+ if (route === "GET __onboard/status") return json(await status());
284
+ if (route === "POST __onboard/install" || route === "POST __onboard/disable") {
285
+ if (!isSameOrigin(request)) return new Response(null, { status: 403 });
286
+ if (route.endsWith("disable")) return disable();
287
+ if (state === "idle" || state === "error") install();
288
+ return new Response(null, { status: 202 });
289
+ }
290
+ return new Response(null, { status: 404 });
291
+ };
292
+ return {
293
+ handler,
294
+ nodeMiddleware(req, res, next) {
295
+ if (!(req.url ?? "/").startsWith(base)) {
296
+ next?.();
297
+ return;
298
+ }
299
+ toRequest(req).then((request) => handler(request)).then((response) => sendResponse(res, response)).catch((cause) => next ? next(cause) : res.destroy(cause));
300
+ },
301
+ disabled,
302
+ installed,
303
+ scriptSrc: `${base}embedded.js`
304
+ };
305
+ }
306
+ //#endregion
307
+ export { createOnboarding };
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@devframes/hub-ui-onboard",
3
+ "type": "module",
4
+ "version": "1.2.1",
5
+ "description": "A tiny stand-in for the devframe hub UI: a floating button that installs it on demand.",
6
+ "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/devframes/devframe#readme",
9
+ "repository": {
10
+ "directory": "packages/hub-ui-onboard",
11
+ "type": "git",
12
+ "url": "git+https://github.com/devframes/devframe.git"
13
+ },
14
+ "bugs": "https://github.com/devframes/devframe/issues",
15
+ "keywords": [
16
+ "devtools",
17
+ "devframe",
18
+ "hub",
19
+ "onboard"
20
+ ],
21
+ "sideEffects": false,
22
+ "exports": {
23
+ ".": "./dist/index.mjs",
24
+ "./package.json": "./package.json"
25
+ },
26
+ "types": "./dist/index.d.mts",
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "dependencies": {
31
+ "nostics": "^1.3.0",
32
+ "package-manager-detector": "^1.8.0",
33
+ "tinyexec": "^1.3.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^26.6.4",
37
+ "tsdown": "^0.23.0",
38
+ "tsx": "^4.23.15",
39
+ "unocss": "^66.10.5",
40
+ "vite": "^8.3.2",
41
+ "vitest": "^5.0.3"
42
+ },
43
+ "scripts": {
44
+ "build": "pnpm run build:css && tsdown && vite build --config vite.client.config.ts",
45
+ "build:css": "tsx scripts/build-css.ts",
46
+ "typecheck": "tsc --noEmit",
47
+ "test": "vitest run"
48
+ }
49
+ }