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,62 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.pu-annimg {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: 8px;
|
|
6
|
+
}
|
|
7
|
+
.pu-annimg-frame {
|
|
8
|
+
position: relative;
|
|
9
|
+
display: inline-block;
|
|
10
|
+
line-height: 0;
|
|
11
|
+
}
|
|
12
|
+
.pu-annimg-base {
|
|
13
|
+
display: block;
|
|
14
|
+
max-width: 100%;
|
|
15
|
+
border-radius: 8px;
|
|
16
|
+
}
|
|
17
|
+
.pu-annimg-mask {
|
|
18
|
+
position: absolute;
|
|
19
|
+
inset: 0;
|
|
20
|
+
width: 100%;
|
|
21
|
+
height: 100%;
|
|
22
|
+
opacity: 0.5;
|
|
23
|
+
mix-blend-mode: multiply;
|
|
24
|
+
pointer-events: none;
|
|
25
|
+
}
|
|
26
|
+
.pu-annimg-box {
|
|
27
|
+
position: absolute;
|
|
28
|
+
border: 2px solid;
|
|
29
|
+
border-radius: 4px;
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
}
|
|
32
|
+
.pu-annimg-tag {
|
|
33
|
+
position: absolute;
|
|
34
|
+
top: -1px;
|
|
35
|
+
left: -2px;
|
|
36
|
+
transform: translateY(-100%);
|
|
37
|
+
color: #fff;
|
|
38
|
+
font-size: 11px;
|
|
39
|
+
font-weight: 650;
|
|
40
|
+
line-height: 1.4;
|
|
41
|
+
padding: 1px 5px;
|
|
42
|
+
border-radius: 4px 4px 4px 0;
|
|
43
|
+
white-space: nowrap;
|
|
44
|
+
}
|
|
45
|
+
.pu-annimg-legend {
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-wrap: wrap;
|
|
48
|
+
gap: 6px 12px;
|
|
49
|
+
}
|
|
50
|
+
.pu-annimg-chip {
|
|
51
|
+
display: inline-flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
gap: 6px;
|
|
54
|
+
font-size: 13px;
|
|
55
|
+
color: var(--pu-muted, #6b7280);
|
|
56
|
+
}
|
|
57
|
+
.pu-annimg-swatch {
|
|
58
|
+
width: 12px;
|
|
59
|
+
height: 12px;
|
|
60
|
+
border-radius: 3px;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import "./annotated-image.css";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import type { ViewProps } from "../../view.js";
|
|
4
|
+
import type { Annotation } from "../../../lib/widgets/annotated-image.js";
|
|
5
|
+
|
|
6
|
+
const PALETTE = ["#2563eb", "#16a34a", "#dc2626", "#d97706", "#7c3aed", "#0891b2", "#db2777", "#65a30d"];
|
|
7
|
+
|
|
8
|
+
function colorFor(label: string, colorMap: Record<string, string>): string {
|
|
9
|
+
if (colorMap[label]) return colorMap[label];
|
|
10
|
+
let h = 0;
|
|
11
|
+
for (let i = 0; i < label.length; i++) h = (h * 31 + label.charCodeAt(i)) | 0;
|
|
12
|
+
return PALETTE[Math.abs(h) % PALETTE.length];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function AnnotatedImageView({
|
|
16
|
+
state,
|
|
17
|
+
}: ViewProps<any, { src: string; annotations: Annotation[]; colorMap: Record<string, string> }>) {
|
|
18
|
+
// The image's natural pixel size — only known once the <img> loads — turns the author's pixel boxes
|
|
19
|
+
// into percentages of the (possibly resized) displayed image. Boxes wait for it.
|
|
20
|
+
const [nat, setNat] = useState<{ w: number; h: number } | null>(null);
|
|
21
|
+
if (!state.src) return <div className="pu-output">—</div>;
|
|
22
|
+
const annotations = state.annotations ?? [];
|
|
23
|
+
const colorMap = state.colorMap ?? {};
|
|
24
|
+
// Distinct labels for the legend, in first-seen order.
|
|
25
|
+
const labels = [...new Set(annotations.map((a) => a.label))];
|
|
26
|
+
return (
|
|
27
|
+
<div className="pu-annimg">
|
|
28
|
+
<div className="pu-annimg-frame">
|
|
29
|
+
<img
|
|
30
|
+
className="pu-annimg-base"
|
|
31
|
+
src={state.src}
|
|
32
|
+
alt=""
|
|
33
|
+
onLoad={(e) => setNat({ w: e.currentTarget.naturalWidth, h: e.currentTarget.naturalHeight })}
|
|
34
|
+
/>
|
|
35
|
+
{nat &&
|
|
36
|
+
annotations.map((a, i) => {
|
|
37
|
+
const color = colorFor(a.label, colorMap);
|
|
38
|
+
return (
|
|
39
|
+
<span key={i}>
|
|
40
|
+
{a.mask && <img className="pu-annimg-mask" src={a.mask} alt="" />}
|
|
41
|
+
{a.box && (
|
|
42
|
+
<span
|
|
43
|
+
className="pu-annimg-box"
|
|
44
|
+
style={{
|
|
45
|
+
left: `${(a.box[0] / nat.w) * 100}%`,
|
|
46
|
+
top: `${(a.box[1] / nat.h) * 100}%`,
|
|
47
|
+
width: `${((a.box[2] - a.box[0]) / nat.w) * 100}%`,
|
|
48
|
+
height: `${((a.box[3] - a.box[1]) / nat.h) * 100}%`,
|
|
49
|
+
borderColor: color,
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<span className="pu-annimg-tag" style={{ background: color }}>
|
|
53
|
+
{a.label}
|
|
54
|
+
</span>
|
|
55
|
+
</span>
|
|
56
|
+
)}
|
|
57
|
+
</span>
|
|
58
|
+
);
|
|
59
|
+
})}
|
|
60
|
+
</div>
|
|
61
|
+
{labels.length > 0 && (
|
|
62
|
+
<div className="pu-annimg-legend">
|
|
63
|
+
{labels.map((l) => (
|
|
64
|
+
<span key={l} className="pu-annimg-chip">
|
|
65
|
+
<span className="pu-annimg-swatch" style={{ background: colorFor(l, colorMap) }} />
|
|
66
|
+
{l}
|
|
67
|
+
</span>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.pu-submit {
|
|
3
|
+
padding: 9px 16px;
|
|
4
|
+
border: none;
|
|
5
|
+
border-radius: 8px;
|
|
6
|
+
background: var(--pu-accent, #2563eb);
|
|
7
|
+
color: #fff;
|
|
8
|
+
font: inherit;
|
|
9
|
+
font-weight: 600;
|
|
10
|
+
cursor: pointer;
|
|
11
|
+
transition: background 0.12s, transform 0.06s;
|
|
12
|
+
}
|
|
13
|
+
.pu-submit:hover {
|
|
14
|
+
background: var(--pu-accent-press, #1d4ed8);
|
|
15
|
+
}
|
|
16
|
+
.pu-submit:active {
|
|
17
|
+
transform: translateY(1px);
|
|
18
|
+
}
|
|
19
|
+
.pu-submit:disabled {
|
|
20
|
+
opacity: 0.55;
|
|
21
|
+
cursor: default;
|
|
22
|
+
background: var(--pu-accent, #2563eb);
|
|
23
|
+
transform: none;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "./button.css";
|
|
2
|
+
import type { ViewProps } from "../../view.js";
|
|
3
|
+
import type { ButtonWidget, ButtonState } from "../../../lib/widgets/button.js";
|
|
4
|
+
|
|
5
|
+
export function ButtonView({ widget, state, enabled, emit }: ViewProps<ButtonWidget, ButtonState>) {
|
|
6
|
+
return (
|
|
7
|
+
<button className="pu-submit" disabled={!enabled} onClick={() => emit(widget.clicked())}>
|
|
8
|
+
{state.label}
|
|
9
|
+
</button>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.pu-chart {
|
|
3
|
+
width: 100%;
|
|
4
|
+
border: 1px solid var(--pu-border, #e3e6ea);
|
|
5
|
+
border-radius: 8px;
|
|
6
|
+
padding: 12px 8px 4px;
|
|
7
|
+
background: #fff;
|
|
8
|
+
}
|
|
9
|
+
.pu-chart .recharts-cartesian-axis-tick text {
|
|
10
|
+
fill: var(--pu-muted, #6b7280);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// chart.css is imported by ./lazy.tsx (the static entry wrapper) so it lands in client.css.
|
|
2
|
+
import { ResponsiveContainer, CartesianGrid, XAxis, YAxis, Tooltip, Legend, BarChart, Bar, LineChart, Line, AreaChart, Area } from "recharts";
|
|
3
|
+
import type { ReactElement } from "react";
|
|
4
|
+
import type { ViewProps } from "../../view.js";
|
|
5
|
+
import type { ChartWidget, Point } from "../../../lib/widgets/chart.js";
|
|
6
|
+
|
|
7
|
+
const COLORS = ["#2563eb", "#16a34a", "#dc2626", "#d97706", "#7c3aed"]; // knobkit accent + a small palette
|
|
8
|
+
|
|
9
|
+
export function ChartView({ widget, state }: ViewProps<ChartWidget, { data: Point[] }>) {
|
|
10
|
+
const kind = (widget.kind as string) ?? "bar";
|
|
11
|
+
const x = widget.x as string;
|
|
12
|
+
const series = Array.isArray(widget.y) ? (widget.y as string[]) : [widget.y as string];
|
|
13
|
+
const height = (widget.maxHeight as number) ?? 300;
|
|
14
|
+
const data = state.data;
|
|
15
|
+
|
|
16
|
+
const axes = (
|
|
17
|
+
<>
|
|
18
|
+
<CartesianGrid strokeDasharray="3 3" stroke="var(--pu-border, #e3e6ea)" />
|
|
19
|
+
<XAxis dataKey={x} tick={{ fontSize: 12 }} />
|
|
20
|
+
<YAxis tick={{ fontSize: 12 }} />
|
|
21
|
+
<Tooltip />
|
|
22
|
+
{series.length > 1 && <Legend />}
|
|
23
|
+
</>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
let inner: ReactElement;
|
|
27
|
+
if (kind === "line") {
|
|
28
|
+
inner = (
|
|
29
|
+
<LineChart data={data}>
|
|
30
|
+
{axes}
|
|
31
|
+
{series.map((k, i) => (
|
|
32
|
+
<Line key={k} type="monotone" dataKey={k} stroke={COLORS[i % COLORS.length]} dot={false} />
|
|
33
|
+
))}
|
|
34
|
+
</LineChart>
|
|
35
|
+
);
|
|
36
|
+
} else if (kind === "area") {
|
|
37
|
+
inner = (
|
|
38
|
+
<AreaChart data={data}>
|
|
39
|
+
{axes}
|
|
40
|
+
{series.map((k, i) => (
|
|
41
|
+
<Area key={k} type="monotone" dataKey={k} stroke={COLORS[i % COLORS.length]} fill={COLORS[i % COLORS.length]} fillOpacity={0.2} />
|
|
42
|
+
))}
|
|
43
|
+
</AreaChart>
|
|
44
|
+
);
|
|
45
|
+
} else {
|
|
46
|
+
inner = (
|
|
47
|
+
<BarChart data={data}>
|
|
48
|
+
{axes}
|
|
49
|
+
{series.map((k, i) => (
|
|
50
|
+
<Bar key={k} dataKey={k} fill={COLORS[i % COLORS.length]} />
|
|
51
|
+
))}
|
|
52
|
+
</BarChart>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="pu-chart">
|
|
58
|
+
<ResponsiveContainer width="100%" height={height}>
|
|
59
|
+
{inner}
|
|
60
|
+
</ResponsiveContainer>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import "./chart.css"; // kept in the entry bundle (client.css) so no /assets css chunk to serve
|
|
2
|
+
import { lazy, Suspense, type ComponentType } from "react";
|
|
3
|
+
import type { ViewProps } from "../../view.js";
|
|
4
|
+
|
|
5
|
+
// Recharts is sizeable; load it only when an app actually renders a chart widget.
|
|
6
|
+
const Impl = lazy(async () => ({ default: (await import("./index.js")).ChartView as unknown as ComponentType<ViewProps> }));
|
|
7
|
+
|
|
8
|
+
export function ChartView(props: ViewProps) {
|
|
9
|
+
const height = ((props.widget as { maxHeight?: number }).maxHeight ?? 300) + 16; // + container padding
|
|
10
|
+
return (
|
|
11
|
+
<Suspense fallback={<div className="pu-chart" style={{ height }} />}>
|
|
12
|
+
<Impl {...props} />
|
|
13
|
+
</Suspense>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.pu-chat {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: 8px;
|
|
6
|
+
}
|
|
7
|
+
.pu-msg {
|
|
8
|
+
margin: 0;
|
|
9
|
+
max-width: 85%;
|
|
10
|
+
padding: 8px 12px;
|
|
11
|
+
border-radius: 12px;
|
|
12
|
+
white-space: pre-wrap;
|
|
13
|
+
word-break: break-word;
|
|
14
|
+
}
|
|
15
|
+
.pu-msg b {
|
|
16
|
+
display: none;
|
|
17
|
+
}
|
|
18
|
+
.pu-user {
|
|
19
|
+
align-self: flex-end;
|
|
20
|
+
background: var(--pu-accent, #2563eb);
|
|
21
|
+
color: #fff;
|
|
22
|
+
border-bottom-right-radius: 4px;
|
|
23
|
+
}
|
|
24
|
+
.pu-assistant {
|
|
25
|
+
align-self: flex-start;
|
|
26
|
+
background: #f1f3f5;
|
|
27
|
+
color: var(--pu-text, #1c1f23);
|
|
28
|
+
border-bottom-left-radius: 4px;
|
|
29
|
+
}
|
|
30
|
+
.pu-composer {
|
|
31
|
+
display: flex;
|
|
32
|
+
gap: 8px;
|
|
33
|
+
align-items: center;
|
|
34
|
+
margin-top: 4px;
|
|
35
|
+
}
|
|
36
|
+
.pu-composer .pu-input {
|
|
37
|
+
flex: 1;
|
|
38
|
+
}
|
|
39
|
+
.pu-mic {
|
|
40
|
+
flex: none;
|
|
41
|
+
width: 40px;
|
|
42
|
+
height: 40px;
|
|
43
|
+
border: none;
|
|
44
|
+
border-radius: 50%;
|
|
45
|
+
background: #f1f3f5;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
font-size: 16px;
|
|
48
|
+
line-height: 1;
|
|
49
|
+
}
|
|
50
|
+
.pu-mic.pu-rec {
|
|
51
|
+
background: #dc2626;
|
|
52
|
+
color: #fff;
|
|
53
|
+
}
|
|
54
|
+
.pu-msg-image {
|
|
55
|
+
display: block;
|
|
56
|
+
margin-top: 6px;
|
|
57
|
+
max-width: 160px;
|
|
58
|
+
max-height: 160px;
|
|
59
|
+
border-radius: 8px;
|
|
60
|
+
}
|
|
61
|
+
.pu-attachment {
|
|
62
|
+
position: relative;
|
|
63
|
+
align-self: flex-end;
|
|
64
|
+
width: 96px;
|
|
65
|
+
}
|
|
66
|
+
.pu-attachment img {
|
|
67
|
+
width: 96px;
|
|
68
|
+
height: 96px;
|
|
69
|
+
object-fit: cover;
|
|
70
|
+
border-radius: 8px;
|
|
71
|
+
}
|
|
72
|
+
.pu-attach-x {
|
|
73
|
+
position: absolute;
|
|
74
|
+
top: -6px;
|
|
75
|
+
right: -6px;
|
|
76
|
+
width: 20px;
|
|
77
|
+
height: 20px;
|
|
78
|
+
border: none;
|
|
79
|
+
border-radius: 50%;
|
|
80
|
+
background: #1c1f23;
|
|
81
|
+
color: #fff;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
font-size: 11px;
|
|
84
|
+
line-height: 1;
|
|
85
|
+
}
|
|
86
|
+
.pu-attach {
|
|
87
|
+
flex: none;
|
|
88
|
+
width: 40px;
|
|
89
|
+
height: 40px;
|
|
90
|
+
border: none;
|
|
91
|
+
border-radius: 50%;
|
|
92
|
+
background: #f1f3f5;
|
|
93
|
+
cursor: pointer;
|
|
94
|
+
font-size: 20px;
|
|
95
|
+
line-height: 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import "./chat.css";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
import type { ViewProps } from "../../view.js";
|
|
4
|
+
import type { ChatWidget, Message } from "../../../lib/widgets/chat.js";
|
|
5
|
+
|
|
6
|
+
// shrink a picked image so attachments (and later history reads) stay small
|
|
7
|
+
async function downscale(dataUrl: string, max = 768): Promise<string> {
|
|
8
|
+
const img = await new Promise<HTMLImageElement>((res, rej) => {
|
|
9
|
+
const i = new Image();
|
|
10
|
+
i.onload = () => res(i);
|
|
11
|
+
i.onerror = rej;
|
|
12
|
+
i.src = dataUrl;
|
|
13
|
+
});
|
|
14
|
+
const scale = Math.min(1, max / Math.max(img.width, img.height));
|
|
15
|
+
if (scale === 1) return dataUrl;
|
|
16
|
+
const canvas = document.createElement("canvas");
|
|
17
|
+
canvas.width = Math.round(img.width * scale);
|
|
18
|
+
canvas.height = Math.round(img.height * scale);
|
|
19
|
+
canvas.getContext("2d")!.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
20
|
+
return canvas.toDataURL("image/jpeg", 0.85);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function ChatView({ widget, state, emit }: ViewProps<ChatWidget, { messages: Message[] }>) {
|
|
24
|
+
const streamRef = useRef<MediaStream | null>(null);
|
|
25
|
+
const recRef = useRef<MediaRecorder | null>(null);
|
|
26
|
+
const pressedRef = useRef(false);
|
|
27
|
+
const fileRef = useRef<HTMLInputElement>(null);
|
|
28
|
+
const [recording, setRecording] = useState(false);
|
|
29
|
+
const [pending, setPending] = useState<string | null>(null);
|
|
30
|
+
|
|
31
|
+
const send = (input: HTMLInputElement) => {
|
|
32
|
+
if (!input.value.trim() && !pending) return;
|
|
33
|
+
emit(widget.sent({ text: input.value, image: pending ?? undefined }));
|
|
34
|
+
input.value = "";
|
|
35
|
+
setPending(null);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const pick = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
39
|
+
const f = e.currentTarget.files?.[0];
|
|
40
|
+
if (!f) return;
|
|
41
|
+
const r = new FileReader();
|
|
42
|
+
r.onload = async () => setPending(await downscale(String(r.result)));
|
|
43
|
+
r.readAsDataURL(f);
|
|
44
|
+
e.currentTarget.value = "";
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const start = async () => {
|
|
48
|
+
if (streamRef.current || pressedRef.current || !navigator.mediaDevices?.getUserMedia) return;
|
|
49
|
+
pressedRef.current = true;
|
|
50
|
+
setRecording(true);
|
|
51
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
52
|
+
if (!pressedRef.current) return stream.getTracks().forEach((t) => t.stop());
|
|
53
|
+
streamRef.current = stream;
|
|
54
|
+
const ac = new AudioContext({ sampleRate: 16000 });
|
|
55
|
+
const chunks: Blob[] = [];
|
|
56
|
+
const rec = new MediaRecorder(stream);
|
|
57
|
+
recRef.current = rec;
|
|
58
|
+
rec.ondataavailable = (e) => chunks.push(e.data);
|
|
59
|
+
rec.onstop = async () => {
|
|
60
|
+
const a = await ac.decodeAudioData(await new Blob(chunks).arrayBuffer());
|
|
61
|
+
emit(widget.recorded(a.getChannelData(0)));
|
|
62
|
+
};
|
|
63
|
+
rec.start();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const stop = () => {
|
|
67
|
+
pressedRef.current = false;
|
|
68
|
+
setRecording(false);
|
|
69
|
+
if (recRef.current?.state === "recording") recRef.current.stop();
|
|
70
|
+
streamRef.current?.getTracks().forEach((t) => t.stop());
|
|
71
|
+
streamRef.current = null;
|
|
72
|
+
recRef.current = null;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="pu-chat">
|
|
77
|
+
{state.messages.map((m, i) => (
|
|
78
|
+
<p key={i} className={`pu-msg pu-${m.role}`}>
|
|
79
|
+
<b>{m.role}:</b> {m.content}
|
|
80
|
+
{m.image && <img className="pu-msg-image" src={m.image} alt="" />}
|
|
81
|
+
</p>
|
|
82
|
+
))}
|
|
83
|
+
{pending && (
|
|
84
|
+
<div className="pu-attachment">
|
|
85
|
+
<img src={pending} alt="" />
|
|
86
|
+
<button className="pu-attach-x" onClick={() => setPending(null)} aria-label="Remove image">
|
|
87
|
+
✕
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
<div className="pu-composer">
|
|
92
|
+
{widget.images && (
|
|
93
|
+
<>
|
|
94
|
+
<input ref={fileRef} type="file" accept="image/*" hidden onChange={pick} />
|
|
95
|
+
<button className="pu-attach" onClick={() => fileRef.current?.click()} aria-label="Attach image">
|
|
96
|
+
+
|
|
97
|
+
</button>
|
|
98
|
+
</>
|
|
99
|
+
)}
|
|
100
|
+
<input
|
|
101
|
+
className="pu-input"
|
|
102
|
+
placeholder={widget.placeholder}
|
|
103
|
+
onKeyDown={(e) => {
|
|
104
|
+
if (e.key === "Enter") send(e.currentTarget);
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
{widget.voice && (
|
|
108
|
+
<button
|
|
109
|
+
className={`pu-mic${recording ? " pu-rec" : ""}`}
|
|
110
|
+
onPointerDown={start}
|
|
111
|
+
onPointerUp={stop}
|
|
112
|
+
onPointerLeave={() => pressedRef.current && stop()}
|
|
113
|
+
aria-label="Hold to talk"
|
|
114
|
+
>
|
|
115
|
+
{recording ? "●" : "🎤"}
|
|
116
|
+
</button>
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.pu-check {
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: 8px;
|
|
6
|
+
cursor: pointer;
|
|
7
|
+
user-select: none;
|
|
8
|
+
}
|
|
9
|
+
.pu-check input {
|
|
10
|
+
width: 16px;
|
|
11
|
+
height: 16px;
|
|
12
|
+
accent-color: var(--pu-accent, #2563eb);
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import "./checkbox.css";
|
|
2
|
+
import type { ViewProps } from "../../view.js";
|
|
3
|
+
import type { ValueWidget } from "../../../lib/widgets/value.js";
|
|
4
|
+
|
|
5
|
+
export function CheckboxView({ widget, state, emit, set }: ViewProps<ValueWidget<boolean>, { value: boolean }>) {
|
|
6
|
+
const update = (v: boolean) => {
|
|
7
|
+
set(["value"], v);
|
|
8
|
+
emit(widget.changed(v));
|
|
9
|
+
};
|
|
10
|
+
return (
|
|
11
|
+
<label className="pu-check">
|
|
12
|
+
<input type="checkbox" checked={state.value} onChange={(e) => update(e.currentTarget.checked)} /> {widget.label as string}
|
|
13
|
+
</label>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.pu-checkgroup {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: 8px;
|
|
6
|
+
}
|
|
7
|
+
.pu-checkgroup-opt {
|
|
8
|
+
display: inline-flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
gap: 8px;
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
user-select: none;
|
|
13
|
+
}
|
|
14
|
+
.pu-checkgroup-opt input {
|
|
15
|
+
width: 16px;
|
|
16
|
+
height: 16px;
|
|
17
|
+
accent-color: var(--pu-accent, #2563eb);
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import "./checkbox-group.css";
|
|
2
|
+
import type { ViewProps } from "../../view.js";
|
|
3
|
+
import type { ValueWidget } from "../../../lib/widgets/value.js";
|
|
4
|
+
|
|
5
|
+
export function CheckboxGroupView({ widget, state, emit, set }: ViewProps<ValueWidget<string[]>, { value: string[] }>) {
|
|
6
|
+
const choices = (widget.choices as string[]) ?? [];
|
|
7
|
+
const selected = state.value ?? [];
|
|
8
|
+
const toggle = (c: string) => {
|
|
9
|
+
const next = selected.includes(c) ? selected.filter((x) => x !== c) : [...selected, c];
|
|
10
|
+
set(["value"], next);
|
|
11
|
+
emit(widget.changed(next));
|
|
12
|
+
};
|
|
13
|
+
return (
|
|
14
|
+
<div className="pu-checkgroup">
|
|
15
|
+
{choices.map((c) => (
|
|
16
|
+
<label key={c} className="pu-checkgroup-opt">
|
|
17
|
+
<input type="checkbox" checked={selected.includes(c)} onChange={() => toggle(c)} /> {c}
|
|
18
|
+
</label>
|
|
19
|
+
))}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.pu-code .cm-editor {
|
|
3
|
+
border: 1px solid var(--pu-border, #e3e6ea);
|
|
4
|
+
border-radius: 8px;
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
background: #fff;
|
|
7
|
+
}
|
|
8
|
+
.pu-code .cm-editor.cm-focused {
|
|
9
|
+
border-color: var(--pu-accent, #2563eb);
|
|
10
|
+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.12);
|
|
11
|
+
outline: none;
|
|
12
|
+
}
|
|
13
|
+
.pu-code .cm-scroller {
|
|
14
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
15
|
+
font-size: 13px;
|
|
16
|
+
line-height: 1.55;
|
|
17
|
+
max-height: 420px;
|
|
18
|
+
}
|
|
19
|
+
.pu-code .cm-gutters {
|
|
20
|
+
background: var(--pu-bg, #f6f7f9);
|
|
21
|
+
border-right: 1px solid var(--pu-border, #e3e6ea);
|
|
22
|
+
color: var(--pu-muted, #6b7280);
|
|
23
|
+
}
|
|
24
|
+
/* a read-only viewer: dim the gutter, hide the caret, sit on the page background */
|
|
25
|
+
.pu-code-ro .cm-editor {
|
|
26
|
+
background: var(--pu-bg, #f6f7f9);
|
|
27
|
+
}
|
|
28
|
+
.pu-code-ro .cm-cursor {
|
|
29
|
+
display: none;
|
|
30
|
+
}
|
|
31
|
+
}
|