knobkit 0.0.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 +21 -0
- package/dist/assets/cell-renderer-CLTRlCa5-DIlwS99c.js +1 -0
- package/dist/assets/chart-D8ctp-_1.js +36 -0
- package/dist/assets/code-BMuLQBYq.js +2 -0
- package/dist/assets/column.service-C6hByxPy-XG9X0y3N.js +1 -0
- package/dist/assets/debounce-PCRWZliA-BjJpj_P7.js +32 -0
- package/dist/assets/dimension.helpers-CGKwSvw6-D_czicbS.js +1 -0
- package/dist/assets/dist-1hsZpGRf.js +23 -0
- package/dist/assets/dist-B-y4Etc5.js +1 -0
- package/dist/assets/dist-B8BXgMDk.js +10 -0
- package/dist/assets/dist-BJlXPLNt.js +1 -0
- package/dist/assets/dist-ByhR2UY_.js +1 -0
- package/dist/assets/dist-C0bxYHYH.js +2 -0
- package/dist/assets/dist-C8dagUDy.js +6 -0
- package/dist/assets/dist-CtLpohkg.js +1 -0
- package/dist/assets/dist-D00mNtIr.js +1 -0
- package/dist/assets/dist-Dh1Dvy3h.js +1 -0
- package/dist/assets/dist-DlwQ1Qqm.js +1 -0
- package/dist/assets/dist-DtZDI7jp.js +1 -0
- package/dist/assets/dist-thZFs69d.js +9 -0
- package/dist/assets/edit.utils-Dnnbd0xG-OAxDw8WC.js +1 -0
- package/dist/assets/events-BvSmBueA-4kqQ57iN.js +1 -0
- package/dist/assets/filter.button-BFwo1uvz-CyvQhOO5.js +1 -0
- package/dist/assets/header-cell-renderer-BMmXRsd_-BHbC7fao.js +1 -0
- package/dist/assets/index-Db3qZoW5-peeY7EGw.js +1 -0
- package/dist/assets/markdown-dDCgur7g.js +29 -0
- package/dist/assets/revo-grid.entry-CfI6s-uT.js +1 -0
- package/dist/assets/revogr-attribution_7.entry-6fUjzImt.js +1 -0
- package/dist/assets/revogr-clipboard_3.entry-DmI7LkER.js +2 -0
- package/dist/assets/revogr-data_4.entry-CYZIiXNw.js +1 -0
- package/dist/assets/revogr-filter-panel.entry-TmQHTQxw.js +1 -0
- package/dist/assets/table-Zn7rpfG-.js +1 -0
- package/dist/assets/text-editor-C3RUSwH5-DuDr9wKc.js +1 -0
- package/dist/assets/theme.service-BmnDvr6P-DftEgmbe.js +3 -0
- package/dist/assets/throttle-CaUDyxyU-Djj__DCp.js +1 -0
- package/dist/assets/viewport.helpers-CoCAvmZs-ByVRjjkF.js +1 -0
- package/dist/assets/viewport.store-_c579YyM-B_ZSqqka.js +1 -0
- package/dist/cli/config.d.ts +5 -0
- package/dist/cli/config.js +82 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +77 -0
- package/dist/cli/mount.d.ts +2 -0
- package/dist/cli/mount.js +26 -0
- package/dist/cli/serve.d.ts +1 -0
- package/dist/cli/serve.js +21 -0
- package/dist/client/app.d.ts +8 -0
- package/dist/client/app.js +40 -0
- package/dist/client/context.d.ts +2 -0
- package/dist/client/context.js +16 -0
- package/dist/client/mount.d.ts +3 -0
- package/dist/client/mount.js +42 -0
- package/dist/client/runtime.d.ts +19 -0
- package/dist/client/runtime.js +88 -0
- package/dist/client/view.d.ts +12 -0
- package/dist/client/view.js +1 -0
- package/dist/client/widgets/accordion/index.d.ts +5 -0
- package/dist/client/widgets/accordion/index.js +8 -0
- package/dist/client/widgets/annotated-image/index.d.ts +8 -0
- package/dist/client/widgets/annotated-image/index.js +34 -0
- package/dist/client/widgets/audio/index.d.ts +5 -0
- package/dist/client/widgets/audio/index.js +5 -0
- package/dist/client/widgets/button/index.d.ts +4 -0
- package/dist/client/widgets/button/index.js +5 -0
- package/dist/client/widgets/chart/index.d.ts +5 -0
- package/dist/client/widgets/chart/index.js +23 -0
- package/dist/client/widgets/chart/lazy.d.ts +3 -0
- package/dist/client/widgets/chart/lazy.js +9 -0
- package/dist/client/widgets/chat/index.d.ts +6 -0
- package/dist/client/widgets/chat/index.js +77 -0
- package/dist/client/widgets/checkbox/index.d.ts +6 -0
- package/dist/client/widgets/checkbox/index.js +9 -0
- package/dist/client/widgets/checkbox-group/index.d.ts +6 -0
- package/dist/client/widgets/checkbox-group/index.js +12 -0
- package/dist/client/widgets/code/index.d.ts +5 -0
- package/dist/client/widgets/code/index.js +101 -0
- package/dist/client/widgets/code/lazy.d.ts +3 -0
- package/dist/client/widgets/code/lazy.js +10 -0
- package/dist/client/widgets/dropdown/index.d.ts +5 -0
- package/dist/client/widgets/dropdown/index.js +9 -0
- package/dist/client/widgets/file/index.d.ts +6 -0
- package/dist/client/widgets/file/index.js +7 -0
- package/dist/client/widgets/frame/index.d.ts +6 -0
- package/dist/client/widgets/frame/index.js +11 -0
- package/dist/client/widgets/gallery/index.d.ts +6 -0
- package/dist/client/widgets/gallery/index.js +8 -0
- package/dist/client/widgets/highlighted-text/index.d.ts +7 -0
- package/dist/client/widgets/highlighted-text/index.js +20 -0
- package/dist/client/widgets/html/index.d.ts +4 -0
- package/dist/client/widgets/html/index.js +8 -0
- package/dist/client/widgets/image/index.d.ts +4 -0
- package/dist/client/widgets/image/index.js +4 -0
- package/dist/client/widgets/json/index.d.ts +4 -0
- package/dist/client/widgets/json/index.js +4 -0
- package/dist/client/widgets/label/index.d.ts +7 -0
- package/dist/client/widgets/label/index.js +8 -0
- package/dist/client/widgets/layout/index.d.ts +4 -0
- package/dist/client/widgets/layout/index.js +7 -0
- package/dist/client/widgets/log/index.d.ts +4 -0
- package/dist/client/widgets/log/index.js +4 -0
- package/dist/client/widgets/mic/index.d.ts +6 -0
- package/dist/client/widgets/mic/index.js +70 -0
- package/dist/client/widgets/number/index.d.ts +5 -0
- package/dist/client/widgets/number/index.js +8 -0
- package/dist/client/widgets/output/index.d.ts +6 -0
- package/dist/client/widgets/output/index.js +13 -0
- package/dist/client/widgets/output/markdown.d.ts +3 -0
- package/dist/client/widgets/output/markdown.js +8 -0
- package/dist/client/widgets/progress/index.d.ts +6 -0
- package/dist/client/widgets/progress/index.js +6 -0
- package/dist/client/widgets/radio/index.d.ts +6 -0
- package/dist/client/widgets/radio/index.js +10 -0
- package/dist/client/widgets/registry.d.ts +2 -0
- package/dist/client/widgets/registry.js +69 -0
- package/dist/client/widgets/slider/index.d.ts +6 -0
- package/dist/client/widgets/slider/index.js +9 -0
- package/dist/client/widgets/table/index.d.ts +6 -0
- package/dist/client/widgets/table/index.js +72 -0
- package/dist/client/widgets/table/lazy.d.ts +3 -0
- package/dist/client/widgets/table/lazy.js +16 -0
- package/dist/client/widgets/tabs/index.d.ts +5 -0
- package/dist/client/widgets/tabs/index.js +10 -0
- package/dist/client/widgets/text/index.d.ts +6 -0
- package/dist/client/widgets/text/index.js +10 -0
- package/dist/client/widgets/upload/index.d.ts +6 -0
- package/dist/client/widgets/upload/index.js +16 -0
- package/dist/client/widgets/video/index.d.ts +5 -0
- package/dist/client/widgets/video/index.js +5 -0
- package/dist/client/widgets/webcam/index.d.ts +6 -0
- package/dist/client/widgets/webcam/index.js +62 -0
- package/dist/client.css +1 -0
- package/dist/client.js +11 -0
- package/dist/knobkit.browser.css +2 -0
- package/dist/knobkit.browser.js +151 -0
- package/dist/lib/bound.d.ts +12 -0
- package/dist/lib/bound.js +10 -0
- package/dist/lib/controls.d.ts +9 -0
- package/dist/lib/controls.js +35 -0
- package/dist/lib/ctx.d.ts +9 -0
- package/dist/lib/ctx.js +22 -0
- package/dist/lib/declare.d.ts +18 -0
- package/dist/lib/declare.js +43 -0
- package/dist/lib/event.d.ts +2 -0
- package/dist/lib/event.js +9 -0
- package/dist/lib/index.d.ts +10 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/knobkit.d.ts +19 -0
- package/dist/lib/knobkit.js +48 -0
- package/dist/lib/on.d.ts +2 -0
- package/dist/lib/on.js +1 -0
- package/dist/lib/stream.d.ts +1 -0
- package/dist/lib/stream.js +33 -0
- package/dist/lib/types.d.ts +48 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/widget.d.ts +7 -0
- package/dist/lib/widget.js +4 -0
- package/dist/lib/widgets/annotated-image.d.ts +14 -0
- package/dist/lib/widgets/annotated-image.js +16 -0
- package/dist/lib/widgets/audio.d.ts +10 -0
- package/dist/lib/widgets/audio.js +13 -0
- package/dist/lib/widgets/button.d.ts +11 -0
- package/dist/lib/widgets/button.js +17 -0
- package/dist/lib/widgets/chart.d.ts +20 -0
- package/dist/lib/widgets/chart.js +24 -0
- package/dist/lib/widgets/chat.d.ts +26 -0
- package/dist/lib/widgets/chat.js +24 -0
- package/dist/lib/widgets/checkbox-group.d.ts +4 -0
- package/dist/lib/widgets/checkbox-group.js +5 -0
- package/dist/lib/widgets/checkbox.d.ts +4 -0
- package/dist/lib/widgets/checkbox.js +4 -0
- package/dist/lib/widgets/code.d.ts +5 -0
- package/dist/lib/widgets/code.js +10 -0
- package/dist/lib/widgets/dropdown.d.ts +4 -0
- package/dist/lib/widgets/dropdown.js +4 -0
- package/dist/lib/widgets/embed.d.ts +5 -0
- package/dist/lib/widgets/embed.js +23 -0
- package/dist/lib/widgets/file.d.ts +11 -0
- package/dist/lib/widgets/file.js +15 -0
- package/dist/lib/widgets/frame.d.ts +18 -0
- package/dist/lib/widgets/frame.js +25 -0
- package/dist/lib/widgets/gallery.d.ts +12 -0
- package/dist/lib/widgets/gallery.js +17 -0
- package/dist/lib/widgets/highlighted-text.d.ts +12 -0
- package/dist/lib/widgets/highlighted-text.js +15 -0
- package/dist/lib/widgets/html.d.ts +9 -0
- package/dist/lib/widgets/html.js +12 -0
- package/dist/lib/widgets/image.d.ts +7 -0
- package/dist/lib/widgets/image.js +12 -0
- package/dist/lib/widgets/index.d.ts +31 -0
- package/dist/lib/widgets/index.js +31 -0
- package/dist/lib/widgets/json.d.ts +7 -0
- package/dist/lib/widgets/json.js +12 -0
- package/dist/lib/widgets/label.d.ts +15 -0
- package/dist/lib/widgets/label.js +18 -0
- package/dist/lib/widgets/layout.d.ts +21 -0
- package/dist/lib/widgets/layout.js +29 -0
- package/dist/lib/widgets/log.d.ts +8 -0
- package/dist/lib/widgets/log.js +15 -0
- package/dist/lib/widgets/mic.d.ts +19 -0
- package/dist/lib/widgets/mic.js +28 -0
- package/dist/lib/widgets/number.d.ts +5 -0
- package/dist/lib/widgets/number.js +4 -0
- package/dist/lib/widgets/output.d.ts +10 -0
- package/dist/lib/widgets/output.js +13 -0
- package/dist/lib/widgets/progress.d.ts +10 -0
- package/dist/lib/widgets/progress.js +15 -0
- package/dist/lib/widgets/radio.d.ts +4 -0
- package/dist/lib/widgets/radio.js +5 -0
- package/dist/lib/widgets/slider.d.ts +6 -0
- package/dist/lib/widgets/slider.js +6 -0
- package/dist/lib/widgets/table.d.ts +32 -0
- package/dist/lib/widgets/table.js +36 -0
- package/dist/lib/widgets/text.d.ts +4 -0
- package/dist/lib/widgets/text.js +4 -0
- package/dist/lib/widgets/upload.d.ts +3 -0
- package/dist/lib/widgets/upload.js +5 -0
- package/dist/lib/widgets/value.d.ts +9 -0
- package/dist/lib/widgets/value.js +20 -0
- package/dist/lib/widgets/video.d.ts +12 -0
- package/dist/lib/widgets/video.js +14 -0
- package/dist/lib/widgets/webcam.d.ts +21 -0
- package/dist/lib/widgets/webcam.js +29 -0
- package/dist/server/context.d.ts +2 -0
- package/dist/server/context.js +10 -0
- package/dist/server/serve.d.ts +5 -0
- package/dist/server/serve.js +131 -0
- package/package.json +71 -0
- package/src/cli/config.ts +83 -0
- package/src/cli/index.ts +82 -0
- package/src/cli/mount.ts +25 -0
- package/src/cli/serve.ts +22 -0
- package/src/client/app.test.tsx +70 -0
- package/src/client/app.tsx +62 -0
- package/src/client/browser-runtime.test.ts +22 -0
- package/src/client/browser.ts +3 -0
- package/src/client/context.ts +17 -0
- package/src/client/embed.test.tsx +58 -0
- package/src/client/entry.tsx +25 -0
- package/src/client/mount.test.tsx +36 -0
- package/src/client/mount.tsx +48 -0
- package/src/client/runtime.test.ts +64 -0
- package/src/client/runtime.ts +112 -0
- package/src/client/serve-stub.ts +3 -0
- package/src/client/styles.css +131 -0
- package/src/client/view.ts +16 -0
- package/src/client/widgets/accordion/accordion.css +35 -0
- package/src/client/widgets/accordion/index.tsx +17 -0
- package/src/client/widgets/annotated-image/annotated-image.css +62 -0
- package/src/client/widgets/annotated-image/index.tsx +73 -0
- package/src/client/widgets/audio/audio.css +6 -0
- package/src/client/widgets/audio/index.tsx +6 -0
- package/src/client/widgets/button/button.css +25 -0
- package/src/client/widgets/button/index.tsx +11 -0
- package/src/client/widgets/chart/chart.css +12 -0
- package/src/client/widgets/chart/index.tsx +63 -0
- package/src/client/widgets/chart/lazy.tsx +15 -0
- package/src/client/widgets/chat/chat.css +97 -0
- package/src/client/widgets/chat/index.tsx +121 -0
- package/src/client/widgets/checkbox/checkbox.css +15 -0
- package/src/client/widgets/checkbox/index.tsx +15 -0
- package/src/client/widgets/checkbox-group/checkbox-group.css +20 -0
- package/src/client/widgets/checkbox-group/index.tsx +22 -0
- package/src/client/widgets/code/code.css +31 -0
- package/src/client/widgets/code/index.tsx +108 -0
- package/src/client/widgets/code/lazy.tsx +16 -0
- package/src/client/widgets/dropdown/dropdown.css +0 -0
- package/src/client/widgets/dropdown/index.tsx +19 -0
- package/src/client/widgets/file/file.css +26 -0
- package/src/client/widgets/file/index.tsx +12 -0
- package/src/client/widgets/frame/frame.css +17 -0
- package/src/client/widgets/frame/index.tsx +15 -0
- package/src/client/widgets/gallery/gallery.css +26 -0
- package/src/client/widgets/gallery/index.tsx +18 -0
- package/src/client/widgets/highlighted-text/highlighted-text.css +21 -0
- package/src/client/widgets/highlighted-text/index.tsx +42 -0
- package/src/client/widgets/html/index.tsx +8 -0
- package/src/client/widgets/image/index.tsx +5 -0
- package/src/client/widgets/json/index.tsx +5 -0
- package/src/client/widgets/json/json.css +0 -0
- package/src/client/widgets/label/index.tsx +20 -0
- package/src/client/widgets/label/label.css +39 -0
- package/src/client/widgets/layout/index.tsx +14 -0
- package/src/client/widgets/log/index.tsx +5 -0
- package/src/client/widgets/log/log.css +0 -0
- package/src/client/widgets/mic/index.tsx +85 -0
- package/src/client/widgets/mic/mic.css +8 -0
- package/src/client/widgets/number/index.tsx +10 -0
- package/src/client/widgets/number/number.css +0 -0
- package/src/client/widgets/output/index.tsx +19 -0
- package/src/client/widgets/output/markdown.tsx +12 -0
- package/src/client/widgets/output/output.css +75 -0
- package/src/client/widgets/progress/index.tsx +14 -0
- package/src/client/widgets/progress/progress.css +26 -0
- package/src/client/widgets/radio/index.tsx +20 -0
- package/src/client/widgets/radio/radio.css +20 -0
- package/src/client/widgets/registry.tsx +71 -0
- package/src/client/widgets/slider/index.tsx +23 -0
- package/src/client/widgets/slider/slider.css +18 -0
- package/src/client/widgets/table/index.tsx +95 -0
- package/src/client/widgets/table/lazy.tsx +23 -0
- package/src/client/widgets/table/table.css +15 -0
- package/src/client/widgets/tabs/index.tsx +28 -0
- package/src/client/widgets/tabs/tabs.css +30 -0
- package/src/client/widgets/text/index.tsx +16 -0
- package/src/client/widgets/text/text.css +21 -0
- package/src/client/widgets/upload/index.tsx +30 -0
- package/src/client/widgets/upload/upload.css +30 -0
- package/src/client/widgets/video/index.tsx +10 -0
- package/src/client/widgets/video/video.css +8 -0
- package/src/client/widgets/webcam/index.tsx +81 -0
- package/src/client/widgets/webcam/webcam.css +46 -0
- package/src/css.d.ts +1 -0
- package/src/env.d.ts +1 -0
- package/src/lib/bound.ts +30 -0
- package/src/lib/controls.ts +36 -0
- package/src/lib/ctx.ts +31 -0
- package/src/lib/declare.test.ts +46 -0
- package/src/lib/declare.ts +74 -0
- package/src/lib/event.ts +12 -0
- package/src/lib/index.ts +21 -0
- package/src/lib/knobkit.ts +57 -0
- package/src/lib/on.ts +3 -0
- package/src/lib/stream.ts +38 -0
- package/src/lib/types.ts +63 -0
- package/src/lib/widget.ts +11 -0
- package/src/lib/widgets/annotated-image.ts +34 -0
- package/src/lib/widgets/audio.ts +20 -0
- package/src/lib/widgets/button.ts +27 -0
- package/src/lib/widgets/chart.ts +44 -0
- package/src/lib/widgets/chat.ts +43 -0
- package/src/lib/widgets/checkbox-group.ts +6 -0
- package/src/lib/widgets/checkbox.ts +5 -0
- package/src/lib/widgets/code.ts +11 -0
- package/src/lib/widgets/dropdown.ts +5 -0
- package/src/lib/widgets/embed.ts +26 -0
- package/src/lib/widgets/file.ts +23 -0
- package/src/lib/widgets/frame.ts +36 -0
- package/src/lib/widgets/gallery.ts +29 -0
- package/src/lib/widgets/highlighted-text.ts +29 -0
- package/src/lib/widgets/html.ts +18 -0
- package/src/lib/widgets/image.ts +18 -0
- package/src/lib/widgets/index.ts +31 -0
- package/src/lib/widgets/json.ts +18 -0
- package/src/lib/widgets/label.ts +29 -0
- package/src/lib/widgets/layout.ts +47 -0
- package/src/lib/widgets/log.ts +22 -0
- package/src/lib/widgets/mic.ts +42 -0
- package/src/lib/widgets/number.ts +5 -0
- package/src/lib/widgets/output.ts +20 -0
- package/src/lib/widgets/progress.ts +21 -0
- package/src/lib/widgets/radio.ts +6 -0
- package/src/lib/widgets/slider.ts +7 -0
- package/src/lib/widgets/table.ts +58 -0
- package/src/lib/widgets/text.ts +5 -0
- package/src/lib/widgets/upload.ts +6 -0
- package/src/lib/widgets/value.ts +28 -0
- package/src/lib/widgets/video.ts +22 -0
- package/src/lib/widgets/webcam.ts +46 -0
- package/src/server/context.ts +12 -0
- package/src/server/serve.test.ts +121 -0
- package/src/server/serve.ts +130 -0
- package/tsconfig.base.json +14 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// table.css is imported by ./lazy.tsx (the static entry wrapper) so it lands in client.css, not a
|
|
3
|
+
// split css chunk serve() wouldn't route.
|
|
4
|
+
import { useMemo } from "react";
|
|
5
|
+
import { RevoGrid } from "@revolist/react-datagrid";
|
|
6
|
+
// "compact" theme metrics (from revo-grid-style.css): header row 45px, data row 32px. Used to fit the
|
|
7
|
+
// grid to its content up to maxHeight, then scroll — matching Gradio's max_height behavior.
|
|
8
|
+
const HEADER_PX = 45;
|
|
9
|
+
const ROW_PX = 32;
|
|
10
|
+
// A column with no explicit width defaults to one that fits its *header* — the header is usually the
|
|
11
|
+
// wider constraint for compact tables (e.g. "Δ from mean" over numeric cells), and unlike content-based
|
|
12
|
+
// auto-size it stays stable as data changes. Measured with canvas for proportional-font accuracy.
|
|
13
|
+
const HEADER_FONT = "600 14px Helvetica, Arial, sans-serif"; // compact theme header
|
|
14
|
+
const COL_PAD = 34; // header cell padding + room for the sort arrow
|
|
15
|
+
const MIN_COL = 80;
|
|
16
|
+
const MAX_COL = 240;
|
|
17
|
+
let measureCtx;
|
|
18
|
+
function headerWidth(label) {
|
|
19
|
+
if (measureCtx === undefined)
|
|
20
|
+
measureCtx = document.createElement("canvas").getContext("2d");
|
|
21
|
+
let w;
|
|
22
|
+
if (measureCtx) {
|
|
23
|
+
measureCtx.font = HEADER_FONT;
|
|
24
|
+
w = measureCtx.measureText(label).width;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
w = label.length * 8; // canvas unavailable: rough fallback
|
|
28
|
+
}
|
|
29
|
+
return Math.max(MIN_COL, Math.min(MAX_COL, Math.ceil(w) + COL_PAD));
|
|
30
|
+
}
|
|
31
|
+
export function TableView({ widget, state, emit, set }) {
|
|
32
|
+
const editable = widget.editable ?? false;
|
|
33
|
+
const maxHeight = widget.maxHeight ?? 500;
|
|
34
|
+
// fit to content (header + rows), capped at maxHeight; +2 leaves room for a horizontal scrollbar so
|
|
35
|
+
// a fitting grid doesn't sprout a vertical one
|
|
36
|
+
const height = Math.min(maxHeight, HEADER_PX + state.rows.length * ROW_PX + 2);
|
|
37
|
+
// RevoGrid warns that columns/source need stable references; the store hands us a fresh state object
|
|
38
|
+
// only on a real edit (edits are immutable), so memoizing on those references is both stable and correct.
|
|
39
|
+
const columns = useMemo(() => state.columns.map((c) => ({
|
|
40
|
+
prop: c.key,
|
|
41
|
+
name: c.label ?? c.key,
|
|
42
|
+
sortable: true,
|
|
43
|
+
readonly: !editable,
|
|
44
|
+
size: c.width ?? headerWidth(c.label ?? c.key),
|
|
45
|
+
})), [state.columns, editable]);
|
|
46
|
+
// clone each row: RevoGrid mutates its own source in place on edit — we must not let it touch the
|
|
47
|
+
// store's state objects. The real change is applied through the store via onAfteredit below.
|
|
48
|
+
const source = useMemo(() => state.rows.map((r) => ({ ...r })), [state.rows]);
|
|
49
|
+
const apply = (row, key, value) => {
|
|
50
|
+
// a range/paste commit reports every cell in the selection; skip the ones that didn't change so
|
|
51
|
+
// handlers don't see no-op edits
|
|
52
|
+
if (state.rows[row]?.[key] === value)
|
|
53
|
+
return;
|
|
54
|
+
set(["rows", row, key], value); // local edit by path; reads reflect it immediately
|
|
55
|
+
emit(widget.edited({ row, key, value }));
|
|
56
|
+
};
|
|
57
|
+
const onAfteredit = (e) => {
|
|
58
|
+
const d = e.detail;
|
|
59
|
+
if (d && "data" in d) {
|
|
60
|
+
// range / paste edit: { data: { [rowIndex]: changedRowModel } }
|
|
61
|
+
for (const [ri, model] of Object.entries(d.data)) {
|
|
62
|
+
for (const [key, value] of Object.entries(model))
|
|
63
|
+
apply(Number(ri), key, value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (d) {
|
|
67
|
+
// single cell: val is the editor's new value, not yet in the model
|
|
68
|
+
apply(d.rowIndex, d.prop, d.val);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
return (_jsx("div", { className: "pu-table", children: _jsx(RevoGrid, { columns: columns, source: source, readonly: !editable, range: true, resize: true, rowHeaders: true, theme: "compact", stretch: true, hideAttribution: true, style: { height }, onAfteredit: onAfteredit }) }));
|
|
72
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import "./table.css"; // kept in the entry bundle (client.css) so no /assets css chunk to serve
|
|
3
|
+
import { lazy, Suspense } from "react";
|
|
4
|
+
// RevoGrid is a heavy web-component grid; load it only when an app actually renders a table widget.
|
|
5
|
+
// TableView narrows ViewProps internally; the registry hands it the generic shape, so widen here.
|
|
6
|
+
const Impl = lazy(async () => ({ default: (await import("./index.js")).TableView }));
|
|
7
|
+
// mirror index.tsx's fit math so the loading placeholder is the grid's real height — no layout jump
|
|
8
|
+
// when the chunk resolves (can't import the constants from index.js without pulling it into the entry)
|
|
9
|
+
const fitHeight = (props) => {
|
|
10
|
+
const rows = props.state.rows ?? [];
|
|
11
|
+
const maxHeight = props.widget.maxHeight ?? 500;
|
|
12
|
+
return Math.min(maxHeight, 45 + rows.length * 32 + 2);
|
|
13
|
+
};
|
|
14
|
+
export function TableView(props) {
|
|
15
|
+
return (_jsx(Suspense, { fallback: _jsx("div", { className: "pu-table", style: { height: fitHeight(props) } }), children: _jsx(Impl, { ...props }) }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import "./tabs.css";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
export function TabsView({ widget, state, slot }) {
|
|
5
|
+
const items = state.items ?? [];
|
|
6
|
+
const labels = widget.labels ?? [];
|
|
7
|
+
const [active, setActive] = useState(0);
|
|
8
|
+
const current = Math.min(active, Math.max(0, items.length - 1));
|
|
9
|
+
return (_jsxs("div", { className: "pu-tabs", children: [_jsx("div", { className: "pu-tabs-bar", role: "tablist", children: items.map((_key, i) => (_jsx("button", { role: "tab", "aria-selected": i === current, className: `pu-tab${i === current ? " pu-tab-active" : ""}`, onClick: () => setActive(i), children: labels[i] ?? `Tab ${i + 1}` }, i))) }), _jsx("div", { className: "pu-tabs-panel", children: items[current] != null ? slot(items[current]) : null })] }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "./text.css";
|
|
2
|
+
import type { ViewProps } from "../../view.js";
|
|
3
|
+
import type { ValueWidget } from "../../../lib/widgets/value.js";
|
|
4
|
+
export declare function TextView({ widget, state, emit, set }: ViewProps<ValueWidget<string>, {
|
|
5
|
+
value: string;
|
|
6
|
+
}>): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import "./text.css";
|
|
3
|
+
export function TextView({ widget, state, emit, set }) {
|
|
4
|
+
const lines = widget.lines ?? 1;
|
|
5
|
+
const update = (v) => {
|
|
6
|
+
set(["value"], v); // local, so the controlled input reflects typing and reads see it
|
|
7
|
+
emit(widget.changed(v));
|
|
8
|
+
};
|
|
9
|
+
return lines > 1 ? (_jsx("textarea", { className: "pu-input", rows: lines, placeholder: widget.placeholder, value: state.value, onChange: (e) => update(e.currentTarget.value) })) : (_jsx("input", { className: "pu-input", placeholder: widget.placeholder, value: state.value, onChange: (e) => update(e.currentTarget.value) }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "./upload.css";
|
|
2
|
+
import type { ViewProps } from "../../view.js";
|
|
3
|
+
import type { ValueWidget } from "../../../lib/widgets/value.js";
|
|
4
|
+
export declare function UploadView({ widget, state, emit, set }: ViewProps<ValueWidget<string | null>, {
|
|
5
|
+
value: string | null;
|
|
6
|
+
}>): import("react").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import "./upload.css";
|
|
3
|
+
export function UploadView({ widget, state, emit, set }) {
|
|
4
|
+
return (_jsxs("div", { className: "pu-upload", children: [_jsxs("label", { className: "pu-upload-drop", children: [_jsx("input", { type: "file", hidden: true, accept: widget.accept, onChange: (e) => {
|
|
5
|
+
const f = e.currentTarget.files?.[0];
|
|
6
|
+
if (!f)
|
|
7
|
+
return;
|
|
8
|
+
const r = new FileReader();
|
|
9
|
+
r.onload = () => {
|
|
10
|
+
const url = String(r.result);
|
|
11
|
+
set(["value"], url);
|
|
12
|
+
emit(widget.changed(url));
|
|
13
|
+
};
|
|
14
|
+
r.readAsDataURL(f);
|
|
15
|
+
} }), _jsx("span", { children: state.value ? "Choose another image" : "Choose an image…" })] }), state.value && _jsx("img", { className: "pu-image", src: state.value, alt: "" })] }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import "./video.css";
|
|
3
|
+
export function VideoView({ widget, state }) {
|
|
4
|
+
return state.src ? (_jsx("video", { src: state.src, controls: true, autoPlay: Boolean(widget.autoplay), loop: Boolean(widget.loop), muted: Boolean(widget.autoplay) })) : (_jsx("div", { className: "pu-output", children: "\u2014" }));
|
|
5
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "./webcam.css";
|
|
2
|
+
import type { ViewProps } from "../../view.js";
|
|
3
|
+
import type { WebcamWidget } from "../../../lib/widgets/webcam.js";
|
|
4
|
+
export declare function WebcamView({ widget, state, enabled, emit, set }: ViewProps<WebcamWidget, {
|
|
5
|
+
live: boolean;
|
|
6
|
+
}>): import("react").JSX.Element;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import "./webcam.css";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
export function WebcamView({ widget, state, enabled, emit, set }) {
|
|
5
|
+
const streamRef = useRef(null);
|
|
6
|
+
const videoRef = useRef(null);
|
|
7
|
+
const timerRef = useRef(null);
|
|
8
|
+
const startingRef = useRef(false);
|
|
9
|
+
// capture follows `live`; the view sets `live` locally so it starts/stops without a round-trip
|
|
10
|
+
const toggle = (live) => {
|
|
11
|
+
set(["live"], live);
|
|
12
|
+
emit(widget.toggled(live));
|
|
13
|
+
};
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
let cancelled = false;
|
|
16
|
+
if (state.live && enabled && !streamRef.current && !startingRef.current && navigator.mediaDevices?.getUserMedia) {
|
|
17
|
+
startingRef.current = true;
|
|
18
|
+
void (async () => {
|
|
19
|
+
try {
|
|
20
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: widget.facing } });
|
|
21
|
+
if (cancelled)
|
|
22
|
+
return stream.getTracks().forEach((t) => t.stop());
|
|
23
|
+
streamRef.current = stream;
|
|
24
|
+
const video = videoRef.current;
|
|
25
|
+
video.srcObject = stream;
|
|
26
|
+
await video.play();
|
|
27
|
+
if (widget.every > 0) {
|
|
28
|
+
const canvas = document.createElement("canvas");
|
|
29
|
+
timerRef.current = setInterval(() => {
|
|
30
|
+
if (streamRef.current !== stream || video.readyState < 2 || !video.videoWidth)
|
|
31
|
+
return;
|
|
32
|
+
canvas.width = video.videoWidth;
|
|
33
|
+
canvas.height = video.videoHeight;
|
|
34
|
+
canvas.getContext("2d")?.drawImage(video, 0, 0);
|
|
35
|
+
emit(widget.frame(canvas.toDataURL("image/jpeg", 0.8)));
|
|
36
|
+
}, widget.every);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error(err);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
startingRef.current = false;
|
|
44
|
+
}
|
|
45
|
+
})();
|
|
46
|
+
}
|
|
47
|
+
else if ((!state.live || !enabled) && streamRef.current) {
|
|
48
|
+
if (timerRef.current)
|
|
49
|
+
clearInterval(timerRef.current);
|
|
50
|
+
timerRef.current = null;
|
|
51
|
+
streamRef.current.getTracks().forEach((t) => t.stop());
|
|
52
|
+
streamRef.current = null;
|
|
53
|
+
if (videoRef.current)
|
|
54
|
+
videoRef.current.srcObject = null;
|
|
55
|
+
}
|
|
56
|
+
return () => {
|
|
57
|
+
cancelled = true;
|
|
58
|
+
};
|
|
59
|
+
}, [state.live, enabled]);
|
|
60
|
+
const mirror = widget.facing === "user" ? " pu-webcam-mirror" : "";
|
|
61
|
+
return (_jsxs("div", { className: "pu-webcam", children: [widget.preview ? (_jsxs("div", { className: "pu-webcam-stage", children: [_jsx("video", { ref: videoRef, className: `pu-webcam-video${mirror}`, muted: true, playsInline: true }), !state.live && _jsx("div", { className: "pu-webcam-placeholder", children: "Camera off" })] })) : (_jsx("video", { ref: videoRef, className: "pu-webcam-offscreen", muted: true, playsInline: true })), widget.control && (_jsx("button", { className: `pu-submit${state.live ? " pu-rec" : ""}`, disabled: !enabled, onClick: () => toggle(!state.live), children: state.live ? "● Stop camera" : "Go live" }))] }));
|
|
62
|
+
}
|
package/dist/client.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@layer tokens{:root{--pu-bg:#f6f7f9;--pu-panel:#fff;--pu-border:#e3e6ea;--pu-text:#1c1f23;--pu-muted:#6b7280;--pu-accent:#2563eb;--pu-accent-press:#1d4ed8;--pu-radius:12px}}@layer base{*{box-sizing:border-box}body{background:var(--pu-bg);color:var(--pu-text);margin:0;font:15px/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif}.pu-page{background:var(--pu-panel);border:1px solid var(--pu-border);border-radius:var(--pu-radius);max-width:720px;margin:48px auto;padding:28px 28px 32px;box-shadow:0 1px 2px #0000000a,0 10px 28px #0000000d}:is(.pu-page:has(.pu-layout-row),.pu-page:has(.pu-layout-grid)){max-width:1040px}.pu-page h1{letter-spacing:-.01em;margin:0 0 4px;font-size:24px;font-weight:650}.pu-desc{color:var(--pu-muted);margin:0 0 24px}.pu-field:empty{display:none}.pu-disabled{opacity:.5;pointer-events:none}.pu-busy{position:relative}.pu-busy-bar{z-index:2;background:#2563eb26;border-radius:2px;height:2px;position:absolute;top:0;left:0;right:0;overflow:hidden}.pu-busy-bar:after{content:"";background:var(--pu-accent);border-radius:2px;width:40%;animation:1.1s ease-in-out infinite pu-busy-slide;position:absolute;inset:0 auto 0 0}@keyframes pu-busy-slide{0%{transform:translate(-100%)}to{transform:translate(350%)}}@media (prefers-reduced-motion:reduce){.pu-busy-bar:after{animation-duration:2.4s}}.pu-layout{gap:18px;display:flex}.pu-layout-col{flex-direction:column}.pu-layout-row{flex-direction:row}.pu-layout-row>.pu-field{flex:1 1 0;min-width:0}.pu-layout-grid{gap:18px;display:grid}@media (width<=720px){.pu-page{box-shadow:none;border:none;border-radius:0;min-height:100vh;margin:0}}}@layer components{.pu-input{border:1px solid var(--pu-border,#e3e6ea);width:100%;font:inherit;color:inherit;background:#fff;border-radius:8px;outline:none;padding:9px 11px;transition:border-color .12s,box-shadow .12s}.pu-input:focus{border-color:var(--pu-accent,#2563eb);box-shadow:0 0 0 3px #2563eb1f}textarea.pu-input{resize:vertical;line-height:1.5}.pu-slider{align-items:center;gap:12px;display:flex}.pu-slider input[type=range]{accent-color:var(--pu-accent,#2563eb);cursor:pointer;flex:1}.pu-slider-val{text-align:right;font-variant-numeric:tabular-nums;min-width:3ch;color:var(--pu-muted,#6b7280)}.pu-code .cm-editor{border:1px solid var(--pu-border,#e3e6ea);background:#fff;border-radius:8px;overflow:hidden}.pu-code .cm-editor.cm-focused{border-color:var(--pu-accent,#2563eb);outline:none;box-shadow:0 0 0 3px #2563eb1f}.pu-code .cm-scroller{max-height:420px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,Liberation Mono,monospace;font-size:13px;line-height:1.55}.pu-code .cm-gutters{background:var(--pu-bg,#f6f7f9);border-right:1px solid var(--pu-border,#e3e6ea);color:var(--pu-muted,#6b7280)}.pu-code-ro .cm-editor{background:var(--pu-bg,#f6f7f9)}.pu-code-ro .cm-cursor{display:none}.pu-table{border:1px solid var(--pu-border,#e3e6ea);border-radius:8px;overflow:hidden}.pu-table revo-grid{width:100%;display:block;min-height:0!important}.pu-chart{border:1px solid var(--pu-border,#e3e6ea);background:#fff;border-radius:8px;width:100%;padding:12px 8px 4px}.pu-chart .recharts-cartesian-axis-tick text{fill:var(--pu-muted,#6b7280)}.pu-check{cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:8px;display:inline-flex}.pu-check input{width:16px;height:16px;accent-color:var(--pu-accent,#2563eb);cursor:pointer}.pu-checkgroup{flex-direction:column;gap:8px;display:flex}.pu-checkgroup-opt{cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:8px;display:inline-flex}.pu-checkgroup-opt input{width:16px;height:16px;accent-color:var(--pu-accent,#2563eb);cursor:pointer}.pu-radio{flex-direction:column;gap:8px;display:flex}.pu-radio-opt{cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:8px;display:inline-flex}.pu-radio-opt input{width:16px;height:16px;accent-color:var(--pu-accent,#2563eb);cursor:pointer}.pu-gallery{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px;display:grid}.pu-gallery-cell{flex-direction:column;gap:4px;margin:0;display:flex}.pu-gallery-cell img{aspect-ratio:1;object-fit:cover;border:1px solid var(--pu-border,#e3e6ea);border-radius:8px;width:100%;display:block}.pu-gallery-cell figcaption{color:var(--pu-muted,#6b7280);text-align:center;font-size:12px}video{background:#000;border-radius:8px;width:100%;display:block}.pu-label{flex-direction:column;gap:6px;display:flex}.pu-label-top{letter-spacing:-.01em;font-size:18px;font-weight:650}.pu-label-row{border:1px solid var(--pu-border,#e3e6ea);border-radius:8px;align-items:center;gap:8px;padding:6px 10px;display:flex;position:relative;overflow:hidden}.pu-label-bar{z-index:0;background:#2563eb1f;position:absolute;inset:0 auto 0 0}.pu-label-name{z-index:1;flex:1;position:relative}.pu-label-score{z-index:1;color:var(--pu-muted,#6b7280);font-variant-numeric:tabular-nums;position:relative}.pu-hltext{white-space:pre-wrap;word-break:break-word;line-height:2}.pu-hltext-span{border:1px solid;border-radius:6px;margin:0 1px;padding:1px 4px}.pu-hltext-label{text-transform:uppercase;letter-spacing:.02em;vertical-align:1px;margin-left:4px;font-size:11px;font-weight:650}.pu-annimg{flex-direction:column;gap:8px;display:flex}.pu-annimg-frame{line-height:0;display:inline-block;position:relative}.pu-annimg-base{border-radius:8px;max-width:100%;display:block}.pu-annimg-mask{opacity:.5;mix-blend-mode:multiply;pointer-events:none;width:100%;height:100%;position:absolute;inset:0}.pu-annimg-box{box-sizing:border-box;border:2px solid;border-radius:4px;position:absolute}.pu-annimg-tag{color:#fff;white-space:nowrap;border-radius:4px 4px 4px 0;padding:1px 5px;font-size:11px;font-weight:650;line-height:1.4;position:absolute;top:-1px;left:-2px;transform:translateY(-100%)}.pu-annimg-legend{flex-wrap:wrap;gap:6px 12px;display:flex}.pu-annimg-chip{color:var(--pu-muted,#6b7280);align-items:center;gap:6px;font-size:13px;display:inline-flex}.pu-annimg-swatch{border-radius:3px;width:12px;height:12px}.pu-file{border:1px solid var(--pu-border,#e3e6ea);color:var(--pu-text,#1c1f23);cursor:pointer;background:#fafbfc;border-radius:8px;align-items:center;gap:8px;padding:9px 14px;text-decoration:none;transition:border-color .12s,background .12s,color .12s;display:inline-flex}.pu-file:hover{border-color:var(--pu-accent,#2563eb);color:var(--pu-accent,#2563eb);background:#2563eb0a}.pu-file-icon{font-size:16px}.pu-file-name{font-weight:500}.pu-progress{align-items:center;gap:10px;display:flex}.pu-progress-track{background:var(--pu-border,#e3e6ea);border-radius:999px;flex:1;height:8px;overflow:hidden}.pu-progress-fill{background:var(--pu-accent,#2563eb);border-radius:999px;height:100%;transition:width .2s}.pu-progress-label{color:var(--pu-muted,#6b7280);font-variant-numeric:tabular-nums;text-align:right;min-width:3ch}.pu-frame{border:1px solid var(--pu-border);background:var(--pu-panel);border-radius:8px;width:100%;height:100%;min-height:360px;display:block}.pu-frame-empty{color:var(--pu-muted);place-items:center;display:grid}.pu-upload{flex-direction:column;gap:12px;display:flex}.pu-image{border:1px solid var(--pu-border,#e3e6ea);border-radius:8px;max-width:100%;display:block}.pu-upload-drop{border:1.5px dashed var(--pu-border,#e3e6ea);color:var(--pu-muted,#6b7280);cursor:pointer;background:#fafbfc;border-radius:8px;justify-content:center;align-items:center;padding:22px 14px;transition:border-color .12s,background .12s,color .12s;display:flex}.pu-upload-drop:hover{border-color:var(--pu-accent,#2563eb);color:var(--pu-accent,#2563eb);background:#2563eb0a}.pu-submit{background:var(--pu-accent,#2563eb);color:#fff;font:inherit;cursor:pointer;border:none;border-radius:8px;padding:9px 16px;font-weight:600;transition:background .12s,transform 60ms}.pu-submit:hover{background:var(--pu-accent-press,#1d4ed8)}.pu-submit:active{transform:translateY(1px)}.pu-submit:disabled{opacity:.55;cursor:default;background:var(--pu-accent,#2563eb);transform:none}.pu-submit.pu-rec{background:#dc2626}.pu-submit.pu-rec:hover{background:#b91c1c}.pu-chat{flex-direction:column;gap:8px;display:flex}.pu-msg{white-space:pre-wrap;word-break:break-word;border-radius:12px;max-width:85%;margin:0;padding:8px 12px}.pu-msg b{display:none}.pu-user{background:var(--pu-accent,#2563eb);color:#fff;border-bottom-right-radius:4px;align-self:flex-end}.pu-assistant{color:var(--pu-text,#1c1f23);background:#f1f3f5;border-bottom-left-radius:4px;align-self:flex-start}.pu-composer{align-items:center;gap:8px;margin-top:4px;display:flex}.pu-composer .pu-input{flex:1}.pu-mic{cursor:pointer;background:#f1f3f5;border:none;border-radius:50%;flex:none;width:40px;height:40px;font-size:16px;line-height:1}.pu-mic.pu-rec{color:#fff;background:#dc2626}.pu-msg-image{border-radius:8px;max-width:160px;max-height:160px;margin-top:6px;display:block}.pu-attachment{align-self:flex-end;width:96px;position:relative}.pu-attachment img{object-fit:cover;border-radius:8px;width:96px;height:96px}.pu-attach-x{color:#fff;cursor:pointer;background:#1c1f23;border:none;border-radius:50%;width:20px;height:20px;font-size:11px;line-height:1;position:absolute;top:-6px;right:-6px}.pu-attach{cursor:pointer;background:#f1f3f5;border:none;border-radius:50%;flex:none;width:40px;height:40px;font-size:20px;line-height:1}.pu-output{border:1px solid var(--pu-border,#e3e6ea);min-height:42px;color:var(--pu-text,#1c1f23);white-space:pre-wrap;word-break:break-word;font:inherit;background:#fafbfc;border-radius:8px;margin:0;padding:10px 12px}pre.pu-output{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;line-height:1.5;overflow-x:auto}.pu-md{border:1px solid var(--pu-border,#e3e6ea);min-height:42px;color:var(--pu-text,#1c1f23);background:#fafbfc;border-radius:8px;padding:2px 12px}.pu-md>:first-child{margin-top:0}.pu-md>:last-child{margin-bottom:0}.pu-md h1,.pu-md h2,.pu-md h3{margin:1em 0 .4em;line-height:1.25}.pu-md p,.pu-md ul,.pu-md ol,.pu-md blockquote,.pu-md pre,.pu-md table{margin:.6em 0}.pu-md code{background:#0000000d;border-radius:4px;padding:.1em .35em;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.9em}.pu-md pre{background:#0000000d;border-radius:8px;padding:10px 12px;overflow-x:auto}.pu-md pre code{background:0 0;padding:0}.pu-md a{color:var(--pu-accent,#2563eb)}.pu-md th,.pu-md td{border:1px solid var(--pu-border,#e3e6ea);padding:4px 8px}audio{width:100%;display:block}.pu-webcam{flex-direction:column;align-items:flex-start;gap:8px;display:flex}.pu-webcam-stage{aspect-ratio:4/3;background:#111418;border-radius:12px;width:480px;max-width:100%;position:relative;overflow:hidden}.pu-webcam-video{object-fit:cover;width:100%;height:100%;position:absolute;inset:0}.pu-webcam-mirror{transform:scaleX(-1)}.pu-webcam-offscreen{opacity:0;pointer-events:none;width:1px;height:1px;position:fixed;top:0;left:0}.pu-webcam-placeholder{color:#6b7280;letter-spacing:.02em;justify-content:center;align-items:center;font-size:14px;display:flex;position:absolute;inset:0}.pu-tabs{flex-direction:column;gap:14px;display:flex}.pu-tabs-bar{border-bottom:1px solid var(--pu-border,#e3e6ea);gap:4px;display:flex}.pu-tab{appearance:none;font:inherit;color:var(--pu-muted,#6b7280);cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;margin-bottom:-1px;padding:8px 14px}.pu-tab:hover{color:var(--pu-text,#1c1f23)}.pu-tab-active{color:var(--pu-accent,#2563eb);border-bottom-color:var(--pu-accent,#2563eb)}.pu-accordion{border:1px solid var(--pu-border,#e3e6ea);border-radius:8px;overflow:hidden}.pu-accordion-head{appearance:none;width:100%;font:inherit;text-align:left;cursor:pointer;background:#fafbfc;border:none;align-items:center;gap:8px;padding:10px 14px;font-weight:550;display:flex}.pu-accordion-caret{color:var(--pu-muted,#6b7280);transition:transform .12s;display:inline-block}.pu-accordion-open .pu-accordion-caret{transform:rotate(90deg)}.pu-accordion-body{flex-direction:column;gap:18px;padding:16px 14px;display:flex}}
|