@svgrid/grid 3.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -88
- package/README.md +199 -199
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +181 -181
- package/dist/SvAutoComplete.svelte +169 -169
- package/dist/SvAvatar.svelte +75 -75
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +102 -102
- package/dist/SvCircularProgress.svelte +109 -109
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +279 -279
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +163 -163
- package/dist/SvDrawer.svelte +254 -254
- package/dist/SvDropDownList.svelte +378 -378
- package/dist/SvDurationInput.svelte +126 -126
- package/dist/SvField.svelte +293 -293
- package/dist/SvForm.svelte +437 -437
- package/dist/SvGridCellEditor.svelte +748 -744
- package/dist/SvGridChart.svelte +1716 -1716
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +728 -728
- package/dist/SvGridSelect.svelte +270 -270
- package/dist/SvGroupCell.svelte +116 -116
- package/dist/SvListBox.svelte +334 -334
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +293 -293
- package/dist/SvNumberInput.svelte +172 -172
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +133 -133
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvProgress.svelte +116 -116
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRating.svelte +112 -112
- package/dist/SvResult.svelte +73 -73
- package/dist/SvRichText.svelte +211 -211
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +203 -203
- package/dist/SvSwitchButton.svelte +108 -108
- package/dist/SvTagsInput.svelte +115 -115
- package/dist/SvTextInput.svelte +147 -147
- package/dist/SvTimePicker.svelte +245 -245
- package/dist/SvToaster.svelte +159 -159
- package/dist/SvToggleButton.svelte +85 -85
- package/dist/SvTooltip.svelte +161 -161
- package/dist/SvTour.svelte +208 -208
- package/dist/SvTree.svelte +444 -444
- package/dist/SvTreeSelect.svelte +239 -239
- package/dist/cdn/{GridMenus-DMihUtma.js → GridMenus-CYzOvmNQ.js} +3 -3
- package/dist/cdn/{GridMenus-nmNDj1a3.js → GridMenus-DFB6iwF8.js} +3 -3
- package/dist/cdn/{SvDateRangeInput-DflbiP7N.js → SvDateRangeInput-CaOuMs8O.js} +1 -1
- package/dist/cdn/{SvDateRangeInput-Bh0A0JkF.js → SvDateRangeInput-DMLKmGEc.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-Bivn8dAP.js → SvDateTimePicker-CCbDZNZB.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-BWwpfB_o.js → SvDateTimePicker-sonaH0oh.js} +1 -1
- package/dist/cdn/{SvGridCellEditor-Ba7rY3eu.js → SvGridCellEditor-BWnTi2N7.js} +2 -2
- package/dist/cdn/{SvGridCellEditor-AXL8cHGO.js → SvGridCellEditor-CjEJMJKc.js} +2 -2
- package/dist/cdn/{SvGridChart-CwhFz7GV.js → SvGridChart-BEJmNNx9.js} +1 -1
- package/dist/cdn/{SvGridChart-Bs2GIR2Q.js → SvGridChart-BwVos976.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-1WXuStzM.js → SvGridChartPanel-BOknOkrP.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-e5_nqNP9.js → SvGridChartPanel-D2PjII4O.js} +1 -1
- package/dist/cdn/{SvGridChartView-SmPW10dI.js → SvGridChartView-00LPUHL1.js} +1 -1
- package/dist/cdn/{SvGridChartView-eSPuJE6g.js → SvGridChartView-BhUEJ5ki.js} +1 -1
- package/dist/cdn/{SvGridDropdown-B1ZcLpgs.js → SvGridDropdown-D0VdjeR8.js} +1 -1
- package/dist/cdn/{SvGridDropdown-CtdbHcIS.js → SvGridDropdown-D13MtJ2j.js} +1 -1
- package/dist/cdn/{date-format-DZT7T1wf.js → date-format-BNii4zeD.js} +2 -2
- package/dist/cdn/{date-format-CcP1tafP.js → date-format-BnnHlqGw.js} +2 -2
- package/dist/cdn/{src-B_YS5AOc.js → src-C8QjSaq-.js} +42 -44
- package/dist/cdn/{src-DYXXpuSk.js → src-D0hO2eYN.js} +41 -43
- package/dist/cdn/svgrid.js +7 -7
- package/dist/cdn/svgrid.svelte-external.js +7 -7
- package/dist/chart-export.js +8 -8
- package/dist/editing.js +22 -13
- package/package.json +11 -11
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +181 -181
- package/src/SvAutoComplete.svelte +169 -169
- package/src/SvAvatar.svelte +75 -75
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +102 -102
- package/src/SvCircularProgress.svelte +109 -109
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +279 -279
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +163 -163
- package/src/SvDrawer.svelte +254 -254
- package/src/SvDropDownList.svelte +378 -378
- package/src/SvDurationInput.svelte +126 -126
- package/src/SvField.svelte +293 -293
- package/src/SvForm.svelte +437 -437
- package/src/SvForm.test.ts +411 -411
- package/src/SvGrid.types.ts +2092 -2092
- package/src/SvGridCellEditor.svelte +748 -744
- package/src/SvGridChart.svelte +1716 -1716
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +728 -728
- package/src/SvGridSelect.svelte +270 -270
- package/src/SvGroupCell.svelte +116 -116
- package/src/SvListBox.svelte +334 -334
- package/src/SvMaskedInput.svelte +122 -122
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +146 -146
- package/src/SvMultiSelect.svelte +293 -293
- package/src/SvNumberInput.svelte +172 -172
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +133 -133
- package/src/SvPopover.svelte +197 -197
- package/src/SvProgress.svelte +116 -116
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRating.svelte +112 -112
- package/src/SvResult.svelte +73 -73
- package/src/SvRichText.svelte +211 -211
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +203 -203
- package/src/SvSwitchButton.svelte +108 -108
- package/src/SvTagsInput.svelte +115 -115
- package/src/SvTextInput.svelte +147 -147
- package/src/SvTimePicker.svelte +245 -245
- package/src/SvToaster.svelte +159 -159
- package/src/SvToaster.test.ts +95 -95
- package/src/SvToggleButton.svelte +85 -85
- package/src/SvTooltip.svelte +161 -161
- package/src/SvTour.svelte +208 -208
- package/src/SvTree.svelte +444 -444
- package/src/SvTreeSelect.svelte +239 -239
- package/src/a11y/dismissable.test.ts +119 -119
- package/src/a11y/dismissable.ts +114 -114
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/ai.test.ts +502 -502
- package/src/ai.ts +1419 -1419
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +846 -846
- package/src/builtin-editors.grid.test.ts +83 -83
- package/src/cell-formatting.ts +171 -171
- package/src/cell-render.test.ts +513 -513
- package/src/cell-render.ts +496 -496
- package/src/cell-values.ts +148 -148
- package/src/chart-export.ts +202 -202
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2321 -2321
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/column-groups.ts +78 -78
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1865 -1865
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +191 -191
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +168 -168
- package/src/createForm.svelte.ts +386 -386
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createListbox.svelte.ts +250 -250
- package/src/createMenu.svelte.ts +224 -224
- package/src/createPopoverSelect.svelte.ts +213 -213
- package/src/createSlider.svelte.ts +191 -191
- package/src/createTooltip.svelte.ts +144 -144
- package/src/createTree.svelte.ts +322 -322
- package/src/datetime/date-core.ts +206 -206
- package/src/datetime/date-restrict.ts +61 -61
- package/src/datetime/timezone.ts +135 -135
- package/src/dock-manager-model.ts +596 -596
- package/src/dock-model.ts +374 -374
- package/src/editing.test.ts +974 -974
- package/src/editing.ts +618 -609
- package/src/editor-contract.ts +171 -171
- package/src/editor-registry.grid.test.ts +144 -144
- package/src/editor-registry.ts +122 -122
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +601 -601
- package/src/filter-operators.ts +160 -160
- package/src/filtering/excel-filters.ts +325 -325
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/group-display.test.ts +167 -167
- package/src/group-display.ts +200 -200
- package/src/headless.ts +87 -87
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard-handlers.ts +270 -270
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/list-option.test.ts +56 -56
- package/src/list-option.ts +179 -179
- package/src/menus.ts +597 -597
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/positioning.ts +268 -268
- package/src/render-component.ts +28 -28
- package/src/row-drag.test.ts +401 -401
- package/src/row-drag.ts +419 -419
- package/src/row-resize.test.ts +524 -524
- package/src/row-resize.ts +228 -228
- package/src/scheduler-ical.ts +181 -181
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +873 -873
- package/src/selection.test.ts +885 -885
- package/src/server-data-source.test.ts +383 -383
- package/src/server-data-source.ts +469 -469
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +488 -488
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/summaries.ts +113 -113
- package/src/svgrid-wrapper.types.ts +563 -563
- package/src/svgrid.async-editor-options.test.ts +273 -273
- package/src/svgrid.auto-row-height.test.ts +204 -204
- package/src/svgrid.behavior.test.ts +910 -910
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.comments-autocomplete.test.ts +127 -127
- package/src/svgrid.context-menu.test.ts +147 -147
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-depth.test.ts +163 -163
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +382 -377
- package/src/svgrid.filter-menu-scroll.test.ts +112 -112
- package/src/svgrid.grand-total.test.ts +188 -188
- package/src/svgrid.group-display-mode.test.ts +171 -171
- package/src/svgrid.group-footers.test.ts +121 -121
- package/src/svgrid.group-pagination.test.ts +153 -153
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.tree-data.test.ts +186 -186
- package/src/svgrid.wrapper.test.ts +63 -63
- package/src/svgriddropdown.async-panel.svelte.test.ts +195 -195
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +288 -288
- package/src/toast-store.svelte.ts +250 -250
- package/src/toast-store.test.ts +147 -147
- package/src/tree-row-model.test.ts +168 -168
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-localization.test.ts +113 -113
- package/src/ui-range.test.ts +70 -70
- package/src/ui-selection.test.ts +155 -155
- package/src/ui-tier1.test.ts +142 -142
- package/src/virtual.test.ts +88 -88
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +322 -322
package/src/positioning.ts
CHANGED
|
@@ -1,268 +1,268 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* positioning - a small, dependency-free anchor-positioning engine for the
|
|
3
|
-
* overlay layer (popover, tooltip, menu, dropdowns). It replaces the vertical-
|
|
4
|
-
* flip-only `anchoredRect` for components that need the full placement matrix.
|
|
5
|
-
*
|
|
6
|
-
* Two halves:
|
|
7
|
-
*
|
|
8
|
-
* 1. `computePosition(reference, floating, options)` - PURE geometry. Given the
|
|
9
|
-
* reference rect and the floating size (plus a viewport), it returns the
|
|
10
|
-
* floating `x`/`y`, the resolved `placement` (after flip), the arrow offset,
|
|
11
|
-
* and the available `maxWidth`/`maxHeight` on the chosen side. No DOM, so it
|
|
12
|
-
* is fully unit-tested by feeding rects and asserting placement.
|
|
13
|
-
*
|
|
14
|
-
* 2. `autoUpdate(referenceEl, floatingEl, update)` - the browser half. Re-runs
|
|
15
|
-
* `update` whenever the reference/floating resize, an ancestor scrolls, or the
|
|
16
|
-
* window resizes (via `ResizeObserver` + scroll/resize listeners). SSR-safe
|
|
17
|
-
* (a no-op without `window`).
|
|
18
|
-
*
|
|
19
|
-
* The middleware (flip / shift / size / arrow) is implemented inline - no
|
|
20
|
-
* Floating-UI dependency - matching the kit's zero-runtime-dep posture.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/** A physical side the floating element is placed on, relative to the reference. */
|
|
24
|
-
export type Side = 'top' | 'bottom' | 'left' | 'right'
|
|
25
|
-
|
|
26
|
-
/** Cross-axis alignment. `center` is the bare side (e.g. `'bottom'`). */
|
|
27
|
-
export type Align = 'start' | 'center' | 'end'
|
|
28
|
-
|
|
29
|
-
/** `'bottom'` = bottom-center; `'bottom-start'` / `'bottom-end'` align the edges. */
|
|
30
|
-
export type Placement = Side | `${Side}-start` | `${Side}-end`
|
|
31
|
-
|
|
32
|
-
/** A viewport-relative rectangle (CSS `getBoundingClientRect` shape suffices). */
|
|
33
|
-
export type Rect = { x: number; y: number; width: number; height: number }
|
|
34
|
-
|
|
35
|
-
/** The available viewport (defaults to `window` in the browser). */
|
|
36
|
-
export type Viewport = { width: number; height: number }
|
|
37
|
-
|
|
38
|
-
/** Where to put a floating element: its preferred side, offsets, and collision behaviour. */
|
|
39
|
-
export type ComputePositionOptions = {
|
|
40
|
-
/** Preferred placement. Default `'bottom-start'`. */
|
|
41
|
-
placement?: Placement
|
|
42
|
-
/** Main-axis gap between reference and floating. Default 6. */
|
|
43
|
-
offset?: number
|
|
44
|
-
/** Viewport edge kept clear on every side. Default 8. */
|
|
45
|
-
padding?: number
|
|
46
|
-
/** Flip to the opposite side when the preferred side overflows. Default true. */
|
|
47
|
-
flip?: boolean
|
|
48
|
-
/** Shift along the cross axis to stay in view (without flipping). Default true. */
|
|
49
|
-
shift?: boolean
|
|
50
|
-
/** Compute `maxWidth`/`maxHeight` for the chosen side. Default true. */
|
|
51
|
-
size?: boolean
|
|
52
|
-
/** When set, compute the arrow offset for an arrow element of this size (px). */
|
|
53
|
-
arrow?: { size: number }
|
|
54
|
-
/** Extra placements to try (after the automatic opposite) before giving up. */
|
|
55
|
-
fallbackPlacements?: Placement[]
|
|
56
|
-
/** Viewport to fit within. Defaults to the window; pure callers pass it. */
|
|
57
|
-
viewport?: Viewport
|
|
58
|
-
/** Floor for the available main-axis size returned by `size`. Default 0. */
|
|
59
|
-
minMainAxis?: number
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** The resolved position, including the side actually used after collision handling. */
|
|
63
|
-
export type ComputePositionResult = {
|
|
64
|
-
/** Floating left, in viewport (fixed-position) coordinates. */
|
|
65
|
-
x: number
|
|
66
|
-
/** Floating top, in viewport (fixed-position) coordinates. */
|
|
67
|
-
y: number
|
|
68
|
-
/** The resolved placement after flip. */
|
|
69
|
-
placement: Placement
|
|
70
|
-
/** The resolved side after flip. */
|
|
71
|
-
side: Side
|
|
72
|
-
/** The resolved cross-axis alignment. */
|
|
73
|
-
align: Align
|
|
74
|
-
/** Space available for the floating on the chosen side's main axis (+cross). */
|
|
75
|
-
maxWidth: number
|
|
76
|
-
maxHeight: number
|
|
77
|
-
/**
|
|
78
|
-
* Arrow offset within the floating box: `x` for top/bottom placements, `y` for
|
|
79
|
-
* left/right. Only present when `options.arrow` is set. Clamped so the arrow
|
|
80
|
-
* stays inside the floating (minus padding).
|
|
81
|
-
*/
|
|
82
|
-
arrow?: { x?: number; y?: number }
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const SIDES: Record<Side, Side> = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' }
|
|
86
|
-
|
|
87
|
-
/** Split a placement into its side + alignment (`center` when no suffix). */
|
|
88
|
-
export function parsePlacement(p: Placement): { side: Side; align: Align } {
|
|
89
|
-
const [side, align] = p.split('-') as [Side, 'start' | 'end' | undefined]
|
|
90
|
-
return { side, align: align ?? 'center' }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function opposite(p: Placement): Placement {
|
|
94
|
-
const { side, align } = parsePlacement(p)
|
|
95
|
-
return (align === 'center' ? SIDES[side] : `${SIDES[side]}-${align}`) as Placement
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function clamp(v: number, min: number, max: number): number {
|
|
99
|
-
if (max < min) return min
|
|
100
|
-
return v < min ? min : v > max ? max : v
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Floating x/y for a given side+align (before flip/shift). */
|
|
104
|
-
function coordsFor(side: Side, align: Align, r: Rect, f: Viewport, offset: number): { x: number; y: number } {
|
|
105
|
-
if (side === 'top' || side === 'bottom') {
|
|
106
|
-
const y = side === 'top' ? r.y - f.height - offset : r.y + r.height + offset
|
|
107
|
-
const x =
|
|
108
|
-
align === 'start' ? r.x : align === 'end' ? r.x + r.width - f.width : r.x + r.width / 2 - f.width / 2
|
|
109
|
-
return { x, y }
|
|
110
|
-
}
|
|
111
|
-
const x = side === 'left' ? r.x - f.width - offset : r.x + r.width + offset
|
|
112
|
-
const y =
|
|
113
|
-
align === 'start' ? r.y : align === 'end' ? r.y + r.height - f.height : r.y + r.height / 2 - f.height / 2
|
|
114
|
-
return { x, y }
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** How far the floating exceeds the viewport on the placement's MAIN axis. >0 = overflow. */
|
|
118
|
-
function mainAxisOverflow(side: Side, x: number, y: number, f: Viewport, v: Viewport, pad: number): number {
|
|
119
|
-
switch (side) {
|
|
120
|
-
case 'top':
|
|
121
|
-
return pad - y
|
|
122
|
-
case 'bottom':
|
|
123
|
-
return y + f.height - (v.height - pad)
|
|
124
|
-
case 'left':
|
|
125
|
-
return pad - x
|
|
126
|
-
default:
|
|
127
|
-
return x + f.width - (v.width - pad)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function resolveViewport(v?: Viewport): Viewport {
|
|
132
|
-
if (v) return v
|
|
133
|
-
if (typeof window !== 'undefined') return { width: window.innerWidth, height: window.innerHeight }
|
|
134
|
-
return { width: 0, height: 0 }
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Compute the floating position anchored to `reference`, applying flip (opposite
|
|
139
|
-
* side when the preferred side overflows), shift (cross-axis clamp to stay in
|
|
140
|
-
* view), size (available max width/height on the chosen side) and an optional
|
|
141
|
-
* arrow offset. Pure - pass an explicit `viewport` in tests.
|
|
142
|
-
*/
|
|
143
|
-
export function computePosition(
|
|
144
|
-
reference: Rect,
|
|
145
|
-
floating: Viewport,
|
|
146
|
-
options: ComputePositionOptions = {},
|
|
147
|
-
): ComputePositionResult {
|
|
148
|
-
const offset = options.offset ?? 6
|
|
149
|
-
const pad = options.padding ?? 8
|
|
150
|
-
const doFlip = options.flip !== false
|
|
151
|
-
const doShift = options.shift !== false
|
|
152
|
-
const v = resolveViewport(options.viewport)
|
|
153
|
-
const preferred = options.placement ?? 'bottom-start'
|
|
154
|
-
|
|
155
|
-
// ---- flip: pick the placement whose main-axis overflow is smallest ----
|
|
156
|
-
const candidates: Placement[] = [preferred]
|
|
157
|
-
if (doFlip) {
|
|
158
|
-
candidates.push(opposite(preferred))
|
|
159
|
-
for (const fb of options.fallbackPlacements ?? []) candidates.push(fb)
|
|
160
|
-
}
|
|
161
|
-
let chosen = preferred
|
|
162
|
-
let best = Infinity
|
|
163
|
-
for (const cand of candidates) {
|
|
164
|
-
const { side, align } = parsePlacement(cand)
|
|
165
|
-
const { x, y } = coordsFor(side, align, reference, floating, offset)
|
|
166
|
-
const overflow = mainAxisOverflow(side, x, y, floating, v, pad)
|
|
167
|
-
if (overflow <= 0) {
|
|
168
|
-
chosen = cand
|
|
169
|
-
best = overflow
|
|
170
|
-
break
|
|
171
|
-
}
|
|
172
|
-
if (overflow < best) {
|
|
173
|
-
best = overflow
|
|
174
|
-
chosen = cand
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const { side, align } = parsePlacement(chosen)
|
|
179
|
-
let { x, y } = coordsFor(side, align, reference, floating, offset)
|
|
180
|
-
|
|
181
|
-
// ---- shift: clamp along the CROSS axis so the floating stays in view ----
|
|
182
|
-
if (doShift) {
|
|
183
|
-
if (side === 'top' || side === 'bottom') {
|
|
184
|
-
x = clamp(x, pad, v.width - pad - floating.width)
|
|
185
|
-
} else {
|
|
186
|
-
y = clamp(y, pad, v.height - pad - floating.height)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ---- size: available room on the chosen side ----
|
|
191
|
-
const minMain = options.minMainAxis ?? 0
|
|
192
|
-
let maxWidth: number
|
|
193
|
-
let maxHeight: number
|
|
194
|
-
if (side === 'top' || side === 'bottom') {
|
|
195
|
-
const avail = side === 'top' ? reference.y - offset - pad : v.height - pad - (reference.y + reference.height + offset)
|
|
196
|
-
maxHeight = Math.max(minMain, Math.floor(avail))
|
|
197
|
-
maxWidth = Math.max(0, v.width - 2 * pad)
|
|
198
|
-
} else {
|
|
199
|
-
const avail = side === 'left' ? reference.x - offset - pad : v.width - pad - (reference.x + reference.width + offset)
|
|
200
|
-
maxWidth = Math.max(minMain, Math.floor(avail))
|
|
201
|
-
maxHeight = Math.max(0, v.height - 2 * pad)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const result: ComputePositionResult = { x, y, placement: chosen, side, align, maxWidth, maxHeight }
|
|
205
|
-
|
|
206
|
-
// ---- arrow: point at the reference center, clamped inside the floating ----
|
|
207
|
-
if (options.arrow) {
|
|
208
|
-
const a = options.arrow.size
|
|
209
|
-
if (side === 'top' || side === 'bottom') {
|
|
210
|
-
const center = reference.x + reference.width / 2
|
|
211
|
-
const ax = clamp(center - x - a / 2, pad, floating.width - a - pad)
|
|
212
|
-
result.arrow = { x: ax }
|
|
213
|
-
} else {
|
|
214
|
-
const center = reference.y + reference.height / 2
|
|
215
|
-
const ay = clamp(center - y - a / 2, pad, floating.height - a - pad)
|
|
216
|
-
result.arrow = { y: ay }
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return result
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/** Collect scrollable ancestors so `autoUpdate` can listen to their scroll. */
|
|
224
|
-
function scrollParents(node: Element): Element[] {
|
|
225
|
-
const out: Element[] = []
|
|
226
|
-
let el: Element | null = node.parentElement
|
|
227
|
-
while (el) {
|
|
228
|
-
const style = getComputedStyle(el)
|
|
229
|
-
if (/(auto|scroll|overlay|hidden)/.test(style.overflow + style.overflowY + style.overflowX)) out.push(el)
|
|
230
|
-
el = el.parentElement
|
|
231
|
-
}
|
|
232
|
-
return out
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Re-run `update` whenever the anchored position could change: the reference or
|
|
237
|
-
* floating element resizes, an ancestor scrolls, or the window scrolls/resizes.
|
|
238
|
-
* Returns a cleanup function. SSR-safe (a no-op without `window`).
|
|
239
|
-
*
|
|
240
|
-
* ```ts
|
|
241
|
-
* const stop = autoUpdate(triggerEl, panelEl, () => reposition())
|
|
242
|
-
* // ...later
|
|
243
|
-
* stop()
|
|
244
|
-
* ```
|
|
245
|
-
*/
|
|
246
|
-
export function autoUpdate(reference: Element, floating: HTMLElement, update: () => void): () => void {
|
|
247
|
-
if (typeof window === 'undefined') return () => {}
|
|
248
|
-
const ancestors = scrollParents(reference)
|
|
249
|
-
for (const a of ancestors) a.addEventListener('scroll', update, { passive: true })
|
|
250
|
-
window.addEventListener('scroll', update, { passive: true })
|
|
251
|
-
window.addEventListener('resize', update)
|
|
252
|
-
|
|
253
|
-
let ro: ResizeObserver | undefined
|
|
254
|
-
if (typeof ResizeObserver === 'function') {
|
|
255
|
-
ro = new ResizeObserver(update)
|
|
256
|
-
ro.observe(reference)
|
|
257
|
-
ro.observe(floating)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
update()
|
|
261
|
-
|
|
262
|
-
return () => {
|
|
263
|
-
for (const a of ancestors) a.removeEventListener('scroll', update)
|
|
264
|
-
window.removeEventListener('scroll', update)
|
|
265
|
-
window.removeEventListener('resize', update)
|
|
266
|
-
ro?.disconnect()
|
|
267
|
-
}
|
|
268
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* positioning - a small, dependency-free anchor-positioning engine for the
|
|
3
|
+
* overlay layer (popover, tooltip, menu, dropdowns). It replaces the vertical-
|
|
4
|
+
* flip-only `anchoredRect` for components that need the full placement matrix.
|
|
5
|
+
*
|
|
6
|
+
* Two halves:
|
|
7
|
+
*
|
|
8
|
+
* 1. `computePosition(reference, floating, options)` - PURE geometry. Given the
|
|
9
|
+
* reference rect and the floating size (plus a viewport), it returns the
|
|
10
|
+
* floating `x`/`y`, the resolved `placement` (after flip), the arrow offset,
|
|
11
|
+
* and the available `maxWidth`/`maxHeight` on the chosen side. No DOM, so it
|
|
12
|
+
* is fully unit-tested by feeding rects and asserting placement.
|
|
13
|
+
*
|
|
14
|
+
* 2. `autoUpdate(referenceEl, floatingEl, update)` - the browser half. Re-runs
|
|
15
|
+
* `update` whenever the reference/floating resize, an ancestor scrolls, or the
|
|
16
|
+
* window resizes (via `ResizeObserver` + scroll/resize listeners). SSR-safe
|
|
17
|
+
* (a no-op without `window`).
|
|
18
|
+
*
|
|
19
|
+
* The middleware (flip / shift / size / arrow) is implemented inline - no
|
|
20
|
+
* Floating-UI dependency - matching the kit's zero-runtime-dep posture.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** A physical side the floating element is placed on, relative to the reference. */
|
|
24
|
+
export type Side = 'top' | 'bottom' | 'left' | 'right'
|
|
25
|
+
|
|
26
|
+
/** Cross-axis alignment. `center` is the bare side (e.g. `'bottom'`). */
|
|
27
|
+
export type Align = 'start' | 'center' | 'end'
|
|
28
|
+
|
|
29
|
+
/** `'bottom'` = bottom-center; `'bottom-start'` / `'bottom-end'` align the edges. */
|
|
30
|
+
export type Placement = Side | `${Side}-start` | `${Side}-end`
|
|
31
|
+
|
|
32
|
+
/** A viewport-relative rectangle (CSS `getBoundingClientRect` shape suffices). */
|
|
33
|
+
export type Rect = { x: number; y: number; width: number; height: number }
|
|
34
|
+
|
|
35
|
+
/** The available viewport (defaults to `window` in the browser). */
|
|
36
|
+
export type Viewport = { width: number; height: number }
|
|
37
|
+
|
|
38
|
+
/** Where to put a floating element: its preferred side, offsets, and collision behaviour. */
|
|
39
|
+
export type ComputePositionOptions = {
|
|
40
|
+
/** Preferred placement. Default `'bottom-start'`. */
|
|
41
|
+
placement?: Placement
|
|
42
|
+
/** Main-axis gap between reference and floating. Default 6. */
|
|
43
|
+
offset?: number
|
|
44
|
+
/** Viewport edge kept clear on every side. Default 8. */
|
|
45
|
+
padding?: number
|
|
46
|
+
/** Flip to the opposite side when the preferred side overflows. Default true. */
|
|
47
|
+
flip?: boolean
|
|
48
|
+
/** Shift along the cross axis to stay in view (without flipping). Default true. */
|
|
49
|
+
shift?: boolean
|
|
50
|
+
/** Compute `maxWidth`/`maxHeight` for the chosen side. Default true. */
|
|
51
|
+
size?: boolean
|
|
52
|
+
/** When set, compute the arrow offset for an arrow element of this size (px). */
|
|
53
|
+
arrow?: { size: number }
|
|
54
|
+
/** Extra placements to try (after the automatic opposite) before giving up. */
|
|
55
|
+
fallbackPlacements?: Placement[]
|
|
56
|
+
/** Viewport to fit within. Defaults to the window; pure callers pass it. */
|
|
57
|
+
viewport?: Viewport
|
|
58
|
+
/** Floor for the available main-axis size returned by `size`. Default 0. */
|
|
59
|
+
minMainAxis?: number
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The resolved position, including the side actually used after collision handling. */
|
|
63
|
+
export type ComputePositionResult = {
|
|
64
|
+
/** Floating left, in viewport (fixed-position) coordinates. */
|
|
65
|
+
x: number
|
|
66
|
+
/** Floating top, in viewport (fixed-position) coordinates. */
|
|
67
|
+
y: number
|
|
68
|
+
/** The resolved placement after flip. */
|
|
69
|
+
placement: Placement
|
|
70
|
+
/** The resolved side after flip. */
|
|
71
|
+
side: Side
|
|
72
|
+
/** The resolved cross-axis alignment. */
|
|
73
|
+
align: Align
|
|
74
|
+
/** Space available for the floating on the chosen side's main axis (+cross). */
|
|
75
|
+
maxWidth: number
|
|
76
|
+
maxHeight: number
|
|
77
|
+
/**
|
|
78
|
+
* Arrow offset within the floating box: `x` for top/bottom placements, `y` for
|
|
79
|
+
* left/right. Only present when `options.arrow` is set. Clamped so the arrow
|
|
80
|
+
* stays inside the floating (minus padding).
|
|
81
|
+
*/
|
|
82
|
+
arrow?: { x?: number; y?: number }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const SIDES: Record<Side, Side> = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' }
|
|
86
|
+
|
|
87
|
+
/** Split a placement into its side + alignment (`center` when no suffix). */
|
|
88
|
+
export function parsePlacement(p: Placement): { side: Side; align: Align } {
|
|
89
|
+
const [side, align] = p.split('-') as [Side, 'start' | 'end' | undefined]
|
|
90
|
+
return { side, align: align ?? 'center' }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function opposite(p: Placement): Placement {
|
|
94
|
+
const { side, align } = parsePlacement(p)
|
|
95
|
+
return (align === 'center' ? SIDES[side] : `${SIDES[side]}-${align}`) as Placement
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function clamp(v: number, min: number, max: number): number {
|
|
99
|
+
if (max < min) return min
|
|
100
|
+
return v < min ? min : v > max ? max : v
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Floating x/y for a given side+align (before flip/shift). */
|
|
104
|
+
function coordsFor(side: Side, align: Align, r: Rect, f: Viewport, offset: number): { x: number; y: number } {
|
|
105
|
+
if (side === 'top' || side === 'bottom') {
|
|
106
|
+
const y = side === 'top' ? r.y - f.height - offset : r.y + r.height + offset
|
|
107
|
+
const x =
|
|
108
|
+
align === 'start' ? r.x : align === 'end' ? r.x + r.width - f.width : r.x + r.width / 2 - f.width / 2
|
|
109
|
+
return { x, y }
|
|
110
|
+
}
|
|
111
|
+
const x = side === 'left' ? r.x - f.width - offset : r.x + r.width + offset
|
|
112
|
+
const y =
|
|
113
|
+
align === 'start' ? r.y : align === 'end' ? r.y + r.height - f.height : r.y + r.height / 2 - f.height / 2
|
|
114
|
+
return { x, y }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** How far the floating exceeds the viewport on the placement's MAIN axis. >0 = overflow. */
|
|
118
|
+
function mainAxisOverflow(side: Side, x: number, y: number, f: Viewport, v: Viewport, pad: number): number {
|
|
119
|
+
switch (side) {
|
|
120
|
+
case 'top':
|
|
121
|
+
return pad - y
|
|
122
|
+
case 'bottom':
|
|
123
|
+
return y + f.height - (v.height - pad)
|
|
124
|
+
case 'left':
|
|
125
|
+
return pad - x
|
|
126
|
+
default:
|
|
127
|
+
return x + f.width - (v.width - pad)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function resolveViewport(v?: Viewport): Viewport {
|
|
132
|
+
if (v) return v
|
|
133
|
+
if (typeof window !== 'undefined') return { width: window.innerWidth, height: window.innerHeight }
|
|
134
|
+
return { width: 0, height: 0 }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Compute the floating position anchored to `reference`, applying flip (opposite
|
|
139
|
+
* side when the preferred side overflows), shift (cross-axis clamp to stay in
|
|
140
|
+
* view), size (available max width/height on the chosen side) and an optional
|
|
141
|
+
* arrow offset. Pure - pass an explicit `viewport` in tests.
|
|
142
|
+
*/
|
|
143
|
+
export function computePosition(
|
|
144
|
+
reference: Rect,
|
|
145
|
+
floating: Viewport,
|
|
146
|
+
options: ComputePositionOptions = {},
|
|
147
|
+
): ComputePositionResult {
|
|
148
|
+
const offset = options.offset ?? 6
|
|
149
|
+
const pad = options.padding ?? 8
|
|
150
|
+
const doFlip = options.flip !== false
|
|
151
|
+
const doShift = options.shift !== false
|
|
152
|
+
const v = resolveViewport(options.viewport)
|
|
153
|
+
const preferred = options.placement ?? 'bottom-start'
|
|
154
|
+
|
|
155
|
+
// ---- flip: pick the placement whose main-axis overflow is smallest ----
|
|
156
|
+
const candidates: Placement[] = [preferred]
|
|
157
|
+
if (doFlip) {
|
|
158
|
+
candidates.push(opposite(preferred))
|
|
159
|
+
for (const fb of options.fallbackPlacements ?? []) candidates.push(fb)
|
|
160
|
+
}
|
|
161
|
+
let chosen = preferred
|
|
162
|
+
let best = Infinity
|
|
163
|
+
for (const cand of candidates) {
|
|
164
|
+
const { side, align } = parsePlacement(cand)
|
|
165
|
+
const { x, y } = coordsFor(side, align, reference, floating, offset)
|
|
166
|
+
const overflow = mainAxisOverflow(side, x, y, floating, v, pad)
|
|
167
|
+
if (overflow <= 0) {
|
|
168
|
+
chosen = cand
|
|
169
|
+
best = overflow
|
|
170
|
+
break
|
|
171
|
+
}
|
|
172
|
+
if (overflow < best) {
|
|
173
|
+
best = overflow
|
|
174
|
+
chosen = cand
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const { side, align } = parsePlacement(chosen)
|
|
179
|
+
let { x, y } = coordsFor(side, align, reference, floating, offset)
|
|
180
|
+
|
|
181
|
+
// ---- shift: clamp along the CROSS axis so the floating stays in view ----
|
|
182
|
+
if (doShift) {
|
|
183
|
+
if (side === 'top' || side === 'bottom') {
|
|
184
|
+
x = clamp(x, pad, v.width - pad - floating.width)
|
|
185
|
+
} else {
|
|
186
|
+
y = clamp(y, pad, v.height - pad - floating.height)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ---- size: available room on the chosen side ----
|
|
191
|
+
const minMain = options.minMainAxis ?? 0
|
|
192
|
+
let maxWidth: number
|
|
193
|
+
let maxHeight: number
|
|
194
|
+
if (side === 'top' || side === 'bottom') {
|
|
195
|
+
const avail = side === 'top' ? reference.y - offset - pad : v.height - pad - (reference.y + reference.height + offset)
|
|
196
|
+
maxHeight = Math.max(minMain, Math.floor(avail))
|
|
197
|
+
maxWidth = Math.max(0, v.width - 2 * pad)
|
|
198
|
+
} else {
|
|
199
|
+
const avail = side === 'left' ? reference.x - offset - pad : v.width - pad - (reference.x + reference.width + offset)
|
|
200
|
+
maxWidth = Math.max(minMain, Math.floor(avail))
|
|
201
|
+
maxHeight = Math.max(0, v.height - 2 * pad)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const result: ComputePositionResult = { x, y, placement: chosen, side, align, maxWidth, maxHeight }
|
|
205
|
+
|
|
206
|
+
// ---- arrow: point at the reference center, clamped inside the floating ----
|
|
207
|
+
if (options.arrow) {
|
|
208
|
+
const a = options.arrow.size
|
|
209
|
+
if (side === 'top' || side === 'bottom') {
|
|
210
|
+
const center = reference.x + reference.width / 2
|
|
211
|
+
const ax = clamp(center - x - a / 2, pad, floating.width - a - pad)
|
|
212
|
+
result.arrow = { x: ax }
|
|
213
|
+
} else {
|
|
214
|
+
const center = reference.y + reference.height / 2
|
|
215
|
+
const ay = clamp(center - y - a / 2, pad, floating.height - a - pad)
|
|
216
|
+
result.arrow = { y: ay }
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return result
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Collect scrollable ancestors so `autoUpdate` can listen to their scroll. */
|
|
224
|
+
function scrollParents(node: Element): Element[] {
|
|
225
|
+
const out: Element[] = []
|
|
226
|
+
let el: Element | null = node.parentElement
|
|
227
|
+
while (el) {
|
|
228
|
+
const style = getComputedStyle(el)
|
|
229
|
+
if (/(auto|scroll|overlay|hidden)/.test(style.overflow + style.overflowY + style.overflowX)) out.push(el)
|
|
230
|
+
el = el.parentElement
|
|
231
|
+
}
|
|
232
|
+
return out
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Re-run `update` whenever the anchored position could change: the reference or
|
|
237
|
+
* floating element resizes, an ancestor scrolls, or the window scrolls/resizes.
|
|
238
|
+
* Returns a cleanup function. SSR-safe (a no-op without `window`).
|
|
239
|
+
*
|
|
240
|
+
* ```ts
|
|
241
|
+
* const stop = autoUpdate(triggerEl, panelEl, () => reposition())
|
|
242
|
+
* // ...later
|
|
243
|
+
* stop()
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
export function autoUpdate(reference: Element, floating: HTMLElement, update: () => void): () => void {
|
|
247
|
+
if (typeof window === 'undefined') return () => {}
|
|
248
|
+
const ancestors = scrollParents(reference)
|
|
249
|
+
for (const a of ancestors) a.addEventListener('scroll', update, { passive: true })
|
|
250
|
+
window.addEventListener('scroll', update, { passive: true })
|
|
251
|
+
window.addEventListener('resize', update)
|
|
252
|
+
|
|
253
|
+
let ro: ResizeObserver | undefined
|
|
254
|
+
if (typeof ResizeObserver === 'function') {
|
|
255
|
+
ro = new ResizeObserver(update)
|
|
256
|
+
ro.observe(reference)
|
|
257
|
+
ro.observe(floating)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
update()
|
|
261
|
+
|
|
262
|
+
return () => {
|
|
263
|
+
for (const a of ancestors) a.removeEventListener('scroll', update)
|
|
264
|
+
window.removeEventListener('scroll', update)
|
|
265
|
+
window.removeEventListener('resize', update)
|
|
266
|
+
ro?.disconnect()
|
|
267
|
+
}
|
|
268
|
+
}
|
package/src/render-component.ts
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import type { Component, ComponentProps, Snippet } from 'svelte'
|
|
2
|
-
|
|
3
|
-
export class RenderComponentConfig<TComponent extends Component> {
|
|
4
|
-
constructor(
|
|
5
|
-
public component: TComponent,
|
|
6
|
-
public props?: ComponentProps<TComponent> | Record<string, never>,
|
|
7
|
-
) {}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export class RenderSnippetConfig<TProps> {
|
|
11
|
-
constructor(
|
|
12
|
-
public snippet: Snippet<[TProps]>,
|
|
13
|
-
public params?: TProps,
|
|
14
|
-
) {}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const renderComponent = <
|
|
18
|
-
TComponent extends Component<any>,
|
|
19
|
-
TProps extends ComponentProps<TComponent>,
|
|
20
|
-
>(
|
|
21
|
-
component: TComponent,
|
|
22
|
-
props?: TProps,
|
|
23
|
-
) => new RenderComponentConfig(component, props)
|
|
24
|
-
|
|
25
|
-
export const renderSnippet = <TProps>(
|
|
26
|
-
snippet: Snippet<[TProps]>,
|
|
27
|
-
params?: TProps,
|
|
28
|
-
) => new RenderSnippetConfig(snippet, params)
|
|
1
|
+
import type { Component, ComponentProps, Snippet } from 'svelte'
|
|
2
|
+
|
|
3
|
+
export class RenderComponentConfig<TComponent extends Component> {
|
|
4
|
+
constructor(
|
|
5
|
+
public component: TComponent,
|
|
6
|
+
public props?: ComponentProps<TComponent> | Record<string, never>,
|
|
7
|
+
) {}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class RenderSnippetConfig<TProps> {
|
|
11
|
+
constructor(
|
|
12
|
+
public snippet: Snippet<[TProps]>,
|
|
13
|
+
public params?: TProps,
|
|
14
|
+
) {}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const renderComponent = <
|
|
18
|
+
TComponent extends Component<any>,
|
|
19
|
+
TProps extends ComponentProps<TComponent>,
|
|
20
|
+
>(
|
|
21
|
+
component: TComponent,
|
|
22
|
+
props?: TProps,
|
|
23
|
+
) => new RenderComponentConfig(component, props)
|
|
24
|
+
|
|
25
|
+
export const renderSnippet = <TProps>(
|
|
26
|
+
snippet: Snippet<[TProps]>,
|
|
27
|
+
params?: TProps,
|
|
28
|
+
) => new RenderSnippetConfig(snippet, params)
|