@fungi.computer/caps 0.0.0-stage → 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/README.md +1125 -2
- package/dist/blocks/app-bar.d.ts +43 -0
- package/dist/blocks/app-bar.js +244 -0
- package/dist/blocks/app-status-bar.d.ts +93 -0
- package/dist/blocks/app-status-bar.js +42 -0
- package/dist/blocks/async-state.d.ts +21 -0
- package/dist/blocks/async-state.js +8 -0
- package/dist/blocks/card-grid.d.ts +11 -0
- package/dist/blocks/card-grid.js +21 -0
- package/dist/blocks/command-menu.d.ts +26 -0
- package/dist/blocks/command-menu.js +70 -0
- package/dist/blocks/composer.d.ts +23 -0
- package/dist/blocks/composer.js +13 -0
- package/dist/blocks/empty-state.d.ts +59 -0
- package/dist/blocks/empty-state.js +35 -0
- package/dist/blocks/faq-chat.d.ts +62 -0
- package/dist/blocks/faq-chat.js +77 -0
- package/dist/blocks/game-share-card.d.ts +17 -0
- package/dist/blocks/game-share-card.js +9 -0
- package/dist/blocks/launcher-grid.d.ts +50 -0
- package/dist/blocks/launcher-grid.js +11 -0
- package/dist/blocks/listing-detail.d.ts +23 -0
- package/dist/blocks/listing-detail.js +12 -0
- package/dist/blocks/loading-state.d.ts +5 -0
- package/dist/blocks/loading-state.js +8 -0
- package/dist/blocks/mosaic.d.ts +29 -0
- package/dist/blocks/mosaic.js +19 -0
- package/dist/blocks/permission-grid.d.ts +45 -0
- package/dist/blocks/permission-grid.js +14 -0
- package/dist/blocks/pricing-card.d.ts +53 -0
- package/dist/blocks/pricing-card.js +12 -0
- package/dist/blocks/release-tag.d.ts +20 -0
- package/dist/blocks/release-tag.js +10 -0
- package/dist/blocks/resource-select.d.ts +22 -0
- package/dist/blocks/resource-select.js +16 -0
- package/dist/blocks/result-state.d.ts +15 -0
- package/dist/blocks/result-state.js +8 -0
- package/dist/blocks/section-card.d.ts +31 -0
- package/dist/blocks/section-card.js +7 -0
- package/dist/blocks/section-heading.d.ts +13 -0
- package/dist/blocks/section-heading.js +11 -0
- package/dist/blocks/spec-sheet.d.ts +125 -0
- package/dist/blocks/spec-sheet.js +47 -0
- package/dist/blocks/status-tray.d.ts +83 -0
- package/dist/blocks/status-tray.js +79 -0
- package/dist/blocks/upload-queue.d.ts +48 -0
- package/dist/blocks/upload-queue.js +24 -0
- package/dist/components/action-row.d.ts +16 -0
- package/dist/components/action-row.js +11 -0
- package/dist/components/activity-line.d.ts +33 -0
- package/dist/components/activity-line.js +69 -0
- package/dist/components/alert.d.ts +17 -0
- package/dist/components/alert.js +17 -0
- package/dist/components/badge.d.ts +19 -0
- package/dist/components/badge.js +24 -0
- package/dist/components/button.d.ts +55 -0
- package/dist/components/button.js +230 -0
- package/dist/components/card.d.ts +61 -0
- package/dist/components/card.js +48 -0
- package/dist/components/chat-bubble.d.ts +34 -0
- package/dist/components/chat-bubble.js +10 -0
- package/dist/components/checkbox-bead.d.ts +6 -0
- package/dist/components/checkbox-bead.js +67 -0
- package/dist/components/checkbox.d.ts +21 -0
- package/dist/components/checkbox.js +46 -0
- package/dist/components/code-input.d.ts +35 -0
- package/dist/components/code-input.js +126 -0
- package/dist/components/combobox.d.ts +86 -0
- package/dist/components/combobox.js +218 -0
- package/dist/components/confirm-action.d.ts +23 -0
- package/dist/components/confirm-action.js +64 -0
- package/dist/components/copy-field.d.ts +53 -0
- package/dist/components/copy-field.js +49 -0
- package/dist/components/count-badge.d.ts +15 -0
- package/dist/components/count-badge.js +9 -0
- package/dist/components/cover-pattern.d.ts +19 -0
- package/dist/components/cover-pattern.js +74 -0
- package/dist/components/data-list.d.ts +25 -0
- package/dist/components/data-list.js +16 -0
- package/dist/components/dialog.d.ts +39 -0
- package/dist/components/dialog.js +73 -0
- package/dist/components/diff-view.d.ts +14 -0
- package/dist/components/diff-view.js +13 -0
- package/dist/components/disclosure.d.ts +29 -0
- package/dist/components/disclosure.js +19 -0
- package/dist/components/dropdown-menu.d.ts +49 -0
- package/dist/components/dropdown-menu.js +98 -0
- package/dist/components/field-guide.d.ts +41 -0
- package/dist/components/field-guide.js +18 -0
- package/dist/components/field.d.ts +164 -0
- package/dist/components/field.js +228 -0
- package/dist/components/game-feedback.d.ts +84 -0
- package/dist/components/game-feedback.js +48 -0
- package/dist/components/game-hud.d.ts +126 -0
- package/dist/components/game-hud.js +136 -0
- package/dist/components/generation-frame.d.ts +36 -0
- package/dist/components/generation-frame.js +97 -0
- package/dist/components/gooey.d.ts +32 -0
- package/dist/components/gooey.js +25 -0
- package/dist/components/hold-button.d.ts +26 -0
- package/dist/components/hold-button.js +115 -0
- package/dist/components/icon-button.d.ts +24 -0
- package/dist/components/icon-button.js +18 -0
- package/dist/components/icon-checkbox.d.ts +20 -0
- package/dist/components/icon-checkbox.js +9 -0
- package/dist/components/icon-choice-mark.d.ts +6 -0
- package/dist/components/icon-choice-mark.js +5 -0
- package/dist/components/icon-lamp.d.ts +42 -0
- package/dist/components/icon-lamp.js +15 -0
- package/dist/components/icon-radio.d.ts +16 -0
- package/dist/components/icon-radio.js +9 -0
- package/dist/components/inline-code.d.ts +3 -0
- package/dist/components/inline-code.js +7 -0
- package/dist/components/input-group.d.ts +9 -0
- package/dist/components/input-group.js +15 -0
- package/dist/components/input.d.ts +15 -0
- package/dist/components/input.js +15 -0
- package/dist/components/item-grid.d.ts +110 -0
- package/dist/components/item-grid.js +136 -0
- package/dist/components/keycap.d.ts +80 -0
- package/dist/components/keycap.js +18 -0
- package/dist/components/label.d.ts +13 -0
- package/dist/components/label.js +7 -0
- package/dist/components/listbox.d.ts +50 -0
- package/dist/components/listbox.js +134 -0
- package/dist/components/loader-blobs.d.ts +5 -0
- package/dist/components/loader-blobs.js +128 -0
- package/dist/components/loader.d.ts +21 -0
- package/dist/components/loader.js +25 -0
- package/dist/components/markdown.d.ts +23 -0
- package/dist/components/markdown.js +70 -0
- package/dist/components/minimap-labels.d.ts +39 -0
- package/dist/components/minimap-labels.js +113 -0
- package/dist/components/monogram.d.ts +32 -0
- package/dist/components/monogram.js +24 -0
- package/dist/components/motion-visibility.d.ts +2 -0
- package/dist/components/motion-visibility.js +25 -0
- package/dist/components/navigation-rail.d.ts +11 -0
- package/dist/components/navigation-rail.js +55 -0
- package/dist/components/overlay-root.d.ts +16 -0
- package/dist/components/overlay-root.js +24 -0
- package/dist/components/pixel-number.d.ts +50 -0
- package/dist/components/pixel-number.js +113 -0
- package/dist/components/popover.d.ts +31 -0
- package/dist/components/popover.js +24 -0
- package/dist/components/presence.d.ts +59 -0
- package/dist/components/presence.js +232 -0
- package/dist/components/price-tag.d.ts +11 -0
- package/dist/components/price-tag.js +6 -0
- package/dist/components/progress.d.ts +25 -0
- package/dist/components/progress.js +24 -0
- package/dist/components/prose.d.ts +14 -0
- package/dist/components/prose.js +10 -0
- package/dist/components/quota-meter.d.ts +24 -0
- package/dist/components/quota-meter.js +11 -0
- package/dist/components/radio-bead.d.ts +4 -0
- package/dist/components/radio-bead.js +53 -0
- package/dist/components/radio-group.d.ts +54 -0
- package/dist/components/radio-group.js +64 -0
- package/dist/components/resize-handle.d.ts +23 -0
- package/dist/components/resize-handle.js +31 -0
- package/dist/components/rough-drawing.d.ts +42 -0
- package/dist/components/rough-drawing.js +231 -0
- package/dist/components/rough.d.ts +68 -0
- package/dist/components/rough.js +33 -0
- package/dist/components/scroll-area.d.ts +12 -0
- package/dist/components/scroll-area.js +96 -0
- package/dist/components/scroll-motion.d.ts +12 -0
- package/dist/components/scroll-motion.js +31 -0
- package/dist/components/scroll-to-bottom-blob-state.d.ts +40 -0
- package/dist/components/scroll-to-bottom-blob-state.js +109 -0
- package/dist/components/scroll-to-bottom-blob.d.ts +16 -0
- package/dist/components/scroll-to-bottom-blob.js +174 -0
- package/dist/components/select.d.ts +9 -0
- package/dist/components/select.js +12 -0
- package/dist/components/selectable.d.ts +13 -0
- package/dist/components/selectable.js +45 -0
- package/dist/components/send-action.d.ts +33 -0
- package/dist/components/send-action.js +96 -0
- package/dist/components/separator.d.ts +11 -0
- package/dist/components/separator.js +7 -0
- package/dist/components/skeleton.d.ts +5 -0
- package/dist/components/skeleton.js +7 -0
- package/dist/components/slider.d.ts +18 -0
- package/dist/components/slider.js +31 -0
- package/dist/components/stat.d.ts +17 -0
- package/dist/components/stat.js +7 -0
- package/dist/components/status-bead.d.ts +9 -0
- package/dist/components/status-bead.js +107 -0
- package/dist/components/status-mark.d.ts +12 -0
- package/dist/components/status-mark.js +177 -0
- package/dist/components/status.d.ts +38 -0
- package/dist/components/status.js +28 -0
- package/dist/components/store-card.d.ts +21 -0
- package/dist/components/store-card.js +10 -0
- package/dist/components/surface.d.ts +89 -0
- package/dist/components/surface.js +31 -0
- package/dist/components/switch-bead.d.ts +4 -0
- package/dist/components/switch-bead.js +62 -0
- package/dist/components/switch.d.ts +11 -0
- package/dist/components/switch.js +36 -0
- package/dist/components/tabs.d.ts +55 -0
- package/dist/components/tabs.js +81 -0
- package/dist/components/technonomicon.d.ts +192 -0
- package/dist/components/technonomicon.js +121 -0
- package/dist/components/textarea.d.ts +9 -0
- package/dist/components/textarea.js +11 -0
- package/dist/components/toaster.d.ts +4 -0
- package/dist/components/toaster.js +13 -0
- package/dist/components/tool-call-chip.d.ts +22 -0
- package/dist/components/tool-call-chip.js +100 -0
- package/dist/components/tooltip.d.ts +41 -0
- package/dist/components/tooltip.js +169 -0
- package/dist/components/trading-card.d.ts +60 -0
- package/dist/components/trading-card.js +13 -0
- package/dist/components/undo-action.d.ts +52 -0
- package/dist/components/undo-action.js +100 -0
- package/dist/components/undo-toast.d.ts +21 -0
- package/dist/components/undo-toast.js +58 -0
- package/dist/components/visually-hidden.d.ts +9 -0
- package/dist/components/visually-hidden.js +11 -0
- package/dist/components/window.d.ts +65 -0
- package/dist/components/window.js +31 -0
- package/dist/components/wizard.d.ts +17 -0
- package/dist/components/wizard.js +19 -0
- package/dist/enhance/cigarette-card.d.ts +8 -0
- package/dist/enhance/cigarette-card.js +33 -0
- package/dist/enhance/sketch.d.ts +16 -0
- package/dist/enhance/sketch.js +130 -0
- package/dist/form-error.d.ts +7 -0
- package/dist/form-error.js +14 -0
- package/dist/form-fields.d.ts +15 -0
- package/dist/form-fields.js +250 -0
- package/dist/form-schema.d.ts +166 -0
- package/dist/form-schema.js +398 -0
- package/dist/form.d.ts +30 -0
- package/dist/form.js +20 -0
- package/dist/lib/cn.d.ts +8 -0
- package/dist/lib/cn.js +12 -0
- package/dist/lib/format-money.d.ts +2 -0
- package/dist/lib/format-money.js +22 -0
- package/dist/lib/glide.d.ts +12 -0
- package/dist/lib/glide.js +143 -0
- package/dist/lib/ime.d.ts +3 -0
- package/dist/lib/ime.js +8 -0
- package/dist/lib/motion-spring.d.ts +27 -0
- package/dist/lib/motion-spring.js +36 -0
- package/dist/lib/motion.d.ts +51 -0
- package/dist/lib/motion.js +161 -0
- package/dist/lib/reorder-motion.d.ts +13 -0
- package/dist/lib/reorder-motion.js +201 -0
- package/dist/lib/seed-hash.d.ts +6 -0
- package/dist/lib/seed-hash.js +13 -0
- package/dist/lib/tanstack-highlight.d.ts +4 -0
- package/dist/lib/tanstack-highlight.js +41 -0
- package/dist/schema-form.d.ts +120 -0
- package/dist/schema-form.js +129 -0
- package/dist/styles.css +18814 -0
- package/docs/api/README.md +134 -0
- package/docs/api/blocks/app-bar.md +227 -0
- package/docs/api/blocks/app-status-bar.md +257 -0
- package/docs/api/blocks/async-state.md +53 -0
- package/docs/api/blocks/card-grid.md +31 -0
- package/docs/api/blocks/command-menu.md +89 -0
- package/docs/api/blocks/composer.md +40 -0
- package/docs/api/blocks/empty-state.md +97 -0
- package/docs/api/blocks/faq-chat.md +147 -0
- package/docs/api/blocks/game-share-card.md +40 -0
- package/docs/api/blocks/launcher-grid.md +86 -0
- package/docs/api/blocks/listing-detail.md +51 -0
- package/docs/api/blocks/loading-state.md +21 -0
- package/docs/api/blocks/mosaic.md +94 -0
- package/docs/api/blocks/permission-grid.md +79 -0
- package/docs/api/blocks/pricing-card.md +90 -0
- package/docs/api/blocks/release-tag.md +62 -0
- package/docs/api/blocks/resource-select.md +126 -0
- package/docs/api/blocks/result-state.md +42 -0
- package/docs/api/blocks/section-card.md +42 -0
- package/docs/api/blocks/section-heading.md +61 -0
- package/docs/api/blocks/spec-sheet.md +400 -0
- package/docs/api/blocks/status-tray.md +141 -0
- package/docs/api/blocks/upload-queue.md +110 -0
- package/docs/api/components/action-row.md +51 -0
- package/docs/api/components/activity-line.md +103 -0
- package/docs/api/components/alert.md +60 -0
- package/docs/api/components/badge.md +77 -0
- package/docs/api/components/button.md +94 -0
- package/docs/api/components/card.md +165 -0
- package/docs/api/components/chat-bubble.md +66 -0
- package/docs/api/components/checkbox-bead.md +20 -0
- package/docs/api/components/checkbox.md +42 -0
- package/docs/api/components/code-input.md +61 -0
- package/docs/api/components/combobox.md +303 -0
- package/docs/api/components/confirm-action.md +58 -0
- package/docs/api/components/copy-field.md +106 -0
- package/docs/api/components/count-badge.md +39 -0
- package/docs/api/components/cover-pattern.md +43 -0
- package/docs/api/components/data-list.md +80 -0
- package/docs/api/components/dialog.md +235 -0
- package/docs/api/components/diff-view.md +59 -0
- package/docs/api/components/disclosure.md +71 -0
- package/docs/api/components/dropdown-menu.md +269 -0
- package/docs/api/components/field-guide.md +174 -0
- package/docs/api/components/field.md +252 -0
- package/docs/api/components/game-feedback.md +157 -0
- package/docs/api/components/game-hud.md +359 -0
- package/docs/api/components/generation-frame.md +59 -0
- package/docs/api/components/hold-button.md +44 -0
- package/docs/api/components/icon-button.md +56 -0
- package/docs/api/components/icon-checkbox.md +42 -0
- package/docs/api/components/icon-choice-mark.md +31 -0
- package/docs/api/components/icon-lamp.md +92 -0
- package/docs/api/components/icon-radio.md +42 -0
- package/docs/api/components/inline-code.md +21 -0
- package/docs/api/components/input-group.md +53 -0
- package/docs/api/components/input.md +54 -0
- package/docs/api/components/item-grid.md +262 -0
- package/docs/api/components/keycap.md +102 -0
- package/docs/api/components/label.md +40 -0
- package/docs/api/components/listbox.md +131 -0
- package/docs/api/components/loader-blobs.md +9 -0
- package/docs/api/components/loader.md +51 -0
- package/docs/api/components/markdown.md +91 -0
- package/docs/api/components/minimap-labels.md +84 -0
- package/docs/api/components/monogram.md +96 -0
- package/docs/api/components/motion-visibility.md +9 -0
- package/docs/api/components/navigation-rail.md +37 -0
- package/docs/api/components/overlay-root.md +92 -0
- package/docs/api/components/pixel-number.md +120 -0
- package/docs/api/components/popover.md +80 -0
- package/docs/api/components/presence.md +106 -0
- package/docs/api/components/price-tag.md +48 -0
- package/docs/api/components/progress.md +90 -0
- package/docs/api/components/prose.md +48 -0
- package/docs/api/components/quota-meter.md +47 -0
- package/docs/api/components/radio-bead.md +9 -0
- package/docs/api/components/radio-group.md +126 -0
- package/docs/api/components/resize-handle.md +46 -0
- package/docs/api/components/rough-drawing.md +122 -0
- package/docs/api/components/rough.md +66 -0
- package/docs/api/components/scroll-area.md +22 -0
- package/docs/api/components/scroll-motion.md +9 -0
- package/docs/api/components/scroll-to-bottom-blob-state.md +162 -0
- package/docs/api/components/scroll-to-bottom-blob.md +73 -0
- package/docs/api/components/select.md +39 -0
- package/docs/api/components/send-action.md +58 -0
- package/docs/api/components/separator.md +38 -0
- package/docs/api/components/skeleton.md +31 -0
- package/docs/api/components/slider.md +70 -0
- package/docs/api/components/stat.md +42 -0
- package/docs/api/components/status-bead.md +19 -0
- package/docs/api/components/status-mark.md +19 -0
- package/docs/api/components/status.md +105 -0
- package/docs/api/components/store-card.md +53 -0
- package/docs/api/components/surface.md +161 -0
- package/docs/api/components/switch-bead.md +9 -0
- package/docs/api/components/switch.md +40 -0
- package/docs/api/components/tabs.md +138 -0
- package/docs/api/components/technonomicon.md +773 -0
- package/docs/api/components/textarea.md +40 -0
- package/docs/api/components/toaster.md +29 -0
- package/docs/api/components/tool-call-chip.md +68 -0
- package/docs/api/components/tooltip.md +113 -0
- package/docs/api/components/trading-card.md +95 -0
- package/docs/api/components/undo-action.md +117 -0
- package/docs/api/components/undo-toast.md +49 -0
- package/docs/api/components/visually-hidden.md +37 -0
- package/docs/api/components/window.md +291 -0
- package/docs/api/components/wizard.md +65 -0
- package/docs/api/enhance/cigarette-card.md +52 -0
- package/docs/api/enhance/sketch.md +42 -0
- package/docs/api/form.md +1623 -0
- package/docs/api/lib/cn.md +46 -0
- package/docs/api/lib/format-money.md +30 -0
- package/docs/api/lib/glide.md +36 -0
- package/docs/api/lib/ime.md +29 -0
- package/docs/api/lib/motion-spring.md +91 -0
- package/docs/api/lib/motion.md +212 -0
- package/docs/api/lib/reorder-motion.md +46 -0
- package/docs/api/lib/seed-hash.md +31 -0
- package/docs/api/lib/tanstack-highlight.md +40 -0
- package/package.json +597 -3
- package/src/app-status-bar.css +158 -0
- package/src/fonts.css +1 -0
- package/src/status-tray.css +282 -0
- package/src/styles.css +10177 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { Option, Schema, SchemaAST } from "effect";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
/** Holds a field annotation inside schema annotations (Effect or Zod). */
|
|
4
|
+
export class FieldMeta {
|
|
5
|
+
annotation;
|
|
6
|
+
constructor(annotation) {
|
|
7
|
+
this.annotation = annotation;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** Holds the section list inside a struct's annotations. */
|
|
11
|
+
export class SectionsMeta {
|
|
12
|
+
sections;
|
|
13
|
+
constructor(sections) {
|
|
14
|
+
this.sections = sections;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const formKey = "caps/form";
|
|
18
|
+
/**
|
|
19
|
+
* Each `field()` call gets its own key, because both libraries replace an
|
|
20
|
+
* annotation key wholesale; reading merges every form key in order.
|
|
21
|
+
*/
|
|
22
|
+
let fieldCalls = 0;
|
|
23
|
+
/**
|
|
24
|
+
* Annotate a schema field for forms. Works with Effect
|
|
25
|
+
* (`Schema.String.annotate(field({ … }))`) and Zod (`z.string().meta(field({ … }))`).
|
|
26
|
+
* `label` and `help` also become the schema's `title` and `description`.
|
|
27
|
+
*/
|
|
28
|
+
export function field(annotation) {
|
|
29
|
+
fieldCalls += 1;
|
|
30
|
+
const annotations = {
|
|
31
|
+
[`${formKey}:${String(fieldCalls)}`]: new FieldMeta(annotation),
|
|
32
|
+
};
|
|
33
|
+
if (annotation.label !== undefined)
|
|
34
|
+
annotations.title = annotation.label;
|
|
35
|
+
if (annotation.help !== undefined)
|
|
36
|
+
annotations.description = annotation.help;
|
|
37
|
+
return annotations;
|
|
38
|
+
}
|
|
39
|
+
/** Annotate a struct with its sections, in display order. */
|
|
40
|
+
export function sections(list) {
|
|
41
|
+
return { [formKey]: new SectionsMeta(list) };
|
|
42
|
+
}
|
|
43
|
+
/** A key or token: masked, never autofilled, with a reveal key. */
|
|
44
|
+
export const Secret = Schema.String.annotate(field({ kind: "secret" })).pipe(Schema.brand("Secret"));
|
|
45
|
+
const humanize = (key) => {
|
|
46
|
+
const words = key
|
|
47
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
48
|
+
.replace(/[-_]+/g, " ")
|
|
49
|
+
.toLowerCase();
|
|
50
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
51
|
+
};
|
|
52
|
+
function annotationOf(record) {
|
|
53
|
+
return Object.values(record ?? {}).reduce((merged, value) => value instanceof FieldMeta ? { ...merged, ...value.annotation } : merged, {});
|
|
54
|
+
}
|
|
55
|
+
/** Later annotations win key by key, so `Secret.annotate(field({ label }))` stays a secret. */
|
|
56
|
+
function mergeAnnotations(records) {
|
|
57
|
+
return records.reduce((merged, record) => ({ ...merged, ...annotationOf(record) }), {});
|
|
58
|
+
}
|
|
59
|
+
function sectionsOf(record) {
|
|
60
|
+
const value = record?.[formKey];
|
|
61
|
+
return value instanceof SectionsMeta ? value.sections : {};
|
|
62
|
+
}
|
|
63
|
+
// ---- Effect -------------------------------------------------------------
|
|
64
|
+
const readLiteral = Schema.decodeUnknownOption(Schema.String);
|
|
65
|
+
function effectLiterals(ast) {
|
|
66
|
+
if (SchemaAST.isLiteral(ast))
|
|
67
|
+
return Option.match(readLiteral(ast.literal), {
|
|
68
|
+
onNone: () => null,
|
|
69
|
+
onSome: (literal) => [literal],
|
|
70
|
+
});
|
|
71
|
+
if (!SchemaAST.isUnion(ast))
|
|
72
|
+
return null;
|
|
73
|
+
const literals = [];
|
|
74
|
+
for (const member of ast.types) {
|
|
75
|
+
if (SchemaAST.isUndefined(member))
|
|
76
|
+
continue;
|
|
77
|
+
const inner = effectLiterals(member);
|
|
78
|
+
if (inner === null)
|
|
79
|
+
return null;
|
|
80
|
+
literals.push(...inner);
|
|
81
|
+
}
|
|
82
|
+
return literals;
|
|
83
|
+
}
|
|
84
|
+
function effectValueType(ast) {
|
|
85
|
+
if (SchemaAST.isString(ast))
|
|
86
|
+
return { type: "string", literals: [] };
|
|
87
|
+
if (SchemaAST.isNumber(ast))
|
|
88
|
+
return { type: "number", nullable: false };
|
|
89
|
+
if (SchemaAST.isBoolean(ast))
|
|
90
|
+
return { type: "boolean" };
|
|
91
|
+
const literals = effectLiterals(ast);
|
|
92
|
+
if (literals !== null)
|
|
93
|
+
return { type: "string", literals };
|
|
94
|
+
if (SchemaAST.isArrays(ast)) {
|
|
95
|
+
const [item] = ast.rest;
|
|
96
|
+
if (item === undefined)
|
|
97
|
+
return null;
|
|
98
|
+
if (SchemaAST.isString(item))
|
|
99
|
+
return { type: "strings", literals: [] };
|
|
100
|
+
const options = effectLiterals(item);
|
|
101
|
+
return options === null ? null : { type: "strings", literals: options };
|
|
102
|
+
}
|
|
103
|
+
if (SchemaAST.isUnion(ast)) {
|
|
104
|
+
const present = ast.types.filter((member) => !SchemaAST.isUndefined(member) && !SchemaAST.isNull(member));
|
|
105
|
+
const nullable = ast.types.some((member) => SchemaAST.isNull(member));
|
|
106
|
+
const [only] = present;
|
|
107
|
+
if (present.length !== 1 || only === undefined)
|
|
108
|
+
return null;
|
|
109
|
+
const inner = effectValueType(only);
|
|
110
|
+
if (inner?.type === "number")
|
|
111
|
+
return { type: "number", nullable };
|
|
112
|
+
return nullable ? null : inner;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
function effectFields(schema) {
|
|
117
|
+
const ast = schema.ast;
|
|
118
|
+
if (!SchemaAST.isObjects(ast))
|
|
119
|
+
throw new Error("A schema form needs a Schema.Struct.");
|
|
120
|
+
const fields = ast.propertySignatures.map((property) => {
|
|
121
|
+
const name = String(property.name);
|
|
122
|
+
const encoded = SchemaAST.toEncoded(property.type);
|
|
123
|
+
// File declarations are opaque to inference; the annotation names the input.
|
|
124
|
+
// `.check()` moves later annotations onto the check; read them all.
|
|
125
|
+
const annotation = mergeAnnotations([
|
|
126
|
+
property.type.annotations,
|
|
127
|
+
...(property.type.checks ?? []).map((check) => check.annotations),
|
|
128
|
+
]);
|
|
129
|
+
const valueType = annotation.kind === "file" ? { type: "files" } : effectValueType(encoded);
|
|
130
|
+
if (valueType === null)
|
|
131
|
+
throw new Error(`Schema field "${name}" has no form control. Annotate it with field({ render }) or a kind.`);
|
|
132
|
+
return {
|
|
133
|
+
name,
|
|
134
|
+
valueType,
|
|
135
|
+
optional: property.type.context?.isOptional === true,
|
|
136
|
+
annotation,
|
|
137
|
+
title: SchemaAST.resolveTitle(property.type),
|
|
138
|
+
description: SchemaAST.resolveDescription(property.type),
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
fields,
|
|
143
|
+
sections: sectionsOf(SchemaAST.resolve(ast)),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function zodUnwrap(node) {
|
|
147
|
+
let current = node;
|
|
148
|
+
let optional = false;
|
|
149
|
+
let nullable = false;
|
|
150
|
+
for (;;) {
|
|
151
|
+
if (current instanceof z.ZodOptional) {
|
|
152
|
+
optional = true;
|
|
153
|
+
current = current.unwrap();
|
|
154
|
+
}
|
|
155
|
+
else if (current instanceof z.ZodNullable) {
|
|
156
|
+
nullable = true;
|
|
157
|
+
current = current.unwrap();
|
|
158
|
+
}
|
|
159
|
+
else if (current instanceof z.ZodDefault) {
|
|
160
|
+
current = current.unwrap();
|
|
161
|
+
}
|
|
162
|
+
else if (current instanceof z.ZodPipe) {
|
|
163
|
+
current = current.in;
|
|
164
|
+
}
|
|
165
|
+
else
|
|
166
|
+
return { node: current, optional, nullable };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function zodLiterals(node) {
|
|
170
|
+
if (node instanceof z.ZodUnion) {
|
|
171
|
+
const members = node.options.map((option) => zodLiterals(option));
|
|
172
|
+
return members.some((member) => member === null)
|
|
173
|
+
? null
|
|
174
|
+
: members.flatMap((member) => member ?? []);
|
|
175
|
+
}
|
|
176
|
+
const values = node instanceof z.ZodEnum
|
|
177
|
+
? node.options
|
|
178
|
+
: node instanceof z.ZodLiteral
|
|
179
|
+
? [...node.values]
|
|
180
|
+
: null;
|
|
181
|
+
if (values === null)
|
|
182
|
+
return null;
|
|
183
|
+
const literals = values.map((value) => readLiteral(value));
|
|
184
|
+
return literals.every(Option.isSome)
|
|
185
|
+
? literals.map((literal) => literal.value)
|
|
186
|
+
: null;
|
|
187
|
+
}
|
|
188
|
+
function zodValueType(node) {
|
|
189
|
+
const { node: inner, nullable } = zodUnwrap(node);
|
|
190
|
+
if (inner instanceof z.ZodString)
|
|
191
|
+
return { type: "string", literals: [] };
|
|
192
|
+
if (inner instanceof z.ZodNumber)
|
|
193
|
+
return { type: "number", nullable };
|
|
194
|
+
if (inner instanceof z.ZodBoolean)
|
|
195
|
+
return { type: "boolean" };
|
|
196
|
+
const literals = zodLiterals(inner);
|
|
197
|
+
if (literals !== null)
|
|
198
|
+
return { type: "string", literals };
|
|
199
|
+
if (inner instanceof z.ZodArray) {
|
|
200
|
+
const item = zodUnwrap(inner.element).node;
|
|
201
|
+
if (item instanceof z.ZodString)
|
|
202
|
+
return { type: "strings", literals: [] };
|
|
203
|
+
const options = zodLiterals(item);
|
|
204
|
+
return options === null ? null : { type: "strings", literals: options };
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
/** Zod keeps `.meta()` on the node it was called on: collect inner to outer. */
|
|
209
|
+
function zodMetas(node) {
|
|
210
|
+
const chain = [node];
|
|
211
|
+
for (;;) {
|
|
212
|
+
const last = chain[chain.length - 1];
|
|
213
|
+
if (last === undefined)
|
|
214
|
+
break;
|
|
215
|
+
const next = last instanceof z.ZodOptional ||
|
|
216
|
+
last instanceof z.ZodNullable ||
|
|
217
|
+
last instanceof z.ZodDefault
|
|
218
|
+
? last.unwrap()
|
|
219
|
+
: last instanceof z.ZodPipe
|
|
220
|
+
? last.in
|
|
221
|
+
: last;
|
|
222
|
+
if (next === last)
|
|
223
|
+
break;
|
|
224
|
+
chain.push(next);
|
|
225
|
+
}
|
|
226
|
+
return chain.reverse().map((entry) => z.globalRegistry.get(entry));
|
|
227
|
+
}
|
|
228
|
+
function zodMeta(node) {
|
|
229
|
+
return zodMetas(node).reduce((merged, meta) => ({ ...merged, ...meta }), {});
|
|
230
|
+
}
|
|
231
|
+
function zodEntries(object) {
|
|
232
|
+
return Object.entries(object.shape);
|
|
233
|
+
}
|
|
234
|
+
function zodFields(schema) {
|
|
235
|
+
const root = zodUnwrap(schema).node;
|
|
236
|
+
if (!(root instanceof z.ZodObject))
|
|
237
|
+
throw new Error("A schema form needs a z.object.");
|
|
238
|
+
const fields = zodEntries(root).map(([name, node]) => {
|
|
239
|
+
const metas = zodMetas(node);
|
|
240
|
+
const annotation = mergeAnnotations(metas);
|
|
241
|
+
const valueType = annotation.kind === "file" ? { type: "files" } : zodValueType(node);
|
|
242
|
+
if (valueType === null)
|
|
243
|
+
throw new Error(`Schema field "${name}" has no form control. Annotate it with field({ render }) or a kind.`);
|
|
244
|
+
const meta = zodMeta(node);
|
|
245
|
+
return {
|
|
246
|
+
name,
|
|
247
|
+
valueType,
|
|
248
|
+
optional: zodUnwrap(node).optional,
|
|
249
|
+
annotation,
|
|
250
|
+
title: meta.title,
|
|
251
|
+
description: meta.description,
|
|
252
|
+
};
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
fields,
|
|
256
|
+
sections: sectionsOf(zodMeta(schema)),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
// ---- Resolution ---------------------------------------------------------
|
|
260
|
+
function resolveOptions(values, annotation) {
|
|
261
|
+
const labels = annotation.options ?? {};
|
|
262
|
+
const keys = values.length > 0 ? values : Object.keys(labels);
|
|
263
|
+
return keys.map((value) => {
|
|
264
|
+
const entry = labels[value];
|
|
265
|
+
const option = entry === undefined
|
|
266
|
+
? {}
|
|
267
|
+
: entry instanceof Object
|
|
268
|
+
? entry
|
|
269
|
+
: { label: entry };
|
|
270
|
+
return {
|
|
271
|
+
value,
|
|
272
|
+
label: option.label ?? humanize(value),
|
|
273
|
+
description: option.description,
|
|
274
|
+
meta: option.meta,
|
|
275
|
+
icon: option.icon,
|
|
276
|
+
caption: option.caption,
|
|
277
|
+
disabled: option.disabled === true,
|
|
278
|
+
};
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/** The kind a field renders as: its annotation wins, then the schema type. */
|
|
282
|
+
function inferKind(valueType, options) {
|
|
283
|
+
const icons = options.some((option) => option.icon !== undefined);
|
|
284
|
+
switch (valueType.type) {
|
|
285
|
+
case "boolean":
|
|
286
|
+
return "switch";
|
|
287
|
+
case "number":
|
|
288
|
+
return "number";
|
|
289
|
+
case "strings":
|
|
290
|
+
return icons ? "icon-checkboxes" : "checkboxes";
|
|
291
|
+
case "files":
|
|
292
|
+
return "file";
|
|
293
|
+
case "string": {
|
|
294
|
+
if (options.length === 0)
|
|
295
|
+
return "text";
|
|
296
|
+
if (options.length > 12)
|
|
297
|
+
return "combobox";
|
|
298
|
+
if (icons)
|
|
299
|
+
return "icon-radio";
|
|
300
|
+
const cards = options.some((option) => option.description !== undefined || option.meta !== undefined);
|
|
301
|
+
return cards || options.length <= 3 ? "radio" : "select";
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function emptyValue(valueType, options) {
|
|
306
|
+
switch (valueType.type) {
|
|
307
|
+
case "boolean":
|
|
308
|
+
return false;
|
|
309
|
+
case "number":
|
|
310
|
+
return valueType.nullable ? null : 0;
|
|
311
|
+
case "strings":
|
|
312
|
+
case "files":
|
|
313
|
+
return [];
|
|
314
|
+
case "string":
|
|
315
|
+
return options[0]?.value ?? "";
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/** Read a form's fields, sections and empty defaults from its schema. */
|
|
319
|
+
export function describeSchema(schema) {
|
|
320
|
+
const raw = schema instanceof z.ZodType ? zodFields(schema) : effectFields(schema);
|
|
321
|
+
const fields = raw.fields.map((entry, index) => {
|
|
322
|
+
const { kind: annotatedKind, ...annotation } = entry.annotation;
|
|
323
|
+
const options = resolveOptions(entry.valueType.type === "string" || entry.valueType.type === "strings"
|
|
324
|
+
? entry.valueType.literals
|
|
325
|
+
: [], entry.annotation);
|
|
326
|
+
const help = annotation.help ?? entry.description;
|
|
327
|
+
return {
|
|
328
|
+
...annotation,
|
|
329
|
+
name: entry.name,
|
|
330
|
+
label: annotation.label ?? entry.title ?? humanize(entry.name),
|
|
331
|
+
help,
|
|
332
|
+
kind: annotatedKind ?? inferKind(entry.valueType, options),
|
|
333
|
+
options,
|
|
334
|
+
optionalInSchema: entry.optional,
|
|
335
|
+
index,
|
|
336
|
+
};
|
|
337
|
+
});
|
|
338
|
+
const defaults = {};
|
|
339
|
+
raw.fields.forEach((entry, index) => {
|
|
340
|
+
const resolved = fields[index];
|
|
341
|
+
if (resolved)
|
|
342
|
+
defaults[entry.name] = emptyValue(entry.valueType, resolved.options);
|
|
343
|
+
});
|
|
344
|
+
const order = (field) => field.order ?? field.index;
|
|
345
|
+
// A section the struct doesn't declare (a field reused from another
|
|
346
|
+
// schema) falls back to the untitled group rather than disappearing.
|
|
347
|
+
const sectionOf = (field) => field.section !== undefined && Object.hasOwn(raw.sections, field.section)
|
|
348
|
+
? field.section
|
|
349
|
+
: null;
|
|
350
|
+
const grouped = (id) => fields
|
|
351
|
+
.filter((field) => sectionOf(field) === id)
|
|
352
|
+
.sort((left, right) => order(left) - order(right));
|
|
353
|
+
const declared = Object.entries(raw.sections).map(([id, section]) => ({
|
|
354
|
+
id,
|
|
355
|
+
title: section.title,
|
|
356
|
+
description: section.description,
|
|
357
|
+
fields: grouped(id),
|
|
358
|
+
}));
|
|
359
|
+
const loose = {
|
|
360
|
+
id: null,
|
|
361
|
+
title: undefined,
|
|
362
|
+
description: undefined,
|
|
363
|
+
fields: grouped(null),
|
|
364
|
+
};
|
|
365
|
+
return {
|
|
366
|
+
fields,
|
|
367
|
+
sections: [loose, ...declared].filter((section) => section.fields.length > 0),
|
|
368
|
+
defaults,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
/** The schema as a Standard Schema validator for TanStack Form. */
|
|
372
|
+
export function standardSchema(schema) {
|
|
373
|
+
const validator = schema instanceof z.ZodType ? schema : Schema.toStandardSchemaV1(schema);
|
|
374
|
+
return {
|
|
375
|
+
"~standard": {
|
|
376
|
+
...validator["~standard"],
|
|
377
|
+
validate: (value) => {
|
|
378
|
+
const result = validator["~standard"].validate(value);
|
|
379
|
+
return result instanceof Promise
|
|
380
|
+
? result.then((validated) => formIssues(validated))
|
|
381
|
+
: formIssues(result);
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/** Flat form controls own their array-item errors, such as one oversized file. */
|
|
387
|
+
function formIssues(result) {
|
|
388
|
+
return result.issues === undefined
|
|
389
|
+
? result
|
|
390
|
+
: {
|
|
391
|
+
issues: result.issues.map((issue) => issue.path === undefined
|
|
392
|
+
? issue
|
|
393
|
+
: {
|
|
394
|
+
...issue,
|
|
395
|
+
path: issue.path.slice(0, 1),
|
|
396
|
+
}),
|
|
397
|
+
};
|
|
398
|
+
}
|
package/dist/form.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Field as BoundField, type DeepKeys, type DeepValue, type FieldValidateOrFn, type FieldAsyncValidateOrFn, type FormValidateOrFn, type FormAsyncValidateOrFn } from "@tanstack/react-form";
|
|
2
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
3
|
+
import { type ButtonProps } from "./components/button.js";
|
|
4
|
+
import { type FieldProps, type FormFooterProps } from "./components/field.js";
|
|
5
|
+
/** TanStack field binding with Caps labels, descriptions, errors, and control ARIA. */
|
|
6
|
+
export declare function FormField<TParentData, TName extends DeepKeys<TParentData>, TData extends DeepValue<TParentData, TName>, TOnMount extends undefined | FieldValidateOrFn<TParentData, TName, TData>, TOnChange extends undefined | FieldValidateOrFn<TParentData, TName, TData>, TOnChangeAsync extends undefined | FieldAsyncValidateOrFn<TParentData, TName, TData>, TOnBlur extends undefined | FieldValidateOrFn<TParentData, TName, TData>, TOnBlurAsync extends undefined | FieldAsyncValidateOrFn<TParentData, TName, TData>, TOnSubmit extends undefined | FieldValidateOrFn<TParentData, TName, TData>, TOnSubmitAsync extends undefined | FieldAsyncValidateOrFn<TParentData, TName, TData>, TOnDynamic extends undefined | FieldValidateOrFn<TParentData, TName, TData>, TOnDynamicAsync extends undefined | FieldAsyncValidateOrFn<TParentData, TName, TData>, TFormOnMount extends undefined | FormValidateOrFn<TParentData>, TFormOnChange extends undefined | FormValidateOrFn<TParentData>, TFormOnChangeAsync extends undefined | FormAsyncValidateOrFn<TParentData>, TFormOnBlur extends undefined | FormValidateOrFn<TParentData>, TFormOnBlurAsync extends undefined | FormAsyncValidateOrFn<TParentData>, TFormOnSubmit extends undefined | FormValidateOrFn<TParentData>, TFormOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TParentData>, TFormOnDynamic extends undefined | FormValidateOrFn<TParentData>, TFormOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TParentData>, TFormOnServer extends undefined | FormAsyncValidateOrFn<TParentData>, TSubmitMeta>({ children, label, description, orientation, optional, required, className, ...options }: ComponentProps<typeof BoundField<TParentData, TName, TData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TOnDynamic, TOnDynamicAsync, TFormOnMount, TFormOnChange, TFormOnChangeAsync, TFormOnBlur, TFormOnBlurAsync, TFormOnSubmit, TFormOnSubmitAsync, TFormOnDynamic, TFormOnDynamicAsync, TFormOnServer, TSubmitMeta>> & Pick<FieldProps, "label" | "description" | "orientation" | "optional" | "required" | "className">): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
/** Values read by the submit action from TanStack’s form store. */
|
|
8
|
+
export type SubmissionState = {
|
|
9
|
+
canSubmit: boolean;
|
|
10
|
+
isSubmitting: boolean;
|
|
11
|
+
};
|
|
12
|
+
/** Footer presentation plus the submit action; TanStack owns availability and busy state. */
|
|
13
|
+
export type SubmitFooterProps = Omit<FormFooterProps, "children"> & {
|
|
14
|
+
form: {
|
|
15
|
+
/** TanStack’s reactive selector component. */
|
|
16
|
+
Subscribe: (props: {
|
|
17
|
+
selector: (state: SubmissionState) => SubmissionState;
|
|
18
|
+
children: (state: SubmissionState) => ReactNode;
|
|
19
|
+
}) => ReactNode;
|
|
20
|
+
};
|
|
21
|
+
label: ReactNode;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
buttonProps?: Omit<ButtonProps, "type" | "disabled" | "loading" | "children" | "onClick" | "asChild">;
|
|
24
|
+
};
|
|
25
|
+
/** Submit button subscribed to TanStack's canSubmit/isSubmitting store state. */
|
|
26
|
+
export declare function SubmitFooter({ form, label, disabled, buttonProps, ...props }: SubmitFooterProps): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
export { effectValidator, fieldError } from "./form-error.js";
|
|
28
|
+
export * from "./schema-form.js";
|
|
29
|
+
export { FormFields, type FormFieldsProps } from "./form-fields.js";
|
|
30
|
+
export * from "./form-schema.js";
|
package/dist/form.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Field as BoundField, useSelector, } from "@tanstack/react-form";
|
|
3
|
+
import { Button } from "./components/button.js";
|
|
4
|
+
import { fieldError } from "./form-error.js";
|
|
5
|
+
import { Field, FormFooter, } from "./components/field.js";
|
|
6
|
+
/** TanStack field binding with Caps labels, descriptions, errors, and control ARIA. */
|
|
7
|
+
export function FormField({ children, label, description, orientation = "vertical", optional = false, required = false, className, ...options }) {
|
|
8
|
+
const submitted = useSelector(options.form.store, (state) => state.submissionAttempts > 0);
|
|
9
|
+
return (_jsx(BoundField, { ...options, children: (field) => (_jsx(Field, { label: label, description: description, orientation: orientation, optional: optional, required: required, className: className, error: field.state.meta.isBlurred || submitted
|
|
10
|
+
? fieldError(field.state.meta)
|
|
11
|
+
: undefined, children: children(field) })) }));
|
|
12
|
+
}
|
|
13
|
+
/** Submit button subscribed to TanStack's canSubmit/isSubmitting store state. */
|
|
14
|
+
export function SubmitFooter({ form, label, disabled = false, buttonProps, ...props }) {
|
|
15
|
+
return (_jsx(form.Subscribe, { selector: ({ canSubmit, isSubmitting }) => ({ canSubmit, isSubmitting }), children: ({ canSubmit, isSubmitting }) => (_jsx(FormFooter, { ...props, children: _jsx(Button, { ...buttonProps, type: "submit", disabled: disabled || !canSubmit || isSubmitting, loading: isSubmitting, children: label }) })) }));
|
|
16
|
+
}
|
|
17
|
+
export { effectValidator, fieldError } from "./form-error.js";
|
|
18
|
+
export * from "./schema-form.js";
|
|
19
|
+
export { FormFields } from "./form-fields.js";
|
|
20
|
+
export * from "./form-schema.js";
|
package/dist/lib/cn.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { cva } from "class-variance-authority";
|
|
2
|
+
/** A conditional class value accepted by cn. */
|
|
3
|
+
export type { ClassValue } from "cn";
|
|
4
|
+
export { cva };
|
|
5
|
+
/** Merge conditional classes with Tailwind rules and Stipe's named spacing. */
|
|
6
|
+
export declare const cn: import("cn").CnFunction;
|
|
7
|
+
/** Resolve a typed React class-name prop before merging its classes. */
|
|
8
|
+
export declare function resolveClassName<State>(className: string | ((state: State) => string | undefined) | undefined, state: State): string | undefined;
|
package/dist/lib/cn.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { STIPE_SPACING_NAMES } from "@fungi.computer/stipe";
|
|
2
|
+
import { cva } from "class-variance-authority";
|
|
3
|
+
import { createCn } from "cn/config";
|
|
4
|
+
export { cva };
|
|
5
|
+
/** Merge conditional classes with Tailwind rules and Stipe's named spacing. */
|
|
6
|
+
export const cn = createCn({
|
|
7
|
+
extend: { theme: { spacing: [...STIPE_SPACING_NAMES] } },
|
|
8
|
+
});
|
|
9
|
+
/** Resolve a typed React class-name prop before merging its classes. */
|
|
10
|
+
export function resolveClassName(className, state) {
|
|
11
|
+
return typeof className === "function" ? className(state) : className;
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const regularUsd = new Intl.NumberFormat("en-US", {
|
|
2
|
+
style: "currency",
|
|
3
|
+
currency: "USD",
|
|
4
|
+
minimumFractionDigits: 2,
|
|
5
|
+
maximumFractionDigits: 2,
|
|
6
|
+
});
|
|
7
|
+
const subcentUsd = new Intl.NumberFormat("en-US", {
|
|
8
|
+
style: "currency",
|
|
9
|
+
currency: "USD",
|
|
10
|
+
minimumFractionDigits: 2,
|
|
11
|
+
maximumFractionDigits: 5,
|
|
12
|
+
});
|
|
13
|
+
/** Format USD with cents, preserving sub-cent precision without displaying non-zero amounts as zero. */
|
|
14
|
+
export function formatMoney(amount) {
|
|
15
|
+
if (!Number.isFinite(amount))
|
|
16
|
+
throw new RangeError("Money amount must be finite");
|
|
17
|
+
if (amount !== 0 && Math.abs(amount) < 0.00001)
|
|
18
|
+
return amount < 0 ? ">-$0.01" : "<$0.01";
|
|
19
|
+
return amount !== 0 && Math.abs(amount) < 0.01
|
|
20
|
+
? subcentUsd.format(amount)
|
|
21
|
+
: regularUsd.format(amount);
|
|
22
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type RefCallback } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* One highlight that glides between a list's rows instead of blinking from
|
|
4
|
+
* row to row. `lit` selects the highlighted row (keyboard or pointer). While
|
|
5
|
+
* the highlight moves, a single stand-in carrying that row's own resting
|
|
6
|
+
* background and radius travels from the previous row and the rows' own
|
|
7
|
+
* highlights are held back, so at rest every row looks exactly as it always
|
|
8
|
+
* has. Within one open list the glide remembers its last row, so leaving and
|
|
9
|
+
* re-entering glides from where it was. Timing and easing are the theme's;
|
|
10
|
+
* reduced motion moves the highlight at once.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useGlide(lit: string): RefCallback<HTMLElement>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { useCallback, useRef } from "react";
|
|
2
|
+
import { canAnimate, motionEasing, readMotion } from "./motion.js";
|
|
3
|
+
/**
|
|
4
|
+
* The attribute a row carries while the glide stands in for its highlight:
|
|
5
|
+
* empty while the stand-in travels, `measure` while its resting colour is read.
|
|
6
|
+
*/
|
|
7
|
+
const glideStandInAttribute = "data-glide-stand-in";
|
|
8
|
+
function boxIn(host, element) {
|
|
9
|
+
const outer = host.getBoundingClientRect();
|
|
10
|
+
const inner = element.getBoundingClientRect();
|
|
11
|
+
// Host-relative and scroll-aware, so a scrolled list glides true.
|
|
12
|
+
return {
|
|
13
|
+
x: inner.left - outer.left + host.scrollLeft - host.clientLeft,
|
|
14
|
+
y: inner.top - outer.top + host.scrollTop - host.clientTop,
|
|
15
|
+
width: inner.width,
|
|
16
|
+
height: inner.height,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function place(box) {
|
|
20
|
+
return {
|
|
21
|
+
transform: `translate(${String(box.x)}px, ${String(box.y)}px)`,
|
|
22
|
+
width: `${String(box.width)}px`,
|
|
23
|
+
height: `${String(box.height)}px`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* One highlight that glides between a list's rows instead of blinking from
|
|
28
|
+
* row to row. `lit` selects the highlighted row (keyboard or pointer). While
|
|
29
|
+
* the highlight moves, a single stand-in carrying that row's own resting
|
|
30
|
+
* background and radius travels from the previous row and the rows' own
|
|
31
|
+
* highlights are held back, so at rest every row looks exactly as it always
|
|
32
|
+
* has. Within one open list the glide remembers its last row, so leaving and
|
|
33
|
+
* re-entering glides from where it was. Timing and easing are the theme's;
|
|
34
|
+
* reduced motion moves the highlight at once.
|
|
35
|
+
*/
|
|
36
|
+
export function useGlide(lit) {
|
|
37
|
+
const detach = useRef(null);
|
|
38
|
+
// A callback ref, so a list that mounts later (a popover's) still glides.
|
|
39
|
+
return useCallback((element) => {
|
|
40
|
+
detach.current?.();
|
|
41
|
+
detach.current = element ? attachGlide(element, lit) : null;
|
|
42
|
+
}, [lit]);
|
|
43
|
+
}
|
|
44
|
+
function attachGlide(element, lit) {
|
|
45
|
+
if (!canAnimate(element))
|
|
46
|
+
return () => undefined;
|
|
47
|
+
const document = element.ownerDocument;
|
|
48
|
+
let current = element.querySelector(lit);
|
|
49
|
+
let last = current ? boxIn(element, current) : null;
|
|
50
|
+
let ghost = null;
|
|
51
|
+
let flight = null;
|
|
52
|
+
const standIns = new Set();
|
|
53
|
+
const release = () => {
|
|
54
|
+
for (const row of standIns)
|
|
55
|
+
row.removeAttribute(glideStandInAttribute);
|
|
56
|
+
standIns.clear();
|
|
57
|
+
if (ghost)
|
|
58
|
+
ghost.hidden = true;
|
|
59
|
+
flight = null;
|
|
60
|
+
};
|
|
61
|
+
const stage = () => {
|
|
62
|
+
if (ghost)
|
|
63
|
+
return ghost;
|
|
64
|
+
const created = document.createElement("span");
|
|
65
|
+
created.className = "caps-glide";
|
|
66
|
+
created.setAttribute("aria-hidden", "true");
|
|
67
|
+
element.prepend(created);
|
|
68
|
+
// The stand-in paints above the list's background, below its rows.
|
|
69
|
+
element.classList.add("caps-glide-host");
|
|
70
|
+
if (getComputedStyle(element).position === "static")
|
|
71
|
+
element.classList.add("caps-glide-anchor");
|
|
72
|
+
ghost = created;
|
|
73
|
+
return created;
|
|
74
|
+
};
|
|
75
|
+
const glide = (to, away) => {
|
|
76
|
+
const motion = readMotion(element);
|
|
77
|
+
const target = boxIn(element, to);
|
|
78
|
+
const from = flight && ghost ? boxIn(element, ghost) : last;
|
|
79
|
+
last = target;
|
|
80
|
+
flight?.cancel();
|
|
81
|
+
release();
|
|
82
|
+
if (motion.reduced || from === null)
|
|
83
|
+
return;
|
|
84
|
+
if (Math.abs(from.y - target.y) < 1 && Math.abs(from.x - target.x) < 1)
|
|
85
|
+
return;
|
|
86
|
+
// Read the row's resting highlight, not a frame of its colour transition.
|
|
87
|
+
to.setAttribute(glideStandInAttribute, "measure");
|
|
88
|
+
const style = getComputedStyle(to);
|
|
89
|
+
const background = style.backgroundColor;
|
|
90
|
+
const radius = style.borderRadius;
|
|
91
|
+
for (const row of [to, away]) {
|
|
92
|
+
if (!row)
|
|
93
|
+
continue;
|
|
94
|
+
row.setAttribute(glideStandInAttribute, "");
|
|
95
|
+
standIns.add(row);
|
|
96
|
+
}
|
|
97
|
+
const stand = stage();
|
|
98
|
+
stand.hidden = false;
|
|
99
|
+
stand.style.backgroundColor = background;
|
|
100
|
+
stand.style.borderRadius = radius;
|
|
101
|
+
const animation = stand.animate([place(from), place(target)], {
|
|
102
|
+
duration: motion.normal,
|
|
103
|
+
easing: motionEasing(motion, motion.ease),
|
|
104
|
+
fill: "forwards",
|
|
105
|
+
});
|
|
106
|
+
flight = animation;
|
|
107
|
+
animation.addEventListener("finish", () => {
|
|
108
|
+
if (flight === animation)
|
|
109
|
+
release();
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
const update = () => {
|
|
113
|
+
const next = element.querySelector(lit);
|
|
114
|
+
if (next === current)
|
|
115
|
+
return;
|
|
116
|
+
const away = current;
|
|
117
|
+
current = next;
|
|
118
|
+
// A row that lights from nothing glides from the remembered row.
|
|
119
|
+
if (next)
|
|
120
|
+
glide(next, away);
|
|
121
|
+
};
|
|
122
|
+
const observer = new MutationObserver(update);
|
|
123
|
+
observer.observe(element, {
|
|
124
|
+
subtree: true,
|
|
125
|
+
attributes: true,
|
|
126
|
+
attributeFilter: [
|
|
127
|
+
"data-highlighted",
|
|
128
|
+
"data-active",
|
|
129
|
+
"data-navigation",
|
|
130
|
+
"aria-selected",
|
|
131
|
+
],
|
|
132
|
+
});
|
|
133
|
+
element.addEventListener("pointerover", update);
|
|
134
|
+
element.addEventListener("pointerout", update);
|
|
135
|
+
return () => {
|
|
136
|
+
observer.disconnect();
|
|
137
|
+
element.removeEventListener("pointerover", update);
|
|
138
|
+
element.removeEventListener("pointerout", update);
|
|
139
|
+
flight?.cancel();
|
|
140
|
+
release();
|
|
141
|
+
ghost?.remove();
|
|
142
|
+
};
|
|
143
|
+
}
|
package/dist/lib/ime.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as z from "zod/mini";
|
|
2
|
+
// Some browsers clear isComposing before dispatching the confirmation key.
|
|
3
|
+
const compositionConfirmation = z.object({ keyCode: z.literal(229) });
|
|
4
|
+
/** Native composition, including confirmation events whose flag is already false. */
|
|
5
|
+
export function isImeComposing(event) {
|
|
6
|
+
return (event.nativeEvent.isComposing ||
|
|
7
|
+
z.safeParse(compositionConfirmation, event.nativeEvent).success);
|
|
8
|
+
}
|