foss-design 0.9.10 → 0.9.11
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/dist/cli.js +622 -382
- package/dist/runtime/index.js +27 -0
- package/dist/viewer/assets/index-Cy6JlR85.css +2 -0
- package/dist/viewer/assets/index-mvGp1q3N.js +159 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer-lib/index.js +986 -474
- package/dist/viewer-lib/styles.css +132 -12
- package/dist/viewer-lib/types/src/shared/types.d.ts +17 -0
- package/dist/viewer-lib/types/viewer/src/app.d.ts +8 -1
- package/dist/viewer-lib/types/viewer/src/components/corner-plate.d.ts +21 -0
- package/dist/viewer-lib/types/viewer/src/components/error-boundary.d.ts +5 -0
- package/dist/viewer-lib/types/viewer/src/components/markdown.d.ts +4 -1
- package/dist/viewer-lib/types/viewer/src/lib/frames.d.ts +10 -2
- package/dist/viewer-lib/types/viewer/src/lib/messages.d.ts +12 -0
- package/dist/viewer-lib/types/viewer/src/lib/viewer.d.ts +14 -0
- package/dist/viewer-lib/types/viewer/src/library.d.ts +4 -1
- package/dist/viewer-lib/types/viewer/src/pages/canvas/full-page.d.ts +1 -1
- package/dist/viewer-lib/types/viewer/src/ui/copy-button.d.ts +7 -0
- package/dist/viewer-lib/types/viewer/src/ui/dialog.d.ts +7 -2
- package/package.json +1 -1
- package/dist/viewer/assets/index-DKqmJSbn.js +0 -158
- package/dist/viewer/assets/index-DnxkAHbH.css +0 -2
package/dist/viewer-lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { QueryClient, QueryClientProvider, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
2
2
|
import { AnimatePresence, MotionConfig, motion, useDragControls } from "motion/react";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { Children, createContext, forwardRef, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
|
+
import { Children, Component, createContext, forwardRef, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
5
|
import { Link, Redirect, Route, Router, Switch as Switch$1, useLocation, useRouter } from "wouter";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import * as ReactDOM from "react-dom";
|
|
@@ -2444,6 +2444,8 @@ function viewerKeys(id) {
|
|
|
2444
2444
|
]
|
|
2445
2445
|
};
|
|
2446
2446
|
}
|
|
2447
|
+
/** What frames of the local viewer may use: the clipboard, both ways, and fullscreen. */
|
|
2448
|
+
var DEFAULT_FRAME_ALLOW = "clipboard-read; clipboard-write; fullscreen";
|
|
2447
2449
|
var ids = /* @__PURE__ */ new WeakMap();
|
|
2448
2450
|
var lastId = 0;
|
|
2449
2451
|
/** A stable id per source object. */
|
|
@@ -3114,6 +3116,414 @@ function CommandPalette() {
|
|
|
3114
3116
|
}) : null })
|
|
3115
3117
|
});
|
|
3116
3118
|
}
|
|
3119
|
+
//#endregion
|
|
3120
|
+
//#region viewer/src/ui/button.tsx
|
|
3121
|
+
var KIND = {
|
|
3122
|
+
primary: "border border-primary bg-primary text-primary-ink hover:opacity-90",
|
|
3123
|
+
secondary: "border border-rule-strong bg-surface text-ink hover:bg-soft",
|
|
3124
|
+
ghost: "border border-transparent bg-transparent text-ink2 hover:bg-soft2",
|
|
3125
|
+
dashed: "border border-dashed border-rule-strong bg-transparent text-ink2 hover:bg-soft",
|
|
3126
|
+
danger: "border border-danger bg-danger text-on-danger hover:opacity-90"
|
|
3127
|
+
};
|
|
3128
|
+
var buttonClass = (kind = "secondary", extra) => cn("inline-flex h-ctrl shrink-0 cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-[6px] px-ctrl text-ctrl font-medium leading-none no-underline transition-[background-color,opacity,scale] duration-100 active:not-disabled:scale-[0.97]", "disabled:cursor-not-allowed disabled:opacity-45 aria-disabled:cursor-not-allowed aria-disabled:opacity-45", KIND[kind], extra);
|
|
3129
|
+
var Button = forwardRef(function Button({ kind = "secondary", icon, loading, disabled, className, children, type = "button", ...rest }, ref) {
|
|
3130
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
3131
|
+
ref,
|
|
3132
|
+
type,
|
|
3133
|
+
disabled: disabled || loading,
|
|
3134
|
+
"aria-busy": loading || void 0,
|
|
3135
|
+
className: buttonClass(kind, className),
|
|
3136
|
+
...rest,
|
|
3137
|
+
children: [loading ? /* @__PURE__ */ jsx(Icon, {
|
|
3138
|
+
name: "loader",
|
|
3139
|
+
size: 15,
|
|
3140
|
+
className: "animate-spin"
|
|
3141
|
+
}) : icon ? /* @__PURE__ */ jsx(Icon, {
|
|
3142
|
+
name: icon,
|
|
3143
|
+
size: 15
|
|
3144
|
+
}) : null, children]
|
|
3145
|
+
});
|
|
3146
|
+
});
|
|
3147
|
+
var ICON_KIND = {
|
|
3148
|
+
ghost: "border border-transparent bg-transparent text-muted hover:bg-soft2 hover:text-ink2",
|
|
3149
|
+
secondary: "border border-rule-strong bg-surface text-ink2 hover:bg-soft"
|
|
3150
|
+
};
|
|
3151
|
+
var iconButtonClass = (kind = "ghost", extra) => cn("inline-flex h-ctrl w-ctrl shrink-0 cursor-pointer items-center justify-center rounded-[6px] p-0 transition-[color,background-color,scale] duration-100 active:not-disabled:scale-[0.94] disabled:cursor-not-allowed disabled:opacity-45", ICON_KIND[kind], extra);
|
|
3152
|
+
var IconButton = forwardRef(function IconButton({ icon, label, kind = "ghost", size = 16, className, type = "button", ...rest }, ref) {
|
|
3153
|
+
return /* @__PURE__ */ jsx("button", {
|
|
3154
|
+
ref,
|
|
3155
|
+
type,
|
|
3156
|
+
"aria-label": label,
|
|
3157
|
+
title: label,
|
|
3158
|
+
className: iconButtonClass(kind, className),
|
|
3159
|
+
...rest,
|
|
3160
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
3161
|
+
name: icon,
|
|
3162
|
+
size
|
|
3163
|
+
})
|
|
3164
|
+
});
|
|
3165
|
+
});
|
|
3166
|
+
/** An in-app link styled as a button. */
|
|
3167
|
+
function ButtonLink({ to, kind = "secondary", icon, className, children }) {
|
|
3168
|
+
return /* @__PURE__ */ jsxs(Link, {
|
|
3169
|
+
href: to,
|
|
3170
|
+
className: buttonClass(kind, className),
|
|
3171
|
+
children: [icon ? /* @__PURE__ */ jsx(Icon, {
|
|
3172
|
+
name: icon,
|
|
3173
|
+
size: 15
|
|
3174
|
+
}) : null, children]
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3177
|
+
/** A link out of the app (a frame in a new tab), styled as a button. */
|
|
3178
|
+
function ButtonAnchor({ kind = "secondary", icon, className, children, ...rest }) {
|
|
3179
|
+
return /* @__PURE__ */ jsxs("a", {
|
|
3180
|
+
className: buttonClass(kind, className),
|
|
3181
|
+
...rest,
|
|
3182
|
+
children: [icon ? /* @__PURE__ */ jsx(Icon, {
|
|
3183
|
+
name: icon,
|
|
3184
|
+
size: 15
|
|
3185
|
+
}) : null, children]
|
|
3186
|
+
});
|
|
3187
|
+
}
|
|
3188
|
+
//#endregion
|
|
3189
|
+
//#region viewer/src/ui/badge.tsx
|
|
3190
|
+
var BADGE = {
|
|
3191
|
+
neutral: "bg-soft2 text-ink2",
|
|
3192
|
+
action: "bg-action-soft text-action",
|
|
3193
|
+
ok: "bg-ok-soft text-ok-text",
|
|
3194
|
+
danger: "bg-danger-soft text-danger-text"
|
|
3195
|
+
};
|
|
3196
|
+
function Badge({ tone = "neutral", children, className }) {
|
|
3197
|
+
return /* @__PURE__ */ jsx("span", {
|
|
3198
|
+
className: cn("inline-flex h-[22px] shrink-0 items-center gap-1 whitespace-nowrap rounded-[6px] px-2 text-[12px] font-medium", BADGE[tone], className),
|
|
3199
|
+
children
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
function Count({ value, tone = "neutral" }) {
|
|
3203
|
+
return /* @__PURE__ */ jsx("span", {
|
|
3204
|
+
className: cn("inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-[4px] px-[5px] text-[11.5px] font-semibold tabular", tone === "action" ? "bg-action-soft text-action" : tone === "danger" ? "bg-danger-soft text-danger-text" : "bg-soft2 text-ink2"),
|
|
3205
|
+
children: value
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
//#endregion
|
|
3209
|
+
//#region viewer/src/ui/panel.tsx
|
|
3210
|
+
function Panel({ children, className, style }) {
|
|
3211
|
+
return /* @__PURE__ */ jsx("section", {
|
|
3212
|
+
className: cn("min-w-0 shrink-0 overflow-hidden rounded-[10px] border border-rule bg-surface", className),
|
|
3213
|
+
style,
|
|
3214
|
+
children
|
|
3215
|
+
});
|
|
3216
|
+
}
|
|
3217
|
+
function PanelHead({ title, count, sub, right, as: Heading = "h2" }) {
|
|
3218
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3219
|
+
className: "flex min-h-[46px] items-center justify-between gap-3 px-4",
|
|
3220
|
+
children: [/* @__PURE__ */ jsxs(Heading, {
|
|
3221
|
+
className: "m-0 flex min-w-0 items-center gap-2 whitespace-nowrap text-[14px] font-semibold",
|
|
3222
|
+
children: [
|
|
3223
|
+
title,
|
|
3224
|
+
count !== void 0 ? /* @__PURE__ */ jsx(Count, { value: count }) : null,
|
|
3225
|
+
sub ? /* @__PURE__ */ jsx("span", {
|
|
3226
|
+
className: "truncate text-[12.5px] font-normal text-muted",
|
|
3227
|
+
children: sub
|
|
3228
|
+
}) : null
|
|
3229
|
+
]
|
|
3230
|
+
}), right ? /* @__PURE__ */ jsx("div", {
|
|
3231
|
+
className: "flex items-center gap-2",
|
|
3232
|
+
children: right
|
|
3233
|
+
}) : null]
|
|
3234
|
+
});
|
|
3235
|
+
}
|
|
3236
|
+
function PanelTabs({ items, value, onChange, right }) {
|
|
3237
|
+
const indicator = useId();
|
|
3238
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3239
|
+
className: "flex h-[46px] items-center justify-between gap-3 px-4",
|
|
3240
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
3241
|
+
role: "tablist",
|
|
3242
|
+
className: "flex gap-5",
|
|
3243
|
+
children: items.map((item) => {
|
|
3244
|
+
const on = item.value === value;
|
|
3245
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
3246
|
+
type: "button",
|
|
3247
|
+
role: "tab",
|
|
3248
|
+
"aria-selected": on,
|
|
3249
|
+
onClick: () => onChange(item.value),
|
|
3250
|
+
className: cn("relative flex h-[46px] cursor-pointer items-center gap-1.5 border-0 bg-transparent px-0.5 text-[14px] transition-colors", on ? "font-semibold text-ink" : "font-medium text-ink2 hover:text-ink"),
|
|
3251
|
+
children: [
|
|
3252
|
+
item.label,
|
|
3253
|
+
item.count !== void 0 ? /* @__PURE__ */ jsx(Count, { value: item.count }) : null,
|
|
3254
|
+
on ? /* @__PURE__ */ jsx(motion.span, {
|
|
3255
|
+
layoutId: indicator,
|
|
3256
|
+
className: "absolute inset-x-0 bottom-0 h-[2px] bg-ink",
|
|
3257
|
+
transition: {
|
|
3258
|
+
type: "spring",
|
|
3259
|
+
stiffness: 520,
|
|
3260
|
+
damping: 42
|
|
3261
|
+
}
|
|
3262
|
+
}) : null
|
|
3263
|
+
]
|
|
3264
|
+
}, item.value);
|
|
3265
|
+
})
|
|
3266
|
+
}), right ? /* @__PURE__ */ jsx("div", {
|
|
3267
|
+
className: "flex items-center gap-2",
|
|
3268
|
+
children: right
|
|
3269
|
+
}) : null]
|
|
3270
|
+
});
|
|
3271
|
+
}
|
|
3272
|
+
function Cell({ align, children }) {
|
|
3273
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3274
|
+
className: cn("flex min-w-0 items-center gap-2", align === "r" && "justify-end text-right", align === "c" && "justify-center"),
|
|
3275
|
+
children
|
|
3276
|
+
});
|
|
3277
|
+
}
|
|
3278
|
+
function cells(children, align) {
|
|
3279
|
+
return Children.toArray(children).map((child, index) => /* @__PURE__ */ jsx(Cell, {
|
|
3280
|
+
align: align?.[index] ?? "l",
|
|
3281
|
+
children: child
|
|
3282
|
+
}, index));
|
|
3283
|
+
}
|
|
3284
|
+
function THead({ cols, align, labels }) {
|
|
3285
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3286
|
+
className: "grid h-8 items-center gap-x-4 border-t border-rule bg-soft px-4 text-[12.5px] text-muted",
|
|
3287
|
+
style: { gridTemplateColumns: cols },
|
|
3288
|
+
children: labels.map((label, index) => /* @__PURE__ */ jsx(Cell, {
|
|
3289
|
+
align: align?.[index] ?? "l",
|
|
3290
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3291
|
+
className: "whitespace-nowrap",
|
|
3292
|
+
children: label
|
|
3293
|
+
})
|
|
3294
|
+
}, index))
|
|
3295
|
+
});
|
|
3296
|
+
}
|
|
3297
|
+
var rowClass = (first, interactive, className) => cn("grid items-center gap-x-4 border-rule", !first && "border-t", interactive && "cursor-pointer transition-colors hover:bg-soft", className);
|
|
3298
|
+
function Row({ cols, align, minH = 44, pad = "10px 16px", children, first, className, onClick, title }) {
|
|
3299
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3300
|
+
className: rowClass(first, !!onClick, className),
|
|
3301
|
+
style: {
|
|
3302
|
+
gridTemplateColumns: cols,
|
|
3303
|
+
minHeight: minH,
|
|
3304
|
+
padding: pad
|
|
3305
|
+
},
|
|
3306
|
+
onClick,
|
|
3307
|
+
title,
|
|
3308
|
+
children: cells(children, align)
|
|
3309
|
+
});
|
|
3310
|
+
}
|
|
3311
|
+
/** A table row that navigates somewhere in the app. */
|
|
3312
|
+
function RowLink({ to, cols, align, minH = 44, pad = "10px 16px", first, className, children }) {
|
|
3313
|
+
return /* @__PURE__ */ jsx(Link, {
|
|
3314
|
+
href: to,
|
|
3315
|
+
className: cn(rowClass(first, true, className), "text-inherit no-underline"),
|
|
3316
|
+
style: {
|
|
3317
|
+
gridTemplateColumns: cols,
|
|
3318
|
+
minHeight: minH,
|
|
3319
|
+
padding: pad
|
|
3320
|
+
},
|
|
3321
|
+
children: cells(children, align)
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
function KV({ label, children, labelWidth = 180, minH = 40, end = false }) {
|
|
3325
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3326
|
+
className: "grid content-center items-baseline gap-x-4 border-t border-rule px-4 py-2",
|
|
3327
|
+
style: {
|
|
3328
|
+
gridTemplateColumns: `min(${labelWidth}px, 42%) minmax(0, 1fr)`,
|
|
3329
|
+
minHeight: minH
|
|
3330
|
+
},
|
|
3331
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
3332
|
+
className: "text-[13px] text-muted",
|
|
3333
|
+
children: label
|
|
3334
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
3335
|
+
className: cn("flex min-w-0 flex-wrap items-center gap-2", end && "justify-end"),
|
|
3336
|
+
children
|
|
3337
|
+
})]
|
|
3338
|
+
});
|
|
3339
|
+
}
|
|
3340
|
+
function PanelBody({ children, pad = "16px", gap = 16, top = true, className }) {
|
|
3341
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3342
|
+
className: cn("flex flex-col", top && "border-t border-rule", className),
|
|
3343
|
+
style: {
|
|
3344
|
+
padding: pad,
|
|
3345
|
+
gap
|
|
3346
|
+
},
|
|
3347
|
+
children
|
|
3348
|
+
});
|
|
3349
|
+
}
|
|
3350
|
+
function PanelFoot({ left, right }) {
|
|
3351
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3352
|
+
className: "flex items-center justify-between gap-3 border-t border-rule bg-soft px-4 py-3",
|
|
3353
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
3354
|
+
className: "flex min-w-0 items-center gap-2 text-[12.5px] text-muted",
|
|
3355
|
+
children: left
|
|
3356
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
3357
|
+
className: "flex items-center gap-2",
|
|
3358
|
+
children: right
|
|
3359
|
+
})]
|
|
3360
|
+
});
|
|
3361
|
+
}
|
|
3362
|
+
/** Bottom summary strip. */
|
|
3363
|
+
function Strip({ children, className }) {
|
|
3364
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3365
|
+
className: cn("flex min-h-[38px] shrink-0 items-center gap-2 border-t border-rule bg-soft px-4 py-2 text-[12.5px] text-muted", className),
|
|
3366
|
+
children
|
|
3367
|
+
});
|
|
3368
|
+
}
|
|
3369
|
+
/** Group header row inside tables. */
|
|
3370
|
+
function GroupRow({ children, sub }) {
|
|
3371
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3372
|
+
className: "flex h-9 items-center gap-2.5 border-t border-rule bg-soft px-4 text-[13px] font-medium",
|
|
3373
|
+
children: [children, sub ? /* @__PURE__ */ jsx("span", {
|
|
3374
|
+
className: "text-[12.5px] font-normal text-muted",
|
|
3375
|
+
children: sub
|
|
3376
|
+
}) : null]
|
|
3377
|
+
});
|
|
3378
|
+
}
|
|
3379
|
+
//#endregion
|
|
3380
|
+
//#region viewer/src/ui/page.tsx
|
|
3381
|
+
/** Page body under the top bar. */
|
|
3382
|
+
function Content({ children, className, gap = 20, wide = false }) {
|
|
3383
|
+
return /* @__PURE__ */ jsx("main", {
|
|
3384
|
+
className: cn("mx-auto flex w-full min-h-0 grow flex-col px-8 pt-6 pb-7 animate-[q-fade-in_160ms_ease-out] max-md:px-4 max-md:pt-4", wide ? "max-w-[1440px]" : "max-w-[1200px]", className),
|
|
3385
|
+
style: { gap },
|
|
3386
|
+
children
|
|
3387
|
+
});
|
|
3388
|
+
}
|
|
3389
|
+
function PageHead({ title, sub, right, badge, crumbs }) {
|
|
3390
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3391
|
+
className: "flex shrink-0 items-end justify-between gap-6 max-md:flex-col max-md:items-stretch max-md:gap-3",
|
|
3392
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
3393
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
3394
|
+
children: [
|
|
3395
|
+
crumbs ? /* @__PURE__ */ jsx("div", {
|
|
3396
|
+
className: "flex items-center gap-1.5 text-[13px] text-muted",
|
|
3397
|
+
children: crumbs
|
|
3398
|
+
}) : null,
|
|
3399
|
+
/* @__PURE__ */ jsxs("div", {
|
|
3400
|
+
className: "flex items-center gap-3",
|
|
3401
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
3402
|
+
className: "m-0 text-[22px] font-semibold tracking-[-0.01em]",
|
|
3403
|
+
children: title
|
|
3404
|
+
}), badge]
|
|
3405
|
+
}),
|
|
3406
|
+
sub ? /* @__PURE__ */ jsx("div", {
|
|
3407
|
+
className: "text-[13px] [overflow-wrap:anywhere] text-muted",
|
|
3408
|
+
children: sub
|
|
3409
|
+
}) : null
|
|
3410
|
+
]
|
|
3411
|
+
}), right ? /* @__PURE__ */ jsx("div", {
|
|
3412
|
+
className: "flex flex-wrap items-center gap-2",
|
|
3413
|
+
children: right
|
|
3414
|
+
}) : null]
|
|
3415
|
+
});
|
|
3416
|
+
}
|
|
3417
|
+
function SubNav({ items, label }) {
|
|
3418
|
+
const [location] = useLocation();
|
|
3419
|
+
return /* @__PURE__ */ jsx("nav", {
|
|
3420
|
+
"aria-label": label,
|
|
3421
|
+
className: "flex flex-col gap-0.5",
|
|
3422
|
+
children: items.map((item) => {
|
|
3423
|
+
const active = location === item.to;
|
|
3424
|
+
return /* @__PURE__ */ jsxs(Link, {
|
|
3425
|
+
href: item.to,
|
|
3426
|
+
"aria-current": active ? "page" : void 0,
|
|
3427
|
+
className: cn("relative flex h-8 items-center gap-2 whitespace-nowrap rounded-[6px] px-3 text-[13.5px] no-underline transition-colors focus-visible:outline-offset-[-2px]", active ? "font-medium text-ink" : "text-ink2 hover:bg-soft2"),
|
|
3428
|
+
children: [
|
|
3429
|
+
active ? /* @__PURE__ */ jsx(motion.span, {
|
|
3430
|
+
layoutId: "sub-nav-active",
|
|
3431
|
+
"aria-hidden": true,
|
|
3432
|
+
className: "absolute inset-0 z-0 rounded-[6px] bg-soft2",
|
|
3433
|
+
transition: {
|
|
3434
|
+
type: "spring",
|
|
3435
|
+
stiffness: 520,
|
|
3436
|
+
damping: 42
|
|
3437
|
+
}
|
|
3438
|
+
}) : null,
|
|
3439
|
+
/* @__PURE__ */ jsx("span", {
|
|
3440
|
+
className: "relative z-[1] min-w-0 truncate",
|
|
3441
|
+
children: item.label
|
|
3442
|
+
}),
|
|
3443
|
+
item.hint ? /* @__PURE__ */ jsx("span", {
|
|
3444
|
+
className: "relative z-[1] ml-auto text-[12px] font-normal text-muted",
|
|
3445
|
+
children: item.hint
|
|
3446
|
+
}) : null
|
|
3447
|
+
]
|
|
3448
|
+
}, item.to);
|
|
3449
|
+
})
|
|
3450
|
+
});
|
|
3451
|
+
}
|
|
3452
|
+
function NavLabel({ children }) {
|
|
3453
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3454
|
+
className: "px-3 pt-4 pb-1 text-[12px] text-muted",
|
|
3455
|
+
children
|
|
3456
|
+
});
|
|
3457
|
+
}
|
|
3458
|
+
/** Standalone message card: not found, empty, failed to load. */
|
|
3459
|
+
function NoticeCard({ title, text, action }) {
|
|
3460
|
+
return /* @__PURE__ */ jsxs(Panel, {
|
|
3461
|
+
className: "flex w-[480px] max-w-full flex-col items-center gap-2 px-6 py-8 text-center",
|
|
3462
|
+
children: [
|
|
3463
|
+
/* @__PURE__ */ jsx("h1", {
|
|
3464
|
+
className: "m-0 text-[16px] font-semibold",
|
|
3465
|
+
children: title
|
|
3466
|
+
}),
|
|
3467
|
+
text ? /* @__PURE__ */ jsx("div", {
|
|
3468
|
+
className: "m-0 max-w-[400px] text-[14px] text-ink2",
|
|
3469
|
+
children: text
|
|
3470
|
+
}) : null,
|
|
3471
|
+
action ? /* @__PURE__ */ jsx("div", {
|
|
3472
|
+
className: "mt-3 flex flex-wrap justify-center gap-2",
|
|
3473
|
+
children: action
|
|
3474
|
+
}) : null
|
|
3475
|
+
]
|
|
3476
|
+
});
|
|
3477
|
+
}
|
|
3478
|
+
/** NoticeCard centred in the free space. */
|
|
3479
|
+
function Notice(props) {
|
|
3480
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3481
|
+
className: "flex min-h-[50vh] grow items-center justify-center p-6 max-md:px-4",
|
|
3482
|
+
children: /* @__PURE__ */ jsx(NoticeCard, { ...props })
|
|
3483
|
+
});
|
|
3484
|
+
}
|
|
3485
|
+
//#endregion
|
|
3486
|
+
//#region viewer/src/components/error-boundary.tsx
|
|
3487
|
+
function Failed({ error }) {
|
|
3488
|
+
const scoped = !!useViewer().scope;
|
|
3489
|
+
return /* @__PURE__ */ jsx(Notice, {
|
|
3490
|
+
title: "This page failed to show",
|
|
3491
|
+
text: /* @__PURE__ */ jsx("span", {
|
|
3492
|
+
className: "font-mono text-[12.5px] break-words",
|
|
3493
|
+
children: error.message.slice(0, 500)
|
|
3494
|
+
}),
|
|
3495
|
+
action: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
|
|
3496
|
+
onClick: () => window.location.reload(),
|
|
3497
|
+
children: "Reload"
|
|
3498
|
+
}), scoped ? null : /* @__PURE__ */ jsx(ButtonLink, {
|
|
3499
|
+
to: "/",
|
|
3500
|
+
children: "All canvases"
|
|
3501
|
+
})] })
|
|
3502
|
+
});
|
|
3503
|
+
}
|
|
3504
|
+
var Boundary = class extends Component {
|
|
3505
|
+
state = { error: null };
|
|
3506
|
+
static getDerivedStateFromError(error) {
|
|
3507
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
3508
|
+
}
|
|
3509
|
+
componentDidCatch(error, info) {
|
|
3510
|
+
console.error("[design] a page failed to render", error, info.componentStack);
|
|
3511
|
+
}
|
|
3512
|
+
componentDidUpdate(previous) {
|
|
3513
|
+
if (this.state.error && previous.at !== this.props.at) this.setState({ error: null });
|
|
3514
|
+
}
|
|
3515
|
+
render() {
|
|
3516
|
+
return this.state.error ? /* @__PURE__ */ jsx(Failed, { error: this.state.error }) : this.props.children;
|
|
3517
|
+
}
|
|
3518
|
+
};
|
|
3519
|
+
/** One page that throws shows what went wrong in its place, instead of blanking the whole viewer. */
|
|
3520
|
+
function PageBoundary({ children }) {
|
|
3521
|
+
const [location] = useLocation();
|
|
3522
|
+
return /* @__PURE__ */ jsx(Boundary, {
|
|
3523
|
+
at: location,
|
|
3524
|
+
children
|
|
3525
|
+
});
|
|
3526
|
+
}
|
|
3117
3527
|
/** The shortcut modifier as the user's keyboard labels it. */
|
|
3118
3528
|
var MOD_KEY = /Mac|iPhone|iPad/.test(navigator.platform) ? "⌘" : "Ctrl";
|
|
3119
3529
|
//#endregion
|
|
@@ -3142,7 +3552,7 @@ var oppositeSideMap = {
|
|
|
3142
3552
|
bottom: "top",
|
|
3143
3553
|
top: "bottom"
|
|
3144
3554
|
};
|
|
3145
|
-
function clamp(start, value, end) {
|
|
3555
|
+
function clamp$1(start, value, end) {
|
|
3146
3556
|
return max(start, min(value, end));
|
|
3147
3557
|
}
|
|
3148
3558
|
function evaluate(value, param) {
|
|
@@ -3442,7 +3852,7 @@ var arrow$3 = (options) => ({
|
|
|
3442
3852
|
const maxPadding = min(paddingObject[maxProp], largestPossiblePadding);
|
|
3443
3853
|
const max = clientSize - arrowDimensions[length] - maxPadding;
|
|
3444
3854
|
const center = clientSize / 2 - arrowDimensions[length] / 2 + centerToReference;
|
|
3445
|
-
const offset = clamp(minPadding, center, max);
|
|
3855
|
+
const offset = clamp$1(minPadding, center, max);
|
|
3446
3856
|
const shouldAddOffset = !middlewareData.arrow && getAlignment(placement) != null && center !== offset && rects.reference[length] / 2 - (center < minPadding ? minPadding : maxPadding) - arrowDimensions[length] / 2 < 0;
|
|
3447
3857
|
const alignmentOffset = shouldAddOffset ? center < minPadding ? center - minPadding : center - max : 0;
|
|
3448
3858
|
return {
|
|
@@ -3661,7 +4071,7 @@ var shift$2 = function(options) {
|
|
|
3661
4071
|
const mainAxis = getOppositeAxis(crossAxis);
|
|
3662
4072
|
let mainAxisCoord = coords[mainAxis];
|
|
3663
4073
|
let crossAxisCoord = coords[crossAxis];
|
|
3664
|
-
const clampCoord = (axis, coord) => clamp(coord + overflow[axis === "y" ? "top" : "left"], coord, coord - overflow[axis === "y" ? "bottom" : "right"]);
|
|
4074
|
+
const clampCoord = (axis, coord) => clamp$1(coord + overflow[axis === "y" ? "top" : "left"], coord, coord - overflow[axis === "y" ? "bottom" : "right"]);
|
|
3665
4075
|
if (checkMainAxis) mainAxisCoord = clampCoord(mainAxis, mainAxisCoord);
|
|
3666
4076
|
if (checkCrossAxis) crossAxisCoord = clampCoord(crossAxis, crossAxisCoord);
|
|
3667
4077
|
const limitedCoords = limiter.fn({
|
|
@@ -5607,75 +6017,6 @@ function SearchButton({ width = 240, label = "Search and jump", compact }) {
|
|
|
5607
6017
|
});
|
|
5608
6018
|
}
|
|
5609
6019
|
//#endregion
|
|
5610
|
-
//#region viewer/src/ui/button.tsx
|
|
5611
|
-
var KIND = {
|
|
5612
|
-
primary: "border border-primary bg-primary text-primary-ink hover:opacity-90",
|
|
5613
|
-
secondary: "border border-rule-strong bg-surface text-ink hover:bg-soft",
|
|
5614
|
-
ghost: "border border-transparent bg-transparent text-ink2 hover:bg-soft2",
|
|
5615
|
-
dashed: "border border-dashed border-rule-strong bg-transparent text-ink2 hover:bg-soft",
|
|
5616
|
-
danger: "border border-danger bg-danger text-on-danger hover:opacity-90"
|
|
5617
|
-
};
|
|
5618
|
-
var buttonClass = (kind = "secondary", extra) => cn("inline-flex h-ctrl shrink-0 cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-[6px] px-ctrl text-ctrl font-medium leading-none no-underline transition-[background-color,opacity] duration-100", "disabled:cursor-not-allowed disabled:opacity-45 aria-disabled:cursor-not-allowed aria-disabled:opacity-45", KIND[kind], extra);
|
|
5619
|
-
var Button = forwardRef(function Button({ kind = "secondary", icon, loading, disabled, className, children, type = "button", ...rest }, ref) {
|
|
5620
|
-
return /* @__PURE__ */ jsxs("button", {
|
|
5621
|
-
ref,
|
|
5622
|
-
type,
|
|
5623
|
-
disabled: disabled || loading,
|
|
5624
|
-
"aria-busy": loading || void 0,
|
|
5625
|
-
className: buttonClass(kind, className),
|
|
5626
|
-
...rest,
|
|
5627
|
-
children: [loading ? /* @__PURE__ */ jsx(Icon, {
|
|
5628
|
-
name: "loader",
|
|
5629
|
-
size: 15,
|
|
5630
|
-
className: "animate-spin"
|
|
5631
|
-
}) : icon ? /* @__PURE__ */ jsx(Icon, {
|
|
5632
|
-
name: icon,
|
|
5633
|
-
size: 15
|
|
5634
|
-
}) : null, children]
|
|
5635
|
-
});
|
|
5636
|
-
});
|
|
5637
|
-
var ICON_KIND = {
|
|
5638
|
-
ghost: "border border-transparent bg-transparent text-muted hover:bg-soft2 hover:text-ink2",
|
|
5639
|
-
secondary: "border border-rule-strong bg-surface text-ink2 hover:bg-soft"
|
|
5640
|
-
};
|
|
5641
|
-
var iconButtonClass = (kind = "ghost", extra) => cn("inline-flex h-ctrl w-ctrl shrink-0 cursor-pointer items-center justify-center rounded-[6px] p-0 transition-colors duration-100 disabled:cursor-not-allowed disabled:opacity-45", ICON_KIND[kind], extra);
|
|
5642
|
-
var IconButton = forwardRef(function IconButton({ icon, label, kind = "ghost", size = 16, className, type = "button", ...rest }, ref) {
|
|
5643
|
-
return /* @__PURE__ */ jsx("button", {
|
|
5644
|
-
ref,
|
|
5645
|
-
type,
|
|
5646
|
-
"aria-label": label,
|
|
5647
|
-
title: label,
|
|
5648
|
-
className: iconButtonClass(kind, className),
|
|
5649
|
-
...rest,
|
|
5650
|
-
children: /* @__PURE__ */ jsx(Icon, {
|
|
5651
|
-
name: icon,
|
|
5652
|
-
size
|
|
5653
|
-
})
|
|
5654
|
-
});
|
|
5655
|
-
});
|
|
5656
|
-
/** An in-app link styled as a button. */
|
|
5657
|
-
function ButtonLink({ to, kind = "secondary", icon, className, children }) {
|
|
5658
|
-
return /* @__PURE__ */ jsxs(Link, {
|
|
5659
|
-
href: to,
|
|
5660
|
-
className: buttonClass(kind, className),
|
|
5661
|
-
children: [icon ? /* @__PURE__ */ jsx(Icon, {
|
|
5662
|
-
name: icon,
|
|
5663
|
-
size: 15
|
|
5664
|
-
}) : null, children]
|
|
5665
|
-
});
|
|
5666
|
-
}
|
|
5667
|
-
/** A link out of the app (a frame in a new tab), styled as a button. */
|
|
5668
|
-
function ButtonAnchor({ kind = "secondary", icon, className, children, ...rest }) {
|
|
5669
|
-
return /* @__PURE__ */ jsxs("a", {
|
|
5670
|
-
className: buttonClass(kind, className),
|
|
5671
|
-
...rest,
|
|
5672
|
-
children: [icon ? /* @__PURE__ */ jsx(Icon, {
|
|
5673
|
-
name: icon,
|
|
5674
|
-
size: 15
|
|
5675
|
-
}) : null, children]
|
|
5676
|
-
});
|
|
5677
|
-
}
|
|
5678
|
-
//#endregion
|
|
5679
6020
|
//#region ../../node_modules/.pnpm/@radix-ui+react-collection@1.1.15_@types+react-dom@19.3.0_@types+react@19.3.0__@types+r_ed97e98f526881a1c9b799d335ea2adc/node_modules/@radix-ui/react-collection/dist/index.mjs
|
|
5680
6021
|
var __defProp$6 = Object.defineProperty;
|
|
5681
6022
|
var __name$6 = (target, value) => __defProp$6(target, "name", {
|
|
@@ -9521,14 +9862,44 @@ var SAFE_SCHEMES = /* @__PURE__ */ new Set([
|
|
|
9521
9862
|
"https:",
|
|
9522
9863
|
"mailto:"
|
|
9523
9864
|
]);
|
|
9524
|
-
/**
|
|
9865
|
+
/**
|
|
9866
|
+
* http, https, mailto or relative, read as the browser reads a URL (it drops whitespace and
|
|
9867
|
+
* control characters inside a scheme, and so does URL parsing).
|
|
9868
|
+
*/
|
|
9525
9869
|
function isSafeHref(href) {
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9870
|
+
try {
|
|
9871
|
+
return SAFE_SCHEMES.has(new URL(href, "http://relative.invalid/").protocol);
|
|
9872
|
+
} catch {
|
|
9873
|
+
return false;
|
|
9874
|
+
}
|
|
9529
9875
|
}
|
|
9530
9876
|
function escapeHtml(text) {
|
|
9531
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
9877
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
9878
|
+
}
|
|
9879
|
+
var NAMED = {
|
|
9880
|
+
amp: "&",
|
|
9881
|
+
lt: "<",
|
|
9882
|
+
gt: ">",
|
|
9883
|
+
quot: "\"",
|
|
9884
|
+
apos: "'",
|
|
9885
|
+
colon: ":",
|
|
9886
|
+
sol: "/",
|
|
9887
|
+
period: ".",
|
|
9888
|
+
num: "#",
|
|
9889
|
+
quest: "?",
|
|
9890
|
+
equals: "=",
|
|
9891
|
+
percnt: "%",
|
|
9892
|
+
tab: " ",
|
|
9893
|
+
newline: "\n",
|
|
9894
|
+
nbsp: "\xA0"
|
|
9895
|
+
};
|
|
9896
|
+
/** Character references in a link or image destination, which Markdown reads as the characters they name. */
|
|
9897
|
+
function decodeEntities(text) {
|
|
9898
|
+
return text.replace(/&(?:#(\d{1,7})|#x([0-9a-f]{1,6})|([a-z]{2,8}));/gi, (whole, dec, hex, name) => {
|
|
9899
|
+
if (name) return NAMED[name.toLowerCase()] ?? whole;
|
|
9900
|
+
const code = dec ? Number(dec) : Number.parseInt(hex, 16);
|
|
9901
|
+
return code > 0 && code <= 1114111 && (code < 55296 || code > 57343) ? String.fromCodePoint(code) : "�";
|
|
9902
|
+
});
|
|
9532
9903
|
}
|
|
9533
9904
|
var markdown = new F({
|
|
9534
9905
|
gfm: true,
|
|
@@ -9536,14 +9907,22 @@ var markdown = new F({
|
|
|
9536
9907
|
}, { renderer: {
|
|
9537
9908
|
html: ({ text }) => escapeHtml(text),
|
|
9538
9909
|
link(token) {
|
|
9539
|
-
|
|
9910
|
+
const text = this.parser.parseInline(token.tokens);
|
|
9911
|
+
const href = decodeEntities(token.href);
|
|
9912
|
+
if (!isSafeHref(href)) return text;
|
|
9913
|
+
const title = token.title ? ` title="${escapeHtml(decodeEntities(token.title))}"` : "";
|
|
9914
|
+
return `<a target="_blank" rel="noreferrer" href="${escapeHtml(href)}"${title}>${text}</a>`;
|
|
9540
9915
|
},
|
|
9541
9916
|
image(token) {
|
|
9542
|
-
|
|
9917
|
+
const alt = escapeHtml(decodeEntities(token.text));
|
|
9918
|
+
const src = decodeEntities(token.href);
|
|
9919
|
+
if (!isSafeHref(src)) return alt;
|
|
9920
|
+
const title = token.title ? ` title="${escapeHtml(decodeEntities(token.title))}"` : "";
|
|
9921
|
+
return `<img src="${escapeHtml(src)}" alt="${alt}"${title}>`;
|
|
9543
9922
|
}
|
|
9544
9923
|
} });
|
|
9545
9924
|
function renderMarkdown(text) {
|
|
9546
|
-
return markdown.parse(text, { async: false })
|
|
9925
|
+
return markdown.parse(text, { async: false });
|
|
9547
9926
|
}
|
|
9548
9927
|
/** Markdown from the project's own files (guidelines, notes, descriptions). */
|
|
9549
9928
|
function Markdown({ text, className }) {
|
|
@@ -9554,13 +9933,155 @@ function Markdown({ text, className }) {
|
|
|
9554
9933
|
});
|
|
9555
9934
|
}
|
|
9556
9935
|
//#endregion
|
|
9936
|
+
//#region viewer/src/lib/messages.ts
|
|
9937
|
+
/**
|
|
9938
|
+
* Frames run code anyone who can push wrote, and any of them may post the viewer anything: a
|
|
9939
|
+
* message only reaches the viewer's handlers with the fields its type has, of their types.
|
|
9940
|
+
* Strings are cut and sizes kept within reason, so a frame cannot swell the viewer's state.
|
|
9941
|
+
*/
|
|
9942
|
+
var MAX_FRAME_SIZE = 1e5;
|
|
9943
|
+
var MAX_TEXT = 2e3;
|
|
9944
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9945
|
+
var isString = (value) => typeof value === "string";
|
|
9946
|
+
var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
9947
|
+
var isBoolean = (value) => typeof value === "boolean";
|
|
9948
|
+
var optional = (check) => (value) => value === void 0 || check(value);
|
|
9949
|
+
var listOf = (check) => (value) => Array.isArray(value) && value.every(check);
|
|
9950
|
+
var shape = (fields) => (value) => isObject(value) && Object.entries(fields).every(([key, check]) => check(value[key]));
|
|
9951
|
+
var isPair = (value) => Array.isArray(value) && value.length === 2 && isString(value[0]) && isString(value[1]);
|
|
9952
|
+
var isBox = (value) => Array.isArray(value) && value.length === 4 && value.every(isNumber);
|
|
9953
|
+
var isCrumb = shape({
|
|
9954
|
+
ref: isNumber,
|
|
9955
|
+
label: isString
|
|
9956
|
+
});
|
|
9957
|
+
var isColor = shape({
|
|
9958
|
+
value: isString,
|
|
9959
|
+
css: isString,
|
|
9960
|
+
token: optional(isString)
|
|
9961
|
+
});
|
|
9962
|
+
var isTokenValue = shape({
|
|
9963
|
+
value: isString,
|
|
9964
|
+
token: optional(isString)
|
|
9965
|
+
});
|
|
9966
|
+
var isElementInfo = shape({
|
|
9967
|
+
ref: isNumber,
|
|
9968
|
+
tag: isString,
|
|
9969
|
+
label: isString,
|
|
9970
|
+
text: optional(isString),
|
|
9971
|
+
classes: listOf(isString),
|
|
9972
|
+
attributes: listOf(isPair),
|
|
9973
|
+
component: optional(shape({
|
|
9974
|
+
owners: listOf(isString),
|
|
9975
|
+
file: optional(isString)
|
|
9976
|
+
})),
|
|
9977
|
+
rect: shape({
|
|
9978
|
+
x: isNumber,
|
|
9979
|
+
y: isNumber,
|
|
9980
|
+
w: isNumber,
|
|
9981
|
+
h: isNumber
|
|
9982
|
+
}),
|
|
9983
|
+
margin: isBox,
|
|
9984
|
+
border: isBox,
|
|
9985
|
+
padding: isBox,
|
|
9986
|
+
layout: listOf(isPair),
|
|
9987
|
+
typography: optional(shape({
|
|
9988
|
+
family: isString,
|
|
9989
|
+
familyToken: optional(isString),
|
|
9990
|
+
size: isString,
|
|
9991
|
+
sizeToken: optional(isString),
|
|
9992
|
+
weight: isString,
|
|
9993
|
+
lineHeight: isString,
|
|
9994
|
+
letterSpacing: isString,
|
|
9995
|
+
color: isColor,
|
|
9996
|
+
align: isString,
|
|
9997
|
+
transform: optional(isString)
|
|
9998
|
+
})),
|
|
9999
|
+
appearance: shape({
|
|
10000
|
+
background: optional(isColor),
|
|
10001
|
+
backgroundImage: optional(isString),
|
|
10002
|
+
border: optional(shape({
|
|
10003
|
+
width: isString,
|
|
10004
|
+
style: isString,
|
|
10005
|
+
color: isColor
|
|
10006
|
+
})),
|
|
10007
|
+
radius: optional(isTokenValue),
|
|
10008
|
+
shadow: optional(isTokenValue),
|
|
10009
|
+
opacity: optional(isString)
|
|
10010
|
+
}),
|
|
10011
|
+
path: listOf(isCrumb),
|
|
10012
|
+
children: listOf(isCrumb),
|
|
10013
|
+
css: isString
|
|
10014
|
+
});
|
|
10015
|
+
/** What each message type carries besides `source`, `key` and `type`. */
|
|
10016
|
+
var FIELDS = {
|
|
10017
|
+
ready: {},
|
|
10018
|
+
blur: {},
|
|
10019
|
+
updated: {},
|
|
10020
|
+
"inspect-escape": {},
|
|
10021
|
+
size: {
|
|
10022
|
+
width: isNumber,
|
|
10023
|
+
height: isNumber
|
|
10024
|
+
},
|
|
10025
|
+
error: { message: isString },
|
|
10026
|
+
go: { target: isString },
|
|
10027
|
+
link: {
|
|
10028
|
+
href: isString,
|
|
10029
|
+
path: (value) => value === null || isString(value),
|
|
10030
|
+
form: isBoolean,
|
|
10031
|
+
raw: optional(isString),
|
|
10032
|
+
pushed: optional(isBoolean)
|
|
10033
|
+
},
|
|
10034
|
+
wheel: {
|
|
10035
|
+
deltaX: isNumber,
|
|
10036
|
+
deltaY: isNumber,
|
|
10037
|
+
x: isNumber,
|
|
10038
|
+
y: isNumber,
|
|
10039
|
+
zoom: isBoolean
|
|
10040
|
+
},
|
|
10041
|
+
keydown: { code: isString },
|
|
10042
|
+
pointer: {
|
|
10043
|
+
x: isNumber,
|
|
10044
|
+
y: isNumber,
|
|
10045
|
+
down: isBoolean
|
|
10046
|
+
},
|
|
10047
|
+
key: {
|
|
10048
|
+
name: isString,
|
|
10049
|
+
down: isBoolean
|
|
10050
|
+
},
|
|
10051
|
+
inspect: { info: (value) => value === null || isElementInfo(value) }
|
|
10052
|
+
};
|
|
10053
|
+
var clamp = (value) => Math.min(MAX_FRAME_SIZE, Math.max(0, value));
|
|
10054
|
+
/** `data` as a runtime message, with its sizes and texts bounded; null when it is none. */
|
|
10055
|
+
function readRuntimeMessage(data) {
|
|
10056
|
+
if (!isObject(data) || data.source !== "design-runtime" || !isString(data.key) || !isString(data.type)) return null;
|
|
10057
|
+
const fields = Object.hasOwn(FIELDS, data.type) ? FIELDS[data.type] : null;
|
|
10058
|
+
if (!fields || !shape(fields)(data)) return null;
|
|
10059
|
+
const message = data;
|
|
10060
|
+
if (message.type === "size") return {
|
|
10061
|
+
...message,
|
|
10062
|
+
width: clamp(message.width),
|
|
10063
|
+
height: clamp(message.height)
|
|
10064
|
+
};
|
|
10065
|
+
if (message.type === "error") return {
|
|
10066
|
+
...message,
|
|
10067
|
+
message: message.message.slice(0, MAX_TEXT)
|
|
10068
|
+
};
|
|
10069
|
+
return message;
|
|
10070
|
+
}
|
|
10071
|
+
/** A frame's error list with `message` added: once, and no more than `MAX_FRAME_ERRORS` of them. */
|
|
10072
|
+
function withError(list, message) {
|
|
10073
|
+
return list.includes(message) || list.length >= 20 ? list : [...list, message];
|
|
10074
|
+
}
|
|
10075
|
+
//#endregion
|
|
9557
10076
|
//#region viewer/src/lib/frames.ts
|
|
9558
10077
|
/** Routes runtime messages to whoever listens to the frame that sent them. */
|
|
9559
10078
|
var handlers = /* @__PURE__ */ new Map();
|
|
9560
10079
|
window.addEventListener("message", (event) => {
|
|
9561
|
-
const
|
|
9562
|
-
if (
|
|
9563
|
-
|
|
10080
|
+
const listeners = event.source ? handlers.get(event.source) : void 0;
|
|
10081
|
+
if (!listeners) return;
|
|
10082
|
+
const message = readRuntimeMessage(event.data);
|
|
10083
|
+
if (!message) return;
|
|
10084
|
+
for (const handler of listeners) handler(message);
|
|
9564
10085
|
});
|
|
9565
10086
|
function listenToFrame(frame, handler) {
|
|
9566
10087
|
let win = null;
|
|
@@ -9637,7 +10158,8 @@ function reloadFrame(frame) {
|
|
|
9637
10158
|
* process, so frames served from the viewer's twin host (localhost ↔ 127.0.0.1,
|
|
9638
10159
|
* both reach the same server) run their scripts and animations off the viewer's
|
|
9639
10160
|
* thread: a canvas full of live screens still pans smoothly. Any other host,
|
|
9640
|
-
* and static builds, keep frames on the viewer's origin.
|
|
10161
|
+
* and static builds, keep frames on the viewer's origin. The dev server keeps
|
|
10162
|
+
* its viewer on localhost and gives 127.0.0.1 to frames (server/hosts.ts).
|
|
9641
10163
|
*/
|
|
9642
10164
|
var FRAME_ORIGIN = (() => {
|
|
9643
10165
|
if (STATIC_SITE) return "";
|
|
@@ -9664,7 +10186,7 @@ function absoluteUrl(url) {
|
|
|
9664
10186
|
* link to another site opens in a new tab. A push nobody's route matches belongs to the
|
|
9665
10187
|
* screen's own router; a link or form that leads nowhere stays put with a note.
|
|
9666
10188
|
*/
|
|
9667
|
-
function followLink(message, canvas, go, from) {
|
|
10189
|
+
function followLink(message, canvas, go, from, missing) {
|
|
9668
10190
|
if (message.form) {
|
|
9669
10191
|
toast("Forms are not sent from a screen", void 0, "info");
|
|
9670
10192
|
return;
|
|
@@ -9677,7 +10199,7 @@ function followLink(message, canvas, go, from) {
|
|
|
9677
10199
|
if (!window.open(message.href, "_blank", "noopener,noreferrer")) toast("Link to another site", message.href, "info");
|
|
9678
10200
|
return;
|
|
9679
10201
|
}
|
|
9680
|
-
const frames = canvas.pages.flatMap((page) => page.sections.flatMap((section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url")));
|
|
10202
|
+
const frames = Array.isArray(canvas) ? canvas : canvas.pages.flatMap((page) => page.sections.flatMap((section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url")));
|
|
9681
10203
|
const here = frames.find((item) => item.id === from);
|
|
9682
10204
|
const path = message.raw === void 0 ? message.path : linkPath(message.raw, here?.routes?.[0]);
|
|
9683
10205
|
if (path === null) return;
|
|
@@ -9695,7 +10217,9 @@ function followLink(message, canvas, go, from) {
|
|
|
9695
10217
|
name = decodeURIComponent(name);
|
|
9696
10218
|
} catch {}
|
|
9697
10219
|
if (name && name !== from && frames.some((item) => item.id === name)) go(name);
|
|
9698
|
-
else if (
|
|
10220
|
+
else if (message.pushed) return;
|
|
10221
|
+
else if (missing) missing(path);
|
|
10222
|
+
else toast("No screen for this link", path, "info");
|
|
9699
10223
|
}
|
|
9700
10224
|
//#endregion
|
|
9701
10225
|
//#region viewer/src/components/inspector.tsx
|
|
@@ -9712,11 +10236,13 @@ function useInspection(frames, enabled, options) {
|
|
|
9712
10236
|
const [selected, setSelected] = useState(null);
|
|
9713
10237
|
const state = useRef({
|
|
9714
10238
|
enabled,
|
|
9715
|
-
tokens
|
|
10239
|
+
tokens,
|
|
10240
|
+
picked: null
|
|
9716
10241
|
});
|
|
9717
10242
|
state.current = {
|
|
9718
10243
|
enabled,
|
|
9719
|
-
tokens
|
|
10244
|
+
tokens,
|
|
10245
|
+
picked: selected?.frame ?? null
|
|
9720
10246
|
};
|
|
9721
10247
|
const onEscape = useRef(options.onEscape);
|
|
9722
10248
|
onEscape.current = options.onEscape;
|
|
@@ -9751,7 +10277,7 @@ function useInspection(frames, enabled, options) {
|
|
|
9751
10277
|
if (state.current.enabled) tell(frame, true);
|
|
9752
10278
|
else told.current.set(frame, { on: false });
|
|
9753
10279
|
} else if (message.type === "inspect") {
|
|
9754
|
-
if (message.info) {
|
|
10280
|
+
if (message.info && (state.current.enabled || state.current.picked === frame)) {
|
|
9755
10281
|
for (const other of framesRef.current) if (other !== frame) sendToFrame(other, {
|
|
9756
10282
|
type: "inspect-select",
|
|
9757
10283
|
ref: null
|
|
@@ -10220,233 +10746,42 @@ function InspectorPanel({ info, onSelect, onClose, active }) {
|
|
|
10220
10746
|
children: info.attributes.map(([name, value]) => /* @__PURE__ */ jsx(Line, {
|
|
10221
10747
|
label: name,
|
|
10222
10748
|
children: /* @__PURE__ */ jsx(Value$1, {
|
|
10223
|
-
title: value,
|
|
10224
|
-
children: value
|
|
10225
|
-
})
|
|
10226
|
-
}, name))
|
|
10227
|
-
}) : null,
|
|
10228
|
-
info.children.length ? /* @__PURE__ */ jsx(Section, {
|
|
10229
|
-
title: `Children · ${info.children.length}`,
|
|
10230
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
10231
|
-
className: "flex flex-col px-2 pb-1",
|
|
10232
|
-
children: info.children.map((child, index) => /* @__PURE__ */ jsxs("button", {
|
|
10233
|
-
type: "button",
|
|
10234
|
-
onClick: () => onSelect(child.ref),
|
|
10235
|
-
className: "flex h-7 cursor-pointer items-center gap-2 rounded-[6px] border-0 bg-transparent px-2 text-left font-mono text-[12px] text-ink2 hover:bg-soft2",
|
|
10236
|
-
children: [/* @__PURE__ */ jsx(Icon, {
|
|
10237
|
-
name: "chevron-right",
|
|
10238
|
-
size: 12,
|
|
10239
|
-
className: "text-muted"
|
|
10240
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
10241
|
-
className: "truncate",
|
|
10242
|
-
children: child.label
|
|
10243
|
-
})]
|
|
10244
|
-
}, index))
|
|
10245
|
-
})
|
|
10246
|
-
}) : null
|
|
10247
|
-
]
|
|
10248
|
-
}),
|
|
10249
|
-
info ? /* @__PURE__ */ jsx("div", {
|
|
10250
|
-
className: "flex shrink-0 items-center gap-2 border-t border-rule bg-soft px-4 py-2.5",
|
|
10251
|
-
children: /* @__PURE__ */ jsx(Button, {
|
|
10252
|
-
icon: "copy",
|
|
10253
|
-
onClick: () => void copyText(info.css, "CSS copied"),
|
|
10254
|
-
className: "grow",
|
|
10255
|
-
children: "Copy CSS"
|
|
10256
|
-
})
|
|
10257
|
-
}) : null
|
|
10258
|
-
]
|
|
10259
|
-
});
|
|
10260
|
-
}
|
|
10261
|
-
//#endregion
|
|
10262
|
-
//#region viewer/src/ui/badge.tsx
|
|
10263
|
-
var BADGE = {
|
|
10264
|
-
neutral: "bg-soft2 text-ink2",
|
|
10265
|
-
action: "bg-action-soft text-action",
|
|
10266
|
-
ok: "bg-ok-soft text-ok-text",
|
|
10267
|
-
danger: "bg-danger-soft text-danger-text"
|
|
10268
|
-
};
|
|
10269
|
-
function Badge({ tone = "neutral", children, className }) {
|
|
10270
|
-
return /* @__PURE__ */ jsx("span", {
|
|
10271
|
-
className: cn("inline-flex h-[22px] shrink-0 items-center gap-1 whitespace-nowrap rounded-[6px] px-2 text-[12px] font-medium", BADGE[tone], className),
|
|
10272
|
-
children
|
|
10273
|
-
});
|
|
10274
|
-
}
|
|
10275
|
-
function Count({ value, tone = "neutral" }) {
|
|
10276
|
-
return /* @__PURE__ */ jsx("span", {
|
|
10277
|
-
className: cn("inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-[4px] px-[5px] text-[11.5px] font-semibold tabular", tone === "action" ? "bg-action-soft text-action" : tone === "danger" ? "bg-danger-soft text-danger-text" : "bg-soft2 text-ink2"),
|
|
10278
|
-
children: value
|
|
10279
|
-
});
|
|
10280
|
-
}
|
|
10281
|
-
//#endregion
|
|
10282
|
-
//#region viewer/src/ui/panel.tsx
|
|
10283
|
-
function Panel({ children, className, style }) {
|
|
10284
|
-
return /* @__PURE__ */ jsx("section", {
|
|
10285
|
-
className: cn("min-w-0 shrink-0 overflow-hidden rounded-[10px] border border-rule bg-surface", className),
|
|
10286
|
-
style,
|
|
10287
|
-
children
|
|
10288
|
-
});
|
|
10289
|
-
}
|
|
10290
|
-
function PanelHead({ title, count, sub, right, as: Heading = "h2" }) {
|
|
10291
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
10292
|
-
className: "flex min-h-[46px] items-center justify-between gap-3 px-4",
|
|
10293
|
-
children: [/* @__PURE__ */ jsxs(Heading, {
|
|
10294
|
-
className: "m-0 flex min-w-0 items-center gap-2 whitespace-nowrap text-[14px] font-semibold",
|
|
10295
|
-
children: [
|
|
10296
|
-
title,
|
|
10297
|
-
count !== void 0 ? /* @__PURE__ */ jsx(Count, { value: count }) : null,
|
|
10298
|
-
sub ? /* @__PURE__ */ jsx("span", {
|
|
10299
|
-
className: "truncate text-[12.5px] font-normal text-muted",
|
|
10300
|
-
children: sub
|
|
10301
|
-
}) : null
|
|
10302
|
-
]
|
|
10303
|
-
}), right ? /* @__PURE__ */ jsx("div", {
|
|
10304
|
-
className: "flex items-center gap-2",
|
|
10305
|
-
children: right
|
|
10306
|
-
}) : null]
|
|
10307
|
-
});
|
|
10308
|
-
}
|
|
10309
|
-
function PanelTabs({ items, value, onChange, right }) {
|
|
10310
|
-
const indicator = useId();
|
|
10311
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
10312
|
-
className: "flex h-[46px] items-center justify-between gap-3 px-4",
|
|
10313
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
10314
|
-
role: "tablist",
|
|
10315
|
-
className: "flex gap-5",
|
|
10316
|
-
children: items.map((item) => {
|
|
10317
|
-
const on = item.value === value;
|
|
10318
|
-
return /* @__PURE__ */ jsxs("button", {
|
|
10319
|
-
type: "button",
|
|
10320
|
-
role: "tab",
|
|
10321
|
-
"aria-selected": on,
|
|
10322
|
-
onClick: () => onChange(item.value),
|
|
10323
|
-
className: cn("relative flex h-[46px] cursor-pointer items-center gap-1.5 border-0 bg-transparent px-0.5 text-[14px] transition-colors", on ? "font-semibold text-ink" : "font-medium text-ink2 hover:text-ink"),
|
|
10324
|
-
children: [
|
|
10325
|
-
item.label,
|
|
10326
|
-
item.count !== void 0 ? /* @__PURE__ */ jsx(Count, { value: item.count }) : null,
|
|
10327
|
-
on ? /* @__PURE__ */ jsx(motion.span, {
|
|
10328
|
-
layoutId: indicator,
|
|
10329
|
-
className: "absolute inset-x-0 bottom-0 h-[2px] bg-ink",
|
|
10330
|
-
transition: {
|
|
10331
|
-
type: "spring",
|
|
10332
|
-
stiffness: 520,
|
|
10333
|
-
damping: 42
|
|
10334
|
-
}
|
|
10335
|
-
}) : null
|
|
10336
|
-
]
|
|
10337
|
-
}, item.value);
|
|
10338
|
-
})
|
|
10339
|
-
}), right ? /* @__PURE__ */ jsx("div", {
|
|
10340
|
-
className: "flex items-center gap-2",
|
|
10341
|
-
children: right
|
|
10342
|
-
}) : null]
|
|
10343
|
-
});
|
|
10344
|
-
}
|
|
10345
|
-
function Cell({ align, children }) {
|
|
10346
|
-
return /* @__PURE__ */ jsx("div", {
|
|
10347
|
-
className: cn("flex min-w-0 items-center gap-2", align === "r" && "justify-end text-right", align === "c" && "justify-center"),
|
|
10348
|
-
children
|
|
10349
|
-
});
|
|
10350
|
-
}
|
|
10351
|
-
function cells(children, align) {
|
|
10352
|
-
return Children.toArray(children).map((child, index) => /* @__PURE__ */ jsx(Cell, {
|
|
10353
|
-
align: align?.[index] ?? "l",
|
|
10354
|
-
children: child
|
|
10355
|
-
}, index));
|
|
10356
|
-
}
|
|
10357
|
-
function THead({ cols, align, labels }) {
|
|
10358
|
-
return /* @__PURE__ */ jsx("div", {
|
|
10359
|
-
className: "grid h-8 items-center gap-x-4 border-t border-rule bg-soft px-4 text-[12.5px] text-muted",
|
|
10360
|
-
style: { gridTemplateColumns: cols },
|
|
10361
|
-
children: labels.map((label, index) => /* @__PURE__ */ jsx(Cell, {
|
|
10362
|
-
align: align?.[index] ?? "l",
|
|
10363
|
-
children: /* @__PURE__ */ jsx("span", {
|
|
10364
|
-
className: "whitespace-nowrap",
|
|
10365
|
-
children: label
|
|
10366
|
-
})
|
|
10367
|
-
}, index))
|
|
10368
|
-
});
|
|
10369
|
-
}
|
|
10370
|
-
var rowClass = (first, interactive, className) => cn("grid items-center gap-x-4 border-rule", !first && "border-t", interactive && "cursor-pointer transition-colors hover:bg-soft", className);
|
|
10371
|
-
function Row({ cols, align, minH = 44, pad = "10px 16px", children, first, className, onClick, title }) {
|
|
10372
|
-
return /* @__PURE__ */ jsx("div", {
|
|
10373
|
-
className: rowClass(first, !!onClick, className),
|
|
10374
|
-
style: {
|
|
10375
|
-
gridTemplateColumns: cols,
|
|
10376
|
-
minHeight: minH,
|
|
10377
|
-
padding: pad
|
|
10378
|
-
},
|
|
10379
|
-
onClick,
|
|
10380
|
-
title,
|
|
10381
|
-
children: cells(children, align)
|
|
10382
|
-
});
|
|
10383
|
-
}
|
|
10384
|
-
/** A table row that navigates somewhere in the app. */
|
|
10385
|
-
function RowLink({ to, cols, align, minH = 44, pad = "10px 16px", first, className, children }) {
|
|
10386
|
-
return /* @__PURE__ */ jsx(Link, {
|
|
10387
|
-
href: to,
|
|
10388
|
-
className: cn(rowClass(first, true, className), "text-inherit no-underline"),
|
|
10389
|
-
style: {
|
|
10390
|
-
gridTemplateColumns: cols,
|
|
10391
|
-
minHeight: minH,
|
|
10392
|
-
padding: pad
|
|
10393
|
-
},
|
|
10394
|
-
children: cells(children, align)
|
|
10395
|
-
});
|
|
10396
|
-
}
|
|
10397
|
-
function KV({ label, children, labelWidth = 180, minH = 40, end = false }) {
|
|
10398
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
10399
|
-
className: "grid content-center items-baseline gap-x-4 border-t border-rule px-4 py-2",
|
|
10400
|
-
style: {
|
|
10401
|
-
gridTemplateColumns: `min(${labelWidth}px, 42%) minmax(0, 1fr)`,
|
|
10402
|
-
minHeight: minH
|
|
10403
|
-
},
|
|
10404
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
10405
|
-
className: "text-[13px] text-muted",
|
|
10406
|
-
children: label
|
|
10407
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
10408
|
-
className: cn("flex min-w-0 flex-wrap items-center gap-2", end && "justify-end"),
|
|
10409
|
-
children
|
|
10410
|
-
})]
|
|
10411
|
-
});
|
|
10412
|
-
}
|
|
10413
|
-
function PanelBody({ children, pad = "16px", gap = 16, top = true, className }) {
|
|
10414
|
-
return /* @__PURE__ */ jsx("div", {
|
|
10415
|
-
className: cn("flex flex-col", top && "border-t border-rule", className),
|
|
10416
|
-
style: {
|
|
10417
|
-
padding: pad,
|
|
10418
|
-
gap
|
|
10419
|
-
},
|
|
10420
|
-
children
|
|
10421
|
-
});
|
|
10422
|
-
}
|
|
10423
|
-
function PanelFoot({ left, right }) {
|
|
10424
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
10425
|
-
className: "flex items-center justify-between gap-3 border-t border-rule bg-soft px-4 py-3",
|
|
10426
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
10427
|
-
className: "flex min-w-0 items-center gap-2 text-[12.5px] text-muted",
|
|
10428
|
-
children: left
|
|
10429
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
10430
|
-
className: "flex items-center gap-2",
|
|
10431
|
-
children: right
|
|
10432
|
-
})]
|
|
10433
|
-
});
|
|
10434
|
-
}
|
|
10435
|
-
/** Bottom summary strip. */
|
|
10436
|
-
function Strip({ children, className }) {
|
|
10437
|
-
return /* @__PURE__ */ jsx("div", {
|
|
10438
|
-
className: cn("flex min-h-[38px] shrink-0 items-center gap-2 border-t border-rule bg-soft px-4 py-2 text-[12.5px] text-muted", className),
|
|
10439
|
-
children
|
|
10440
|
-
});
|
|
10441
|
-
}
|
|
10442
|
-
/** Group header row inside tables. */
|
|
10443
|
-
function GroupRow({ children, sub }) {
|
|
10444
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
10445
|
-
className: "flex h-9 items-center gap-2.5 border-t border-rule bg-soft px-4 text-[13px] font-medium",
|
|
10446
|
-
children: [children, sub ? /* @__PURE__ */ jsx("span", {
|
|
10447
|
-
className: "text-[12.5px] font-normal text-muted",
|
|
10448
|
-
children: sub
|
|
10449
|
-
}) : null]
|
|
10749
|
+
title: value,
|
|
10750
|
+
children: value
|
|
10751
|
+
})
|
|
10752
|
+
}, name))
|
|
10753
|
+
}) : null,
|
|
10754
|
+
info.children.length ? /* @__PURE__ */ jsx(Section, {
|
|
10755
|
+
title: `Children · ${info.children.length}`,
|
|
10756
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
10757
|
+
className: "flex flex-col px-2 pb-1",
|
|
10758
|
+
children: info.children.map((child, index) => /* @__PURE__ */ jsxs("button", {
|
|
10759
|
+
type: "button",
|
|
10760
|
+
onClick: () => onSelect(child.ref),
|
|
10761
|
+
className: "flex h-7 cursor-pointer items-center gap-2 rounded-[6px] border-0 bg-transparent px-2 text-left font-mono text-[12px] text-ink2 hover:bg-soft2",
|
|
10762
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
10763
|
+
name: "chevron-right",
|
|
10764
|
+
size: 12,
|
|
10765
|
+
className: "text-muted"
|
|
10766
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
10767
|
+
className: "truncate",
|
|
10768
|
+
children: child.label
|
|
10769
|
+
})]
|
|
10770
|
+
}, index))
|
|
10771
|
+
})
|
|
10772
|
+
}) : null
|
|
10773
|
+
]
|
|
10774
|
+
}),
|
|
10775
|
+
info ? /* @__PURE__ */ jsx("div", {
|
|
10776
|
+
className: "flex shrink-0 items-center gap-2 border-t border-rule bg-soft px-4 py-2.5",
|
|
10777
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
10778
|
+
icon: "copy",
|
|
10779
|
+
onClick: () => void copyText(info.css, "CSS copied"),
|
|
10780
|
+
className: "grow",
|
|
10781
|
+
children: "Copy CSS"
|
|
10782
|
+
})
|
|
10783
|
+
}) : null
|
|
10784
|
+
]
|
|
10450
10785
|
});
|
|
10451
10786
|
}
|
|
10452
10787
|
//#endregion
|
|
@@ -10531,112 +10866,6 @@ function SlidePanel({ open, side, width, children }) {
|
|
|
10531
10866
|
});
|
|
10532
10867
|
}
|
|
10533
10868
|
//#endregion
|
|
10534
|
-
//#region viewer/src/ui/page.tsx
|
|
10535
|
-
/** Page body under the top bar. */
|
|
10536
|
-
function Content({ children, className, gap = 20, wide = false }) {
|
|
10537
|
-
return /* @__PURE__ */ jsx("main", {
|
|
10538
|
-
className: cn("mx-auto flex w-full min-h-0 grow flex-col px-8 pt-6 pb-7 animate-[q-fade-in_160ms_ease-out] max-md:px-4 max-md:pt-4", wide ? "max-w-[1440px]" : "max-w-[1200px]", className),
|
|
10539
|
-
style: { gap },
|
|
10540
|
-
children
|
|
10541
|
-
});
|
|
10542
|
-
}
|
|
10543
|
-
function PageHead({ title, sub, right, badge, crumbs }) {
|
|
10544
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
10545
|
-
className: "flex shrink-0 items-end justify-between gap-6 max-md:flex-col max-md:items-stretch max-md:gap-3",
|
|
10546
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
10547
|
-
className: "flex min-w-0 flex-col gap-1",
|
|
10548
|
-
children: [
|
|
10549
|
-
crumbs ? /* @__PURE__ */ jsx("div", {
|
|
10550
|
-
className: "flex items-center gap-1.5 text-[13px] text-muted",
|
|
10551
|
-
children: crumbs
|
|
10552
|
-
}) : null,
|
|
10553
|
-
/* @__PURE__ */ jsxs("div", {
|
|
10554
|
-
className: "flex items-center gap-3",
|
|
10555
|
-
children: [/* @__PURE__ */ jsx("h1", {
|
|
10556
|
-
className: "m-0 text-[22px] font-semibold tracking-[-0.01em]",
|
|
10557
|
-
children: title
|
|
10558
|
-
}), badge]
|
|
10559
|
-
}),
|
|
10560
|
-
sub ? /* @__PURE__ */ jsx("div", {
|
|
10561
|
-
className: "text-[13px] [overflow-wrap:anywhere] text-muted",
|
|
10562
|
-
children: sub
|
|
10563
|
-
}) : null
|
|
10564
|
-
]
|
|
10565
|
-
}), right ? /* @__PURE__ */ jsx("div", {
|
|
10566
|
-
className: "flex flex-wrap items-center gap-2",
|
|
10567
|
-
children: right
|
|
10568
|
-
}) : null]
|
|
10569
|
-
});
|
|
10570
|
-
}
|
|
10571
|
-
function SubNav({ items, label }) {
|
|
10572
|
-
const [location] = useLocation();
|
|
10573
|
-
return /* @__PURE__ */ jsx("nav", {
|
|
10574
|
-
"aria-label": label,
|
|
10575
|
-
className: "flex flex-col gap-0.5",
|
|
10576
|
-
children: items.map((item) => {
|
|
10577
|
-
const active = location === item.to;
|
|
10578
|
-
return /* @__PURE__ */ jsxs(Link, {
|
|
10579
|
-
href: item.to,
|
|
10580
|
-
"aria-current": active ? "page" : void 0,
|
|
10581
|
-
className: cn("relative flex h-8 items-center gap-2 whitespace-nowrap rounded-[6px] px-3 text-[13.5px] no-underline transition-colors focus-visible:outline-offset-[-2px]", active ? "font-medium text-ink" : "text-ink2 hover:bg-soft2"),
|
|
10582
|
-
children: [
|
|
10583
|
-
active ? /* @__PURE__ */ jsx(motion.span, {
|
|
10584
|
-
layoutId: "sub-nav-active",
|
|
10585
|
-
"aria-hidden": true,
|
|
10586
|
-
className: "absolute inset-0 z-0 rounded-[6px] bg-soft2",
|
|
10587
|
-
transition: {
|
|
10588
|
-
type: "spring",
|
|
10589
|
-
stiffness: 520,
|
|
10590
|
-
damping: 42
|
|
10591
|
-
}
|
|
10592
|
-
}) : null,
|
|
10593
|
-
/* @__PURE__ */ jsx("span", {
|
|
10594
|
-
className: "relative z-[1] min-w-0 truncate",
|
|
10595
|
-
children: item.label
|
|
10596
|
-
}),
|
|
10597
|
-
item.hint ? /* @__PURE__ */ jsx("span", {
|
|
10598
|
-
className: "relative z-[1] ml-auto text-[12px] font-normal text-muted",
|
|
10599
|
-
children: item.hint
|
|
10600
|
-
}) : null
|
|
10601
|
-
]
|
|
10602
|
-
}, item.to);
|
|
10603
|
-
})
|
|
10604
|
-
});
|
|
10605
|
-
}
|
|
10606
|
-
function NavLabel({ children }) {
|
|
10607
|
-
return /* @__PURE__ */ jsx("div", {
|
|
10608
|
-
className: "px-3 pt-4 pb-1 text-[12px] text-muted",
|
|
10609
|
-
children
|
|
10610
|
-
});
|
|
10611
|
-
}
|
|
10612
|
-
/** Standalone message card: not found, empty, failed to load. */
|
|
10613
|
-
function NoticeCard({ title, text, action }) {
|
|
10614
|
-
return /* @__PURE__ */ jsxs(Panel, {
|
|
10615
|
-
className: "flex w-[480px] max-w-full flex-col items-center gap-2 px-6 py-8 text-center",
|
|
10616
|
-
children: [
|
|
10617
|
-
/* @__PURE__ */ jsx("h1", {
|
|
10618
|
-
className: "m-0 text-[16px] font-semibold",
|
|
10619
|
-
children: title
|
|
10620
|
-
}),
|
|
10621
|
-
text ? /* @__PURE__ */ jsx("div", {
|
|
10622
|
-
className: "m-0 max-w-[400px] text-[14px] text-ink2",
|
|
10623
|
-
children: text
|
|
10624
|
-
}) : null,
|
|
10625
|
-
action ? /* @__PURE__ */ jsx("div", {
|
|
10626
|
-
className: "mt-3 flex flex-wrap justify-center gap-2",
|
|
10627
|
-
children: action
|
|
10628
|
-
}) : null
|
|
10629
|
-
]
|
|
10630
|
-
});
|
|
10631
|
-
}
|
|
10632
|
-
/** NoticeCard centred in the free space. */
|
|
10633
|
-
function Notice(props) {
|
|
10634
|
-
return /* @__PURE__ */ jsx("div", {
|
|
10635
|
-
className: "flex min-h-[50vh] grow items-center justify-center p-6 max-md:px-4",
|
|
10636
|
-
children: /* @__PURE__ */ jsx(NoticeCard, { ...props })
|
|
10637
|
-
});
|
|
10638
|
-
}
|
|
10639
|
-
//#endregion
|
|
10640
10869
|
//#region viewer/src/pages/not-found.tsx
|
|
10641
10870
|
function NotFound({ title = "Nothing here", text }) {
|
|
10642
10871
|
const scoped = !!useViewer().scope;
|
|
@@ -10805,6 +11034,174 @@ function Segmented({ items, value, onChange, label, stretch, size = "ctrl" }) {
|
|
|
10805
11034
|
});
|
|
10806
11035
|
}
|
|
10807
11036
|
//#endregion
|
|
11037
|
+
//#region viewer/src/components/corner-plate.tsx
|
|
11038
|
+
/** How long the plate stays after the screen loads, or after a tap shows it. */
|
|
11039
|
+
var LINGER_MS = 5500;
|
|
11040
|
+
/** A pointer that left the corner hides the plate after this, so passing by does not flicker it. */
|
|
11041
|
+
var LEAVE_MS = 350;
|
|
11042
|
+
/** The top-right quarter of the window, where the plate lives. */
|
|
11043
|
+
var inCorner = (x, y) => x >= window.innerWidth / 2 && y <= window.innerHeight / 2;
|
|
11044
|
+
/**
|
|
11045
|
+
* A plate in the top-right corner over a full-window frame. It shows while the screen loads and for a
|
|
11046
|
+
* few seconds after, then fades out; a pointer in the window's top-right quarter brings it back and
|
|
11047
|
+
* keeps it while it stays there, a tap there shows it for a while, and so does focus inside it.
|
|
11048
|
+
*
|
|
11049
|
+
* The page does not see the pointer over the frame, so the frame's runtime reports it (0.9.11+).
|
|
11050
|
+
* Until a frame does (an older runtime, a URL item), the plate shows whenever the pointer is over the
|
|
11051
|
+
* page, as before.
|
|
11052
|
+
*/
|
|
11053
|
+
function CornerPlate({ frame, note, badge, children }) {
|
|
11054
|
+
const [lingering, setLingering] = useState(true);
|
|
11055
|
+
const [inside, setInside] = useState(false);
|
|
11056
|
+
const [focused, setFocused] = useState(false);
|
|
11057
|
+
const [reported, setReported] = useState(false);
|
|
11058
|
+
const [overPage, setOverPage] = useState(false);
|
|
11059
|
+
const [loads, setLoads] = useState(0);
|
|
11060
|
+
const linger = useRef(void 0);
|
|
11061
|
+
const leave = useRef(void 0);
|
|
11062
|
+
const showFor = (ms) => {
|
|
11063
|
+
setLingering(true);
|
|
11064
|
+
clearTimeout(linger.current);
|
|
11065
|
+
linger.current = setTimeout(() => setLingering(false), ms);
|
|
11066
|
+
};
|
|
11067
|
+
const at = (x, y) => {
|
|
11068
|
+
const corner = x >= 0 && inCorner(x, y);
|
|
11069
|
+
clearTimeout(leave.current);
|
|
11070
|
+
if (corner) setInside(true);
|
|
11071
|
+
else leave.current = setTimeout(() => setInside(false), LEAVE_MS);
|
|
11072
|
+
};
|
|
11073
|
+
useEffect(() => {
|
|
11074
|
+
if (!frame) return;
|
|
11075
|
+
const ask = () => {
|
|
11076
|
+
sendToFrame(frame, { type: "pointer" });
|
|
11077
|
+
showFor(LINGER_MS);
|
|
11078
|
+
setLoads((n) => n + 1);
|
|
11079
|
+
};
|
|
11080
|
+
frame.addEventListener("load", ask);
|
|
11081
|
+
const stop = listenToFrame(frame, (message) => {
|
|
11082
|
+
if (message.type !== "pointer") return;
|
|
11083
|
+
setReported(true);
|
|
11084
|
+
if (message.x < 0) return at(-1, -1);
|
|
11085
|
+
const rect = frame.getBoundingClientRect();
|
|
11086
|
+
const x = rect.left + message.x;
|
|
11087
|
+
const y = rect.top + message.y;
|
|
11088
|
+
if (message.down && inCorner(x, y)) showFor(LINGER_MS);
|
|
11089
|
+
else at(x, y);
|
|
11090
|
+
});
|
|
11091
|
+
return () => {
|
|
11092
|
+
frame.removeEventListener("load", ask);
|
|
11093
|
+
stop();
|
|
11094
|
+
};
|
|
11095
|
+
}, [frame]);
|
|
11096
|
+
useEffect(() => {
|
|
11097
|
+
const move = (event) => at(event.clientX, event.clientY);
|
|
11098
|
+
const out = (event) => {
|
|
11099
|
+
if (!event.relatedTarget) at(-1, -1);
|
|
11100
|
+
};
|
|
11101
|
+
window.addEventListener("pointermove", move, { passive: true });
|
|
11102
|
+
document.addEventListener("pointerout", out);
|
|
11103
|
+
return () => {
|
|
11104
|
+
window.removeEventListener("pointermove", move);
|
|
11105
|
+
document.removeEventListener("pointerout", out);
|
|
11106
|
+
clearTimeout(linger.current);
|
|
11107
|
+
clearTimeout(leave.current);
|
|
11108
|
+
};
|
|
11109
|
+
}, []);
|
|
11110
|
+
useEffect(() => {
|
|
11111
|
+
if (reported) return;
|
|
11112
|
+
const root = document.documentElement;
|
|
11113
|
+
const enter = () => setOverPage(true);
|
|
11114
|
+
const exit = () => setOverPage(false);
|
|
11115
|
+
root.addEventListener("pointerenter", enter);
|
|
11116
|
+
root.addEventListener("pointerleave", exit);
|
|
11117
|
+
return () => {
|
|
11118
|
+
root.removeEventListener("pointerenter", enter);
|
|
11119
|
+
root.removeEventListener("pointerleave", exit);
|
|
11120
|
+
};
|
|
11121
|
+
}, [reported]);
|
|
11122
|
+
const shown = lingering || inside || focused || !reported && overPage;
|
|
11123
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [badge ? /* @__PURE__ */ jsx(motion.button, {
|
|
11124
|
+
type: "button",
|
|
11125
|
+
"aria-label": "Show the page's notice",
|
|
11126
|
+
initial: false,
|
|
11127
|
+
animate: shown ? {
|
|
11128
|
+
opacity: 0,
|
|
11129
|
+
scale: .8
|
|
11130
|
+
} : {
|
|
11131
|
+
opacity: 1,
|
|
11132
|
+
scale: 1
|
|
11133
|
+
},
|
|
11134
|
+
transition: {
|
|
11135
|
+
duration: .2,
|
|
11136
|
+
delay: shown ? 0 : .25
|
|
11137
|
+
},
|
|
11138
|
+
onClick: () => showFor(LINGER_MS),
|
|
11139
|
+
tabIndex: shown ? -1 : 0,
|
|
11140
|
+
className: cn("absolute top-3 right-3 inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-full border border-rule bg-surface/90 px-2.5 text-[11.5px] text-muted shadow-[0_1px_3px_rgb(0_0_0/0.08)] backdrop-blur-sm hover:text-ink2", shown && "pointer-events-none"),
|
|
11141
|
+
children: badge
|
|
11142
|
+
}) : null, /* @__PURE__ */ jsx(motion.div, {
|
|
11143
|
+
initial: {
|
|
11144
|
+
opacity: 0,
|
|
11145
|
+
scale: .94,
|
|
11146
|
+
y: -4
|
|
11147
|
+
},
|
|
11148
|
+
animate: shown ? {
|
|
11149
|
+
opacity: 1,
|
|
11150
|
+
scale: 1,
|
|
11151
|
+
y: 0
|
|
11152
|
+
} : {
|
|
11153
|
+
opacity: 0,
|
|
11154
|
+
scale: .97,
|
|
11155
|
+
y: -2
|
|
11156
|
+
},
|
|
11157
|
+
transition: shown ? {
|
|
11158
|
+
type: "spring",
|
|
11159
|
+
stiffness: 420,
|
|
11160
|
+
damping: 30
|
|
11161
|
+
} : {
|
|
11162
|
+
duration: .45,
|
|
11163
|
+
ease: "easeOut"
|
|
11164
|
+
},
|
|
11165
|
+
style: { transformOrigin: "top right" },
|
|
11166
|
+
className: cn("absolute top-3 right-3 flex max-w-[calc(100%-24px)] flex-col items-end", !shown && "pointer-events-none"),
|
|
11167
|
+
"aria-hidden": !shown || void 0,
|
|
11168
|
+
onFocus: () => setFocused(true),
|
|
11169
|
+
onBlur: (event) => {
|
|
11170
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setFocused(false);
|
|
11171
|
+
},
|
|
11172
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
11173
|
+
className: "relative flex max-w-full flex-col gap-1 rounded-[8px] border border-rule bg-surface p-1 shadow-pop",
|
|
11174
|
+
children: [
|
|
11175
|
+
loads > 0 ? /* @__PURE__ */ jsx(motion.span, {
|
|
11176
|
+
"aria-hidden": true,
|
|
11177
|
+
initial: {
|
|
11178
|
+
opacity: .55,
|
|
11179
|
+
scale: 1
|
|
11180
|
+
},
|
|
11181
|
+
animate: {
|
|
11182
|
+
opacity: 0,
|
|
11183
|
+
scale: 1.18
|
|
11184
|
+
},
|
|
11185
|
+
transition: {
|
|
11186
|
+
duration: 1.1,
|
|
11187
|
+
ease: "easeOut",
|
|
11188
|
+
delay: .15
|
|
11189
|
+
},
|
|
11190
|
+
className: "pointer-events-none absolute -inset-px rounded-[8px] border-2 border-action"
|
|
11191
|
+
}, loads) : null,
|
|
11192
|
+
children ? /* @__PURE__ */ jsx("div", {
|
|
11193
|
+
className: "flex items-center gap-1",
|
|
11194
|
+
children
|
|
11195
|
+
}) : null,
|
|
11196
|
+
note ? /* @__PURE__ */ jsx("div", {
|
|
11197
|
+
className: "px-2 pb-1 text-[11.5px] leading-snug text-muted",
|
|
11198
|
+
children: note
|
|
11199
|
+
}) : null
|
|
11200
|
+
]
|
|
11201
|
+
})
|
|
11202
|
+
})] });
|
|
11203
|
+
}
|
|
11204
|
+
//#endregion
|
|
10808
11205
|
//#region viewer/src/pages/canvas/full-page.tsx
|
|
10809
11206
|
/** The full-window page of one screen, relative to the viewer's base. */
|
|
10810
11207
|
var fullHref = (canvas, item, theme) => `/c/${encodeURIComponent(canvas)}/full/${encodeURIComponent(item)}?theme=${theme}`;
|
|
@@ -10820,13 +11217,18 @@ function useOpenUrl(canvasId, item, theme) {
|
|
|
10820
11217
|
if (!frameSandbox) return absoluteUrl(frameSrc(item.url, item.theme ?? theme));
|
|
10821
11218
|
return absoluteUrl(`${base}${fullHref(canvasId, item.id, item.theme ?? theme)}`);
|
|
10822
11219
|
}
|
|
10823
|
-
/** `/c/:canvas/full/:item`: one screen filling the window, a way out
|
|
11220
|
+
/** `/c/:canvas/full/:item`: one screen filling the window, a way out in the top-right corner and on Esc. */
|
|
10824
11221
|
function FullPage({ canvasId, itemId }) {
|
|
10825
11222
|
const { data: canvas, error, isLoading } = useCanvas(canvasId);
|
|
10826
11223
|
const [, navigate] = useLocation();
|
|
10827
|
-
const { frameSandbox } = useViewer();
|
|
11224
|
+
const { frameSandbox, frameAllow, slots } = useViewer();
|
|
10828
11225
|
const appTheme = useTheme();
|
|
10829
11226
|
const frame = useRef(null);
|
|
11227
|
+
const [frameEl, setFrameEl] = useState(null);
|
|
11228
|
+
const setFrame = useCallback((el) => {
|
|
11229
|
+
frame.current = el;
|
|
11230
|
+
setFrameEl(el);
|
|
11231
|
+
}, []);
|
|
10830
11232
|
const param = new URLSearchParams(window.location.search).get("theme");
|
|
10831
11233
|
const items = canvas ? canvas.pages.flatMap((page) => page.sections.flatMap((section) => section.items.filter((item) => item.kind === "screen" || item.kind === "url"))) : [];
|
|
10832
11234
|
const item = items.find((entry) => entry.id === itemId);
|
|
@@ -10864,31 +11266,35 @@ function FullPage({ canvasId, itemId }) {
|
|
|
10864
11266
|
text: `No screen "${itemId}" on this canvas.`
|
|
10865
11267
|
});
|
|
10866
11268
|
return /* @__PURE__ */ jsxs("div", {
|
|
10867
|
-
className: "
|
|
11269
|
+
className: "fixed inset-0 bg-surface",
|
|
10868
11270
|
children: [/* @__PURE__ */ jsx("iframe", {
|
|
10869
|
-
ref:
|
|
11271
|
+
ref: setFrame,
|
|
10870
11272
|
src: url,
|
|
10871
11273
|
title: item.title,
|
|
10872
|
-
allow:
|
|
11274
|
+
allow: frameAllow,
|
|
10873
11275
|
sandbox: frameSandbox,
|
|
10874
11276
|
className: "block h-full w-full border-0",
|
|
10875
11277
|
style: { colorScheme: theme },
|
|
10876
11278
|
onLoad: () => item.kind === "screen" && sendTheme(frame.current, theme)
|
|
10877
|
-
}, `${item.id}:${item.kind === "screen" ? item.rev : item.url}`), /* @__PURE__ */
|
|
10878
|
-
|
|
10879
|
-
|
|
11279
|
+
}, `${item.id}:${item.kind === "screen" ? item.rev : item.url}`), /* @__PURE__ */ jsxs(CornerPlate, {
|
|
11280
|
+
frame: frameEl,
|
|
11281
|
+
note: slots.fullPlate?.note,
|
|
11282
|
+
badge: slots.fullPlate?.badge,
|
|
11283
|
+
children: [/* @__PURE__ */ jsxs(Link, {
|
|
10880
11284
|
href: back,
|
|
10881
|
-
className: "
|
|
11285
|
+
className: "flex h-8 min-w-0 items-center gap-2 rounded-[6px] px-2 text-[12.5px] text-ink2 no-underline hover:bg-soft2 hover:text-ink",
|
|
10882
11286
|
children: [
|
|
10883
11287
|
/* @__PURE__ */ jsx(Icon, {
|
|
10884
11288
|
name: "x",
|
|
10885
11289
|
size: 14
|
|
10886
11290
|
}),
|
|
10887
|
-
|
|
10888
|
-
|
|
11291
|
+
/* @__PURE__ */ jsxs("span", {
|
|
11292
|
+
className: "truncate",
|
|
11293
|
+
children: ["Back to ", canvas.title]
|
|
11294
|
+
}),
|
|
10889
11295
|
/* @__PURE__ */ jsx(Kbd, { children: "esc" })
|
|
10890
11296
|
]
|
|
10891
|
-
})
|
|
11297
|
+
}), slots.fullPlate?.actions]
|
|
10892
11298
|
})]
|
|
10893
11299
|
});
|
|
10894
11300
|
}
|
|
@@ -11453,7 +11859,7 @@ function deviceIcon(width) {
|
|
|
11453
11859
|
var FrameItem = memo(function FrameItem({ placed, mode, thumb, theme, store, events, capture }) {
|
|
11454
11860
|
const { item, x, y, w, h } = placed;
|
|
11455
11861
|
const ref = useRef(null);
|
|
11456
|
-
const { frameSandbox } = useViewer();
|
|
11862
|
+
const { frameSandbox, frameAllow } = useViewer();
|
|
11457
11863
|
const setRef = useCallback((el) => {
|
|
11458
11864
|
ref.current = el;
|
|
11459
11865
|
setFrameEl(store, item.id, el);
|
|
@@ -11490,7 +11896,8 @@ var FrameItem = memo(function FrameItem({ placed, mode, thumb, theme, store, eve
|
|
|
11490
11896
|
break;
|
|
11491
11897
|
case "error": {
|
|
11492
11898
|
const current = store.get().frames[item.id]?.errors ?? [];
|
|
11493
|
-
|
|
11899
|
+
const errors = withError(current, message.message);
|
|
11900
|
+
if (errors !== current) setFrame(store, item.id, { errors });
|
|
11494
11901
|
break;
|
|
11495
11902
|
}
|
|
11496
11903
|
case "updated":
|
|
@@ -11556,7 +11963,7 @@ var FrameItem = memo(function FrameItem({ placed, mode, thumb, theme, store, eve
|
|
|
11556
11963
|
ref: setRef,
|
|
11557
11964
|
src,
|
|
11558
11965
|
title: item.title,
|
|
11559
|
-
allow:
|
|
11966
|
+
allow: frameAllow,
|
|
11560
11967
|
sandbox: frameSandbox,
|
|
11561
11968
|
className: cn("absolute top-0 left-0 block border-0 transition-opacity duration-200", ready ? "opacity-100" : "opacity-0"),
|
|
11562
11969
|
style: {
|
|
@@ -12894,7 +13301,7 @@ function PlayPage({ canvasId, itemId }) {
|
|
|
12894
13301
|
}
|
|
12895
13302
|
function Player({ canvas, itemId }) {
|
|
12896
13303
|
const [, navigate] = useLocation();
|
|
12897
|
-
const { frameSandbox, slots } = useViewer();
|
|
13304
|
+
const { frameSandbox, frameAllow, slots } = useViewer();
|
|
12898
13305
|
const pageParam = new URLSearchParams(window.location.search).get("page") ?? (window.location.hash.split("?")[1] ? new URLSearchParams(window.location.hash.split("?")[1]).get("page") : null);
|
|
12899
13306
|
const all = useMemo(() => framesOf(canvas, null), [canvas]);
|
|
12900
13307
|
const current = all.find((entry) => entry.item.id === itemId);
|
|
@@ -12970,7 +13377,7 @@ function Player({ canvas, itemId }) {
|
|
|
12970
13377
|
if (!el || item?.kind !== "screen") return;
|
|
12971
13378
|
setErrors([]);
|
|
12972
13379
|
return listenToFrame(el, (message) => {
|
|
12973
|
-
if (message.type === "error") setErrors((list) =>
|
|
13380
|
+
if (message.type === "error") setErrors((list) => withError(list, message.message));
|
|
12974
13381
|
else if (message.type === "updated") setErrors([]);
|
|
12975
13382
|
else if (message.type === "keydown" && message.code === "Escape") navigate(back);
|
|
12976
13383
|
else if (message.type === "go") {
|
|
@@ -13146,7 +13553,7 @@ function Player({ canvas, itemId }) {
|
|
|
13146
13553
|
ref: setFrame,
|
|
13147
13554
|
src,
|
|
13148
13555
|
title: item.title,
|
|
13149
|
-
allow:
|
|
13556
|
+
allow: frameAllow,
|
|
13150
13557
|
sandbox: frameSandbox,
|
|
13151
13558
|
className: "block border-0",
|
|
13152
13559
|
style: {
|
|
@@ -13202,7 +13609,7 @@ function PhoneCanvas({ canvasId, pageId, itemId }) {
|
|
|
13202
13609
|
}
|
|
13203
13610
|
function PhoneViewer({ canvas, pageId, itemId }) {
|
|
13204
13611
|
const [, navigate] = useLocation();
|
|
13205
|
-
const { frameSandbox, slots, scope } = useViewer();
|
|
13612
|
+
const { frameSandbox, frameAllow, slots, scope } = useViewer();
|
|
13206
13613
|
const appTheme = useTheme();
|
|
13207
13614
|
const all = useMemo(() => framesOf(canvas, null), [canvas]);
|
|
13208
13615
|
const index = Math.max(0, itemId ? all.findIndex((entry) => entry.item.id === itemId) : pageId ? all.findIndex((entry) => entry.pageId === pageId) : 0);
|
|
@@ -13254,7 +13661,7 @@ function PhoneViewer({ canvas, pageId, itemId }) {
|
|
|
13254
13661
|
if (!el || item?.kind !== "screen") return;
|
|
13255
13662
|
return listenToFrame(el, (message) => {
|
|
13256
13663
|
if (message.type === "size") setAutoHeight(message.height);
|
|
13257
|
-
else if (message.type === "error") setErrors((list) =>
|
|
13664
|
+
else if (message.type === "error") setErrors((list) => withError(list, message.message));
|
|
13258
13665
|
else if (message.type === "updated") setErrors([]);
|
|
13259
13666
|
else if (message.type === "go") goTo(message.target.includes("/") ? message.target.split("/")[1] : message.target);
|
|
13260
13667
|
else if (message.type === "link") followLink(message, canvas, goTo, item.id);
|
|
@@ -13427,7 +13834,7 @@ function PhoneViewer({ canvas, pageId, itemId }) {
|
|
|
13427
13834
|
ref: setFrame,
|
|
13428
13835
|
src,
|
|
13429
13836
|
title: item.title,
|
|
13430
|
-
allow:
|
|
13837
|
+
allow: frameAllow,
|
|
13431
13838
|
sandbox: frameSandbox,
|
|
13432
13839
|
className: "block border-0",
|
|
13433
13840
|
style: {
|
|
@@ -14320,7 +14727,7 @@ function ColorsPage({ system }) {
|
|
|
14320
14727
|
*/
|
|
14321
14728
|
function AutoFrame({ src, theme, title, minHeight = 120, width, className }) {
|
|
14322
14729
|
const ref = useRef(null);
|
|
14323
|
-
const { frameSandbox } = useViewer();
|
|
14730
|
+
const { frameSandbox, frameAllow } = useViewer();
|
|
14324
14731
|
const [initial] = useState(() => ({
|
|
14325
14732
|
src,
|
|
14326
14733
|
theme
|
|
@@ -14337,7 +14744,7 @@ function AutoFrame({ src, theme, title, minHeight = 120, width, className }) {
|
|
|
14337
14744
|
return listenToFrame(frame, (message) => {
|
|
14338
14745
|
if (message.type === "size") setHeight(Math.max(minHeight, message.height));
|
|
14339
14746
|
else if (message.type === "ready") setReady(true);
|
|
14340
|
-
else if (message.type === "error") setErrors((list) =>
|
|
14747
|
+
else if (message.type === "error") setErrors((list) => withError(list, message.message));
|
|
14341
14748
|
else if (message.type === "updated") setErrors([]);
|
|
14342
14749
|
else if (message.type === "wheel" && !message.zoom) scrollAround(frame, message);
|
|
14343
14750
|
});
|
|
@@ -14364,6 +14771,7 @@ function AutoFrame({ src, theme, title, minHeight = 120, width, className }) {
|
|
|
14364
14771
|
ref,
|
|
14365
14772
|
src: url,
|
|
14366
14773
|
title,
|
|
14774
|
+
allow: frameAllow,
|
|
14367
14775
|
sandbox: frameSandbox,
|
|
14368
14776
|
className: cn("block w-full border-0 transition-opacity duration-150", ready ? "opacity-100" : "opacity-0"),
|
|
14369
14777
|
style: {
|
|
@@ -18597,7 +19005,7 @@ function ScopedRoutes({ canvas }) {
|
|
|
18597
19005
|
] });
|
|
18598
19006
|
}
|
|
18599
19007
|
/** The whole viewer: home, canvases, screens and the design system of one project. */
|
|
18600
|
-
function DesignViewer({ source, base = "", frameSandbox, slots, scope, queryClient }) {
|
|
19008
|
+
function DesignViewer({ source, base = "", frameSandbox, frameAllow = DEFAULT_FRAME_ALLOW, slots, scope, queryClient }) {
|
|
18601
19009
|
const [ownClient] = useState(() => queryClient ?? createQueryClient(source.live));
|
|
18602
19010
|
const client = queryClient ?? ownClient;
|
|
18603
19011
|
const keys = useMemo(() => viewerKeys(sourceId(source)), [source]);
|
|
@@ -18606,12 +19014,14 @@ function DesignViewer({ source, base = "", frameSandbox, slots, scope, queryClie
|
|
|
18606
19014
|
source,
|
|
18607
19015
|
keys,
|
|
18608
19016
|
frameSandbox,
|
|
19017
|
+
frameAllow,
|
|
18609
19018
|
slots: slots ?? {},
|
|
18610
19019
|
scope: scopeCanvas === void 0 ? null : { canvas: scopeCanvas }
|
|
18611
19020
|
}), [
|
|
18612
19021
|
source,
|
|
18613
19022
|
keys,
|
|
18614
19023
|
frameSandbox,
|
|
19024
|
+
frameAllow,
|
|
18615
19025
|
slots,
|
|
18616
19026
|
scopeCanvas
|
|
18617
19027
|
]);
|
|
@@ -18630,7 +19040,7 @@ function DesignViewer({ source, base = "", frameSandbox, slots, scope, queryClie
|
|
|
18630
19040
|
/* @__PURE__ */ jsx(Title, {}),
|
|
18631
19041
|
/* @__PURE__ */ jsxs(Router, {
|
|
18632
19042
|
base,
|
|
18633
|
-
children: [viewer.scope ? /* @__PURE__ */ jsx(ScopedRoutes, { canvas: viewer.scope.canvas }) : /* @__PURE__ */ jsx(Routes, {}), /* @__PURE__ */ jsx(CommandPalette, {})]
|
|
19043
|
+
children: [/* @__PURE__ */ jsx(PageBoundary, { children: viewer.scope ? /* @__PURE__ */ jsx(ScopedRoutes, { canvas: viewer.scope.canvas }) : /* @__PURE__ */ jsx(Routes, {}) }), /* @__PURE__ */ jsx(CommandPalette, {})]
|
|
18634
19044
|
}),
|
|
18635
19045
|
/* @__PURE__ */ jsx(Toaster, {})
|
|
18636
19046
|
] })
|
|
@@ -18684,13 +19094,65 @@ function Avatar({ src, name, size = 28, shape = "circle", className }) {
|
|
|
18684
19094
|
});
|
|
18685
19095
|
}
|
|
18686
19096
|
//#endregion
|
|
19097
|
+
//#region viewer/src/ui/copy-button.tsx
|
|
19098
|
+
/** A ghost "Copy" button that turns into "Copied" with a check for a moment after it copies. */
|
|
19099
|
+
function CopyButton({ text, label = "Copy", done = "Copied", className }) {
|
|
19100
|
+
const [copied, setCopied] = useState(false);
|
|
19101
|
+
const timer = useRef(void 0);
|
|
19102
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
19103
|
+
const copy = async () => {
|
|
19104
|
+
try {
|
|
19105
|
+
await navigator.clipboard.writeText(text);
|
|
19106
|
+
} catch {
|
|
19107
|
+
toast("Could not copy", "The browser blocked clipboard access", "error");
|
|
19108
|
+
return;
|
|
19109
|
+
}
|
|
19110
|
+
setCopied(true);
|
|
19111
|
+
clearTimeout(timer.current);
|
|
19112
|
+
timer.current = setTimeout(() => setCopied(false), 1600);
|
|
19113
|
+
};
|
|
19114
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
19115
|
+
type: "button",
|
|
19116
|
+
onClick: () => void copy(),
|
|
19117
|
+
className: cn(buttonClass("ghost", "h-7 px-2"), copied && "!text-ok-text", className),
|
|
19118
|
+
children: [/* @__PURE__ */ jsx(AnimatePresence, {
|
|
19119
|
+
mode: "popLayout",
|
|
19120
|
+
initial: false,
|
|
19121
|
+
children: /* @__PURE__ */ jsxs(motion.span, {
|
|
19122
|
+
initial: {
|
|
19123
|
+
opacity: 0,
|
|
19124
|
+
scale: .6
|
|
19125
|
+
},
|
|
19126
|
+
animate: {
|
|
19127
|
+
opacity: 1,
|
|
19128
|
+
scale: 1
|
|
19129
|
+
},
|
|
19130
|
+
exit: {
|
|
19131
|
+
opacity: 0,
|
|
19132
|
+
scale: .6
|
|
19133
|
+
},
|
|
19134
|
+
transition: { duration: .14 },
|
|
19135
|
+
className: "inline-flex items-center gap-1.5",
|
|
19136
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
19137
|
+
name: copied ? "check" : "copy",
|
|
19138
|
+
size: 15
|
|
19139
|
+
}), copied ? done : label]
|
|
19140
|
+
}, copied ? "done" : "copy")
|
|
19141
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
19142
|
+
"aria-live": "polite",
|
|
19143
|
+
className: "sr-only",
|
|
19144
|
+
children: copied ? done : ""
|
|
19145
|
+
})]
|
|
19146
|
+
});
|
|
19147
|
+
}
|
|
19148
|
+
//#endregion
|
|
18687
19149
|
//#region viewer/src/ui/dialog.tsx
|
|
18688
19150
|
/**
|
|
18689
19151
|
* A modal over a dimmed page: focus stays inside until it closes, Escape and a
|
|
18690
19152
|
* click outside close it. Head and foot are a panel's; `actions` go to the foot.
|
|
18691
19153
|
* On a phone it is a sheet from the bottom edge with a handle: swiping it down closes it too.
|
|
18692
19154
|
*/
|
|
18693
|
-
function Dialog({ open, onOpenChange, title, description, children, actions, footNote, below, width = 440 }) {
|
|
19155
|
+
function Dialog({ open, onOpenChange, title, description, children, actions, footNote, below, width = 440, step }) {
|
|
18694
19156
|
const phone = useIsPhone();
|
|
18695
19157
|
const drag = useDragControls();
|
|
18696
19158
|
const autoFocus = (event) => {
|
|
@@ -18778,24 +19240,24 @@ function Dialog({ open, onOpenChange, title, description, children, actions, foo
|
|
|
18778
19240
|
},
|
|
18779
19241
|
tabIndex: -1,
|
|
18780
19242
|
className: "fixed inset-x-0 bottom-0 z-50 flex max-h-[90dvh] flex-col rounded-t-[14px] border-t border-rule bg-surface pb-[env(safe-area-inset-bottom)] shadow-pop outline-none",
|
|
18781
|
-
children: [
|
|
18782
|
-
|
|
18783
|
-
|
|
18784
|
-
|
|
18785
|
-
|
|
18786
|
-
|
|
18787
|
-
|
|
18788
|
-
|
|
18789
|
-
|
|
18790
|
-
|
|
18791
|
-
|
|
18792
|
-
|
|
18793
|
-
|
|
19243
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
19244
|
+
className: "shrink-0 touch-none",
|
|
19245
|
+
onPointerDown: (event) => drag.start(event),
|
|
19246
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
19247
|
+
className: "flex justify-center pt-2",
|
|
19248
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
19249
|
+
"aria-hidden": true,
|
|
19250
|
+
className: "h-1 w-9 rounded-full bg-rule-strong"
|
|
19251
|
+
})
|
|
19252
|
+
}), head]
|
|
19253
|
+
}), /* @__PURE__ */ jsxs(Step, {
|
|
19254
|
+
step,
|
|
19255
|
+
className: "flex min-h-0 flex-col",
|
|
19256
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
18794
19257
|
className: "min-h-0 overflow-y-auto overscroll-contain",
|
|
18795
19258
|
children: [body, list]
|
|
18796
|
-
}),
|
|
18797
|
-
|
|
18798
|
-
]
|
|
19259
|
+
}), foot]
|
|
19260
|
+
})]
|
|
18799
19261
|
})
|
|
18800
19262
|
})]
|
|
18801
19263
|
}) : open ? /* @__PURE__ */ jsx(DialogPortal, {
|
|
@@ -18814,7 +19276,7 @@ function Dialog({ open, onOpenChange, title, description, children, actions, foo
|
|
|
18814
19276
|
forceMount: true,
|
|
18815
19277
|
onOpenAutoFocus: autoFocus,
|
|
18816
19278
|
...described,
|
|
18817
|
-
children: /* @__PURE__ */
|
|
19279
|
+
children: /* @__PURE__ */ jsx(motion.div, {
|
|
18818
19280
|
initial: {
|
|
18819
19281
|
opacity: 0,
|
|
18820
19282
|
scale: .98,
|
|
@@ -18838,12 +19300,15 @@ function Dialog({ open, onOpenChange, title, description, children, actions, foo
|
|
|
18838
19300
|
tabIndex: -1,
|
|
18839
19301
|
className: cn(floatingSurface, "my-auto flex max-w-full flex-col outline-none"),
|
|
18840
19302
|
style: { width },
|
|
18841
|
-
children:
|
|
18842
|
-
|
|
18843
|
-
|
|
18844
|
-
|
|
18845
|
-
|
|
18846
|
-
|
|
19303
|
+
children: /* @__PURE__ */ jsx(Resizing, { children: /* @__PURE__ */ jsxs(Step, {
|
|
19304
|
+
step,
|
|
19305
|
+
children: [
|
|
19306
|
+
head,
|
|
19307
|
+
body,
|
|
19308
|
+
list,
|
|
19309
|
+
foot
|
|
19310
|
+
]
|
|
19311
|
+
}) })
|
|
18847
19312
|
})
|
|
18848
19313
|
})
|
|
18849
19314
|
})
|
|
@@ -18851,6 +19316,53 @@ function Dialog({ open, onOpenChange, title, description, children, actions, foo
|
|
|
18851
19316
|
}) : null })
|
|
18852
19317
|
});
|
|
18853
19318
|
}
|
|
19319
|
+
/** Eases its height to its content's, so a panel whose content changes grows and shrinks smoothly. */
|
|
19320
|
+
function Resizing({ children }) {
|
|
19321
|
+
const inner = useRef(null);
|
|
19322
|
+
const [height, setHeight] = useState("auto");
|
|
19323
|
+
useLayoutEffect(() => {
|
|
19324
|
+
const el = inner.current;
|
|
19325
|
+
if (!el) return;
|
|
19326
|
+
const observer = new ResizeObserver(() => setHeight(el.offsetHeight));
|
|
19327
|
+
observer.observe(el);
|
|
19328
|
+
return () => observer.disconnect();
|
|
19329
|
+
}, []);
|
|
19330
|
+
return /* @__PURE__ */ jsx(motion.div, {
|
|
19331
|
+
initial: false,
|
|
19332
|
+
animate: { height },
|
|
19333
|
+
transition: {
|
|
19334
|
+
type: "spring",
|
|
19335
|
+
stiffness: 520,
|
|
19336
|
+
damping: 44
|
|
19337
|
+
},
|
|
19338
|
+
className: "overflow-hidden",
|
|
19339
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
19340
|
+
ref: inner,
|
|
19341
|
+
className: "flex flex-col",
|
|
19342
|
+
children
|
|
19343
|
+
})
|
|
19344
|
+
});
|
|
19345
|
+
}
|
|
19346
|
+
/** Fades the content in again when the dialog's step changes. */
|
|
19347
|
+
function Step({ step, className, children }) {
|
|
19348
|
+
const first = useRef(step);
|
|
19349
|
+
return /* @__PURE__ */ jsx(motion.div, {
|
|
19350
|
+
initial: step === first.current ? false : {
|
|
19351
|
+
opacity: 0,
|
|
19352
|
+
y: 4
|
|
19353
|
+
},
|
|
19354
|
+
animate: {
|
|
19355
|
+
opacity: 1,
|
|
19356
|
+
y: 0
|
|
19357
|
+
},
|
|
19358
|
+
transition: {
|
|
19359
|
+
duration: .18,
|
|
19360
|
+
ease: "easeOut"
|
|
19361
|
+
},
|
|
19362
|
+
className: className ?? "flex flex-col",
|
|
19363
|
+
children
|
|
19364
|
+
}, step ?? "");
|
|
19365
|
+
}
|
|
18854
19366
|
//#endregion
|
|
18855
19367
|
//#region viewer/src/ui/meter.tsx
|
|
18856
19368
|
/**
|
|
@@ -18967,4 +19479,4 @@ function TextArea({ label, hint, error, className, id, disabled, rows = 4, ...re
|
|
|
18967
19479
|
});
|
|
18968
19480
|
}
|
|
18969
19481
|
//#endregion
|
|
18970
|
-
export { ApiError, Avatar, Badge, Bar, Button, ButtonAnchor, ButtonLink, Content, Count, DesignViewer, Dialog, GroupRow, Icon, IconButton, KV, Kbd, Mark, Menu, MenuItem, MenuLabel, MenuSeparator, Meter, Mono, Muted, NavLabel, Notice, NoticeCard, PageHead, Panel, PanelBody, PanelFoot, PanelHead, PanelTabs, Row, RowLink, Segmented, Sheet, Skeleton, SkeletonRows, SlidePanel, Strip, SubNav, Switch, THead, TextArea, TextField, ThemeMenu, Toaster, Tooltip, TooltipProvider, Two, Wordmark, buttonClass, cn, copyText, floatingSurface, iconButtonClass, initials, localSource, menuItemClass, menuSurface, resolveUrl, setThemePref, staticSource, toast, useBarCompact, useIsPhone, useTheme, useThemePref };
|
|
19482
|
+
export { ApiError, Avatar, Badge, Bar, Button, ButtonAnchor, ButtonLink, Content, CopyButton, CornerPlate, Count, DesignViewer, Dialog, GroupRow, Icon, IconButton, KV, Kbd, Mark, Menu, MenuItem, MenuLabel, MenuSeparator, Meter, Mono, Muted, NavLabel, Notice, NoticeCard, PageHead, Panel, PanelBody, PanelFoot, PanelHead, PanelTabs, Row, RowLink, Segmented, Sheet, Skeleton, SkeletonRows, SlidePanel, Strip, SubNav, Switch, THead, TextArea, TextField, ThemeMenu, Toaster, Tooltip, TooltipProvider, Two, Wordmark, buttonClass, cn, copyText, floatingSurface, followLink, frameSrc, iconButtonClass, initials, listenToFrame, localSource, menuItemClass, menuSurface, resolveUrl, sendTheme, setThemePref, staticSource, toast, useBarCompact, useIsPhone, useTheme, useThemePref };
|