decantr 0.9.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 +868 -0
- package/CHANGELOG.md +255 -0
- package/CLAUDE.md +178 -0
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/cli/art.js +127 -0
- package/cli/commands/a11y.js +61 -0
- package/cli/commands/audit.js +225 -0
- package/cli/commands/build.js +38 -0
- package/cli/commands/dev.js +18 -0
- package/cli/commands/doctor.js +197 -0
- package/cli/commands/figma-sync.js +48 -0
- package/cli/commands/figma-tokens.js +55 -0
- package/cli/commands/generate.js +26 -0
- package/cli/commands/init.js +116 -0
- package/cli/commands/lint.js +209 -0
- package/cli/commands/mcp.js +530 -0
- package/cli/commands/migrate.js +175 -0
- package/cli/commands/test.js +38 -0
- package/cli/commands/validate.js +354 -0
- package/cli/index.js +113 -0
- package/package.json +95 -0
- package/reference/atoms.md +517 -0
- package/reference/behaviors.md +384 -0
- package/reference/build-tooling.md +275 -0
- package/reference/color-guidelines.md +965 -0
- package/reference/component-lifecycle.md +137 -0
- package/reference/compound-spacing.md +95 -0
- package/reference/decantation-process.md +499 -0
- package/reference/dev-server-routes.md +93 -0
- package/reference/form-system.md +253 -0
- package/reference/i18n.md +336 -0
- package/reference/icons.md +576 -0
- package/reference/llm-primer.md +953 -0
- package/reference/plugins.md +252 -0
- package/reference/registry-consumption.md +76 -0
- package/reference/router.md +217 -0
- package/reference/shells.md +116 -0
- package/reference/spatial-guidelines.md +541 -0
- package/reference/ssr.md +234 -0
- package/reference/state-data.md +215 -0
- package/reference/state-patterns.md +166 -0
- package/reference/state.md +194 -0
- package/reference/style-system.md +110 -0
- package/reference/tokens.md +460 -0
- package/src/app.js +19 -0
- package/src/chart/_animate.js +266 -0
- package/src/chart/_base.js +109 -0
- package/src/chart/_data.js +209 -0
- package/src/chart/_format.js +106 -0
- package/src/chart/_interact.js +364 -0
- package/src/chart/_palette.js +105 -0
- package/src/chart/_renderer.js +52 -0
- package/src/chart/_scene.js +262 -0
- package/src/chart/_shared.js +371 -0
- package/src/chart/index.js +637 -0
- package/src/chart/layouts/_layout-base.js +328 -0
- package/src/chart/layouts/cartesian.js +148 -0
- package/src/chart/layouts/hierarchy.js +562 -0
- package/src/chart/layouts/polar.js +101 -0
- package/src/chart/renderers/canvas.js +179 -0
- package/src/chart/renderers/svg.js +256 -0
- package/src/chart/renderers/webgpu.js +715 -0
- package/src/chart/types/_type-base.js +26 -0
- package/src/chart/types/area.js +134 -0
- package/src/chart/types/bar.js +173 -0
- package/src/chart/types/box-plot.js +125 -0
- package/src/chart/types/bubble.js +63 -0
- package/src/chart/types/candlestick.js +115 -0
- package/src/chart/types/chord.js +85 -0
- package/src/chart/types/combination.js +108 -0
- package/src/chart/types/funnel.js +68 -0
- package/src/chart/types/gauge.js +163 -0
- package/src/chart/types/heatmap.js +98 -0
- package/src/chart/types/histogram.js +71 -0
- package/src/chart/types/line.js +111 -0
- package/src/chart/types/org-chart.js +93 -0
- package/src/chart/types/pie.js +81 -0
- package/src/chart/types/radar.js +96 -0
- package/src/chart/types/radial.js +68 -0
- package/src/chart/types/range-area.js +55 -0
- package/src/chart/types/range-bar.js +61 -0
- package/src/chart/types/sankey.js +73 -0
- package/src/chart/types/scatter.js +66 -0
- package/src/chart/types/sparkline.js +81 -0
- package/src/chart/types/sunburst.js +69 -0
- package/src/chart/types/swimlane.js +88 -0
- package/src/chart/types/treemap.js +62 -0
- package/src/chart/types/waterfall.js +100 -0
- package/src/components/_base.js +1658 -0
- package/src/components/_behaviors.js +1140 -0
- package/src/components/_primitives.js +534 -0
- package/src/components/_qr-encoder.js +539 -0
- package/src/components/accordion.js +207 -0
- package/src/components/affix.js +62 -0
- package/src/components/alert-dialog.js +75 -0
- package/src/components/alert.js +47 -0
- package/src/components/aspect-ratio.js +24 -0
- package/src/components/avatar-group.js +55 -0
- package/src/components/avatar.js +38 -0
- package/src/components/back-top.js +75 -0
- package/src/components/badge.js +74 -0
- package/src/components/banner.js +68 -0
- package/src/components/breadcrumb.js +162 -0
- package/src/components/button.js +115 -0
- package/src/components/calendar.js +131 -0
- package/src/components/card.js +192 -0
- package/src/components/carousel.js +98 -0
- package/src/components/cascader.js +261 -0
- package/src/components/checkbox.js +80 -0
- package/src/components/chip.js +81 -0
- package/src/components/code-block.js +82 -0
- package/src/components/collapsible.js +50 -0
- package/src/components/color-palette.js +438 -0
- package/src/components/color-picker.js +314 -0
- package/src/components/combobox.js +181 -0
- package/src/components/command.js +174 -0
- package/src/components/comment.js +206 -0
- package/src/components/context-menu.js +76 -0
- package/src/components/data-table.js +724 -0
- package/src/components/date-picker.js +217 -0
- package/src/components/date-range-picker.js +244 -0
- package/src/components/datetime-picker.js +271 -0
- package/src/components/descriptions.js +68 -0
- package/src/components/drawer.js +179 -0
- package/src/components/dropdown.js +88 -0
- package/src/components/empty.js +41 -0
- package/src/components/float-button.js +90 -0
- package/src/components/form.js +106 -0
- package/src/components/hover-card.js +49 -0
- package/src/components/icon.js +87 -0
- package/src/components/image.js +97 -0
- package/src/components/index.js +117 -0
- package/src/components/input-group.js +75 -0
- package/src/components/input-number.js +155 -0
- package/src/components/input-otp.js +178 -0
- package/src/components/input.js +91 -0
- package/src/components/kbd.js +36 -0
- package/src/components/label.js +25 -0
- package/src/components/list.js +118 -0
- package/src/components/masked-input.js +236 -0
- package/src/components/mentions.js +165 -0
- package/src/components/menu.js +259 -0
- package/src/components/message.js +80 -0
- package/src/components/modal.js +147 -0
- package/src/components/navigation-menu.js +166 -0
- package/src/components/notification.js +84 -0
- package/src/components/pagination.js +104 -0
- package/src/components/placeholder.js +132 -0
- package/src/components/popconfirm.js +70 -0
- package/src/components/popover.js +58 -0
- package/src/components/progress.js +61 -0
- package/src/components/qrcode.js +251 -0
- package/src/components/radiogroup.js +120 -0
- package/src/components/range-slider.js +176 -0
- package/src/components/rate.js +186 -0
- package/src/components/resizable.js +83 -0
- package/src/components/result.js +57 -0
- package/src/components/scroll-area.js +43 -0
- package/src/components/segmented.js +97 -0
- package/src/components/select.js +165 -0
- package/src/components/separator.js +31 -0
- package/src/components/shell.js +407 -0
- package/src/components/skeleton.js +39 -0
- package/src/components/slider.js +141 -0
- package/src/components/sortable-list.js +176 -0
- package/src/components/space.js +42 -0
- package/src/components/spinner.js +112 -0
- package/src/components/splitter.js +147 -0
- package/src/components/statistic.js +136 -0
- package/src/components/steps.js +99 -0
- package/src/components/switch.js +95 -0
- package/src/components/table.js +44 -0
- package/src/components/tabs.js +216 -0
- package/src/components/tag.js +115 -0
- package/src/components/textarea.js +82 -0
- package/src/components/time-picker.js +153 -0
- package/src/components/time-range-picker.js +170 -0
- package/src/components/timeline.js +226 -0
- package/src/components/toast.js +71 -0
- package/src/components/toggle.js +213 -0
- package/src/components/tooltip.js +57 -0
- package/src/components/tour.js +159 -0
- package/src/components/transfer.js +163 -0
- package/src/components/tree-select.js +274 -0
- package/src/components/tree.js +141 -0
- package/src/components/typography.js +136 -0
- package/src/components/upload.js +118 -0
- package/src/components/visually-hidden.js +20 -0
- package/src/components/watermark.js +124 -0
- package/src/core/index.js +539 -0
- package/src/core/lifecycle.js +69 -0
- package/src/css/atoms.js +651 -0
- package/src/css/components.js +940 -0
- package/src/css/derive.js +1296 -0
- package/src/css/index.js +265 -0
- package/src/css/runtime.js +268 -0
- package/src/css/styles/addons/bioluminescent.js +93 -0
- package/src/css/styles/addons/clay.js +70 -0
- package/src/css/styles/addons/clean.js +57 -0
- package/src/css/styles/addons/command-center.js +143 -0
- package/src/css/styles/addons/dopamine.js +83 -0
- package/src/css/styles/addons/editorial.js +80 -0
- package/src/css/styles/addons/glassmorphism.js +99 -0
- package/src/css/styles/addons/liquid-glass.js +105 -0
- package/src/css/styles/addons/prismatic.js +100 -0
- package/src/css/styles/addons/retro.js +63 -0
- package/src/css/styles/auradecantism.js +96 -0
- package/src/css/theme-registry.js +444 -0
- package/src/data/entity.js +281 -0
- package/src/data/index.js +13 -0
- package/src/data/persist.js +225 -0
- package/src/data/query.js +839 -0
- package/src/data/realtime.js +299 -0
- package/src/data/url.js +177 -0
- package/src/data/worker.js +134 -0
- package/src/explorer/archetypes.js +243 -0
- package/src/explorer/atoms.js +228 -0
- package/src/explorer/charts.js +497 -0
- package/src/explorer/components.js +129 -0
- package/src/explorer/foundations.js +949 -0
- package/src/explorer/icons.js +178 -0
- package/src/explorer/patterns.js +247 -0
- package/src/explorer/recipes.js +194 -0
- package/src/explorer/shared/pattern-examples.js +1337 -0
- package/src/explorer/shared/showcase-renderer.js +958 -0
- package/src/explorer/shared/spec-table.js +41 -0
- package/src/explorer/shared/usage-links.js +87 -0
- package/src/explorer/shell-config.js +10 -0
- package/src/explorer/shells.js +551 -0
- package/src/explorer/styles.js +161 -0
- package/src/explorer/tokens.js +262 -0
- package/src/explorer/tools.js +525 -0
- package/src/form/index.js +804 -0
- package/src/i18n/index.js +251 -0
- package/src/icons/essential.js +479 -0
- package/src/icons/index.js +53 -0
- package/src/plugins/index.js +282 -0
- package/src/registry/archetypes/content-site.json +71 -0
- package/src/registry/archetypes/docs-explorer.json +23 -0
- package/src/registry/archetypes/ecommerce.json +104 -0
- package/src/registry/archetypes/financial-dashboard.json +77 -0
- package/src/registry/archetypes/index.json +41 -0
- package/src/registry/archetypes/portfolio.json +82 -0
- package/src/registry/archetypes/recipe-community.json +159 -0
- package/src/registry/archetypes/saas-dashboard.json +86 -0
- package/src/registry/architect/cross-cutting.json +45 -0
- package/src/registry/architect/domains/ecommerce.json +294 -0
- package/src/registry/architect/domains/financial-services.json +302 -0
- package/src/registry/architect/index.json +26 -0
- package/src/registry/architect/traits.json +379 -0
- package/src/registry/atoms.json +16 -0
- package/src/registry/chart-showcase.json +160 -0
- package/src/registry/chart.json +136 -0
- package/src/registry/components.json +8616 -0
- package/src/registry/core.json +216 -0
- package/src/registry/css.json +319 -0
- package/src/registry/data.json +135 -0
- package/src/registry/foundations.json +11 -0
- package/src/registry/icons.json +463 -0
- package/src/registry/index.json +101 -0
- package/src/registry/patterns/activity-feed.json +37 -0
- package/src/registry/patterns/article-content.json +27 -0
- package/src/registry/patterns/auth-form.json +37 -0
- package/src/registry/patterns/author-card.json +20 -0
- package/src/registry/patterns/card-grid.json +127 -0
- package/src/registry/patterns/category-nav.json +26 -0
- package/src/registry/patterns/chart-grid.json +36 -0
- package/src/registry/patterns/chat-interface.json +37 -0
- package/src/registry/patterns/checklist-card.json +55 -0
- package/src/registry/patterns/comparison-panel.json +27 -0
- package/src/registry/patterns/component-showcase.json +24 -0
- package/src/registry/patterns/contact-form.json +31 -0
- package/src/registry/patterns/cta-section.json +20 -0
- package/src/registry/patterns/data-table.json +37 -0
- package/src/registry/patterns/detail-header.json +83 -0
- package/src/registry/patterns/detail-panel.json +27 -0
- package/src/registry/patterns/explorer-shell.json +22 -0
- package/src/registry/patterns/filter-bar.json +33 -0
- package/src/registry/patterns/filter-sidebar.json +27 -0
- package/src/registry/patterns/form-sections.json +110 -0
- package/src/registry/patterns/goal-tracker.json +27 -0
- package/src/registry/patterns/hero.json +107 -0
- package/src/registry/patterns/index.json +47 -0
- package/src/registry/patterns/kpi-grid.json +36 -0
- package/src/registry/patterns/media-gallery.json +20 -0
- package/src/registry/patterns/order-history.json +20 -0
- package/src/registry/patterns/pagination.json +19 -0
- package/src/registry/patterns/photo-to-recipe.json +36 -0
- package/src/registry/patterns/pipeline-tracker.json +28 -0
- package/src/registry/patterns/post-list.json +27 -0
- package/src/registry/patterns/pricing-table.json +32 -0
- package/src/registry/patterns/scorecard.json +28 -0
- package/src/registry/patterns/search-bar.json +20 -0
- package/src/registry/patterns/specimen-grid.json +19 -0
- package/src/registry/patterns/stat-card.json +55 -0
- package/src/registry/patterns/stats-bar.json +55 -0
- package/src/registry/patterns/steps-card.json +55 -0
- package/src/registry/patterns/table-of-contents.json +19 -0
- package/src/registry/patterns/testimonials.json +21 -0
- package/src/registry/patterns/timeline.json +27 -0
- package/src/registry/patterns/token-inspector.json +21 -0
- package/src/registry/patterns/wizard.json +27 -0
- package/src/registry/recipe-auradecantism.json +69 -0
- package/src/registry/recipe-clean.json +65 -0
- package/src/registry/recipe-command-center.json +78 -0
- package/src/registry/router.json +73 -0
- package/src/registry/schema/README.md +197 -0
- package/src/registry/skeletons.json +259 -0
- package/src/registry/state.json +137 -0
- package/src/registry/tokens.json +40 -0
- package/src/router/hash.js +17 -0
- package/src/router/history.js +18 -0
- package/src/router/index.js +598 -0
- package/src/ssr/index.js +922 -0
- package/src/state/arrays.js +181 -0
- package/src/state/devtools.js +647 -0
- package/src/state/index.js +498 -0
- package/src/state/middleware.js +288 -0
- package/src/state/scheduler.js +206 -0
- package/src/state/store.js +300 -0
- package/src/tags/index.js +19 -0
- package/src/tannins/auth.js +396 -0
- package/src/test/dom.js +352 -0
- package/src/test/index.js +62 -0
- package/src/test/state.js +306 -0
- package/tools/a11y-audit.js +487 -0
- package/tools/analyzer.js +315 -0
- package/tools/audit.js +706 -0
- package/tools/builder.js +1422 -0
- package/tools/css-extract.js +188 -0
- package/tools/dev-server.js +316 -0
- package/tools/dts-gen.js +1260 -0
- package/tools/figma-components.js +329 -0
- package/tools/figma-patterns.js +516 -0
- package/tools/figma-plugin/code.js +453 -0
- package/tools/figma-plugin/manifest.json +14 -0
- package/tools/figma-plugin/ui.html +268 -0
- package/tools/figma-render.js +293 -0
- package/tools/figma-tokens.js +712 -0
- package/tools/figma-upload.js +318 -0
- package/tools/generate.js +738 -0
- package/tools/icons.js +133 -0
- package/tools/init-templates.js +265 -0
- package/tools/install-hooks.sh +5 -0
- package/tools/migrations/0.5.0.js +53 -0
- package/tools/migrations/0.6.0.js +95 -0
- package/tools/minify.js +170 -0
- package/tools/pre-commit +4 -0
- package/tools/registry.js +662 -0
- package/tools/reset-playground.js +61 -0
- package/tools/starter-templates/content-site/app.js +49 -0
- package/tools/starter-templates/content-site/essence.js +19 -0
- package/tools/starter-templates/content-site/pages.js +31 -0
- package/tools/starter-templates/ecommerce/app.js +50 -0
- package/tools/starter-templates/ecommerce/essence.js +19 -0
- package/tools/starter-templates/ecommerce/pages.js +31 -0
- package/tools/starter-templates/landing-page/app.js +38 -0
- package/tools/starter-templates/landing-page/essence.js +18 -0
- package/tools/starter-templates/landing-page/pages.js +21 -0
- package/tools/starter-templates/portfolio/app.js +45 -0
- package/tools/starter-templates/portfolio/essence.js +19 -0
- package/tools/starter-templates/portfolio/pages.js +33 -0
- package/tools/starter-templates/saas-dashboard/app.js +70 -0
- package/tools/starter-templates/saas-dashboard/essence.js +19 -0
- package/tools/starter-templates/saas-dashboard/pages.js +31 -0
- package/tools/verify-pack.js +203 -0
- package/types/chart.d.ts +77 -0
- package/types/components.d.ts +587 -0
- package/types/core.d.ts +89 -0
- package/types/css.d.ts +149 -0
- package/types/data.d.ts +238 -0
- package/types/form.d.ts +164 -0
- package/types/i18n.d.ts +51 -0
- package/types/icons.d.ts +27 -0
- package/types/index.d.ts +13 -0
- package/types/router.d.ts +116 -0
- package/types/ssr.d.ts +102 -0
- package/types/state.d.ts +83 -0
- package/types/tags.d.ts +62 -0
- package/types/tannins.d.ts +63 -0
- package/types/test.d.ts +48 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watermark — Renders a repeating text/image watermark over content.
|
|
3
|
+
* Uses canvas to generate watermark pattern as background image.
|
|
4
|
+
*
|
|
5
|
+
* @module decantr/components/watermark
|
|
6
|
+
*/
|
|
7
|
+
import { h } from '../core/index.js';
|
|
8
|
+
import { injectBase, cx } from './_base.js';
|
|
9
|
+
import { getResolvedMode } from '../css/index.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} [props]
|
|
13
|
+
* @param {string} [props.content] - Watermark text
|
|
14
|
+
* @param {string[]} [props.content] - Multi-line text array
|
|
15
|
+
* @param {string} [props.image] - Image URL (takes precedence over text)
|
|
16
|
+
* @param {number} [props.rotate=-22] - Rotation angle in degrees
|
|
17
|
+
* @param {number} [props.fontSize=14]
|
|
18
|
+
* @param {string} [props.fontColor='rgba(0,0,0,0.1)']
|
|
19
|
+
* @param {number[]} [props.gap=[100,100]] - [horizontal, vertical] gap between marks
|
|
20
|
+
* @param {number[]} [props.offset=[0,0]] - [x, y] offset of first mark
|
|
21
|
+
* @param {number} [props.zIndex=9]
|
|
22
|
+
* @param {string} [props.class]
|
|
23
|
+
* @param {...Node} children - Content to watermark over
|
|
24
|
+
* @returns {HTMLElement}
|
|
25
|
+
*/
|
|
26
|
+
export function Watermark(props = {}, ...children) {
|
|
27
|
+
injectBase();
|
|
28
|
+
const {
|
|
29
|
+
content, image, rotate = -22, fontSize = 14,
|
|
30
|
+
fontColor = getResolvedMode() === 'dark' ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)',
|
|
31
|
+
gap = [100, 100], offset = [0, 0], zIndex = 9, class: cls
|
|
32
|
+
} = props;
|
|
33
|
+
|
|
34
|
+
const container = h('div', { class: cx('d-watermark', cls), style: { position: 'relative' } });
|
|
35
|
+
children.forEach(c => { if (c && c.nodeType) container.appendChild(c); });
|
|
36
|
+
|
|
37
|
+
const watermarkLayer = h('div', {
|
|
38
|
+
class: 'd-watermark-layer',
|
|
39
|
+
style: {
|
|
40
|
+
position: 'absolute',
|
|
41
|
+
inset: '0',
|
|
42
|
+
pointerEvents: 'none',
|
|
43
|
+
zIndex: String(zIndex),
|
|
44
|
+
overflow: 'hidden'
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
container.appendChild(watermarkLayer);
|
|
49
|
+
|
|
50
|
+
function render() {
|
|
51
|
+
if (typeof document === 'undefined') return;
|
|
52
|
+
|
|
53
|
+
const canvas = document.createElement('canvas');
|
|
54
|
+
const ctx = canvas.getContext('2d');
|
|
55
|
+
if (!ctx) return;
|
|
56
|
+
|
|
57
|
+
const texts = Array.isArray(content) ? content : (content ? [content] : []);
|
|
58
|
+
const lineHeight = fontSize * 1.5;
|
|
59
|
+
const textHeight = texts.length * lineHeight;
|
|
60
|
+
|
|
61
|
+
const markWidth = 120 + gap[0];
|
|
62
|
+
const markHeight = Math.max(textHeight, 40) + gap[1];
|
|
63
|
+
|
|
64
|
+
canvas.width = markWidth * 2;
|
|
65
|
+
canvas.height = markHeight * 2;
|
|
66
|
+
|
|
67
|
+
ctx.translate(canvas.width / 2, canvas.height / 2);
|
|
68
|
+
ctx.rotate((rotate * Math.PI) / 180);
|
|
69
|
+
ctx.translate(-canvas.width / 2, -canvas.height / 2);
|
|
70
|
+
|
|
71
|
+
if (image) {
|
|
72
|
+
const img = new Image();
|
|
73
|
+
img.crossOrigin = 'anonymous';
|
|
74
|
+
img.onload = () => {
|
|
75
|
+
const imgW = Math.min(img.width, markWidth - gap[0]);
|
|
76
|
+
const imgH = (img.height / img.width) * imgW;
|
|
77
|
+
ctx.drawImage(img, offset[0] + (markWidth - imgW) / 2, offset[1] + (markHeight - imgH) / 2, imgW, imgH);
|
|
78
|
+
applyPattern(canvas);
|
|
79
|
+
};
|
|
80
|
+
img.src = image;
|
|
81
|
+
} else if (texts.length) {
|
|
82
|
+
ctx.font = `${fontSize}px sans-serif`;
|
|
83
|
+
ctx.fillStyle = fontColor;
|
|
84
|
+
ctx.textAlign = 'center';
|
|
85
|
+
ctx.textBaseline = 'middle';
|
|
86
|
+
|
|
87
|
+
const cx0 = canvas.width / 2 + offset[0];
|
|
88
|
+
const cy0 = canvas.height / 2 + offset[1] - (textHeight / 2) + (lineHeight / 2);
|
|
89
|
+
|
|
90
|
+
texts.forEach((line, i) => {
|
|
91
|
+
ctx.fillText(line, cx0, cy0 + i * lineHeight);
|
|
92
|
+
});
|
|
93
|
+
applyPattern(canvas);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function applyPattern(canvas) {
|
|
98
|
+
const dataURL = canvas.toDataURL();
|
|
99
|
+
watermarkLayer.style.backgroundImage = `url(${dataURL})`;
|
|
100
|
+
watermarkLayer.style.backgroundRepeat = 'repeat';
|
|
101
|
+
watermarkLayer.style.backgroundSize = `${canvas.width / 2}px ${canvas.height / 2}px`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// MutationObserver to prevent removal of watermark layer
|
|
105
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
106
|
+
const observer = new MutationObserver((mutations) => {
|
|
107
|
+
for (const m of mutations) {
|
|
108
|
+
for (const removed of m.removedNodes) {
|
|
109
|
+
if (removed === watermarkLayer) {
|
|
110
|
+
container.appendChild(watermarkLayer);
|
|
111
|
+
render();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
observer.observe(container, { childList: true });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Initial render
|
|
121
|
+
requestAnimationFrame(render);
|
|
122
|
+
|
|
123
|
+
return container;
|
|
124
|
+
}
|
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import { createEffect } from '../state/index.js';
|
|
2
|
+
export { onMount, onDestroy } from './lifecycle.js';
|
|
3
|
+
import { drainMountQueue, drainDestroyQueue, pushScope, popScope, runDestroyFns } from './lifecycle.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} tag
|
|
7
|
+
* @param {Object|null} props
|
|
8
|
+
* @param {...(string|number|Node|Function)} children
|
|
9
|
+
* @returns {HTMLElement}
|
|
10
|
+
*/
|
|
11
|
+
export function h(tag, props, ...children) {
|
|
12
|
+
const el = document.createElement(tag);
|
|
13
|
+
|
|
14
|
+
if (props) {
|
|
15
|
+
for (const key in props) {
|
|
16
|
+
const val = props[key];
|
|
17
|
+
if (key.startsWith('on') && typeof val === 'function') {
|
|
18
|
+
el.addEventListener(key.slice(2).toLowerCase(), val);
|
|
19
|
+
} else if (typeof val === 'function' && key !== 'ref') {
|
|
20
|
+
createEffect(() => {
|
|
21
|
+
const v = val();
|
|
22
|
+
if (key === 'class' || key === 'className') {
|
|
23
|
+
el.className = v;
|
|
24
|
+
} else if (key === 'style' && typeof v === 'object') {
|
|
25
|
+
Object.assign(el.style, v);
|
|
26
|
+
} else {
|
|
27
|
+
el.setAttribute(key, v);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
} else if (key === 'ref' && typeof val === 'function') {
|
|
31
|
+
val(el);
|
|
32
|
+
} else if (key === 'class' || key === 'className') {
|
|
33
|
+
el.className = val;
|
|
34
|
+
} else if (key === 'style' && typeof val === 'object') {
|
|
35
|
+
Object.assign(el.style, val);
|
|
36
|
+
} else if (val !== false && val != null) {
|
|
37
|
+
el.setAttribute(key, val === true ? '' : String(val));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
appendChildren(el, children);
|
|
43
|
+
return el;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {Function} getter
|
|
48
|
+
* @returns {Text}
|
|
49
|
+
*/
|
|
50
|
+
export function text(getter) {
|
|
51
|
+
const node = document.createTextNode('');
|
|
52
|
+
createEffect(() => {
|
|
53
|
+
node.nodeValue = String(getter());
|
|
54
|
+
});
|
|
55
|
+
return node;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {Function} condition
|
|
60
|
+
* @param {Function} thenFn
|
|
61
|
+
* @param {Function} [elseFn]
|
|
62
|
+
* @returns {HTMLElement}
|
|
63
|
+
*/
|
|
64
|
+
export function cond(condition, thenFn, elseFn) {
|
|
65
|
+
const container = document.createElement('d-cond');
|
|
66
|
+
let currentNode = null;
|
|
67
|
+
|
|
68
|
+
createEffect(() => {
|
|
69
|
+
const result = condition();
|
|
70
|
+
if (currentNode) {
|
|
71
|
+
container.removeChild(currentNode);
|
|
72
|
+
currentNode = null;
|
|
73
|
+
}
|
|
74
|
+
const fn = result ? thenFn : elseFn;
|
|
75
|
+
if (fn) {
|
|
76
|
+
currentNode = fn();
|
|
77
|
+
if (currentNode) container.appendChild(currentNode);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return container;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {Function} itemsGetter
|
|
86
|
+
* @param {Function} keyFn
|
|
87
|
+
* @param {Function} renderFn
|
|
88
|
+
* @returns {HTMLElement}
|
|
89
|
+
*/
|
|
90
|
+
export function list(itemsGetter, keyFn, renderFn) {
|
|
91
|
+
const container = document.createElement('d-list');
|
|
92
|
+
/** @type {Map<*, {node: Node}>} */
|
|
93
|
+
let currentMap = new Map();
|
|
94
|
+
|
|
95
|
+
createEffect(() => {
|
|
96
|
+
const items = itemsGetter();
|
|
97
|
+
const newMap = new Map();
|
|
98
|
+
const newNodes = [];
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < items.length; i++) {
|
|
101
|
+
const item = items[i];
|
|
102
|
+
const key = keyFn(item, i);
|
|
103
|
+
const existing = currentMap.get(key);
|
|
104
|
+
|
|
105
|
+
if (existing) {
|
|
106
|
+
newMap.set(key, existing);
|
|
107
|
+
newNodes.push(existing.node);
|
|
108
|
+
} else {
|
|
109
|
+
const node = renderFn(item, i);
|
|
110
|
+
newMap.set(key, { node });
|
|
111
|
+
newNodes.push(node);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Remove nodes no longer in list
|
|
116
|
+
for (const [key, entry] of currentMap) {
|
|
117
|
+
if (!newMap.has(key) && entry.node.parentNode === container) {
|
|
118
|
+
container.removeChild(entry.node);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Append/reorder
|
|
123
|
+
for (let i = 0; i < newNodes.length; i++) {
|
|
124
|
+
const node = newNodes[i];
|
|
125
|
+
const current = container.childNodes[i];
|
|
126
|
+
if (node !== current) {
|
|
127
|
+
container.insertBefore(node, current || null);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
currentMap = newMap;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return container;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {HTMLElement} root
|
|
139
|
+
* @param {Function} component
|
|
140
|
+
*/
|
|
141
|
+
export function mount(root, component) {
|
|
142
|
+
pushScope();
|
|
143
|
+
const result = component();
|
|
144
|
+
const destroyFns = popScope();
|
|
145
|
+
if (result) root.appendChild(result);
|
|
146
|
+
// Flush mount queue
|
|
147
|
+
const fns = drainMountQueue();
|
|
148
|
+
for (const fn of fns) {
|
|
149
|
+
const cleanup = fn();
|
|
150
|
+
if (typeof cleanup === 'function') {
|
|
151
|
+
destroyFns.push(cleanup);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Store destroy fns for unmount
|
|
155
|
+
root.__d_destroy = destroyFns;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Unmount a previously mounted component tree.
|
|
160
|
+
* @param {HTMLElement} root
|
|
161
|
+
*/
|
|
162
|
+
export function unmount(root) {
|
|
163
|
+
if (root.__d_destroy) {
|
|
164
|
+
runDestroyFns(root.__d_destroy);
|
|
165
|
+
root.__d_destroy = null;
|
|
166
|
+
}
|
|
167
|
+
while (root.firstChild) root.removeChild(root.firstChild);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─── Dev-mode HMR remount hook ──────────────────────────────────
|
|
171
|
+
// Zero cost in production — entire block gated behind __DECANTR_DEV__
|
|
172
|
+
|
|
173
|
+
if (typeof globalThis !== 'undefined' && globalThis.__DECANTR_DEV__) {
|
|
174
|
+
// Map to track mounted roots by module path
|
|
175
|
+
const _hmrRoots = new Map();
|
|
176
|
+
|
|
177
|
+
globalThis.__d_hmr_remount = function(modulePath, newModule) {
|
|
178
|
+
// Find the mount root for this module
|
|
179
|
+
const root = _hmrRoots.get(modulePath);
|
|
180
|
+
if (root && newModule.default) {
|
|
181
|
+
unmount(root);
|
|
182
|
+
mount(root, newModule.default);
|
|
183
|
+
console.log('[decantr hmr] Updated:', modulePath);
|
|
184
|
+
} else if (newModule.default) {
|
|
185
|
+
// Try the main app root as fallback
|
|
186
|
+
const appRoot = document.getElementById('app');
|
|
187
|
+
if (appRoot) {
|
|
188
|
+
unmount(appRoot);
|
|
189
|
+
mount(appRoot, newModule.default);
|
|
190
|
+
console.log('[decantr hmr] Remounted app for:', modulePath);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Allow pages/components to register their mount root
|
|
196
|
+
globalThis.__d_hmr_register = function(modulePath, root) {
|
|
197
|
+
_hmrRoots.set(modulePath, root);
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ─── Error Telemetry ────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
/** @type {Function|null} */
|
|
204
|
+
let _errorHandler = null;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Set a global error telemetry handler.
|
|
208
|
+
* Called whenever an ErrorBoundary catches an error.
|
|
209
|
+
* @param {(info: { error: Error, component: string|null, stack: string|null, context: Object }) => void} fn
|
|
210
|
+
*/
|
|
211
|
+
export function setErrorHandler(fn) {
|
|
212
|
+
_errorHandler = typeof fn === 'function' ? fn : null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get the current error telemetry handler (for testing/internal use).
|
|
217
|
+
* @returns {Function|null}
|
|
218
|
+
*/
|
|
219
|
+
export function _getErrorHandler() { return _errorHandler; }
|
|
220
|
+
|
|
221
|
+
// ─── ErrorBoundary ──────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
/** @type {Function|null} */
|
|
224
|
+
let _boundaryHandler = null;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get the current boundary error handler (used by effect patching).
|
|
228
|
+
* @returns {Function|null}
|
|
229
|
+
*/
|
|
230
|
+
export function _getBoundaryHandler() { return _boundaryHandler; }
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Error catching component. Wraps children in try/catch during render.
|
|
234
|
+
* Also catches errors thrown inside createEffect within its subtree.
|
|
235
|
+
* @param {Object} props
|
|
236
|
+
* @param {Function} props.fallback — (error, retry) => Node
|
|
237
|
+
* @param {...Node} children
|
|
238
|
+
* @returns {HTMLElement}
|
|
239
|
+
*/
|
|
240
|
+
export function ErrorBoundary(props, ...children) {
|
|
241
|
+
const container = document.createElement('d-boundary');
|
|
242
|
+
let caught = null;
|
|
243
|
+
|
|
244
|
+
function clear() {
|
|
245
|
+
while (container.firstChild) container.removeChild(container.firstChild);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function showFallback(err) {
|
|
249
|
+
caught = err;
|
|
250
|
+
// Notify the global error telemetry handler
|
|
251
|
+
if (_errorHandler) {
|
|
252
|
+
try {
|
|
253
|
+
_errorHandler({
|
|
254
|
+
error: err instanceof Error ? err : new Error(String(err)),
|
|
255
|
+
component: props.name || null,
|
|
256
|
+
stack: err instanceof Error ? err.stack || null : null,
|
|
257
|
+
context: props.context || {}
|
|
258
|
+
});
|
|
259
|
+
} catch (_) {
|
|
260
|
+
// Telemetry handler must never break the boundary
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
clear();
|
|
264
|
+
const fb = props.fallback(err, retry);
|
|
265
|
+
if (fb) container.appendChild(fb);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function retry() {
|
|
269
|
+
caught = null;
|
|
270
|
+
clear();
|
|
271
|
+
renderChildren();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function renderChildren() {
|
|
275
|
+
const prev = _boundaryHandler;
|
|
276
|
+
_boundaryHandler = showFallback;
|
|
277
|
+
try {
|
|
278
|
+
appendChildren(container, children);
|
|
279
|
+
} catch (err) {
|
|
280
|
+
showFallback(err);
|
|
281
|
+
} finally {
|
|
282
|
+
_boundaryHandler = prev;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
renderChildren();
|
|
287
|
+
return container;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Patch createEffect to respect ErrorBoundary.
|
|
291
|
+
// We wrap the original effect factory so errors inside effects bubble to the nearest boundary.
|
|
292
|
+
const _origCreateEffect = createEffect;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Wrapped createEffect that routes errors to the nearest ErrorBoundary.
|
|
296
|
+
* Falls back to rethrowing if no boundary is active.
|
|
297
|
+
* @param {Function} fn
|
|
298
|
+
* @returns {Function} dispose
|
|
299
|
+
*/
|
|
300
|
+
function boundaryAwareEffect(fn) {
|
|
301
|
+
const handler = _boundaryHandler;
|
|
302
|
+
return _origCreateEffect(() => {
|
|
303
|
+
try {
|
|
304
|
+
return fn();
|
|
305
|
+
} catch (err) {
|
|
306
|
+
if (handler) handler(err);
|
|
307
|
+
else throw err;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Re-export the patched version for internal use in this module.
|
|
313
|
+
// External callers import createEffect from state/index.js directly;
|
|
314
|
+
// h() and text() in this file use the module-level import which we shadow below.
|
|
315
|
+
const _createEffect = boundaryAwareEffect;
|
|
316
|
+
|
|
317
|
+
// ─── Portal ─────────────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Render children outside the component tree into a target element.
|
|
321
|
+
* @param {Object} props
|
|
322
|
+
* @param {HTMLElement|string} [props.target] — Target element or CSS selector (defaults to document.body)
|
|
323
|
+
* @param {...Node} children
|
|
324
|
+
* @returns {Comment} — Placeholder comment node in the original tree
|
|
325
|
+
*/
|
|
326
|
+
export function Portal(props, ...children) {
|
|
327
|
+
const placeholder = document.createComment('d-portal');
|
|
328
|
+
const target = resolveTarget(props.target);
|
|
329
|
+
const nodes = [];
|
|
330
|
+
|
|
331
|
+
for (let i = 0; i < children.length; i++) {
|
|
332
|
+
const child = children[i];
|
|
333
|
+
if (child == null || child === false) continue;
|
|
334
|
+
if (child && typeof child === 'object' && child.nodeType) {
|
|
335
|
+
target.appendChild(child);
|
|
336
|
+
nodes.push(child);
|
|
337
|
+
} else if (typeof child === 'string' || typeof child === 'number') {
|
|
338
|
+
const tn = document.createTextNode(String(child));
|
|
339
|
+
target.appendChild(tn);
|
|
340
|
+
nodes.push(tn);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Cleanup: remove portal nodes when placeholder is disconnected
|
|
345
|
+
placeholder.__d_portal_cleanup = function () {
|
|
346
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
347
|
+
if (nodes[i].parentNode === target) target.removeChild(nodes[i]);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
return placeholder;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* @param {HTMLElement|string|undefined} target
|
|
356
|
+
* @returns {HTMLElement}
|
|
357
|
+
*/
|
|
358
|
+
function resolveTarget(target) {
|
|
359
|
+
if (!target) return document.body;
|
|
360
|
+
if (typeof target === 'string') {
|
|
361
|
+
const el = document.querySelector(target);
|
|
362
|
+
if (!el) throw new Error(`Portal target "${target}" not found`);
|
|
363
|
+
return el;
|
|
364
|
+
}
|
|
365
|
+
return target;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ─── Suspense ───────────────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
/** @type {Set<Promise>} */
|
|
371
|
+
export const _pendingQueries = new Set();
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Async boundary. Shows fallback while any createQuery() in children is loading.
|
|
375
|
+
* @param {Object} props
|
|
376
|
+
* @param {Function} props.fallback — () => Node (loading state)
|
|
377
|
+
* @param {...Node} children
|
|
378
|
+
* @returns {HTMLElement}
|
|
379
|
+
*/
|
|
380
|
+
export function Suspense(props, ...children) {
|
|
381
|
+
const container = document.createElement('d-suspense');
|
|
382
|
+
/** @type {Node[]} */
|
|
383
|
+
const childNodes = [];
|
|
384
|
+
let fallbackNode = null;
|
|
385
|
+
let showing = 'children'; // 'children' | 'fallback'
|
|
386
|
+
|
|
387
|
+
// Collect child nodes
|
|
388
|
+
const frag = document.createDocumentFragment();
|
|
389
|
+
appendChildren(frag, children);
|
|
390
|
+
while (frag.firstChild) {
|
|
391
|
+
childNodes.push(frag.firstChild);
|
|
392
|
+
frag.removeChild(frag.firstChild);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function showChildren() {
|
|
396
|
+
if (showing === 'children') return;
|
|
397
|
+
showing = 'children';
|
|
398
|
+
while (container.firstChild) container.removeChild(container.firstChild);
|
|
399
|
+
for (let i = 0; i < childNodes.length; i++) container.appendChild(childNodes[i]);
|
|
400
|
+
fallbackNode = null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function showFallback() {
|
|
404
|
+
if (showing === 'fallback') return;
|
|
405
|
+
showing = 'fallback';
|
|
406
|
+
while (container.firstChild) container.removeChild(container.firstChild);
|
|
407
|
+
fallbackNode = props.fallback();
|
|
408
|
+
if (fallbackNode) container.appendChild(fallbackNode);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Check pending queries and react
|
|
412
|
+
createEffect(() => {
|
|
413
|
+
if (_pendingQueries.size > 0) {
|
|
414
|
+
showFallback();
|
|
415
|
+
// Poll until resolved — queries are promise-based, so we check periodically
|
|
416
|
+
const iv = setInterval(() => {
|
|
417
|
+
if (_pendingQueries.size === 0) {
|
|
418
|
+
clearInterval(iv);
|
|
419
|
+
showChildren();
|
|
420
|
+
}
|
|
421
|
+
}, 16);
|
|
422
|
+
} else {
|
|
423
|
+
showChildren();
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Initial render: if nothing pending, show children right away
|
|
428
|
+
if (_pendingQueries.size === 0) {
|
|
429
|
+
for (let i = 0; i < childNodes.length; i++) container.appendChild(childNodes[i]);
|
|
430
|
+
showing = 'children';
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return container;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ─── Transition ─────────────────────────────────────────────────
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Enter/exit animation wrapper for conditional content.
|
|
440
|
+
* @param {Object} props
|
|
441
|
+
* @param {string} [props.enter] — CSS class to add on enter (e.g., 'd-fadein')
|
|
442
|
+
* @param {string} [props.exit] — CSS class to add on exit
|
|
443
|
+
* @param {number} [props.duration=200] — ms to wait before removing exit node
|
|
444
|
+
* @param {Function} child — getter function returning a Node or null
|
|
445
|
+
* @returns {HTMLElement}
|
|
446
|
+
*/
|
|
447
|
+
export function Transition(props, child) {
|
|
448
|
+
const container = document.createElement('d-transition');
|
|
449
|
+
const duration = props.duration != null ? props.duration : 200;
|
|
450
|
+
let currentNode = null;
|
|
451
|
+
let exitTimer = null;
|
|
452
|
+
|
|
453
|
+
createEffect(() => {
|
|
454
|
+
const next = child();
|
|
455
|
+
|
|
456
|
+
// Exit current node
|
|
457
|
+
if (currentNode && currentNode !== next) {
|
|
458
|
+
const leaving = currentNode;
|
|
459
|
+
currentNode = null;
|
|
460
|
+
|
|
461
|
+
if (props.exit) {
|
|
462
|
+
leaving.classList.add(props.exit);
|
|
463
|
+
if (exitTimer) clearTimeout(exitTimer);
|
|
464
|
+
exitTimer = setTimeout(() => {
|
|
465
|
+
if (leaving.parentNode === container) container.removeChild(leaving);
|
|
466
|
+
exitTimer = null;
|
|
467
|
+
}, duration);
|
|
468
|
+
} else {
|
|
469
|
+
if (leaving.parentNode === container) container.removeChild(leaving);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Enter new node
|
|
474
|
+
if (next && next !== currentNode) {
|
|
475
|
+
currentNode = next;
|
|
476
|
+
container.appendChild(next);
|
|
477
|
+
|
|
478
|
+
if (props.enter) {
|
|
479
|
+
next.classList.add(props.enter);
|
|
480
|
+
// Remove enter class after animation completes
|
|
481
|
+
const entering = next;
|
|
482
|
+
requestAnimationFrame(() => {
|
|
483
|
+
requestAnimationFrame(() => {
|
|
484
|
+
if (entering.parentNode === container) entering.classList.remove(props.enter);
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
return container;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ─── forwardRef ─────────────────────────────────────────────────
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Extract ref from props and pass it as second argument to a component.
|
|
498
|
+
* @param {Function} component — (props, ref) => Node
|
|
499
|
+
* @returns {Function} — (props) => Node
|
|
500
|
+
*/
|
|
501
|
+
export function forwardRef(component) {
|
|
502
|
+
return function (props, ...children) {
|
|
503
|
+
const ref = props && props.ref;
|
|
504
|
+
let cleaned = props;
|
|
505
|
+
if (ref !== undefined) {
|
|
506
|
+
cleaned = {};
|
|
507
|
+
for (const k in props) {
|
|
508
|
+
if (k !== 'ref') cleaned[k] = props[k];
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return component(cleaned, ref, ...children);
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ─── Internal helpers ───────────────────────────────────────────
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* @param {HTMLElement} el
|
|
519
|
+
* @param {Array} children
|
|
520
|
+
*/
|
|
521
|
+
function appendChildren(el, children) {
|
|
522
|
+
for (let i = 0; i < children.length; i++) {
|
|
523
|
+
const child = children[i];
|
|
524
|
+
if (child == null || child === false) continue;
|
|
525
|
+
if (Array.isArray(child)) {
|
|
526
|
+
appendChildren(el, child);
|
|
527
|
+
} else if (child && typeof child === 'object' && child.nodeType) {
|
|
528
|
+
el.appendChild(child);
|
|
529
|
+
} else if (typeof child === 'function') {
|
|
530
|
+
const textNode = document.createTextNode('');
|
|
531
|
+
createEffect(() => {
|
|
532
|
+
textNode.nodeValue = String(child());
|
|
533
|
+
});
|
|
534
|
+
el.appendChild(textNode);
|
|
535
|
+
} else {
|
|
536
|
+
el.appendChild(document.createTextNode(String(child)));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|