@misoto22/folio 0.0.0 → 0.17.0
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/AGENTS.md +88 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +21 -0
- package/README.md +208 -2
- package/bin/folio-design.mjs +277 -0
- package/bin/migrate-legacy.mjs +81 -0
- package/dist/agent/Accordion.md +65 -0
- package/dist/agent/Actions.md +49 -0
- package/dist/agent/Alert.md +65 -0
- package/dist/agent/AppShell.md +69 -0
- package/dist/agent/ArchitectureFigure.md +72 -0
- package/dist/agent/AreaChart.md +94 -0
- package/dist/agent/Article.md +65 -0
- package/dist/agent/AspectRatio.md +58 -0
- package/dist/agent/Avatar.md +53 -0
- package/dist/agent/Badge.md +62 -0
- package/dist/agent/BarChart.md +93 -0
- package/dist/agent/BarList.md +67 -0
- package/dist/agent/BigNumber.md +70 -0
- package/dist/agent/BoxPlot.md +82 -0
- package/dist/agent/Breadcrumb.md +55 -0
- package/dist/agent/BulletChart.md +71 -0
- package/dist/agent/Button.md +81 -0
- package/dist/agent/Calendar.md +77 -0
- package/dist/agent/CalendarHeatmap.md +75 -0
- package/dist/agent/Card.md +72 -0
- package/dist/agent/Checkbox.md +57 -0
- package/dist/agent/Code.md +51 -0
- package/dist/agent/CodeBlock.md +77 -0
- package/dist/agent/Collapsible.md +68 -0
- package/dist/agent/Collection.md +169 -0
- package/dist/agent/ColorPicker.md +76 -0
- package/dist/agent/Combobox.md +77 -0
- package/dist/agent/Command.md +122 -0
- package/dist/agent/ComposedChart.md +82 -0
- package/dist/agent/Contact.md +90 -0
- package/dist/agent/Content.md +70 -0
- package/dist/agent/ContextMenu.md +93 -0
- package/dist/agent/Conversation.md +247 -0
- package/dist/agent/DataflowFigure.md +49 -0
- package/dist/agent/DatePicker.md +104 -0
- package/dist/agent/DescriptionList.md +69 -0
- package/dist/agent/Diagram.md +71 -0
- package/dist/agent/DiagramCanvas.md +71 -0
- package/dist/agent/DiagramExportMenu.md +65 -0
- package/dist/agent/DiagramInspector.md +73 -0
- package/dist/agent/DiagramLegend.md +56 -0
- package/dist/agent/DiagramMinimap.md +69 -0
- package/dist/agent/DiagramToolbar.md +70 -0
- package/dist/agent/Dialog.md +76 -0
- package/dist/agent/DropdownMenu.md +110 -0
- package/dist/agent/EmptyState.md +63 -0
- package/dist/agent/ErrorState.md +67 -0
- package/dist/agent/Evidence.md +165 -0
- package/dist/agent/Facet.md +107 -0
- package/dist/agent/Field.md +79 -0
- package/dist/agent/FigureBand.md +49 -0
- package/dist/agent/FloatingIconButton.md +61 -0
- package/dist/agent/FunnelChart.md +75 -0
- package/dist/agent/Heading.md +65 -0
- package/dist/agent/Heatmap.md +78 -0
- package/dist/agent/Histogram.md +88 -0
- package/dist/agent/Input.md +52 -0
- package/dist/agent/Kbd.md +48 -0
- package/dist/agent/LifecycleFigure.md +50 -0
- package/dist/agent/LineChart.md +85 -0
- package/dist/agent/LinkArrow.md +49 -0
- package/dist/agent/Markdown.md +69 -0
- package/dist/agent/Media.md +261 -0
- package/dist/agent/Metric.md +70 -0
- package/dist/agent/Metrics.md +114 -0
- package/dist/agent/Music.md +86 -0
- package/dist/agent/NativeSelect.md +62 -0
- package/dist/agent/NavItem.md +56 -0
- package/dist/agent/NumberField.md +73 -0
- package/dist/agent/PageHeader.md +69 -0
- package/dist/agent/Pagination.md +70 -0
- package/dist/agent/PieChart.md +80 -0
- package/dist/agent/Popover.md +80 -0
- package/dist/agent/Portfolio.md +93 -0
- package/dist/agent/PortfolioIndex.md +34 -0
- package/dist/agent/Progress.md +58 -0
- package/dist/agent/RadarChart.md +79 -0
- package/dist/agent/RadialChart.md +81 -0
- package/dist/agent/RadioGroup.md +66 -0
- package/dist/agent/Reading.md +233 -0
- package/dist/agent/Recovery.md +72 -0
- package/dist/agent/SankeyChart.md +85 -0
- package/dist/agent/ScatterChart.md +80 -0
- package/dist/agent/ScrollArea.md +62 -0
- package/dist/agent/SearchPalette.md +51 -0
- package/dist/agent/SearchableMenu.md +69 -0
- package/dist/agent/Select.md +102 -0
- package/dist/agent/Separator.md +60 -0
- package/dist/agent/SequenceFigure.md +51 -0
- package/dist/agent/Sheet.md +82 -0
- package/dist/agent/Sidebar.md +194 -0
- package/dist/agent/SiteNavigation.md +62 -0
- package/dist/agent/SiteShell.md +67 -0
- package/dist/agent/Skeleton.md +96 -0
- package/dist/agent/Slider.md +67 -0
- package/dist/agent/Sparkline.md +68 -0
- package/dist/agent/Spinner.md +62 -0
- package/dist/agent/StatusDot.md +56 -0
- package/dist/agent/StatusPill.md +55 -0
- package/dist/agent/Steps.md +68 -0
- package/dist/agent/Switch.md +50 -0
- package/dist/agent/Table.md +111 -0
- package/dist/agent/Tabs.md +71 -0
- package/dist/agent/Tag.md +67 -0
- package/dist/agent/Text.md +66 -0
- package/dist/agent/Textarea.md +46 -0
- package/dist/agent/Timeline.md +114 -0
- package/dist/agent/Timestamp.md +65 -0
- package/dist/agent/Toast.md +51 -0
- package/dist/agent/ToggleGroup.md +67 -0
- package/dist/agent/Toolbar.md +72 -0
- package/dist/agent/Tooltip.md +74 -0
- package/dist/agent/TreemapChart.md +76 -0
- package/dist/agent/WaterfallChart.md +86 -0
- package/dist/agent/Workbench.md +142 -0
- package/dist/agent/WorkflowFigure.md +51 -0
- package/dist/agent/catalog.json +8055 -0
- package/dist/agent/index.md +185 -0
- package/dist/article.css +407 -0
- package/dist/charts/AreaChart/AreaChart.d.ts +198 -0
- package/dist/charts/AreaChart/AreaChart.js +479 -0
- package/dist/charts/AreaChart/AreaChart.js.map +1 -0
- package/dist/charts/BarChart/BarChart.d.ts +190 -0
- package/dist/charts/BarChart/BarChart.js +599 -0
- package/dist/charts/BarChart/BarChart.js.map +1 -0
- package/dist/charts/BarList/BarList.d.ts +65 -0
- package/dist/charts/BarList/BarList.js +79 -0
- package/dist/charts/BarList/BarList.js.map +1 -0
- package/dist/charts/BigNumber/BigNumber.d.ts +72 -0
- package/dist/charts/BigNumber/BigNumber.js +51 -0
- package/dist/charts/BigNumber/BigNumber.js.map +1 -0
- package/dist/charts/BoxPlot/BoxPlot.d.ts +177 -0
- package/dist/charts/BoxPlot/BoxPlot.js +497 -0
- package/dist/charts/BoxPlot/BoxPlot.js.map +1 -0
- package/dist/charts/BulletChart/BulletChart.d.ts +100 -0
- package/dist/charts/BulletChart/BulletChart.js +197 -0
- package/dist/charts/BulletChart/BulletChart.js.map +1 -0
- package/dist/charts/CalendarHeatmap/CalendarHeatmap.d.ts +106 -0
- package/dist/charts/CalendarHeatmap/CalendarHeatmap.js +103 -0
- package/dist/charts/CalendarHeatmap/CalendarHeatmap.js.map +1 -0
- package/dist/charts/ComposedChart/ComposedChart.d.ts +178 -0
- package/dist/charts/ComposedChart/ComposedChart.js +667 -0
- package/dist/charts/ComposedChart/ComposedChart.js.map +1 -0
- package/dist/charts/Facet/Facet.d.ts +240 -0
- package/dist/charts/Facet/Facet.js +133 -0
- package/dist/charts/Facet/Facet.js.map +1 -0
- package/dist/charts/Facet/panels.d.ts +187 -0
- package/dist/charts/Facet/panels.js +203 -0
- package/dist/charts/Facet/panels.js.map +1 -0
- package/dist/charts/FunnelChart/FunnelChart.d.ts +106 -0
- package/dist/charts/FunnelChart/FunnelChart.js +137 -0
- package/dist/charts/FunnelChart/FunnelChart.js.map +1 -0
- package/dist/charts/Heatmap/Heatmap.d.ts +101 -0
- package/dist/charts/Heatmap/Heatmap.js +125 -0
- package/dist/charts/Heatmap/Heatmap.js.map +1 -0
- package/dist/charts/Histogram/Histogram.d.ts +188 -0
- package/dist/charts/Histogram/Histogram.js +382 -0
- package/dist/charts/Histogram/Histogram.js.map +1 -0
- package/dist/charts/LineChart/LineChart.d.ts +174 -0
- package/dist/charts/LineChart/LineChart.js +485 -0
- package/dist/charts/LineChart/LineChart.js.map +1 -0
- package/dist/charts/PieChart/PieChart.d.ts +140 -0
- package/dist/charts/PieChart/PieChart.js +317 -0
- package/dist/charts/PieChart/PieChart.js.map +1 -0
- package/dist/charts/RadarChart/RadarChart.d.ts +135 -0
- package/dist/charts/RadarChart/RadarChart.js +303 -0
- package/dist/charts/RadarChart/RadarChart.js.map +1 -0
- package/dist/charts/RadialChart/RadialChart.d.ts +142 -0
- package/dist/charts/RadialChart/RadialChart.js +279 -0
- package/dist/charts/RadialChart/RadialChart.js.map +1 -0
- package/dist/charts/SankeyChart/SankeyChart.d.ts +149 -0
- package/dist/charts/SankeyChart/SankeyChart.js +509 -0
- package/dist/charts/SankeyChart/SankeyChart.js.map +1 -0
- package/dist/charts/ScatterChart/ScatterChart.d.ts +149 -0
- package/dist/charts/ScatterChart/ScatterChart.js +275 -0
- package/dist/charts/ScatterChart/ScatterChart.js.map +1 -0
- package/dist/charts/Sparkline/Sparkline.d.ts +53 -0
- package/dist/charts/Sparkline/Sparkline.js +95 -0
- package/dist/charts/Sparkline/Sparkline.js.map +1 -0
- package/dist/charts/TreemapChart/TreemapChart.d.ts +98 -0
- package/dist/charts/TreemapChart/TreemapChart.js +127 -0
- package/dist/charts/TreemapChart/TreemapChart.js.map +1 -0
- package/dist/charts/WaterfallChart/WaterfallChart.d.ts +164 -0
- package/dist/charts/WaterfallChart/WaterfallChart.js +321 -0
- package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -0
- package/dist/charts/index.d.ts +43 -0
- package/dist/charts/index.js +130 -0
- package/dist/charts/index.js.map +1 -0
- package/dist/charts/lib/annotations.d.ts +103 -0
- package/dist/charts/lib/annotations.js +99 -0
- package/dist/charts/lib/annotations.js.map +1 -0
- package/dist/charts/lib/axis.d.ts +18 -0
- package/dist/charts/lib/axis.js +21 -0
- package/dist/charts/lib/axis.js.map +1 -0
- package/dist/charts/lib/background.d.ts +33 -0
- package/dist/charts/lib/background.js +173 -0
- package/dist/charts/lib/background.js.map +1 -0
- package/dist/charts/lib/brush.d.ts +94 -0
- package/dist/charts/lib/brush.js +535 -0
- package/dist/charts/lib/brush.js.map +1 -0
- package/dist/charts/lib/chart.d.ts +94 -0
- package/dist/charts/lib/chart.js +149 -0
- package/dist/charts/lib/chart.js.map +1 -0
- package/dist/charts/lib/dot.d.ts +29 -0
- package/dist/charts/lib/dot.js +78 -0
- package/dist/charts/lib/dot.js.map +1 -0
- package/dist/charts/lib/empty.d.ts +31 -0
- package/dist/charts/lib/empty.js +24 -0
- package/dist/charts/lib/empty.js.map +1 -0
- package/dist/charts/lib/export.d.ts +94 -0
- package/dist/charts/lib/export.js +62 -0
- package/dist/charts/lib/export.js.map +1 -0
- package/dist/charts/lib/figure.d.ts +81 -0
- package/dist/charts/lib/figure.js +62 -0
- package/dist/charts/lib/figure.js.map +1 -0
- package/dist/charts/lib/format.d.ts +46 -0
- package/dist/charts/lib/format.js +56 -0
- package/dist/charts/lib/format.js.map +1 -0
- package/dist/charts/lib/legend.d.ts +45 -0
- package/dist/charts/lib/legend.js +117 -0
- package/dist/charts/lib/legend.js.map +1 -0
- package/dist/charts/lib/loading.d.ts +48 -0
- package/dist/charts/lib/loading.js +105 -0
- package/dist/charts/lib/loading.js.map +1 -0
- package/dist/charts/lib/paint.d.ts +154 -0
- package/dist/charts/lib/paint.js +551 -0
- package/dist/charts/lib/paint.js.map +1 -0
- package/dist/charts/lib/scale.d.ts +23 -0
- package/dist/charts/lib/scale.js +13 -0
- package/dist/charts/lib/scale.js.map +1 -0
- package/dist/charts/lib/selection.d.ts +20 -0
- package/dist/charts/lib/selection.js +18 -0
- package/dist/charts/lib/selection.js.map +1 -0
- package/dist/charts/lib/sonify-control.d.ts +163 -0
- package/dist/charts/lib/sonify-control.js +172 -0
- package/dist/charts/lib/sonify-control.js.map +1 -0
- package/dist/charts/lib/sonify.d.ts +248 -0
- package/dist/charts/lib/sonify.js +262 -0
- package/dist/charts/lib/sonify.js.map +1 -0
- package/dist/charts/lib/toolbar.d.ts +144 -0
- package/dist/charts/lib/toolbar.js +156 -0
- package/dist/charts/lib/toolbar.js.map +1 -0
- package/dist/charts/lib/tooltip.d.ts +59 -0
- package/dist/charts/lib/tooltip.js +135 -0
- package/dist/charts/lib/tooltip.js.map +1 -0
- package/dist/charts/lib/values.d.ts +56 -0
- package/dist/charts/lib/values.js +71 -0
- package/dist/charts/lib/values.js.map +1 -0
- package/dist/charts/lib/zoom.d.ts +139 -0
- package/dist/charts/lib/zoom.js +314 -0
- package/dist/charts/lib/zoom.js.map +1 -0
- package/dist/components/Accordion/Accordion.d.ts +34 -0
- package/dist/components/Accordion/Accordion.js +43 -0
- package/dist/components/Accordion/Accordion.js.map +1 -0
- package/dist/components/Alert/Alert.d.ts +32 -0
- package/dist/components/Alert/Alert.js +49 -0
- package/dist/components/Alert/Alert.js.map +1 -0
- package/dist/components/AppShell/AppShell.d.ts +58 -0
- package/dist/components/AppShell/AppShell.js +126 -0
- package/dist/components/AppShell/AppShell.js.map +1 -0
- package/dist/components/Article/Article.d.ts +58 -0
- package/dist/components/Article/Article.js +34 -0
- package/dist/components/Article/Article.js.map +1 -0
- package/dist/components/AspectRatio/AspectRatio.d.ts +46 -0
- package/dist/components/AspectRatio/AspectRatio.js +33 -0
- package/dist/components/AspectRatio/AspectRatio.js.map +1 -0
- package/dist/components/Avatar/Avatar.d.ts +38 -0
- package/dist/components/Avatar/Avatar.js +44 -0
- package/dist/components/Avatar/Avatar.js.map +1 -0
- package/dist/components/Badge/Badge.d.ts +30 -0
- package/dist/components/Badge/Badge.js +29 -0
- package/dist/components/Badge/Badge.js.map +1 -0
- package/dist/components/Breadcrumb/Breadcrumb.d.ts +37 -0
- package/dist/components/Breadcrumb/Breadcrumb.js +46 -0
- package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -0
- package/dist/components/Button/Button.d.ts +70 -0
- package/dist/components/Button/Button.js +95 -0
- package/dist/components/Button/Button.js.map +1 -0
- package/dist/components/Calendar/Calendar.d.ts +54 -0
- package/dist/components/Calendar/Calendar.js +369 -0
- package/dist/components/Calendar/Calendar.js.map +1 -0
- package/dist/components/Card/Card.d.ts +55 -0
- package/dist/components/Card/Card.js +64 -0
- package/dist/components/Card/Card.js.map +1 -0
- package/dist/components/Checkbox/Checkbox.d.ts +26 -0
- package/dist/components/Checkbox/Checkbox.js +34 -0
- package/dist/components/Checkbox/Checkbox.js.map +1 -0
- package/dist/components/Code/Code.d.ts +30 -0
- package/dist/components/Code/Code.js +21 -0
- package/dist/components/Code/Code.js.map +1 -0
- package/dist/components/CodeBlock/CodeBlock.d.ts +145 -0
- package/dist/components/CodeBlock/CodeBlock.js +114 -0
- package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
- package/dist/components/Collapsible/Collapsible.d.ts +45 -0
- package/dist/components/Collapsible/Collapsible.js +60 -0
- package/dist/components/Collapsible/Collapsible.js.map +1 -0
- package/dist/components/ColorPicker/ColorPicker.d.ts +67 -0
- package/dist/components/ColorPicker/ColorPicker.js +334 -0
- package/dist/components/ColorPicker/ColorPicker.js.map +1 -0
- package/dist/components/Combobox/Combobox.d.ts +77 -0
- package/dist/components/Combobox/Combobox.js +170 -0
- package/dist/components/Combobox/Combobox.js.map +1 -0
- package/dist/components/Command/Command.d.ts +125 -0
- package/dist/components/Command/Command.js +204 -0
- package/dist/components/Command/Command.js.map +1 -0
- package/dist/components/ContextMenu/ContextMenu.d.ts +63 -0
- package/dist/components/ContextMenu/ContextMenu.js +109 -0
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -0
- package/dist/components/DatePicker/DatePicker.d.ts +130 -0
- package/dist/components/DatePicker/DatePicker.js +257 -0
- package/dist/components/DatePicker/DatePicker.js.map +1 -0
- package/dist/components/DescriptionList/DescriptionList.d.ts +73 -0
- package/dist/components/DescriptionList/DescriptionList.js +35 -0
- package/dist/components/DescriptionList/DescriptionList.js.map +1 -0
- package/dist/components/Diagram/Diagram.d.ts +86 -0
- package/dist/components/Diagram/Diagram.js +211 -0
- package/dist/components/Diagram/Diagram.js.map +1 -0
- package/dist/components/Dialog/Dialog.d.ts +63 -0
- package/dist/components/Dialog/Dialog.js +90 -0
- package/dist/components/Dialog/Dialog.js.map +1 -0
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +87 -0
- package/dist/components/DropdownMenu/DropdownMenu.js +120 -0
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -0
- package/dist/components/EmptyState/EmptyState.d.ts +55 -0
- package/dist/components/EmptyState/EmptyState.js +32 -0
- package/dist/components/EmptyState/EmptyState.js.map +1 -0
- package/dist/components/ErrorState/ErrorState.d.ts +55 -0
- package/dist/components/ErrorState/ErrorState.js +41 -0
- package/dist/components/ErrorState/ErrorState.js.map +1 -0
- package/dist/components/Field/Field.d.ts +111 -0
- package/dist/components/Field/Field.js +95 -0
- package/dist/components/Field/Field.js.map +1 -0
- package/dist/components/Field/field-control.d.ts +31 -0
- package/dist/components/Field/field-control.js +12 -0
- package/dist/components/Field/field-control.js.map +1 -0
- package/dist/components/FigureBand/FigureBand.d.ts +31 -0
- package/dist/components/FigureBand/FigureBand.js +51 -0
- package/dist/components/FigureBand/FigureBand.js.map +1 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.d.ts +29 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js +34 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js.map +1 -0
- package/dist/components/Heading/Heading.d.ts +62 -0
- package/dist/components/Heading/Heading.js +44 -0
- package/dist/components/Heading/Heading.js.map +1 -0
- package/dist/components/Input/Input.d.ts +22 -0
- package/dist/components/Input/Input.js +21 -0
- package/dist/components/Input/Input.js.map +1 -0
- package/dist/components/Kbd/Kbd.d.ts +27 -0
- package/dist/components/Kbd/Kbd.js +21 -0
- package/dist/components/Kbd/Kbd.js.map +1 -0
- package/dist/components/LinkArrow/LinkArrow.d.ts +24 -0
- package/dist/components/LinkArrow/LinkArrow.js +24 -0
- package/dist/components/LinkArrow/LinkArrow.js.map +1 -0
- package/dist/components/Markdown/Markdown.d.ts +139 -0
- package/dist/components/Markdown/Markdown.js +135 -0
- package/dist/components/Markdown/Markdown.js.map +1 -0
- package/dist/components/Markdown/inline.d.ts +29 -0
- package/dist/components/Markdown/inline.js +74 -0
- package/dist/components/Markdown/inline.js.map +1 -0
- package/dist/components/Markdown/nodes.d.ts +65 -0
- package/dist/components/Markdown/nodes.js +1 -0
- package/dist/components/Markdown/nodes.js.map +1 -0
- package/dist/components/Markdown/parse.d.ts +24 -0
- package/dist/components/Markdown/parse.js +110 -0
- package/dist/components/Markdown/parse.js.map +1 -0
- package/dist/components/Markdown/slug.d.ts +15 -0
- package/dist/components/Markdown/slug.js +7 -0
- package/dist/components/Markdown/slug.js.map +1 -0
- package/dist/components/Metric/Metric.d.ts +61 -0
- package/dist/components/Metric/Metric.js +51 -0
- package/dist/components/Metric/Metric.js.map +1 -0
- package/dist/components/NativeSelect/NativeSelect.d.ts +33 -0
- package/dist/components/NativeSelect/NativeSelect.js +44 -0
- package/dist/components/NativeSelect/NativeSelect.js.map +1 -0
- package/dist/components/NavItem/NavItem.d.ts +39 -0
- package/dist/components/NavItem/NavItem.js +31 -0
- package/dist/components/NavItem/NavItem.js.map +1 -0
- package/dist/components/NumberField/NumberField.d.ts +69 -0
- package/dist/components/NumberField/NumberField.js +162 -0
- package/dist/components/NumberField/NumberField.js.map +1 -0
- package/dist/components/PageHeader/PageHeader.d.ts +92 -0
- package/dist/components/PageHeader/PageHeader.js +37 -0
- package/dist/components/PageHeader/PageHeader.js.map +1 -0
- package/dist/components/Pagination/Pagination.d.ts +67 -0
- package/dist/components/Pagination/Pagination.js +112 -0
- package/dist/components/Pagination/Pagination.js.map +1 -0
- package/dist/components/Popover/Popover.d.ts +39 -0
- package/dist/components/Popover/Popover.js +57 -0
- package/dist/components/Popover/Popover.js.map +1 -0
- package/dist/components/Progress/Progress.d.ts +43 -0
- package/dist/components/Progress/Progress.js +57 -0
- package/dist/components/Progress/Progress.js.map +1 -0
- package/dist/components/RadioGroup/RadioGroup.d.ts +49 -0
- package/dist/components/RadioGroup/RadioGroup.js +53 -0
- package/dist/components/RadioGroup/RadioGroup.js.map +1 -0
- package/dist/components/ScrollArea/ScrollArea.d.ts +46 -0
- package/dist/components/ScrollArea/ScrollArea.js +54 -0
- package/dist/components/ScrollArea/ScrollArea.js.map +1 -0
- package/dist/components/SearchableMenu/SearchableMenu.d.ts +68 -0
- package/dist/components/SearchableMenu/SearchableMenu.js +101 -0
- package/dist/components/SearchableMenu/SearchableMenu.js.map +1 -0
- package/dist/components/Select/Select.d.ts +87 -0
- package/dist/components/Select/Select.js +130 -0
- package/dist/components/Select/Select.js.map +1 -0
- package/dist/components/Separator/Separator.d.ts +48 -0
- package/dist/components/Separator/Separator.js +43 -0
- package/dist/components/Separator/Separator.js.map +1 -0
- package/dist/components/Sheet/Sheet.d.ts +43 -0
- package/dist/components/Sheet/Sheet.js +79 -0
- package/dist/components/Sheet/Sheet.js.map +1 -0
- package/dist/components/Sidebar/Sidebar.d.ts +357 -0
- package/dist/components/Sidebar/Sidebar.js +447 -0
- package/dist/components/Sidebar/Sidebar.js.map +1 -0
- package/dist/components/Skeleton/Skeleton.d.ts +70 -0
- package/dist/components/Skeleton/Skeleton.js +44 -0
- package/dist/components/Skeleton/Skeleton.js.map +1 -0
- package/dist/components/Slider/Slider.d.ts +74 -0
- package/dist/components/Slider/Slider.js +143 -0
- package/dist/components/Slider/Slider.js.map +1 -0
- package/dist/components/Spinner/Spinner.d.ts +58 -0
- package/dist/components/Spinner/Spinner.js +49 -0
- package/dist/components/Spinner/Spinner.js.map +1 -0
- package/dist/components/StatusDot/StatusDot.d.ts +29 -0
- package/dist/components/StatusDot/StatusDot.js +44 -0
- package/dist/components/StatusDot/StatusDot.js.map +1 -0
- package/dist/components/StatusPill/StatusPill.d.ts +30 -0
- package/dist/components/StatusPill/StatusPill.js +37 -0
- package/dist/components/StatusPill/StatusPill.js.map +1 -0
- package/dist/components/Steps/Steps.d.ts +65 -0
- package/dist/components/Steps/Steps.js +25 -0
- package/dist/components/Steps/Steps.js.map +1 -0
- package/dist/components/Switch/Switch.d.ts +23 -0
- package/dist/components/Switch/Switch.js +23 -0
- package/dist/components/Switch/Switch.js.map +1 -0
- package/dist/components/Table/Table.d.ts +116 -0
- package/dist/components/Table/Table.js +137 -0
- package/dist/components/Table/Table.js.map +1 -0
- package/dist/components/Tabs/Tabs.d.ts +37 -0
- package/dist/components/Tabs/Tabs.js +205 -0
- package/dist/components/Tabs/Tabs.js.map +1 -0
- package/dist/components/Tag/Tag.d.ts +87 -0
- package/dist/components/Tag/Tag.js +62 -0
- package/dist/components/Tag/Tag.js.map +1 -0
- package/dist/components/Text/Text.d.ts +62 -0
- package/dist/components/Text/Text.js +30 -0
- package/dist/components/Text/Text.js.map +1 -0
- package/dist/components/Textarea/Textarea.d.ts +20 -0
- package/dist/components/Textarea/Textarea.js +32 -0
- package/dist/components/Textarea/Textarea.js.map +1 -0
- package/dist/components/Timestamp/Timestamp.d.ts +59 -0
- package/dist/components/Timestamp/Timestamp.js +56 -0
- package/dist/components/Timestamp/Timestamp.js.map +1 -0
- package/dist/components/Toast/Toast.d.ts +32 -0
- package/dist/components/Toast/Toast.js +64 -0
- package/dist/components/Toast/Toast.js.map +1 -0
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +46 -0
- package/dist/components/ToggleGroup/ToggleGroup.js +88 -0
- package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -0
- package/dist/components/Toolbar/Toolbar.d.ts +55 -0
- package/dist/components/Toolbar/Toolbar.js +43 -0
- package/dist/components/Toolbar/Toolbar.js.map +1 -0
- package/dist/components/Tooltip/Tooltip.d.ts +43 -0
- package/dist/components/Tooltip/Tooltip.js +40 -0
- package/dist/components/Tooltip/Tooltip.js.map +1 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +55 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +175 -0
- package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -0
- package/dist/diagrams/DataflowFigure/DataflowFigure.d.ts +28 -0
- package/dist/diagrams/DataflowFigure/DataflowFigure.js +148 -0
- package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +78 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +229 -0
- package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -0
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +106 -0
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +123 -0
- package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -0
- package/dist/diagrams/DiagramInspector/DiagramInspector.d.ts +70 -0
- package/dist/diagrams/DiagramInspector/DiagramInspector.js +94 -0
- package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -0
- package/dist/diagrams/DiagramLegend/DiagramLegend.d.ts +42 -0
- package/dist/diagrams/DiagramLegend/DiagramLegend.js +35 -0
- package/dist/diagrams/DiagramLegend/DiagramLegend.js.map +1 -0
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +77 -0
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +106 -0
- package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -0
- package/dist/diagrams/DiagramToolbar/DiagramToolbar.d.ts +48 -0
- package/dist/diagrams/DiagramToolbar/DiagramToolbar.js +53 -0
- package/dist/diagrams/DiagramToolbar/DiagramToolbar.js.map +1 -0
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.d.ts +31 -0
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +234 -0
- package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -0
- package/dist/diagrams/SequenceFigure/SequenceFigure.d.ts +38 -0
- package/dist/diagrams/SequenceFigure/SequenceFigure.js +261 -0
- package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -0
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +36 -0
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +259 -0
- package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -0
- package/dist/diagrams/index.d.ts +17 -0
- package/dist/diagrams/index.js +29 -0
- package/dist/diagrams/index.js.map +1 -0
- package/dist/diagrams/lib/dev.d.ts +61 -0
- package/dist/diagrams/lib/dev.js +96 -0
- package/dist/diagrams/lib/dev.js.map +1 -0
- package/dist/diagrams/lib/frame.d.ts +138 -0
- package/dist/diagrams/lib/frame.js +131 -0
- package/dist/diagrams/lib/frame.js.map +1 -0
- package/dist/diagrams/lib/geometry.d.ts +114 -0
- package/dist/diagrams/lib/geometry.js +113 -0
- package/dist/diagrams/lib/geometry.js.map +1 -0
- package/dist/diagrams/lib/legend.d.ts +32 -0
- package/dist/diagrams/lib/legend.js +84 -0
- package/dist/diagrams/lib/legend.js.map +1 -0
- package/dist/diagrams/lib/marks.d.ts +207 -0
- package/dist/diagrams/lib/marks.js +480 -0
- package/dist/diagrams/lib/marks.js.map +1 -0
- package/dist/diagrams/lib/route.d.ts +106 -0
- package/dist/diagrams/lib/route.js +201 -0
- package/dist/diagrams/lib/route.js.map +1 -0
- package/dist/diagrams/lib/wires.d.ts +59 -0
- package/dist/diagrams/lib/wires.js +94 -0
- package/dist/diagrams/lib/wires.js.map +1 -0
- package/dist/diagrams/spec.d.ts +405 -0
- package/dist/diagrams/spec.js +1 -0
- package/dist/diagrams/spec.js.map +1 -0
- package/dist/fonts/hanken-grotesk-300.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-400.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-500.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-600.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-700.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-400.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-500.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-600.woff2 +0 -0
- package/dist/fonts/newsreader-300.woff2 +0 -0
- package/dist/fonts/newsreader-400.woff2 +0 -0
- package/dist/fonts/newsreader-500.woff2 +0 -0
- package/dist/fonts/newsreader-600.woff2 +0 -0
- package/dist/fonts.css +96 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/keyframes.css +300 -0
- package/dist/lib/cn.d.ts +21 -0
- package/dist/lib/cn.js +9 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/lib/color.d.ts +80 -0
- package/dist/lib/color.js +189 -0
- package/dist/lib/color.js.map +1 -0
- package/dist/lib/control.d.ts +32 -0
- package/dist/lib/control.js +14 -0
- package/dist/lib/control.js.map +1 -0
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/index.js +11 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/numeric.d.ts +35 -0
- package/dist/lib/numeric.js +29 -0
- package/dist/lib/numeric.js.map +1 -0
- package/dist/lib/overlay-container.d.ts +48 -0
- package/dist/lib/overlay-container.js +15 -0
- package/dist/lib/overlay-container.js.map +1 -0
- package/dist/lib/slot-content.d.ts +8 -0
- package/dist/lib/slot-content.js +17 -0
- package/dist/lib/slot-content.js.map +1 -0
- package/dist/lib/step-rail.d.ts +45 -0
- package/dist/lib/step-rail.js +47 -0
- package/dist/lib/step-rail.js.map +1 -0
- package/dist/lib/svg-export.d.ts +138 -0
- package/dist/lib/svg-export.js +198 -0
- package/dist/lib/svg-export.js.map +1 -0
- package/dist/lib/useClipboardCopy.d.ts +30 -0
- package/dist/lib/useClipboardCopy.js +37 -0
- package/dist/lib/useClipboardCopy.js.map +1 -0
- package/dist/lib/useSelectionIndicator.d.ts +29 -0
- package/dist/lib/useSelectionIndicator.js +49 -0
- package/dist/lib/useSelectionIndicator.js.map +1 -0
- package/dist/lib/warn.d.ts +73 -0
- package/dist/lib/warn.js +38 -0
- package/dist/lib/warn.js.map +1 -0
- package/dist/semantic.css +200 -0
- package/dist/styles.css +98 -0
- package/dist/themes.css +607 -0
- package/dist/tokens/brand.d.ts +47 -0
- package/dist/tokens/brand.js +38 -0
- package/dist/tokens/brand.js.map +1 -0
- package/dist/tokens.css +666 -0
- package/dist/tokens.d.ts +205 -0
- package/dist/tokens.js +1056 -0
- package/dist/tokens.json +1055 -0
- package/dist/website/Actions/Actions.d.ts +34 -0
- package/dist/website/Actions/Actions.js +29 -0
- package/dist/website/Actions/Actions.js.map +1 -0
- package/dist/website/Collection/Collection.d.ts +128 -0
- package/dist/website/Collection/Collection.js +162 -0
- package/dist/website/Collection/Collection.js.map +1 -0
- package/dist/website/Contact/Contact.d.ts +87 -0
- package/dist/website/Contact/Contact.js +94 -0
- package/dist/website/Contact/Contact.js.map +1 -0
- package/dist/website/Content/CodePanel.d.ts +27 -0
- package/dist/website/Content/CodePanel.js +30 -0
- package/dist/website/Content/CodePanel.js.map +1 -0
- package/dist/website/Content/Content.d.ts +29 -0
- package/dist/website/Content/Content.js +38 -0
- package/dist/website/Content/Content.js.map +1 -0
- package/dist/website/Conversation/Conversation.d.ts +193 -0
- package/dist/website/Conversation/Conversation.js +252 -0
- package/dist/website/Conversation/Conversation.js.map +1 -0
- package/dist/website/Evidence/Evidence.d.ts +116 -0
- package/dist/website/Evidence/Evidence.js +142 -0
- package/dist/website/Evidence/Evidence.js.map +1 -0
- package/dist/website/Media/Media.d.ts +173 -0
- package/dist/website/Media/Media.js +225 -0
- package/dist/website/Media/Media.js.map +1 -0
- package/dist/website/Media/MediaLightbox.d.ts +18 -0
- package/dist/website/Media/MediaLightbox.js +38 -0
- package/dist/website/Media/MediaLightbox.js.map +1 -0
- package/dist/website/Metrics/Metrics.d.ts +74 -0
- package/dist/website/Metrics/Metrics.js +96 -0
- package/dist/website/Metrics/Metrics.js.map +1 -0
- package/dist/website/Music/Music.d.ts +60 -0
- package/dist/website/Music/Music.js +80 -0
- package/dist/website/Music/Music.js.map +1 -0
- package/dist/website/Portfolio/FrameLayout.d.ts +9 -0
- package/dist/website/Portfolio/FrameLayout.js +42 -0
- package/dist/website/Portfolio/FrameLayout.js.map +1 -0
- package/dist/website/Portfolio/Portfolio.d.ts +49 -0
- package/dist/website/Portfolio/Portfolio.js +66 -0
- package/dist/website/Portfolio/Portfolio.js.map +1 -0
- package/dist/website/Portfolio/QuestionCard.d.ts +22 -0
- package/dist/website/Portfolio/QuestionCard.js +33 -0
- package/dist/website/Portfolio/QuestionCard.js.map +1 -0
- package/dist/website/PortfolioIndex/PortfolioIndex.d.ts +21 -0
- package/dist/website/PortfolioIndex/PortfolioIndex.js +31 -0
- package/dist/website/PortfolioIndex/PortfolioIndex.js.map +1 -0
- package/dist/website/Reading/Reading.d.ts +203 -0
- package/dist/website/Reading/Reading.js +357 -0
- package/dist/website/Reading/Reading.js.map +1 -0
- package/dist/website/Recovery/Recovery.d.ts +39 -0
- package/dist/website/Recovery/Recovery.js +96 -0
- package/dist/website/Recovery/Recovery.js.map +1 -0
- package/dist/website/SearchPalette/SearchPalette.d.ts +59 -0
- package/dist/website/SearchPalette/SearchPalette.js +120 -0
- package/dist/website/SearchPalette/SearchPalette.js.map +1 -0
- package/dist/website/SiteNavigation/SiteNavigation.d.ts +46 -0
- package/dist/website/SiteNavigation/SiteNavigation.js +101 -0
- package/dist/website/SiteNavigation/SiteNavigation.js.map +1 -0
- package/dist/website/SiteShell/SiteShell.d.ts +45 -0
- package/dist/website/SiteShell/SiteShell.js +46 -0
- package/dist/website/SiteShell/SiteShell.js.map +1 -0
- package/dist/website/Timeline/Timeline.d.ts +80 -0
- package/dist/website/Timeline/Timeline.js +99 -0
- package/dist/website/Timeline/Timeline.js.map +1 -0
- package/dist/website/Workbench/Chronology.d.ts +32 -0
- package/dist/website/Workbench/Chronology.js +32 -0
- package/dist/website/Workbench/Chronology.js.map +1 -0
- package/dist/website/Workbench/ContactRow.d.ts +13 -0
- package/dist/website/Workbench/ContactRow.js +11 -0
- package/dist/website/Workbench/ContactRow.js.map +1 -0
- package/dist/website/Workbench/PhotoMosaic.d.ts +13 -0
- package/dist/website/Workbench/PhotoMosaic.js +15 -0
- package/dist/website/Workbench/PhotoMosaic.js.map +1 -0
- package/dist/website/Workbench/Reveal.d.ts +19 -0
- package/dist/website/Workbench/Reveal.js +29 -0
- package/dist/website/Workbench/Reveal.js.map +1 -0
- package/dist/website/Workbench/ToolStrip.d.ts +18 -0
- package/dist/website/Workbench/ToolStrip.js +15 -0
- package/dist/website/Workbench/ToolStrip.js.map +1 -0
- package/dist/website/Workbench/Workbench.d.ts +23 -0
- package/dist/website/Workbench/Workbench.js +22 -0
- package/dist/website/Workbench/Workbench.js.map +1 -0
- package/dist/website/Workbench/WorkbenchCard.d.ts +29 -0
- package/dist/website/Workbench/WorkbenchCard.js +21 -0
- package/dist/website/Workbench/WorkbenchCard.js.map +1 -0
- package/dist/website/Workbench/WorkbenchEntries.d.ts +22 -0
- package/dist/website/Workbench/WorkbenchEntries.js +24 -0
- package/dist/website/Workbench/WorkbenchEntries.js.map +1 -0
- package/dist/website/Workbench/WorkbenchFigure.d.ts +20 -0
- package/dist/website/Workbench/WorkbenchFigure.js +15 -0
- package/dist/website/Workbench/WorkbenchFigure.js.map +1 -0
- package/dist/website/Workbench/WorkbenchHero.d.ts +24 -0
- package/dist/website/Workbench/WorkbenchHero.js +24 -0
- package/dist/website/Workbench/WorkbenchHero.js.map +1 -0
- package/dist/website/Workbench/theme.d.ts +22 -0
- package/dist/website/Workbench/theme.js +12 -0
- package/dist/website/Workbench/theme.js.map +1 -0
- package/dist/website/index.d.ts +38 -0
- package/dist/website/index.js +32 -0
- package/dist/website/index.js.map +1 -0
- package/dist/website-actions.css +6 -0
- package/dist/website-base.css +46 -0
- package/dist/website-collection.css +89 -0
- package/dist/website-contact.css +29 -0
- package/dist/website-content.css +20 -0
- package/dist/website-controls.css +91 -0
- package/dist/website-conversation.css +115 -0
- package/dist/website-evidence.css +56 -0
- package/dist/website-media.css +227 -0
- package/dist/website-metrics.css +15 -0
- package/dist/website-music.css +27 -0
- package/dist/website-portfolio.css +94 -0
- package/dist/website-reading.css +159 -0
- package/dist/website-recovery.css +28 -0
- package/dist/website-search.css +26 -0
- package/dist/website-shell.css +89 -0
- package/dist/website-timeline.css +43 -0
- package/dist/website-workbench.css +162 -0
- package/dist/website.css +26 -0
- package/llms.txt +35 -0
- package/package.json +163 -5
- package/skills/folio-design/SKILL.md +228 -0
- package/skills/folio-design/evals/claims.json +95 -0
- package/skills/folio-design/evals/evals.json +84 -0
- package/skills/folio-design/rules/a11y.md +167 -0
- package/skills/folio-design/rules/composition.md +108 -0
- package/skills/folio-design/rules/forms.md +135 -0
- package/skills/folio-design/rules/naming.md +155 -0
- package/skills/folio-design/rules/tokens.md +135 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taking an `<svg>` off the page as a file.
|
|
3
|
+
*
|
|
4
|
+
* Shared by two entries that both draw in SVG and both need to hand a reader a
|
|
5
|
+
* picture of it: `@misoto22/folio/charts` and `@misoto22/folio/diagrams`.
|
|
6
|
+
* Everything here is engine-agnostic — nothing knows what a series or a node
|
|
7
|
+
* is — so the parts that DO know (which `<svg>` inside a wrapper is the plot,
|
|
8
|
+
* what the file should be called, what sits behind it) stay with the caller.
|
|
9
|
+
*
|
|
10
|
+
* Nothing touches the DOM at module scope. Every browser API — `document`,
|
|
11
|
+
* `canvas`, `URL.createObjectURL` — is reached for inside a function body, so
|
|
12
|
+
* this module imports cleanly into a server component and only fails when a
|
|
13
|
+
* consumer actually asks for a file on a server, which is a call that cannot
|
|
14
|
+
* succeed anywhere.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Copies the resolved paint of every node in `live` onto the matching node in
|
|
18
|
+
* `clone`.
|
|
19
|
+
*
|
|
20
|
+
* ## The problem this function exists to solve
|
|
21
|
+
*
|
|
22
|
+
* Serialising an `<svg>` and handing it to an `<img>` puts it in a document of
|
|
23
|
+
* its own. That document has none of the page's stylesheets, so every
|
|
24
|
+
* `var(--ink)`, `var(--rule-2)` and `var(--diagram-node)` in the markup
|
|
25
|
+
* resolves to nothing — and a paint that resolves to nothing is not a fallback
|
|
26
|
+
* colour, it is an invisible mark. Serialise naively and you get an empty
|
|
27
|
+
* rectangle.
|
|
28
|
+
*
|
|
29
|
+
* ## The solution
|
|
30
|
+
*
|
|
31
|
+
* Walk the live tree and the clone in step, and on every node write
|
|
32
|
+
* `getComputedStyle`'s answer into an inline `style`. Computed values have
|
|
33
|
+
* already had `var()` substituted by the engine, so `fill: var(--ink)` arrives
|
|
34
|
+
* as `fill: rgb(16, 16, 16)` — and, because the walk reads the LIVE element, it
|
|
35
|
+
* reads it under whichever theme is currently applied. Light and dark come out
|
|
36
|
+
* right for free, with no theme argument anywhere in this file.
|
|
37
|
+
*
|
|
38
|
+
* `<stop>` and `<marker>` children are walked too, which is the half that is
|
|
39
|
+
* easy to miss: a gradient's stops and an arrowhead's fill carry custom
|
|
40
|
+
* properties of their own, and a `<defs>` that lost its paint takes every line
|
|
41
|
+
* ending with it.
|
|
42
|
+
*/
|
|
43
|
+
declare function inlinePaint(live: Element, clone: Element): void;
|
|
44
|
+
/**
|
|
45
|
+
* A custom property, resolved against the live element.
|
|
46
|
+
*
|
|
47
|
+
* `var()` inside a custom property is substituted at computed-value time, so
|
|
48
|
+
* `--diagram-surface: var(--paper)` reads back as a colour. The guard is for
|
|
49
|
+
* the case where it did not — an unregistered name, or a value the engine left
|
|
50
|
+
* as a token stream — because writing `var(--paper)` into a serialised SVG
|
|
51
|
+
* paints nothing at all.
|
|
52
|
+
*/
|
|
53
|
+
declare function readToken(element: Element, name: string, fallback: string): string;
|
|
54
|
+
interface SerializeOptions {
|
|
55
|
+
/** Space around the artwork, in user units. Defaults to 16. */
|
|
56
|
+
padding?: number;
|
|
57
|
+
/** Painted behind the artwork. Omit for a transparent ground. */
|
|
58
|
+
background?: string;
|
|
59
|
+
/** A band above the artwork carrying this text. */
|
|
60
|
+
title?: string;
|
|
61
|
+
/** Paint for the title. Defaults to `#101010`. */
|
|
62
|
+
titleColor?: string;
|
|
63
|
+
/** Family for the title. Defaults to `sans-serif`. */
|
|
64
|
+
titleFont?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Forces the output box, letterboxing the artwork inside it.
|
|
67
|
+
*
|
|
68
|
+
* This is what a share card is: a fixed 1200×630 frame that the diagram is
|
|
69
|
+
* fitted into, rather than a frame the diagram sizes. Omit it and the output
|
|
70
|
+
* is the artwork plus its padding.
|
|
71
|
+
*/
|
|
72
|
+
frame?: {
|
|
73
|
+
width: number;
|
|
74
|
+
height: number;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* One standalone `<svg>` document: the element's own artwork, repainted with
|
|
79
|
+
* resolved colours, on an optional plate, under an optional title.
|
|
80
|
+
*
|
|
81
|
+
* Returns markup rather than a blob because the SVG export wants the string
|
|
82
|
+
* and every raster export wants it as a data source — one serialisation, four
|
|
83
|
+
* file formats.
|
|
84
|
+
*
|
|
85
|
+
* ## What this cannot capture, honestly
|
|
86
|
+
*
|
|
87
|
+
* - **Anything drawn in HTML.** A toolbar, a tooltip, an inspector panel are
|
|
88
|
+
* DOM, not SVG. The file is the artwork.
|
|
89
|
+
* - **Web fonts.** The isolated document cannot fetch the page's `@font-face`
|
|
90
|
+
* sources, so text is rasterised in whatever the resolved family stack finds
|
|
91
|
+
* locally. Type metrics will differ from the screen.
|
|
92
|
+
* - **Cross-origin images.** An `<image>` from another origin taints the canvas
|
|
93
|
+
* and `toBlob` throws; there is no way around it from script.
|
|
94
|
+
*/
|
|
95
|
+
declare function serializeSvg(svg: SVGSVGElement, options?: SerializeOptions): string;
|
|
96
|
+
/** The raster formats a browser canvas can encode. */
|
|
97
|
+
type RasterFormat = 'png' | 'jpeg' | 'webp';
|
|
98
|
+
interface RasterOptions {
|
|
99
|
+
/** Multiplier on the on-screen size. 2 is a retina-sharp export. */
|
|
100
|
+
scale?: number;
|
|
101
|
+
/** Encoder quality for `jpeg` and `webp`, 0–1. Ignored by `png`. */
|
|
102
|
+
quality?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Painted behind the image, for a format with no alpha channel.
|
|
105
|
+
*
|
|
106
|
+
* JPEG has no transparency, so a diagram serialised without a plate lands on
|
|
107
|
+
* black. Passing the page's own surface here is what makes the file look
|
|
108
|
+
* like the screen rather than like a negative.
|
|
109
|
+
*/
|
|
110
|
+
background?: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* SVG markup, rasterised through a canvas.
|
|
114
|
+
*
|
|
115
|
+
* This is the browser's own SVG rasteriser re-drawing the markup, not a
|
|
116
|
+
* screenshot: antialiasing, `backdrop-filter` and any effect a page stylesheet
|
|
117
|
+
* applied from outside the `<svg>` are not in it.
|
|
118
|
+
*/
|
|
119
|
+
declare function rasterize(markup: string, format: RasterFormat, options?: RasterOptions): Promise<Blob>;
|
|
120
|
+
/**
|
|
121
|
+
* Hands a blob to the browser as a download.
|
|
122
|
+
*
|
|
123
|
+
* Throws rather than no-oping without a `document`: a download is something a
|
|
124
|
+
* person just asked for, and a silent return would look to them like a click
|
|
125
|
+
* that did nothing.
|
|
126
|
+
*/
|
|
127
|
+
declare function downloadBlob(blob: Blob, filename: string): void;
|
|
128
|
+
/**
|
|
129
|
+
* A title as a filename.
|
|
130
|
+
*
|
|
131
|
+
* Lowercase, ASCII-safe and hyphenated, because a downloaded file crosses into
|
|
132
|
+
* shells, zip archives and Windows paths where a title's spaces and slashes are
|
|
133
|
+
* someone else's problem. A title with no ASCII in it at all — a Chinese one,
|
|
134
|
+
* say — slugs to nothing, so the caller's fallback is what names the file.
|
|
135
|
+
*/
|
|
136
|
+
declare function exportFilename(title: string, extension: string, fallback?: string): string;
|
|
137
|
+
|
|
138
|
+
export { type RasterFormat, type RasterOptions, type SerializeOptions, downloadBlob, exportFilename, inlinePaint, rasterize, readToken, serializeSvg };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
2
|
+
const PAINTED_PROPERTIES = [
|
|
3
|
+
"display",
|
|
4
|
+
"visibility",
|
|
5
|
+
"opacity",
|
|
6
|
+
"color",
|
|
7
|
+
"fill",
|
|
8
|
+
"fill-opacity",
|
|
9
|
+
"fill-rule",
|
|
10
|
+
"stroke",
|
|
11
|
+
"stroke-opacity",
|
|
12
|
+
"stroke-width",
|
|
13
|
+
"stroke-linecap",
|
|
14
|
+
"stroke-linejoin",
|
|
15
|
+
"stroke-dasharray",
|
|
16
|
+
"stroke-dashoffset",
|
|
17
|
+
"stop-color",
|
|
18
|
+
"stop-opacity",
|
|
19
|
+
"flood-color",
|
|
20
|
+
"flood-opacity",
|
|
21
|
+
"marker-start",
|
|
22
|
+
"marker-mid",
|
|
23
|
+
"marker-end",
|
|
24
|
+
"mask",
|
|
25
|
+
"filter",
|
|
26
|
+
"clip-path",
|
|
27
|
+
"clip-rule",
|
|
28
|
+
"paint-order",
|
|
29
|
+
"mix-blend-mode",
|
|
30
|
+
"shape-rendering",
|
|
31
|
+
"text-anchor",
|
|
32
|
+
"dominant-baseline",
|
|
33
|
+
"font-family",
|
|
34
|
+
"font-size",
|
|
35
|
+
"font-style",
|
|
36
|
+
"font-weight",
|
|
37
|
+
"letter-spacing",
|
|
38
|
+
"text-transform"
|
|
39
|
+
];
|
|
40
|
+
function inlinePaint(live, clone) {
|
|
41
|
+
const computed = getComputedStyle(live);
|
|
42
|
+
let css = "";
|
|
43
|
+
for (const property of PAINTED_PROPERTIES) {
|
|
44
|
+
const value = computed.getPropertyValue(property);
|
|
45
|
+
if (value) css += `${property}:${value};`;
|
|
46
|
+
}
|
|
47
|
+
if (css) clone.setAttribute("style", css);
|
|
48
|
+
const children = live.children;
|
|
49
|
+
const clones = clone.children;
|
|
50
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
51
|
+
const liveChild = children[index];
|
|
52
|
+
const cloneChild = clones[index];
|
|
53
|
+
if (liveChild && cloneChild) inlinePaint(liveChild, cloneChild);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function readToken(element, name, fallback) {
|
|
57
|
+
const value = getComputedStyle(element).getPropertyValue(name).trim();
|
|
58
|
+
return value && !value.includes("var(") ? value : fallback;
|
|
59
|
+
}
|
|
60
|
+
function serializeSvg(svg, options = {}) {
|
|
61
|
+
const { padding = 16, background, title, titleColor = "#101010", titleFont = "sans-serif" } = options;
|
|
62
|
+
const box = svg.getBoundingClientRect();
|
|
63
|
+
const width = Math.ceil(box.width) || Number(svg.getAttribute("width")) || 0;
|
|
64
|
+
const height = Math.ceil(box.height) || Number(svg.getAttribute("height")) || 0;
|
|
65
|
+
if (width === 0 || height === 0) {
|
|
66
|
+
throw new Error("serializeSvg: the artwork has no measured size yet \u2014 export it after it renders");
|
|
67
|
+
}
|
|
68
|
+
const clone = svg.cloneNode(true);
|
|
69
|
+
inlinePaint(svg, clone);
|
|
70
|
+
clone.setAttribute("width", String(width));
|
|
71
|
+
clone.setAttribute("height", String(height));
|
|
72
|
+
const titleBand = title ? 34 : 0;
|
|
73
|
+
const contentWidth = width + padding * 2;
|
|
74
|
+
const contentHeight = height + padding * 2 + titleBand;
|
|
75
|
+
const totalWidth = options.frame?.width ?? contentWidth;
|
|
76
|
+
const totalHeight = options.frame?.height ?? contentHeight;
|
|
77
|
+
const scale = options.frame ? Math.min(totalWidth / contentWidth, totalHeight / contentHeight, 1) : 1;
|
|
78
|
+
const offsetX = (totalWidth - contentWidth * scale) / 2;
|
|
79
|
+
const offsetY = (totalHeight - contentHeight * scale) / 2;
|
|
80
|
+
const page = document.createElementNS(SVG_NS, "svg");
|
|
81
|
+
page.setAttribute("xmlns", SVG_NS);
|
|
82
|
+
page.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
83
|
+
page.setAttribute("width", String(totalWidth));
|
|
84
|
+
page.setAttribute("height", String(totalHeight));
|
|
85
|
+
page.setAttribute("viewBox", `0 0 ${totalWidth} ${totalHeight}`);
|
|
86
|
+
if (background) {
|
|
87
|
+
const plate = document.createElementNS(SVG_NS, "rect");
|
|
88
|
+
plate.setAttribute("width", String(totalWidth));
|
|
89
|
+
plate.setAttribute("height", String(totalHeight));
|
|
90
|
+
plate.setAttribute("fill", background);
|
|
91
|
+
page.append(plate);
|
|
92
|
+
}
|
|
93
|
+
const stage = document.createElementNS(SVG_NS, "g");
|
|
94
|
+
stage.setAttribute("transform", `translate(${offsetX}, ${offsetY}) scale(${scale})`);
|
|
95
|
+
page.append(stage);
|
|
96
|
+
if (title) {
|
|
97
|
+
const caption = document.createElementNS(SVG_NS, "text");
|
|
98
|
+
caption.setAttribute("x", String(padding));
|
|
99
|
+
caption.setAttribute("y", String(padding + 15));
|
|
100
|
+
caption.setAttribute("fill", titleColor);
|
|
101
|
+
caption.setAttribute("font-size", "15");
|
|
102
|
+
caption.setAttribute("font-weight", "500");
|
|
103
|
+
caption.setAttribute("font-family", titleFont);
|
|
104
|
+
caption.textContent = title;
|
|
105
|
+
stage.append(caption);
|
|
106
|
+
}
|
|
107
|
+
const group = document.createElementNS(SVG_NS, "g");
|
|
108
|
+
group.setAttribute("transform", `translate(${padding}, ${padding + titleBand})`);
|
|
109
|
+
group.append(clone);
|
|
110
|
+
stage.append(group);
|
|
111
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
112
|
+
${new XMLSerializer().serializeToString(page)}`;
|
|
113
|
+
}
|
|
114
|
+
const MIME = {
|
|
115
|
+
png: "image/png",
|
|
116
|
+
jpeg: "image/jpeg",
|
|
117
|
+
webp: "image/webp"
|
|
118
|
+
};
|
|
119
|
+
async function rasterize(markup, format, options = {}) {
|
|
120
|
+
const { scale = 2, quality = 0.92, background } = options;
|
|
121
|
+
const url = URL.createObjectURL(new Blob([markup], { type: "image/svg+xml;charset=utf-8" }));
|
|
122
|
+
try {
|
|
123
|
+
const image = await loadImage(url);
|
|
124
|
+
const canvas = document.createElement("canvas");
|
|
125
|
+
canvas.width = Math.max(1, Math.ceil(image.width * scale));
|
|
126
|
+
canvas.height = Math.max(1, Math.ceil(image.height * scale));
|
|
127
|
+
const context = canvas.getContext("2d");
|
|
128
|
+
if (!context) throw new Error("rasterize: this browser gave back no 2D canvas context");
|
|
129
|
+
if (background) {
|
|
130
|
+
context.fillStyle = background;
|
|
131
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
132
|
+
}
|
|
133
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
134
|
+
return await canvasToBlob(canvas, MIME[format], quality);
|
|
135
|
+
} finally {
|
|
136
|
+
URL.revokeObjectURL(url);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function loadImage(url) {
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
const image = new Image();
|
|
142
|
+
image.addEventListener("load", () => resolve(image));
|
|
143
|
+
image.addEventListener(
|
|
144
|
+
"error",
|
|
145
|
+
() => reject(new Error("rasterize: the browser refused to decode the serialised artwork"))
|
|
146
|
+
);
|
|
147
|
+
image.src = url;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function canvasToBlob(canvas, mime, quality) {
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
try {
|
|
153
|
+
canvas.toBlob(
|
|
154
|
+
(blob) => {
|
|
155
|
+
if (blob) resolve(blob);
|
|
156
|
+
else reject(new Error(`rasterize: the canvas produced no ${mime} data`));
|
|
157
|
+
},
|
|
158
|
+
mime,
|
|
159
|
+
quality
|
|
160
|
+
);
|
|
161
|
+
} catch (cause) {
|
|
162
|
+
reject(
|
|
163
|
+
new Error(
|
|
164
|
+
"rasterize: the canvas is tainted, which means the artwork contains a cross-origin image",
|
|
165
|
+
{ cause }
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function downloadBlob(blob, filename) {
|
|
172
|
+
if (typeof document === "undefined") {
|
|
173
|
+
throw new Error("downloadBlob: needs a browser document \u2014 call it from an event handler");
|
|
174
|
+
}
|
|
175
|
+
const url = URL.createObjectURL(blob);
|
|
176
|
+
const anchor = document.createElement("a");
|
|
177
|
+
anchor.href = url;
|
|
178
|
+
anchor.download = filename;
|
|
179
|
+
anchor.rel = "noopener";
|
|
180
|
+
anchor.style.display = "none";
|
|
181
|
+
document.body.append(anchor);
|
|
182
|
+
anchor.click();
|
|
183
|
+
anchor.remove();
|
|
184
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
185
|
+
}
|
|
186
|
+
function exportFilename(title, extension, fallback = "figure") {
|
|
187
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
188
|
+
return `${slug || fallback}.${extension}`;
|
|
189
|
+
}
|
|
190
|
+
export {
|
|
191
|
+
downloadBlob,
|
|
192
|
+
exportFilename,
|
|
193
|
+
inlinePaint,
|
|
194
|
+
rasterize,
|
|
195
|
+
readToken,
|
|
196
|
+
serializeSvg
|
|
197
|
+
};
|
|
198
|
+
//# sourceMappingURL=svg-export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/svg-export.ts"],"sourcesContent":["/**\n * Taking an `<svg>` off the page as a file.\n *\n * Shared by two entries that both draw in SVG and both need to hand a reader a\n * picture of it: `@misoto22/folio/charts` and `@misoto22/folio/diagrams`.\n * Everything here is engine-agnostic — nothing knows what a series or a node\n * is — so the parts that DO know (which `<svg>` inside a wrapper is the plot,\n * what the file should be called, what sits behind it) stay with the caller.\n *\n * Nothing touches the DOM at module scope. Every browser API — `document`,\n * `canvas`, `URL.createObjectURL` — is reached for inside a function body, so\n * this module imports cleanly into a server component and only fails when a\n * consumer actually asks for a file on a server, which is a call that cannot\n * succeed anywhere.\n */\n\nconst SVG_NS = 'http://www.w3.org/2000/svg'\n\n/**\n * The properties copied from the live DOM onto the serialised clone.\n *\n * Paint, geometry-adjacent paint, and type. Layout is deliberately absent: an\n * SVG's geometry is in its attributes, which the clone already carries, and\n * copying computed `width`/`height`/`transform` onto every node would fight\n * them.\n */\nconst PAINTED_PROPERTIES = [\n 'display',\n 'visibility',\n 'opacity',\n 'color',\n 'fill',\n 'fill-opacity',\n 'fill-rule',\n 'stroke',\n 'stroke-opacity',\n 'stroke-width',\n 'stroke-linecap',\n 'stroke-linejoin',\n 'stroke-dasharray',\n 'stroke-dashoffset',\n 'stop-color',\n 'stop-opacity',\n 'flood-color',\n 'flood-opacity',\n 'marker-start',\n 'marker-mid',\n 'marker-end',\n 'mask',\n 'filter',\n 'clip-path',\n 'clip-rule',\n 'paint-order',\n 'mix-blend-mode',\n 'shape-rendering',\n 'text-anchor',\n 'dominant-baseline',\n 'font-family',\n 'font-size',\n 'font-style',\n 'font-weight',\n 'letter-spacing',\n 'text-transform',\n] as const\n\n/**\n * Copies the resolved paint of every node in `live` onto the matching node in\n * `clone`.\n *\n * ## The problem this function exists to solve\n *\n * Serialising an `<svg>` and handing it to an `<img>` puts it in a document of\n * its own. That document has none of the page's stylesheets, so every\n * `var(--ink)`, `var(--rule-2)` and `var(--diagram-node)` in the markup\n * resolves to nothing — and a paint that resolves to nothing is not a fallback\n * colour, it is an invisible mark. Serialise naively and you get an empty\n * rectangle.\n *\n * ## The solution\n *\n * Walk the live tree and the clone in step, and on every node write\n * `getComputedStyle`'s answer into an inline `style`. Computed values have\n * already had `var()` substituted by the engine, so `fill: var(--ink)` arrives\n * as `fill: rgb(16, 16, 16)` — and, because the walk reads the LIVE element, it\n * reads it under whichever theme is currently applied. Light and dark come out\n * right for free, with no theme argument anywhere in this file.\n *\n * `<stop>` and `<marker>` children are walked too, which is the half that is\n * easy to miss: a gradient's stops and an arrowhead's fill carry custom\n * properties of their own, and a `<defs>` that lost its paint takes every line\n * ending with it.\n */\nexport function inlinePaint(live: Element, clone: Element): void {\n const computed = getComputedStyle(live)\n let css = ''\n for (const property of PAINTED_PROPERTIES) {\n const value = computed.getPropertyValue(property)\n if (value) css += `${property}:${value};`\n }\n if (css) clone.setAttribute('style', css)\n\n // The clone is a faithful deep copy, so child lists line up by index.\n const children = live.children\n const clones = clone.children\n for (let index = 0; index < children.length; index += 1) {\n const liveChild = children[index]\n const cloneChild = clones[index]\n if (liveChild && cloneChild) inlinePaint(liveChild, cloneChild)\n }\n}\n\n/**\n * A custom property, resolved against the live element.\n *\n * `var()` inside a custom property is substituted at computed-value time, so\n * `--diagram-surface: var(--paper)` reads back as a colour. The guard is for\n * the case where it did not — an unregistered name, or a value the engine left\n * as a token stream — because writing `var(--paper)` into a serialised SVG\n * paints nothing at all.\n */\nexport function readToken(element: Element, name: string, fallback: string): string {\n const value = getComputedStyle(element).getPropertyValue(name).trim()\n return value && !value.includes('var(') ? value : fallback\n}\n\nexport interface SerializeOptions {\n /** Space around the artwork, in user units. Defaults to 16. */\n padding?: number\n /** Painted behind the artwork. Omit for a transparent ground. */\n background?: string\n /** A band above the artwork carrying this text. */\n title?: string\n /** Paint for the title. Defaults to `#101010`. */\n titleColor?: string\n /** Family for the title. Defaults to `sans-serif`. */\n titleFont?: string\n /**\n * Forces the output box, letterboxing the artwork inside it.\n *\n * This is what a share card is: a fixed 1200×630 frame that the diagram is\n * fitted into, rather than a frame the diagram sizes. Omit it and the output\n * is the artwork plus its padding.\n */\n frame?: { width: number; height: number }\n}\n\n/**\n * One standalone `<svg>` document: the element's own artwork, repainted with\n * resolved colours, on an optional plate, under an optional title.\n *\n * Returns markup rather than a blob because the SVG export wants the string\n * and every raster export wants it as a data source — one serialisation, four\n * file formats.\n *\n * ## What this cannot capture, honestly\n *\n * - **Anything drawn in HTML.** A toolbar, a tooltip, an inspector panel are\n * DOM, not SVG. The file is the artwork.\n * - **Web fonts.** The isolated document cannot fetch the page's `@font-face`\n * sources, so text is rasterised in whatever the resolved family stack finds\n * locally. Type metrics will differ from the screen.\n * - **Cross-origin images.** An `<image>` from another origin taints the canvas\n * and `toBlob` throws; there is no way around it from script.\n */\nexport function serializeSvg(svg: SVGSVGElement, options: SerializeOptions = {}): string {\n const { padding = 16, background, title, titleColor = '#101010', titleFont = 'sans-serif' } = options\n\n const box = svg.getBoundingClientRect()\n const width = Math.ceil(box.width) || Number(svg.getAttribute('width')) || 0\n const height = Math.ceil(box.height) || Number(svg.getAttribute('height')) || 0\n if (width === 0 || height === 0) {\n // Artwork that has not been measured yet has no size to export. Silently\n // producing a 0×0 file would look to a reader like a broken download\n // rather than a call made one frame too early.\n throw new Error('serializeSvg: the artwork has no measured size yet — export it after it renders')\n }\n\n const clone = svg.cloneNode(true) as SVGSVGElement\n inlinePaint(svg, clone)\n clone.setAttribute('width', String(width))\n clone.setAttribute('height', String(height))\n\n const titleBand = title ? 34 : 0\n const contentWidth = width + padding * 2\n const contentHeight = height + padding * 2 + titleBand\n\n // A frame letterboxes rather than stretches: a share card that squashed a\n // wide diagram to 1200×630 would be a picture of a different diagram.\n const totalWidth = options.frame?.width ?? contentWidth\n const totalHeight = options.frame?.height ?? contentHeight\n const scale = options.frame\n ? Math.min(totalWidth / contentWidth, totalHeight / contentHeight, 1)\n : 1\n const offsetX = (totalWidth - contentWidth * scale) / 2\n const offsetY = (totalHeight - contentHeight * scale) / 2\n\n const page = document.createElementNS(SVG_NS, 'svg')\n page.setAttribute('xmlns', SVG_NS)\n page.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink')\n page.setAttribute('width', String(totalWidth))\n page.setAttribute('height', String(totalHeight))\n page.setAttribute('viewBox', `0 0 ${totalWidth} ${totalHeight}`)\n\n if (background) {\n const plate = document.createElementNS(SVG_NS, 'rect')\n plate.setAttribute('width', String(totalWidth))\n plate.setAttribute('height', String(totalHeight))\n plate.setAttribute('fill', background)\n page.append(plate)\n }\n\n const stage = document.createElementNS(SVG_NS, 'g')\n stage.setAttribute('transform', `translate(${offsetX}, ${offsetY}) scale(${scale})`)\n page.append(stage)\n\n if (title) {\n const caption = document.createElementNS(SVG_NS, 'text')\n caption.setAttribute('x', String(padding))\n caption.setAttribute('y', String(padding + 15))\n caption.setAttribute('fill', titleColor)\n caption.setAttribute('font-size', '15')\n caption.setAttribute('font-weight', '500')\n caption.setAttribute('font-family', titleFont)\n caption.textContent = title\n stage.append(caption)\n }\n\n const group = document.createElementNS(SVG_NS, 'g')\n group.setAttribute('transform', `translate(${padding}, ${padding + titleBand})`)\n group.append(clone)\n stage.append(group)\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n${new XMLSerializer().serializeToString(page)}`\n}\n\n/** The raster formats a browser canvas can encode. */\nexport type RasterFormat = 'png' | 'jpeg' | 'webp'\n\nconst MIME: Record<RasterFormat, string> = {\n png: 'image/png',\n jpeg: 'image/jpeg',\n webp: 'image/webp',\n}\n\nexport interface RasterOptions {\n /** Multiplier on the on-screen size. 2 is a retina-sharp export. */\n scale?: number\n /** Encoder quality for `jpeg` and `webp`, 0–1. Ignored by `png`. */\n quality?: number\n /**\n * Painted behind the image, for a format with no alpha channel.\n *\n * JPEG has no transparency, so a diagram serialised without a plate lands on\n * black. Passing the page's own surface here is what makes the file look\n * like the screen rather than like a negative.\n */\n background?: string\n}\n\n/**\n * SVG markup, rasterised through a canvas.\n *\n * This is the browser's own SVG rasteriser re-drawing the markup, not a\n * screenshot: antialiasing, `backdrop-filter` and any effect a page stylesheet\n * applied from outside the `<svg>` are not in it.\n */\nexport async function rasterize(\n markup: string,\n format: RasterFormat,\n options: RasterOptions = {},\n): Promise<Blob> {\n const { scale = 2, quality = 0.92, background } = options\n\n const url = URL.createObjectURL(new Blob([markup], { type: 'image/svg+xml;charset=utf-8' }))\n try {\n const image = await loadImage(url)\n const canvas = document.createElement('canvas')\n canvas.width = Math.max(1, Math.ceil(image.width * scale))\n canvas.height = Math.max(1, Math.ceil(image.height * scale))\n\n const context = canvas.getContext('2d')\n if (!context) throw new Error('rasterize: this browser gave back no 2D canvas context')\n\n if (background) {\n context.fillStyle = background\n context.fillRect(0, 0, canvas.width, canvas.height)\n }\n context.drawImage(image, 0, 0, canvas.width, canvas.height)\n\n return await canvasToBlob(canvas, MIME[format], quality)\n } finally {\n URL.revokeObjectURL(url)\n }\n}\n\nfunction loadImage(url: string): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n const image = new Image()\n image.addEventListener('load', () => resolve(image))\n image.addEventListener('error', () =>\n reject(new Error('rasterize: the browser refused to decode the serialised artwork')),\n )\n image.src = url\n })\n}\n\nfunction canvasToBlob(canvas: HTMLCanvasElement, mime: string, quality: number): Promise<Blob> {\n return new Promise((resolve, reject) => {\n try {\n canvas.toBlob(\n (blob) => {\n if (blob) resolve(blob)\n else reject(new Error(`rasterize: the canvas produced no ${mime} data`))\n },\n mime,\n quality,\n )\n } catch (cause) {\n // A cross-origin image anywhere in the artwork taints the canvas and this\n // throws SecurityError. Nothing in script can untaint it, so the honest\n // move is to say which failure it was.\n reject(\n new Error(\n 'rasterize: the canvas is tainted, which means the artwork contains a cross-origin image',\n { cause },\n ),\n )\n }\n })\n}\n\n/**\n * Hands a blob to the browser as a download.\n *\n * Throws rather than no-oping without a `document`: a download is something a\n * person just asked for, and a silent return would look to them like a click\n * that did nothing.\n */\nexport function downloadBlob(blob: Blob, filename: string): void {\n if (typeof document === 'undefined') {\n throw new Error('downloadBlob: needs a browser document — call it from an event handler')\n }\n\n const url = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = filename\n anchor.rel = 'noopener'\n anchor.style.display = 'none'\n document.body.append(anchor)\n anchor.click()\n anchor.remove()\n\n // Revoking in the same tick cancels the download in Safari; one turn of the\n // event loop is enough for the navigation to have started.\n setTimeout(() => URL.revokeObjectURL(url), 0)\n}\n\n/**\n * A title as a filename.\n *\n * Lowercase, ASCII-safe and hyphenated, because a downloaded file crosses into\n * shells, zip archives and Windows paths where a title's spaces and slashes are\n * someone else's problem. A title with no ASCII in it at all — a Chinese one,\n * say — slugs to nothing, so the caller's fallback is what names the file.\n */\nexport function exportFilename(title: string, extension: string, fallback = 'figure'): string {\n const slug = title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n return `${slug || fallback}.${extension}`\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** How long a copy control confirms before it returns to its resting label. */
|
|
2
|
+
declare const COPIED_RESET_MS = 1600;
|
|
3
|
+
interface ClipboardCopy {
|
|
4
|
+
/** True from an accepted write until the confirmation window closes. */
|
|
5
|
+
copied: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Writes the text and resolves `true` only once the browser accepted it.
|
|
8
|
+
*
|
|
9
|
+
* A function is read at activation rather than at render, for a host that
|
|
10
|
+
* owns the rendered code; if it throws, that counts as a failed copy.
|
|
11
|
+
*/
|
|
12
|
+
copy: (source: string | (() => string)) => Promise<boolean>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The copy state every copy control in the package shares: CodeBlock's strip
|
|
16
|
+
* button and the website ClipboardButton.
|
|
17
|
+
*
|
|
18
|
+
* Internal on purpose. It is not a public export, because the public contract
|
|
19
|
+
* is the controls that use it, not a clipboard helper.
|
|
20
|
+
*
|
|
21
|
+
* A rejected write is handled, not swallowed: clipboard access is denied in an
|
|
22
|
+
* insecure context and inside some embeds, and the only honest reaction the
|
|
23
|
+
* control has is to stop claiming success. So a failure clears any confirmation
|
|
24
|
+
* still showing from an earlier copy, resolves `false` for the caller, and says
|
|
25
|
+
* why in development. The snippet stays selectable by hand, so there is nothing
|
|
26
|
+
* further to recover, and an unhandled rejection would take the page with it.
|
|
27
|
+
*/
|
|
28
|
+
declare function useClipboardCopy(component: string): ClipboardCopy;
|
|
29
|
+
|
|
30
|
+
export { COPIED_RESET_MS, type ClipboardCopy, useClipboardCopy };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { warn } from "./warn.js";
|
|
4
|
+
const COPIED_RESET_MS = 1600;
|
|
5
|
+
function useClipboardCopy(component) {
|
|
6
|
+
const [copied, setCopied] = useState(false);
|
|
7
|
+
const timer = useRef(void 0);
|
|
8
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
9
|
+
const copy = useCallback(
|
|
10
|
+
async (source) => {
|
|
11
|
+
clearTimeout(timer.current);
|
|
12
|
+
try {
|
|
13
|
+
await navigator.clipboard.writeText(typeof source === "function" ? source() : source);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
setCopied(false);
|
|
16
|
+
warn({
|
|
17
|
+
code: "CLIPBOARD_WRITE_REJECTED",
|
|
18
|
+
problem: `The browser refused the clipboard write (${error instanceof Error ? error.message : String(error)}), so ${component} kept its copy label instead of confirming.`,
|
|
19
|
+
field: `${component}.copy`,
|
|
20
|
+
fix: "Serve the page from a secure context (HTTPS or localhost) and allow clipboard-write in any embedding iframe.",
|
|
21
|
+
component
|
|
22
|
+
});
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
setCopied(true);
|
|
26
|
+
timer.current = setTimeout(() => setCopied(false), COPIED_RESET_MS);
|
|
27
|
+
return true;
|
|
28
|
+
},
|
|
29
|
+
[component]
|
|
30
|
+
);
|
|
31
|
+
return { copied, copy };
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
COPIED_RESET_MS,
|
|
35
|
+
useClipboardCopy
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=useClipboardCopy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/useClipboardCopy.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { warn } from './warn'\n\n/** How long a copy control confirms before it returns to its resting label. */\nexport const COPIED_RESET_MS = 1600\n\nexport interface ClipboardCopy {\n /** True from an accepted write until the confirmation window closes. */\n copied: boolean\n /**\n * Writes the text and resolves `true` only once the browser accepted it.\n *\n * A function is read at activation rather than at render, for a host that\n * owns the rendered code; if it throws, that counts as a failed copy.\n */\n copy: (source: string | (() => string)) => Promise<boolean>\n}\n\n/**\n * The copy state every copy control in the package shares: CodeBlock's strip\n * button and the website ClipboardButton.\n *\n * Internal on purpose. It is not a public export, because the public contract\n * is the controls that use it, not a clipboard helper.\n *\n * A rejected write is handled, not swallowed: clipboard access is denied in an\n * insecure context and inside some embeds, and the only honest reaction the\n * control has is to stop claiming success. So a failure clears any confirmation\n * still showing from an earlier copy, resolves `false` for the caller, and says\n * why in development. The snippet stays selectable by hand, so there is nothing\n * further to recover, and an unhandled rejection would take the page with it.\n */\nexport function useClipboardCopy(component: string): ClipboardCopy {\n const [copied, setCopied] = useState(false)\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n\n useEffect(() => () => clearTimeout(timer.current), [])\n\n const copy = useCallback(\n async (source: string | (() => string)) => {\n clearTimeout(timer.current)\n try {\n await navigator.clipboard.writeText(typeof source === 'function' ? source() : source)\n } catch (error) {\n setCopied(false)\n warn({\n code: 'CLIPBOARD_WRITE_REJECTED',\n problem: `The browser refused the clipboard write (${error instanceof Error ? error.message : String(error)}), so ${component} kept its copy label instead of confirming.`,\n field: `${component}.copy`,\n fix: 'Serve the page from a secure context (HTTPS or localhost) and allow clipboard-write in any embedding iframe.',\n component,\n })\n return false\n }\n setCopied(true)\n // Restarted on every accepted write, so a second copy gets its own full\n // confirmation window rather than the remainder of the first.\n timer.current = setTimeout(() => setCopied(false), COPIED_RESET_MS)\n return true\n },\n [component],\n )\n\n return { copied, copy }\n}\n"],"mappings":";AAEA,SAAS,aAAa,WAAW,QAAQ,gBAAgB;AACzD,SAAS,YAAY;AAGd,MAAM,kBAAkB;AA4BxB,SAAS,iBAAiB,WAAkC;AACjE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,QAAQ,OAAkD,MAAS;AAEzE,YAAU,MAAM,MAAM,aAAa,MAAM,OAAO,GAAG,CAAC,CAAC;AAErD,QAAM,OAAO;AAAA,IACX,OAAO,WAAoC;AACzC,mBAAa,MAAM,OAAO;AAC1B,UAAI;AACF,cAAM,UAAU,UAAU,UAAU,OAAO,WAAW,aAAa,OAAO,IAAI,MAAM;AAAA,MACtF,SAAS,OAAO;AACd,kBAAU,KAAK;AACf,aAAK;AAAA,UACH,MAAM;AAAA,UACN,SAAS,4CAA4C,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,SAAS,SAAS;AAAA,UAC7H,OAAO,GAAG,SAAS;AAAA,UACnB,KAAK;AAAA,UACL;AAAA,QACF,CAAC;AACD,eAAO;AAAA,MACT;AACA,gBAAU,IAAI;AAGd,YAAM,UAAU,WAAW,MAAM,UAAU,KAAK,GAAG,eAAe;AAClE,aAAO;AAAA,IACT;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAEA,SAAO,EAAE,QAAQ,KAAK;AACxB;","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
interface IndicatorStyle {
|
|
2
|
+
/** Distance from the container's inline start, in pixels. */
|
|
3
|
+
offset: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
top: number;
|
|
7
|
+
/** False until the first measurement, so the pill does not fly in from 0. */
|
|
8
|
+
ready: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Measures the selected child so a single pill can slide between options
|
|
12
|
+
* instead of one filled background switching off while another switches on.
|
|
13
|
+
*
|
|
14
|
+
* Why measure rather than animate each option's own background: a colour that
|
|
15
|
+
* cross-fades reads as two things changing, and a shape that travels reads as
|
|
16
|
+
* one thing moving — which is what actually happened. It is also the only way
|
|
17
|
+
* to get the movement onto the compositor, since `background-color` cannot be
|
|
18
|
+
* transformed.
|
|
19
|
+
*
|
|
20
|
+
* `useLayoutEffect` and not `useEffect`: the measurement has to land in the
|
|
21
|
+
* same frame as the render that caused it, or the pill visibly starts from its
|
|
22
|
+
* old place on first paint.
|
|
23
|
+
*
|
|
24
|
+
* Re-measures on resize and whenever `key` changes — `key` being whatever makes
|
|
25
|
+
* the selection different, usually the selected value.
|
|
26
|
+
*/
|
|
27
|
+
declare function useSelectionIndicator<T extends HTMLElement>(key: string | null): [React.RefObject<T | null>, IndicatorStyle];
|
|
28
|
+
|
|
29
|
+
export { type IndicatorStyle, useSelectionIndicator };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
function useSelectionIndicator(key) {
|
|
4
|
+
const containerRef = useRef(null);
|
|
5
|
+
const [style, setStyle] = useState({
|
|
6
|
+
offset: 0,
|
|
7
|
+
width: 0,
|
|
8
|
+
height: 0,
|
|
9
|
+
top: 0,
|
|
10
|
+
ready: false
|
|
11
|
+
});
|
|
12
|
+
const measure = useCallback(() => {
|
|
13
|
+
const container = containerRef.current;
|
|
14
|
+
const selected = container?.querySelector('[data-indicator-active="true"]');
|
|
15
|
+
if (!container || !selected) {
|
|
16
|
+
setStyle((previous) => ({ ...previous, ready: false }));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let offset = 0;
|
|
20
|
+
let top = 0;
|
|
21
|
+
for (let node = selected; node && node !== container; node = node.offsetParent) {
|
|
22
|
+
offset += node.offsetLeft;
|
|
23
|
+
top += node.offsetTop;
|
|
24
|
+
}
|
|
25
|
+
setStyle({
|
|
26
|
+
// Physical `left`, not `inline-start`: the pill is positioned with
|
|
27
|
+
// `transform`, which is physical, and the measurement has to match. It
|
|
28
|
+
// comes out right in both directions because both sides of the
|
|
29
|
+
// subtraction flip together.
|
|
30
|
+
offset,
|
|
31
|
+
width: selected.offsetWidth,
|
|
32
|
+
height: selected.offsetHeight,
|
|
33
|
+
top,
|
|
34
|
+
ready: true
|
|
35
|
+
});
|
|
36
|
+
}, []);
|
|
37
|
+
useLayoutEffect(() => {
|
|
38
|
+
measure();
|
|
39
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
40
|
+
const observer = new ResizeObserver(measure);
|
|
41
|
+
if (containerRef.current) observer.observe(containerRef.current);
|
|
42
|
+
return () => observer.disconnect();
|
|
43
|
+
}, [key, measure]);
|
|
44
|
+
return [containerRef, style];
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
useSelectionIndicator
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=useSelectionIndicator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/useSelectionIndicator.ts"],"sourcesContent":["'use client'\n\nimport { useCallback, useLayoutEffect, useRef, useState } from 'react'\n\nexport interface IndicatorStyle {\n /** Distance from the container's inline start, in pixels. */\n offset: number\n width: number\n height: number\n top: number\n /** False until the first measurement, so the pill does not fly in from 0. */\n ready: boolean\n}\n\n/**\n * Measures the selected child so a single pill can slide between options\n * instead of one filled background switching off while another switches on.\n *\n * Why measure rather than animate each option's own background: a colour that\n * cross-fades reads as two things changing, and a shape that travels reads as\n * one thing moving — which is what actually happened. It is also the only way\n * to get the movement onto the compositor, since `background-color` cannot be\n * transformed.\n *\n * `useLayoutEffect` and not `useEffect`: the measurement has to land in the\n * same frame as the render that caused it, or the pill visibly starts from its\n * old place on first paint.\n *\n * Re-measures on resize and whenever `key` changes — `key` being whatever makes\n * the selection different, usually the selected value.\n */\nexport function useSelectionIndicator<T extends HTMLElement>(\n key: string | null,\n): [React.RefObject<T | null>, IndicatorStyle] {\n const containerRef = useRef<T>(null)\n const [style, setStyle] = useState<IndicatorStyle>({\n offset: 0,\n width: 0,\n height: 0,\n top: 0,\n ready: false,\n })\n\n const measure = useCallback(() => {\n const container = containerRef.current\n const selected = container?.querySelector<HTMLElement>('[data-indicator-active=\"true\"]')\n if (!container || !selected) {\n setStyle((previous) => ({ ...previous, ready: false }))\n return\n }\n // `offsetLeft` and friends, not `getBoundingClientRect`.\n //\n // The rect is in VISUAL pixels: inside a zoomed or scaled ancestor — a\n // thumbnail, a device preview — it comes back multiplied, while the\n // `transform` this feeds is interpreted in the element's own coordinate\n // space. The pill then landed short of its segment by exactly the scale\n // factor. The offset properties are layout values and are unaffected.\n //\n // Offsets are relative to `offsetParent`, which is usually the strip itself\n // but need not be, so they are accumulated up to the container rather than\n // read once.\n let offset = 0\n let top = 0\n for (\n let node: HTMLElement | null = selected;\n node && node !== container;\n node = node.offsetParent as HTMLElement | null\n ) {\n offset += node.offsetLeft\n top += node.offsetTop\n }\n\n setStyle({\n // Physical `left`, not `inline-start`: the pill is positioned with\n // `transform`, which is physical, and the measurement has to match. It\n // comes out right in both directions because both sides of the\n // subtraction flip together.\n offset,\n width: selected.offsetWidth,\n height: selected.offsetHeight,\n top,\n ready: true,\n })\n }, [])\n\n useLayoutEffect(() => {\n measure()\n if (typeof ResizeObserver === 'undefined') return\n const observer = new ResizeObserver(measure)\n if (containerRef.current) observer.observe(containerRef.current)\n return () => observer.disconnect()\n }, [key, measure])\n\n return [containerRef, style]\n}\n"],"mappings":";AAEA,SAAS,aAAa,iBAAiB,QAAQ,gBAAgB;AA6BxD,SAAS,sBACd,KAC6C;AAC7C,QAAM,eAAe,OAAU,IAAI;AACnC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAyB;AAAA,IACjD,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,OAAO;AAAA,EACT,CAAC;AAED,QAAM,UAAU,YAAY,MAAM;AAChC,UAAM,YAAY,aAAa;AAC/B,UAAM,WAAW,WAAW,cAA2B,gCAAgC;AACvF,QAAI,CAAC,aAAa,CAAC,UAAU;AAC3B,eAAS,CAAC,cAAc,EAAE,GAAG,UAAU,OAAO,MAAM,EAAE;AACtD;AAAA,IACF;AAYA,QAAI,SAAS;AACb,QAAI,MAAM;AACV,aACM,OAA2B,UAC/B,QAAQ,SAAS,WACjB,OAAO,KAAK,cACZ;AACA,gBAAU,KAAK;AACf,aAAO,KAAK;AAAA,IACd;AAEA,aAAS;AAAA;AAAA;AAAA;AAAA;AAAA,MAKP;AAAA,MACA,OAAO,SAAS;AAAA,MAChB,QAAQ,SAAS;AAAA,MACjB;AAAA,MACA,OAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,kBAAgB,MAAM;AACpB,YAAQ;AACR,QAAI,OAAO,mBAAmB,YAAa;AAC3C,UAAM,WAAW,IAAI,eAAe,OAAO;AAC3C,QAAI,aAAa,QAAS,UAAS,QAAQ,aAAa,OAAO;AAC/D,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,KAAK,OAAO,CAAC;AAEjB,SAAO,CAAC,cAAc,KAAK;AAC7B;","names":[]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Development warnings, written for the reader most likely to hit them.
|
|
3
|
+
*
|
|
4
|
+
* The skill in this package documents a handful of ways to misuse a component
|
|
5
|
+
* that fail SILENTLY — a `Field` whose child is a wrapper wires no label, an
|
|
6
|
+
* icon-only `Button` with no accessible name renders perfectly and is invisible
|
|
7
|
+
* to a screen reader. Documentation is the wrong place for those on its own: it
|
|
8
|
+
* only helps a reader who went looking, and the whole problem is that nothing
|
|
9
|
+
* told them to look.
|
|
10
|
+
*
|
|
11
|
+
* So the component says it, at the moment it happens. The shape follows what an
|
|
12
|
+
* agent needs to repair its own call without asking anyone: a stable `code` it
|
|
13
|
+
* can branch on, the `field` that caused it, an imperative one-line `fix`, and
|
|
14
|
+
* where to get the rest. A message that only says "invalid props" makes an
|
|
15
|
+
* agent guess, and it guesses by trying things.
|
|
16
|
+
*
|
|
17
|
+
* Three constraints this has to keep:
|
|
18
|
+
*
|
|
19
|
+
* - **Dev only.** Every call site is behind `process.env.NODE_ENV`, which the
|
|
20
|
+
* consumer's bundler replaces and then drops. Nothing here reaches a
|
|
21
|
+
* production bundle.
|
|
22
|
+
* - **Once per problem.** React renders twice in StrictMode and again on every
|
|
23
|
+
* state change; a warning printed each time is noise that trains people to
|
|
24
|
+
* ignore it.
|
|
25
|
+
* - **Never throws.** A warning that breaks the render is worse than the bug it
|
|
26
|
+
* describes, and this runs during render.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* True outside a production build, and false when nothing said either way.
|
|
30
|
+
*
|
|
31
|
+
* Every bundler replaces `process.env.NODE_ENV` with a literal, which folds
|
|
32
|
+
* this to `false` and lets the guarded calls below be dropped entirely. Off is
|
|
33
|
+
* the right default for the case where nothing replaced it: a warning that
|
|
34
|
+
* reaches a user's console is worse than one an author never sees.
|
|
35
|
+
*/
|
|
36
|
+
declare const DEV: boolean;
|
|
37
|
+
interface Warning {
|
|
38
|
+
/** Stable identifier. Never reused for a different cause. */
|
|
39
|
+
code: string;
|
|
40
|
+
/** What went wrong, in one declarative sentence. */
|
|
41
|
+
problem: string;
|
|
42
|
+
/** The prop or child that caused it. */
|
|
43
|
+
field: string;
|
|
44
|
+
/** Imperative, and self-sufficient — assume nobody opens the docs. */
|
|
45
|
+
fix: string;
|
|
46
|
+
/** The component to ask about. */
|
|
47
|
+
component: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Print one warning, once.
|
|
51
|
+
*
|
|
52
|
+
* Guarded at the call site too, so a production bundle drops the whole
|
|
53
|
+
* expression rather than the branch inside it.
|
|
54
|
+
*/
|
|
55
|
+
declare function warn({ code, problem, field, fix, component }: Warning): void;
|
|
56
|
+
/** Test seam: the dedupe is global state, and a suite needs it reset. */
|
|
57
|
+
declare function resetWarnings(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Warn when a prop that is required for accessibility holds nothing usable.
|
|
60
|
+
*
|
|
61
|
+
* `Table.caption`, `Progress.label` and their siblings are required in the type
|
|
62
|
+
* precisely because forgetting them ships a control nobody can use without
|
|
63
|
+
* sight — and an empty string satisfies the type while satisfying nothing else.
|
|
64
|
+
* That is exactly the shape a model produces when it knows a prop is mandatory
|
|
65
|
+
* and has nothing to put in it.
|
|
66
|
+
*
|
|
67
|
+
* Not applied to `Avatar.alt`. An empty `alt` is the correct, deliberate markup
|
|
68
|
+
* for a decorative image, so warning on it would be a false positive on a
|
|
69
|
+
* pattern the platform endorses.
|
|
70
|
+
*/
|
|
71
|
+
declare function warnBlankName(component: string, field: string, value: unknown, announces: string): void;
|
|
72
|
+
|
|
73
|
+
export { DEV, resetWarnings, warn, warnBlankName };
|
package/dist/lib/warn.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const DEV = typeof process !== "undefined" && process?.env?.NODE_ENV !== "production";
|
|
2
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3
|
+
function warn({ code, problem, field, fix, component }) {
|
|
4
|
+
if (!DEV) return;
|
|
5
|
+
const key = `${code}:${field}`;
|
|
6
|
+
if (seen.has(key)) return;
|
|
7
|
+
seen.add(key);
|
|
8
|
+
console.warn(
|
|
9
|
+
[
|
|
10
|
+
`[@misoto22/folio] ${code}`,
|
|
11
|
+
` ${problem}`,
|
|
12
|
+
` field: ${field}`,
|
|
13
|
+
` fix: ${fix}`,
|
|
14
|
+
` docs: npx @misoto22/folio docs ${component}`
|
|
15
|
+
].join("\n")
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
function resetWarnings() {
|
|
19
|
+
seen.clear();
|
|
20
|
+
}
|
|
21
|
+
function warnBlankName(component, field, value, announces) {
|
|
22
|
+
if (!DEV) return;
|
|
23
|
+
if (typeof value !== "string" || value.trim() !== "") return;
|
|
24
|
+
warn({
|
|
25
|
+
code: "REQUIRED_NAME_BLANK",
|
|
26
|
+
problem: `${component}.${field} is an empty string, so ${announces}. The type is satisfied; nothing else is.`,
|
|
27
|
+
field: `${component}.${field}`,
|
|
28
|
+
fix: `Give ${field} the words a person would need to understand this without seeing it.`,
|
|
29
|
+
component
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
DEV,
|
|
34
|
+
resetWarnings,
|
|
35
|
+
warn,
|
|
36
|
+
warnBlankName
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=warn.js.map
|