entasis 0.9.2 → 0.10.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/dist/components/AppShell/appShell.theme.js +3 -3
- package/dist/components/ButtonGroup/ButtonGroup.svelte +10 -4
- package/dist/components/ButtonGroup/buttonGroup.mcp.d.ts +1 -1
- package/dist/components/ButtonGroup/buttonGroup.mcp.js +23 -3
- package/dist/components/ButtonGroup/buttonGroup.props.d.ts +19 -5
- package/dist/components/Chart/Chart.svelte +1 -1
- package/dist/components/Chart/chart.cartesian.js +3 -2
- package/dist/components/Chart/chart.fit.d.ts +53 -0
- package/dist/components/Chart/chart.fit.js +81 -0
- package/dist/components/Chart/chart.mcp.d.ts +1 -1
- package/dist/components/Chart/chart.mcp.js +2 -1
- package/dist/components/Chart/chart.polar.js +137 -18
- package/dist/components/Chart/chart.polar.props.d.ts +5 -0
- package/dist/components/Chart/chart.proportion.js +12 -6
- package/dist/components/Chart/chart.relation.network.js +32 -4
- package/dist/components/Chart/chart.relation.sankey.js +53 -6
- package/dist/components/Chart/chart.relation.tree.js +84 -24
- package/dist/components/Chart/chart.series.props.d.ts +6 -0
- package/dist/components/Chart/chart.state.svelte.d.ts +1 -0
- package/dist/components/Chart/chart.state.svelte.js +9 -6
- package/dist/components/Chart/chart.viewport.svelte.d.ts +1 -0
- package/dist/components/Chart/chart.viewport.svelte.js +25 -7
- package/dist/components/DataTable/DataTable.svelte +25 -8
- package/dist/components/DataTable/DataTableRow.svelte +33 -10
- package/dist/components/DataTable/dataTable.mcp.d.ts +1 -1
- package/dist/components/DataTable/dataTable.mcp.js +12 -1
- package/dist/components/DataTable/dataTable.model.svelte.js +4 -3
- package/dist/components/DataTable/dataTable.props.d.ts +17 -0
- package/dist/components/DataTable/dataTable.theme.d.ts +12 -0
- package/dist/components/DataTable/dataTable.theme.js +3 -2
- package/dist/components/DataTable/index.d.ts +1 -1
- package/dist/components/Dialog/Dialog.svelte +12 -7
- package/dist/components/Dialog/dialog.state.svelte.d.ts +2 -1
- package/dist/components/Dialog/dialog.state.svelte.js +7 -7
- package/dist/components/Dialog/dialog.theme.js +1 -1
- package/dist/components/FloatingWindow/floatingWindow.theme.js +1 -1
- package/dist/components/Form/Select/Select.svelte +3 -1
- package/dist/components/PageShell/pageShell.state.svelte.d.ts +1 -1
- package/dist/components/PageShell/pageShell.state.svelte.js +6 -13
- package/dist/components/Popover/Popover.svelte +5 -5
- package/dist/components/Popover/index.d.ts +1 -1
- package/dist/components/Popover/popover.mcp.d.ts +1 -1
- package/dist/components/Popover/popover.mcp.js +33 -6
- package/dist/components/Popover/popover.state.svelte.d.ts +9 -1
- package/dist/components/Popover/popover.state.svelte.js +61 -8
- package/dist/components/Popover/popover.theme.js +1 -1
- package/dist/components/Sidebar/Sidebar.svelte +1 -1
- package/dist/components/Sidebar/SidebarDesktopShell.svelte +26 -6
- package/dist/components/Sidebar/sidebar-layout.js +7 -6
- package/dist/components/Sidebar/sidebar.mcp.d.ts +1 -1
- package/dist/components/Sidebar/sidebar.mcp.js +2 -2
- package/dist/components/Sidebar/sidebar.theme.js +36 -20
- package/dist/components/Theme/index.d.ts +1 -1
- package/dist/components/Theme/index.js +1 -1
- package/dist/components/Theme/theme.mcp.d.ts +1 -1
- package/dist/components/Theme/theme.mcp.js +3 -0
- package/dist/components/Theme/theme.state.svelte.d.ts +4 -0
- package/dist/components/Theme/theme.state.svelte.js +4 -0
- package/dist/components/Tooltip/Tooltip.svelte +2 -4
- package/dist/components/Tooltip/tooltip.attachment.svelte.js +5 -0
- package/dist/components/Tooltip/tooltip.mcp.d.ts +1 -1
- package/dist/components/Tooltip/tooltip.mcp.js +4 -2
- package/dist/generated/componentAliases.d.ts +1 -0
- package/dist/generated/componentAliases.js +1 -0
- package/dist/generated/componentContract.d.ts +13 -3
- package/dist/generated/componentContract.js +15 -1
- package/dist/generated/componentMcpRegistry.d.ts +8 -7
- package/dist/generated/componentMcpRegistry.js +2 -0
- package/dist/i18n/ar.js +1 -1
- package/dist/i18n/de.js +1 -1
- package/dist/i18n/en.js +1 -1
- package/dist/i18n/es.js +1 -1
- package/dist/i18n/fr.js +1 -1
- package/dist/i18n/pt.js +1 -1
- package/dist/i18n/zh.js +1 -1
- package/dist/tailwind/colors.d.ts +10 -3
- package/dist/tailwind/colors.js +6 -0
- package/dist/tailwind/palette.d.ts +5 -0
- package/dist/tailwind/palette.js +4 -0
- package/dist/tailwind/palette.mcp.d.ts +1 -0
- package/dist/tailwind/palette.mcp.js +34 -0
- package/dist/utils/layers.svelte.js +9 -8
- package/dist/utils/registry.svelte.d.ts +21 -0
- package/dist/utils/registry.svelte.js +49 -0
- package/package.json +8 -1
|
@@ -422,11 +422,12 @@ export class DataTableModel {
|
|
|
422
422
|
});
|
|
423
423
|
}
|
|
424
424
|
if (this.props.rowActions) {
|
|
425
|
+
const width = this.props.rowActionsWidth ?? 36;
|
|
425
426
|
definitions.push({
|
|
426
427
|
id: DATA_TABLE_ACTIONS_COLUMN,
|
|
427
|
-
size:
|
|
428
|
-
minSize:
|
|
429
|
-
maxSize:
|
|
428
|
+
size: width,
|
|
429
|
+
minSize: width,
|
|
430
|
+
maxSize: width,
|
|
430
431
|
enableHiding: false,
|
|
431
432
|
enablePinning: false,
|
|
432
433
|
enableResizing: false,
|
|
@@ -131,6 +131,10 @@ export type DataTableRowPayload<TData> = {
|
|
|
131
131
|
toggleSelected: () => void;
|
|
132
132
|
toggleExpanded: () => void;
|
|
133
133
|
};
|
|
134
|
+
export type DataTableRowActivation<TData> = DataTableRowPayload<TData> & {
|
|
135
|
+
/** The click or Enter key press that activated the row, for modifier keys. */
|
|
136
|
+
event: MouseEvent | KeyboardEvent;
|
|
137
|
+
};
|
|
134
138
|
export type DataTableCellPayload<TData, TValue = unknown> = DataTableRowPayload<TData> & {
|
|
135
139
|
columnId: string;
|
|
136
140
|
value: TValue;
|
|
@@ -275,6 +279,19 @@ type DataTableBaseProps<TData> = {
|
|
|
275
279
|
bulkActions?: Slot<DataTableToolbarPayload<TData>>;
|
|
276
280
|
/** Actions rendered for an individual row. */
|
|
277
281
|
rowActions?: Slot<DataTableRowPayload<TData>>;
|
|
282
|
+
/**
|
|
283
|
+
* Width of the row actions column, in pixels. The default fits one icon button; widen it for
|
|
284
|
+
* text buttons or several actions.
|
|
285
|
+
* @default 36
|
|
286
|
+
*/
|
|
287
|
+
rowActionsWidth?: number;
|
|
288
|
+
/**
|
|
289
|
+
* Called when a row is activated: clicked anywhere that is not a control inside it, or, in
|
|
290
|
+
* `grid` mode, given Enter on a cell with no editor or control. Rows show a pointer while
|
|
291
|
+
* it is set. In `table` mode rows are not focusable, so keep a link or row action for
|
|
292
|
+
* keyboard users.
|
|
293
|
+
*/
|
|
294
|
+
onRowActivate?: (payload: DataTableRowActivation<TData>) => void;
|
|
278
295
|
/** Additional content displayed below an expanded row. */
|
|
279
296
|
expandedContent?: Slot<DataTableRowPayload<TData>>;
|
|
280
297
|
/** Display the loading state while rows are being fetched. */
|
|
@@ -73,6 +73,10 @@ export declare const dataTableTheme: {
|
|
|
73
73
|
true: string;
|
|
74
74
|
false: string;
|
|
75
75
|
};
|
|
76
|
+
activatable: {
|
|
77
|
+
true: string;
|
|
78
|
+
false: string;
|
|
79
|
+
};
|
|
76
80
|
}> & import("../../utils/cva/types.js").CVAClassProp) | undefined) => string;
|
|
77
81
|
cell: (props?: (import("../../utils/cva/types.js").CVAVariantSchema<{
|
|
78
82
|
density: {
|
|
@@ -234,6 +238,10 @@ export declare const setDataTableTheme: (theme: InferComponentTheme<{
|
|
|
234
238
|
true: string;
|
|
235
239
|
false: string;
|
|
236
240
|
};
|
|
241
|
+
activatable: {
|
|
242
|
+
true: string;
|
|
243
|
+
false: string;
|
|
244
|
+
};
|
|
237
245
|
}> & import("../../utils/cva/types.js").CVAClassProp) | undefined) => string;
|
|
238
246
|
cell: (props?: (import("../../utils/cva/types.js").CVAVariantSchema<{
|
|
239
247
|
density: {
|
|
@@ -393,6 +401,10 @@ export declare const useDataTableTheme: import("../../utils/cva/theme.js").UseCo
|
|
|
393
401
|
true: string;
|
|
394
402
|
false: string;
|
|
395
403
|
};
|
|
404
|
+
activatable: {
|
|
405
|
+
true: string;
|
|
406
|
+
false: string;
|
|
407
|
+
};
|
|
396
408
|
}> & import("../../utils/cva/types.js").CVAClassProp) | undefined) => string;
|
|
397
409
|
cell: (props?: (import("../../utils/cva/types.js").CVAVariantSchema<{
|
|
398
410
|
density: {
|
|
@@ -113,9 +113,10 @@ const row = cva({
|
|
|
113
113
|
base: 'state-layer grid min-w-full border-b border-neutral-muted transition-colors last:border-b-0 data-[selected=true]:bg-selected-muted data-[selected=true]:text-selected-muted-readable',
|
|
114
114
|
variants: {
|
|
115
115
|
density: { compact: 'min-h-row-sm', normal: 'min-h-row-md', comfortable: 'min-h-row-lg' },
|
|
116
|
-
grouped: { true: 'bg-surface-raised font-medium', false: '' }
|
|
116
|
+
grouped: { true: 'bg-surface-raised font-medium', false: '' },
|
|
117
|
+
activatable: { true: 'cursor-pointer', false: '' }
|
|
117
118
|
},
|
|
118
|
-
defaultVariants: { density: 'normal', grouped: false }
|
|
119
|
+
defaultVariants: { density: 'normal', grouped: false, activatable: false }
|
|
119
120
|
});
|
|
120
121
|
const cell = cva({
|
|
121
122
|
base: 'relative flex min-w-0 items-center overflow-hidden border-neutral-muted outline-none',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { default as DataTable } from './DataTable.svelte';
|
|
2
2
|
export { createDataTableColumnHelper } from './dataTable.column-helper.js';
|
|
3
3
|
export { createDataTableState } from './dataTable.model.svelte.js';
|
|
4
|
-
export type { DataTableAggregation, DataTableAlignment, DataTableApi, DataTableBooleanFilter, DataTableBuiltInEditor, DataTableCellCommit, DataTableCellPayload, DataTableCellRenderPayload, DataTableColumn, DataTableColumnFilter, DataTableCustomEditor, DataTableCustomFilter, DataTableDateEditor, DataTableDateFilter, DataTableEditor, DataTableEditorPayload, DataTableFilter, DataTableFilterPayload, DataTableHeaderPayload, DataTableHeaderRenderPayload, DataTableInteractionMode, DataTableNumberEditor, DataTableNumberFilter, DataTableOption, DataTablePaginationConfig, DataTablePaginationState, DataTablePinning, DataTableProcessingMode, DataTableProps, DataTableRowPayload, DataTableSearchConfig, DataTableSelectionMode, DataTableSelectEditor, DataTableSelectFilter, DataTableSorting, DataTableState, DataTableSwitchEditor, DataTableTextEditor, DataTableTextFilter, DataTableToolbarPayload } from './dataTable.props.js';
|
|
4
|
+
export type { DataTableAggregation, DataTableAlignment, DataTableApi, DataTableBooleanFilter, DataTableBuiltInEditor, DataTableCellCommit, DataTableCellPayload, DataTableCellRenderPayload, DataTableColumn, DataTableColumnFilter, DataTableCustomEditor, DataTableCustomFilter, DataTableDateEditor, DataTableDateFilter, DataTableEditor, DataTableEditorPayload, DataTableFilter, DataTableFilterPayload, DataTableHeaderPayload, DataTableHeaderRenderPayload, DataTableInteractionMode, DataTableNumberEditor, DataTableNumberFilter, DataTableOption, DataTablePaginationConfig, DataTablePaginationState, DataTablePinning, DataTableProcessingMode, DataTableProps, DataTableRowActivation, DataTableRowPayload, DataTableSearchConfig, DataTableSelectionMode, DataTableSelectEditor, DataTableSelectFilter, DataTableSorting, DataTableState, DataTableSwitchEditor, DataTableTextEditor, DataTableTextFilter, DataTableToolbarPayload } from './dataTable.props.js';
|
|
5
5
|
export { dataTableTheme, setDataTableTheme, useDataTableTheme, type DataTableClasses, type DataTableTheme, type DataTableThemeProps } from './dataTable.theme.js';
|
|
6
6
|
export { dataTableDescription } from './dataTable.mcp.js';
|
|
@@ -104,7 +104,12 @@
|
|
|
104
104
|
</script>
|
|
105
105
|
|
|
106
106
|
{#snippet CLOSE_BUTTON()}
|
|
107
|
-
|
|
107
|
+
{#if closeButton}
|
|
108
|
+
<!-- The part positions a custom close button through this wrapper. -->
|
|
109
|
+
<Slot class={classes.closeButton({ size: dialog.computedSize })} render={closeButton} />
|
|
110
|
+
{:else}
|
|
111
|
+
<!-- The default button carries the part itself. Wrapped in the same classes, the empty
|
|
112
|
+
wrapper drew a dot at the corner when hovered (padding, rounded, state layer). -->
|
|
108
113
|
<Button
|
|
109
114
|
squared
|
|
110
115
|
class={classes.closeButton({ size: dialog.computedSize })}
|
|
@@ -115,7 +120,7 @@
|
|
|
115
120
|
>
|
|
116
121
|
{@render xIcon({ size: 20 })}
|
|
117
122
|
</Button>
|
|
118
|
-
|
|
123
|
+
{/if}
|
|
119
124
|
{/snippet}
|
|
120
125
|
|
|
121
126
|
{#if dialog.isOpen}
|
|
@@ -212,15 +217,15 @@
|
|
|
212
217
|
{#if typeof trigger === 'function'}
|
|
213
218
|
{@render trigger?.(dialog)}
|
|
214
219
|
{:else}
|
|
220
|
+
{@const { content, children, onclick, ...buttonProps } = trigger}
|
|
215
221
|
<Button
|
|
216
|
-
{...
|
|
222
|
+
{...buttonProps}
|
|
217
223
|
onclick={(event) => {
|
|
218
|
-
|
|
224
|
+
onclick?.(event);
|
|
219
225
|
dialog.open();
|
|
220
226
|
}}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
</Button>
|
|
227
|
+
children={children ?? content}
|
|
228
|
+
/>
|
|
224
229
|
{/if}
|
|
225
230
|
{/if}
|
|
226
231
|
|
|
@@ -10,8 +10,9 @@ interface DialogOptions extends MakeRequired<Pick<DialogProps, 'id' | 'type' | '
|
|
|
10
10
|
}
|
|
11
11
|
declare const DialogState_base: new (props: DialogOptions) => {} & DialogOptions;
|
|
12
12
|
export declare class DialogState extends DialogState_base {
|
|
13
|
+
#private;
|
|
13
14
|
parent: DialogState | null;
|
|
14
|
-
children: DialogState[];
|
|
15
|
+
get children(): readonly DialogState[];
|
|
15
16
|
hasTransitioned: boolean;
|
|
16
17
|
theme: import("../Theme/theme.state.svelte.js").ThemeState;
|
|
17
18
|
layer: import("../../utils/layers.svelte.js").LayerHandle;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createBindableStateClass } from '../../utils/state.svelte.js';
|
|
2
|
+
import { Registry } from '../../utils/registry.svelte.js';
|
|
2
3
|
import { createPointerDrag } from '../../utils/pointerDrag.js';
|
|
3
4
|
// import { useTheme } from '../../utils/theme.svelte.js';
|
|
4
5
|
import { getContext, onMount, setContext, untrack } from 'svelte';
|
|
@@ -8,7 +9,11 @@ import { useDialogMotion } from './dialog.theme.js';
|
|
|
8
9
|
export { DIALOG_Z_BASE, DIALOG_Z_STEP } from '../Theme/theme.layers.js';
|
|
9
10
|
export class DialogState extends createBindableStateClass() {
|
|
10
11
|
parent = getContext('dialog');
|
|
11
|
-
|
|
12
|
+
// Nested dialogs join while they mount and leave when torn down; see `Registry`.
|
|
13
|
+
#children = new Registry();
|
|
14
|
+
get children() {
|
|
15
|
+
return this.#children.items;
|
|
16
|
+
}
|
|
12
17
|
hasTransitioned = $state(false);
|
|
13
18
|
theme = useTheme();
|
|
14
19
|
// Registered in the shared layer stack, which owns Escape / outside-press dismissal,
|
|
@@ -68,12 +73,7 @@ export class DialogState extends createBindableStateClass() {
|
|
|
68
73
|
isActive: () => this.isTop,
|
|
69
74
|
inertSiblings: () => true
|
|
70
75
|
});
|
|
71
|
-
addChild = (child) => () =>
|
|
72
|
-
this.children.push(child);
|
|
73
|
-
return () => {
|
|
74
|
-
this.children = this.children.filter((d) => d.id !== child.id);
|
|
75
|
-
};
|
|
76
|
-
};
|
|
76
|
+
addChild = (child) => () => this.#children.add(child);
|
|
77
77
|
constructor(options) {
|
|
78
78
|
super(options);
|
|
79
79
|
setContext('dialog', this);
|
|
@@ -39,7 +39,7 @@ export const defaultDialogAlign = cva({
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
export const defaultDialogBackdrop = cva({
|
|
42
|
-
base: 'fixed inset-0 bg-
|
|
42
|
+
base: 'fixed inset-0 bg-black/40 backdrop-blur-xs dark:bg-black/60'
|
|
43
43
|
});
|
|
44
44
|
export const defaultDialogContent = cva({
|
|
45
45
|
base: 'z-10 relative px-xl py-md raised-xl h-fit bg-surface-floating text-neutral rounded-xl flex flex-col z-50 will-change-transform transition-transform duration-normal ease-standard',
|
|
@@ -145,7 +145,7 @@ const defaultFloatingWindowDockActions = cva({
|
|
|
145
145
|
// The page dim behind a window opened with `backdrop`: the same wash as Dialog's backdrop, so a
|
|
146
146
|
// modal window and a modal dialog dim the page alike.
|
|
147
147
|
const defaultFloatingWindowBackdrop = cva({
|
|
148
|
-
base: 'fixed inset-0 bg-
|
|
148
|
+
base: 'fixed inset-0 bg-black/40 backdrop-blur-xs dark:bg-black/60'
|
|
149
149
|
});
|
|
150
150
|
export const defaultFloatingWindowMotion = motion({
|
|
151
151
|
base: {
|
|
@@ -231,7 +231,9 @@
|
|
|
231
231
|
aria-activedescendant={select.isOpen ? select.nav.activeDescendant : undefined}
|
|
232
232
|
aria-required={required || undefined}
|
|
233
233
|
aria-label={triggerAttrs?.['aria-label'] ??
|
|
234
|
-
(label
|
|
234
|
+
(label || triggerAttrs?.['aria-labelledby']
|
|
235
|
+
? undefined
|
|
236
|
+
: (placeholder ?? t.selectOption))}
|
|
235
237
|
data-placeholder={select.selectedOption ? undefined : ''}
|
|
236
238
|
disabled={field.disabled}
|
|
237
239
|
class={classes.input({ size, disabled: field.disabled })}
|
|
@@ -3,8 +3,8 @@ type PageShellStateOptions = Readonly<PageShellConfig> & {
|
|
|
3
3
|
readonly isContentScrolled?: boolean;
|
|
4
4
|
};
|
|
5
5
|
export declare class PageShellState {
|
|
6
|
+
#private;
|
|
6
7
|
private options;
|
|
7
|
-
private overrides;
|
|
8
8
|
readonly api: PageShellApi;
|
|
9
9
|
constructor(options: PageShellStateOptions);
|
|
10
10
|
get current(): PageShellConfig;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { getContext, onDestroy, setContext } from 'svelte';
|
|
2
|
+
import { Registry } from '../../utils/registry.svelte.js';
|
|
2
3
|
const PAGE_SHELL_CONTEXT = Symbol('page-shell');
|
|
3
4
|
export class PageShellState {
|
|
4
5
|
options;
|
|
5
|
-
|
|
6
|
+
// Pages join on mount and leave on navigation; see `Registry`.
|
|
7
|
+
#overrides = new Registry();
|
|
6
8
|
api;
|
|
7
9
|
constructor(options) {
|
|
8
10
|
this.options = options;
|
|
@@ -94,7 +96,7 @@ export class PageShellState {
|
|
|
94
96
|
contentWidth: this.options.contentWidth,
|
|
95
97
|
actionOverflow: this.options.actionOverflow,
|
|
96
98
|
mobileActionCount: this.options.mobileActionCount
|
|
97
|
-
}, ...this.
|
|
99
|
+
}, ...this.#overrides.items);
|
|
98
100
|
}
|
|
99
101
|
get hasHeader() {
|
|
100
102
|
const current = this.current;
|
|
@@ -110,16 +112,7 @@ export class PageShellState {
|
|
|
110
112
|
const current = this.current;
|
|
111
113
|
return Boolean(current.footer || current.footerActions);
|
|
112
114
|
}
|
|
113
|
-
set = (config) =>
|
|
114
|
-
this.overrides = [...this.overrides, config];
|
|
115
|
-
let isActive = true;
|
|
116
|
-
return () => {
|
|
117
|
-
if (!isActive)
|
|
118
|
-
return;
|
|
119
|
-
isActive = false;
|
|
120
|
-
this.overrides = this.overrides.filter((override) => override !== config);
|
|
121
|
-
};
|
|
122
|
-
};
|
|
115
|
+
set = (config) => this.#overrides.add(config);
|
|
123
116
|
setEyebrow = (eyebrow) => this.set({ eyebrow });
|
|
124
117
|
setBreadcrumbs = (breadcrumbs) => this.set({ breadcrumbs });
|
|
125
118
|
setBack = (back) => this.set({ back });
|
|
@@ -130,7 +123,7 @@ export class PageShellState {
|
|
|
130
123
|
setFooter = (footer) => this.set({ footer });
|
|
131
124
|
setFooterActions = (footerActions) => this.set({ footerActions });
|
|
132
125
|
reset = () => {
|
|
133
|
-
this.
|
|
126
|
+
this.#overrides.clear();
|
|
134
127
|
};
|
|
135
128
|
}
|
|
136
129
|
export function usePageShell() {
|
|
@@ -168,17 +168,17 @@
|
|
|
168
168
|
{#if typeof trigger === 'function'}
|
|
169
169
|
{@render trigger?.(popover)}
|
|
170
170
|
{:else if typeof trigger !== 'boolean'}
|
|
171
|
+
{@const { content, children, onclick, ...buttonProps } = trigger}
|
|
171
172
|
<Button
|
|
172
|
-
{...
|
|
173
|
+
{...buttonProps}
|
|
173
174
|
{...popover.triggerProps}
|
|
174
175
|
onclick={(event) => {
|
|
175
|
-
|
|
176
|
+
onclick?.(event);
|
|
176
177
|
if (openOnClick) popover.toggle();
|
|
177
178
|
}}
|
|
179
|
+
children={children ?? content}
|
|
178
180
|
{@attach popover.reference}
|
|
179
|
-
|
|
180
|
-
{trigger.content}
|
|
181
|
-
</Button>
|
|
181
|
+
/>
|
|
182
182
|
{/if}
|
|
183
183
|
{/if}
|
|
184
184
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { default as Popover } from './Popover.svelte';
|
|
2
2
|
export type { PopoverProps } from './popover.props.js';
|
|
3
3
|
export { popoverTheme, setPopoverTheme, usePopoverTheme, type PopoverTheme, type PopoverThemeProps } from './popover.theme.js';
|
|
4
|
-
export { usePopoverContext } from './popover.state.svelte.js';
|
|
4
|
+
export { usePopoverContext, type PopoverState } from './popover.state.svelte.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const popoverDescription = "\n# Popover Component\n\nThe Popover component displays floating content positioned relative to a trigger element. It's ideal for tooltips, dropdown menus, and contextual information.\n\n## Basic Usage\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n\t\t\n</script>\n// By default Popover comes with a button that triggers them, no need to define a callback and a $state\n<Popover trigger={{ content: \"Toggle Popover\" }}>\n\tPopover content here\n</Popover>\n```\n\n## Props\n\n### Core Props\n- **open**: boolean (bindable) - Controls popover visibility (optional when using trigger prop)\n- **defaultOpen**: boolean (default: false) - Initial state when open is not provided\n- **ref**: HTMLElement | null - Reference element to position popover against (optional when using trigger prop)\n- **id**: string - Unique identifier\n\n### Layout Props\n- **position**: 'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end' (default: 'bottom')\n - Determines where popover appears relative to trigger\n- **offset**: number - Distance in pixels from the reference element\n- **fitTrigger**: boolean (default: false) - Whether popover should match the width of the trigger element\n- **positionPanel**: ({ panel, reference }) => { x: number; y: number; minWidth?: number } | null - Place the panel yourself instead of floating-ui: return its viewport coordinates (the panel is `position: fixed`) and optionally a `minWidth` in px that replaces `fitTrigger`'s, or `null` to fall back to `position`. Called when the panel mounts and whenever floating-ui would reposition it. Select uses it to open over its trigger\n- **mobileSheet**: boolean (default: false) - On mobile viewports (<768px), render as a bottom sheet instead of an anchored floating panel\n- **inline**: boolean (default: false) - Render the panel in normal document flow where the component sits instead of portaling to the viewport-fixed layer: no floating-ui positioning, no scroll lock, no outside-press dismissal (Escape still closes it). Same panel classes and motion, and the trigger still toggles it. Wins over `mobileSheet`\n- **mobileSheetSizeTransition**: boolean (default: true) - Whether mobile-sheet panels animate intrinsic size changes\n\n### Event Props\n- **onOpenChange**: (open: boolean) => void - Called once for each library-requested state change\n- **onAfterOpen**: (payload: PopoverState) => void - Called after the open transition finishes\n- **onAfterClose**: (payload: PopoverState) => void - Called after the close transition finishes\n\n### Slot Props\n- **children**: Snippet<[PopoverState]> - Popover content\n- **trigger**: Snippet<[PopoverState]> | (ButtonProps & { content?: string }) | false - Trigger element\n - Pass a snippet function for custom trigger: `{#snippet trigger(popover)}...</snippet>`\n - Pass button props object for default button: `trigger={{ content: \"Click Me\", color: \"primary\" }}`\n - Pass `false` to disable trigger (use with external ref)\n\n### Interaction Props\n- **openOnHover**: boolean (default: false) - Open on mouse hover\n- **openOnClick**: boolean (default: true) - Open on click\n- **delay**: number (default: 100) - Delay in ms before opening on hover\n- **closeOnEscape**: boolean (default: true) - Close on Escape. Only the topmost open layer closes, so Escape inside a nested popover leaves its parent open\n- **closeOnClickOutside**: boolean (default: true) - Close on an outside press. A press dismisses this popover and every layer stacked above it, but never the layer that was pressed\n- **closeOnMouseLeave**: boolean (default: false) - Close when the pointer leaves the hover safe area. The safe area is the trigger, the panel, and a prediction cone toward the panel, so a diagonal move to the panel keeps it open.\n- **debugSafeArea**: boolean (default: false) - Show hover safe-area overlays. Trigger/panel rectangles render in blue; the prediction cone toward the panel renders in orange.\n\n### Focus & ARIA Props\n- **focusOnOpen**: 'first' | 'container' | false (default: false) - Where focus goes when the panel opens: `'first'` moves it to an `[autofocus]` / `[data-autofocus]` target or the first tabbable control, `'container'` focuses the panel itself, `false` keeps it on the trigger. Focus always returns to the trigger when the popover closes (Escape or outside press)\n- **haspopup**: 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid' | true (default: 'dialog') - Value of `aria-haspopup` on the trigger, describing what the panel contains. `aria-expanded` and `aria-controls` are managed automatically alongside it, for the built-in Button trigger and for a snippet trigger using `{@attach popover.reference}`\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover focusOnOpen=\"first\" haspopup=\"listbox\" trigger={{ content: 'Pick one' }}>\n\t<ul role=\"listbox\"><li role=\"option\" tabindex=\"0\">First</li></ul>\n</Popover>\n```\n\n### Visual Props\n- **size**: 'small' | 'normal' | 'large' (default: 'normal')\n - small: Compact popover size\n - normal: Standard popover size\n - large: Larger popover size\n- **transition**: TransitionConfig - Custom transition animation\n- **directedTransition**: boolean (default: true) - Transition direction follows position\n\n### Behavior Props\n- **lockScroll**: boolean (default: true) - Lock body scroll when open\n\n### Styling Props\n- **class**: string - Additional CSS classes\n- **theme**: ComponentTheme - Custom theme overrides\n\n## Structure\n\n```\n<PopoverTrigger>\n\t<Trigger />\n</PopoverTrigger>\n\n<PopoverContent>\n\t<Children />\n</PopoverContent>\n```\n\n## Examples\n\n### More Examples\n\n### With a custom trigger snippet\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n\timport { Button } from 'entasis/button';\n</script>\n\n<!-- {@attach popover.reference} anchors the panel to the element and keeps\n aria-haspopup / aria-expanded / aria-controls in sync on it -->\n<Popover>\n\t{#snippet trigger(popover)}\n\t\t<Button onclick={() => popover.toggle()} {@attach popover.reference}>Open</Button>\n\t{/snippet}\n\t\n\t<p>This is a popover!</p>\n</Popover>\n```\n\n### With button props\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover \n\ttrigger={{\n\t\tcontent: \"Click Me\",\n\t\tcolor: \"secondary\",\n\t\tsize: \"small\"\n\t}}\n>\n\t<p>This is a popover!</p>\n</Popover>\n```\n\n### Different Positions\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<!-- Top -->\n<Popover position=\"top\" trigger={{ content: \"Top\" }}>\n\tTop popover\n</Popover>\n\n<!-- Bottom -->\n<Popover position=\"bottom\" trigger={{ content: \"Bottom\" }}>\n\tBottom popover\n</Popover>\n\n<!-- Left -->\n<Popover position=\"left\" trigger={{ content: \"Left\" }}>\n\tLeft popover\n</Popover>\n\n<!-- Right -->\n<Popover position=\"right\" trigger={{ content: \"Right\" }}>\n\tRight popover\n</Popover>\n```\n\n### Advanced Example\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n\timport { Button } from 'entasis/button';\n</script>\n\n<Popover position=\"bottom-start\" trigger={{ content: \"Menu\" }}>\n\t<div class=\"flex flex-col gap-1\">\n\t\t<Button variant=\"ghost\" fullWidth>Profile</Button>\n\t\t<Button variant=\"ghost\" fullWidth>Settings</Button>\n\t\t<Button variant=\"ghost\" fullWidth>Logout</Button>\n\t</div>\n</Popover>\n```\n\n### Open on Hover\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover \n\ttrigger={{ content: \"Hover Me\" }}\n\topenOnHover\n\topenOnClick={false}\n\tdelay={200}\n>\n\tHover content\n</Popover>\n```\n\n### With Custom Offset\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover \n\ttrigger={{ content: \"Trigger\" }}\n\tposition=\"bottom\"\n\toffset={20}\n>\n\t20px away from trigger\n</Popover>\n```\n\n### Fit Trigger Width\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover \n\ttrigger={{ content: \"Click Me\" }}\n\tfitTrigger\n>\n\tPopover matches trigger width\n</Popover>\n```\n\n### Inline (static, in flow)\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<!-- Open in place, no portal: useful for docs, visual tests, or an always-visible panel -->\n<Popover inline open trigger={false}>\n\t<p>Rendered where the component sits.</p>\n</Popover>\n\n<!-- The trigger still toggles an inline panel -->\n<Popover inline trigger={{ content: 'Toggle' }}>\n\t<p>Expands below the trigger, in the flow.</p>\n</Popover>\n```\n\n### Mobile Bottom Sheet\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover\n\ttrigger={{ content: \"Open filters\" }}\n\tposition=\"bottom\"\n\tmobileSheet\n>\n\tFilters content\n</Popover>\n```\n\n### Different Sizes\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<!-- Small -->\n<Popover size=\"small\" trigger={{ content: \"Small\" }}>\n\tSmall popover\n</Popover>\n\n<!-- Large -->\n<Popover size=\"large\" trigger={{ content: \"Large\" }}>\n\tLarge popover with more content\n</Popover>\n```\n\n### Close on Mouse Leave\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover \n\ttrigger={{ content: \"Hover Me\" }}\n\topenOnHover\n\tcloseOnMouseLeave\n>\n\tCloses when you move outside the rectangle tolerance\n</Popover>\n```\n\n### With Lifecycle Hooks\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover \n\ttrigger={{ content: \"Trigger\" }}\n\tonAfterOpen={(payload) => console.log('Popover opened', payload)}\n\tonAfterClose={(payload) => console.log('Popover closed', payload)}\n>\n\tWatch the console\n</Popover>\n```\n\n### User Card Popover with External Ref\n\n```svelte\n<script lang=\"ts\">\n\timport { Popover } from 'entasis/popover';\n\timport { Button } from 'entasis/button';\n\timport { Avatar } from 'entasis/avatar';\n\n\tlet avatarRef = $state<HTMLElement | null>(null);\n\tlet open = $state(false);\n\tconst user = { name: 'John Doe', email: 'john@example.com' };\n</script>\n\n<button type=\"button\" bind:this={avatarRef} onclick={() => (open = !open)}>\n\t<Avatar name={user.name} />\n</button>\n\n<Popover bind:open ref={avatarRef} position=\"bottom\">\n\t<div class=\"p-4\">\n\t\t<h3>{user.name}</h3>\n\t\t<p>{user.email}</p>\n\t\t<Button fullWidth>View Profile</Button>\n\t</div>\n</Popover>\n```\n\n## State Management\n\nThe Popover component uses a `PopoverState` instance that is passed to all slot snippets. This state object provides:\n\n- **id**: string - Popover identifier\n- **size**: Size - Current popover size\n- **position**: Placement - Current popover position\n- **offset**: number - Current offset value\n- **open()**: () => void - Method to open the popover\n- **close()**: () => void - Method to close the popover\n- **toggle()**: () => void - Method to toggle the popover\n- **reference**: attachment for a custom trigger element (`{@attach popover.reference}`); anchors the panel to it and keeps its `aria-haspopup`, `aria-expanded`, and `aria-controls` in sync\n\n## Accessibility\n\n- The trigger carries `aria-haspopup` (from `haspopup`), `aria-expanded`, and `aria-controls` \u2014 automatically for the built-in Button and for a snippet trigger using `{@attach popover.reference}`\n- `focusOnOpen` decides where focus lands on open; focus returns to the trigger on close, whether by Escape or an outside press\n- Non-modal: Tab is not contained and the page is not made inert (use Dialog for that)\n- Escape closes only the topmost open layer; an outside press dismisses every layer stacked above the one pressed. Popovers, menus, and dialogs share one layer stack\n- Keyboard navigation support\n\n## Notes\n\n- Popover is positioned using floating-ui, except with `inline`, where the document lays the panel out\n- Automatically adjusts position to stay in viewport\n- Multiple popovers can be stacked\n- Scroll locking prevents background scroll (when enabled)\n- Transitions animate based on position direction\n\n## Theme Customization\n\nThe Popover component uses a theme object that can be customized using the `theme` prop or by setting a global theme.\n\n### Theme Structure\n\nThe theme object contains the following parts:\n- **motion**: Open/close transition preset, keyed by `mode` (a floating panel scales, the mobile\n sheet slides up). Takes `in` / `out` FSO params plus a `duration` / `easing` motion token;\n the `transition` prop wins over it\n- **popover**: Main popover container styles\n\n### Theme Type Definition\n\n```typescript\nimport type { PopoverThemeProps } from 'entasis/popover';\n\n// Example theme customization\nconst customTheme: PopoverThemeProps = {\n popover: {\n base: 'z-[+50] fixed bg-surface-floating text-neutral w-fit rounded-xl raised isolate h-fit',\n size: {\n small: 'max-w-3xs w-full p-2',\n normal: 'max-w-xs w-full p-3',\n large: 'max-w-sm w-full p-4'\n }\n }\n};\n```\n\n### Available Variants\n\n**popover**:\n- base: Base classes applied to all popovers\n- Variants:\n - size: 'small' | 'normal' | 'large' - Controls max-width, width, and padding\n - mode: 'floating' | 'inline' | 'mobileSheet' - Set from `inline` / `mobileSheet`; `root` positions the wrapper (fixed, in flow, or full-screen sheet)\n\n### Usage Examples\n\n**Basic Theme Override**:\n```svelte\n<Popover \n trigger={{ content: \"Click Me\" }}\n theme={{\n popover: {\n base: 'rounded-xl lift-5 border-2 border-primary',\n size: {\n normal: 'max-w-md p-4'\n }\n }\n }}\n>\n Custom styled popover content\n</Popover>\n```\n\n**Size Customization**:\n```svelte\n<Popover \n size=\"large\"\n trigger={{ content: \"Large Popover\" }}\n theme={{\n popover: {\n size: {\n large: 'max-w-lg p-6'\n }\n }\n }}\n>\n Large popover with more padding\n</Popover>\n```\n\n**Global Theme Setting**:\n```svelte\n<script>\n import { setPopoverTheme } from './index.ts';\n \n setPopoverTheme({\n popover: {\n base: 'rounded-lg lift-5 backdrop-blur-sm bg-white/95',\n size: {\n normal: 'max-w-sm p-4'\n }\n }\n });\n</script>\n```\n";
|
|
1
|
+
export declare const popoverDescription = "\n# Popover Component\n\nThe Popover component displays floating content positioned relative to a trigger element. It's ideal for tooltips, dropdown menus, and contextual information.\n\n## Basic Usage\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n\t\t\n</script>\n// By default Popover comes with a button that triggers them, no need to define a callback and a $state\n<Popover trigger={{ content: \"Toggle Popover\" }}>\n\tPopover content here\n</Popover>\n```\n\n## Props\n\n### Core Props\n- **open**: boolean (bindable) - Controls popover visibility (optional when using trigger prop)\n- **defaultOpen**: boolean (default: false) - Initial state when open is not provided\n- **ref**: HTMLElement | null - Reference element to position popover against (optional when using trigger prop)\n- **id**: string - Unique identifier\n\n### Layout Props\n- **position**: 'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end' (default: 'bottom')\n - Determines where popover appears relative to trigger\n- **offset**: number - Distance in pixels from the reference element\n- **fitTrigger**: boolean (default: false) - Whether popover should match the width of the trigger element\n- **positionPanel**: ({ panel, reference }) => { x: number; y: number; minWidth?: number } | null - Place the panel yourself instead of floating-ui: return its viewport coordinates (the panel is `position: fixed`) and optionally a `minWidth` in px that replaces `fitTrigger`'s, or `null` to fall back to `position`. Called when the panel mounts and whenever floating-ui would reposition it. Select uses it to open over its trigger\n- **mobileSheet**: boolean (default: false) - On mobile viewports (<768px), render as a bottom sheet instead of an anchored floating panel\n- **inline**: boolean (default: false) - Render the panel in normal document flow where the component sits instead of portaling to the viewport-fixed layer: no floating-ui positioning, no scroll lock, no outside-press dismissal (Escape still closes it). Same panel classes and motion, and the trigger still toggles it. Wins over `mobileSheet`\n- **mobileSheetSizeTransition**: boolean (default: true) - Whether mobile-sheet panels animate intrinsic size changes\n\n### Event Props\n- **onOpenChange**: (open: boolean) => void - Called once for each library-requested state change\n- **onAfterOpen**: (payload: PopoverState) => void - Called after the open transition finishes\n- **onAfterClose**: (payload: PopoverState) => void - Called after the close transition finishes\n\n### Slot Props\n- **children**: Snippet<[PopoverState]> - Popover content\n- **trigger**: Snippet<[PopoverState]> | (ButtonProps & { content?: string }) | false - Trigger element\n - Pass a snippet function for custom trigger: `{#snippet trigger(popover)}...</snippet>`. Put\n `{@attach popover.trigger}` on any element (a card, an avatar, a Button) and it opens the\n panel on click and from the keyboard, with the ARIA state kept in sync. Type the parameter\n with `PopoverState` from `entasis/popover`.\n - Pass button props object for default button: `trigger={{ content: \"Click Me\", color: \"primary\" }}`.\n `children` (a string or a snippet) replaces `content` for a richer body; `prefix` and\n `suffix` work as on Button.\n - Pass `false` to disable trigger (use with external ref)\n\n### Interaction Props\n- **openOnHover**: boolean (default: false) - Open on mouse hover\n- **openOnClick**: boolean (default: true) - Open on click\n- **delay**: number (default: 100) - Delay in ms before opening on hover\n- **closeOnEscape**: boolean (default: true) - Close on Escape. Only the topmost open layer closes, so Escape inside a nested popover leaves its parent open\n- **closeOnClickOutside**: boolean (default: true) - Close on an outside press. A press dismisses this popover and every layer stacked above it, but never the layer that was pressed\n- **closeOnMouseLeave**: boolean (default: false) - Close when the pointer leaves the hover safe area. The safe area is the trigger, the panel, and a prediction cone toward the panel, so a diagonal move to the panel keeps it open.\n- **debugSafeArea**: boolean (default: false) - Show hover safe-area overlays. Trigger/panel rectangles render in blue; the prediction cone toward the panel renders in orange.\n\n### Focus & ARIA Props\n- **focusOnOpen**: 'first' | 'container' | false (default: false) - Where focus goes when the panel opens: `'first'` moves it to an `[autofocus]` / `[data-autofocus]` target or the first tabbable control, `'container'` focuses the panel itself, `false` keeps it on the trigger. Focus always returns to the trigger when the popover closes (Escape or outside press)\n- **haspopup**: 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid' | true (default: 'dialog') - Value of `aria-haspopup` on the trigger, describing what the panel contains. `aria-expanded` and `aria-controls` are managed automatically alongside it, for the built-in Button trigger and for a snippet trigger using `{@attach popover.reference}`\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover focusOnOpen=\"first\" haspopup=\"listbox\" trigger={{ content: 'Pick one' }}>\n\t<ul role=\"listbox\"><li role=\"option\" tabindex=\"0\">First</li></ul>\n</Popover>\n```\n\n### Visual Props\n- **size**: 'small' | 'normal' | 'large' (default: 'normal')\n - small: Compact popover size\n - normal: Standard popover size\n - large: Larger popover size\n- **transition**: TransitionConfig - Custom transition animation\n- **directedTransition**: boolean (default: true) - Transition direction follows position\n\n### Behavior Props\n- **lockScroll**: boolean (default: true) - Lock body scroll when open\n\n### Styling Props\n- **class**: string - Additional CSS classes\n- **theme**: ComponentTheme - Custom theme overrides\n\n## Structure\n\n```\n<PopoverTrigger>\n\t<Trigger />\n</PopoverTrigger>\n\n<PopoverContent>\n\t<Children />\n</PopoverContent>\n```\n\n## Examples\n\n### More Examples\n\n### With a custom trigger snippet\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n\timport { Button } from 'entasis/button';\n</script>\n\n<!-- {@attach popover.trigger} anchors the panel to the element, toggles it on click and keeps\n aria-haspopup / aria-expanded / aria-controls in sync on it -->\n<Popover>\n\t{#snippet trigger(popover)}\n\t\t<Button {@attach popover.trigger}>Open</Button>\n\t{/snippet}\n\t\n\t<p>This is a popover!</p>\n</Popover>\n```\n\nAny element works. One that is not a control gets `role=\"button\"`, `tabindex=\"0\"` and Enter/Space\nactivation:\n\n```svelte\n<script lang=\"ts\">\n\timport { Popover, type PopoverState } from 'entasis/popover';\n\timport { Avatar } from 'entasis/avatar';\n</script>\n\n<Popover>\n\t{#snippet trigger(popover: PopoverState)}\n\t\t<div class=\"flex items-center gap-sm\" {@attach popover.trigger}>\n\t\t\t<Avatar name=\"Ada Lovelace\" />\n\t\t\t<span>Ada Lovelace</span>\n\t\t</div>\n\t{/snippet}\n\n\t<p>Profile details</p>\n</Popover>\n```\n\n### With button props\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover \n\ttrigger={{\n\t\tcontent: \"Click Me\",\n\t\tcolor: \"secondary\",\n\t\tsize: \"small\"\n\t}}\n>\n\t<p>This is a popover!</p>\n</Popover>\n```\n\n### Different Positions\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<!-- Top -->\n<Popover position=\"top\" trigger={{ content: \"Top\" }}>\n\tTop popover\n</Popover>\n\n<!-- Bottom -->\n<Popover position=\"bottom\" trigger={{ content: \"Bottom\" }}>\n\tBottom popover\n</Popover>\n\n<!-- Left -->\n<Popover position=\"left\" trigger={{ content: \"Left\" }}>\n\tLeft popover\n</Popover>\n\n<!-- Right -->\n<Popover position=\"right\" trigger={{ content: \"Right\" }}>\n\tRight popover\n</Popover>\n```\n\n### Advanced Example\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n\timport { Button } from 'entasis/button';\n</script>\n\n<Popover position=\"bottom-start\" trigger={{ content: \"Menu\" }}>\n\t<div class=\"flex flex-col gap-1\">\n\t\t<Button variant=\"ghost\" fullWidth>Profile</Button>\n\t\t<Button variant=\"ghost\" fullWidth>Settings</Button>\n\t\t<Button variant=\"ghost\" fullWidth>Logout</Button>\n\t</div>\n</Popover>\n```\n\n### Open on Hover\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover \n\ttrigger={{ content: \"Hover Me\" }}\n\topenOnHover\n\topenOnClick={false}\n\tdelay={200}\n>\n\tHover content\n</Popover>\n```\n\n### With Custom Offset\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover \n\ttrigger={{ content: \"Trigger\" }}\n\tposition=\"bottom\"\n\toffset={20}\n>\n\t20px away from trigger\n</Popover>\n```\n\n### Fit Trigger Width\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover \n\ttrigger={{ content: \"Click Me\" }}\n\tfitTrigger\n>\n\tPopover matches trigger width\n</Popover>\n```\n\n### Inline (static, in flow)\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<!-- Open in place, no portal: useful for docs, visual tests, or an always-visible panel -->\n<Popover inline open trigger={false}>\n\t<p>Rendered where the component sits.</p>\n</Popover>\n\n<!-- The trigger still toggles an inline panel -->\n<Popover inline trigger={{ content: 'Toggle' }}>\n\t<p>Expands below the trigger, in the flow.</p>\n</Popover>\n```\n\n### Mobile Bottom Sheet\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover\n\ttrigger={{ content: \"Open filters\" }}\n\tposition=\"bottom\"\n\tmobileSheet\n>\n\tFilters content\n</Popover>\n```\n\n### Different Sizes\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<!-- Small -->\n<Popover size=\"small\" trigger={{ content: \"Small\" }}>\n\tSmall popover\n</Popover>\n\n<!-- Large -->\n<Popover size=\"large\" trigger={{ content: \"Large\" }}>\n\tLarge popover with more content\n</Popover>\n```\n\n### Close on Mouse Leave\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover \n\ttrigger={{ content: \"Hover Me\" }}\n\topenOnHover\n\tcloseOnMouseLeave\n>\n\tCloses when you move outside the rectangle tolerance\n</Popover>\n```\n\n### With Lifecycle Hooks\n\n```svelte\n<script>\n\timport { Popover } from 'entasis/popover';\n</script>\n\n<Popover \n\ttrigger={{ content: \"Trigger\" }}\n\tonAfterOpen={(payload) => console.log('Popover opened', payload)}\n\tonAfterClose={(payload) => console.log('Popover closed', payload)}\n>\n\tWatch the console\n</Popover>\n```\n\n### User Card Popover with External Ref\n\n```svelte\n<script lang=\"ts\">\n\timport { Popover } from 'entasis/popover';\n\timport { Button } from 'entasis/button';\n\timport { Avatar } from 'entasis/avatar';\n\n\tlet avatarRef = $state<HTMLElement | null>(null);\n\tlet open = $state(false);\n\tconst user = { name: 'John Doe', email: 'john@example.com' };\n</script>\n\n<button type=\"button\" bind:this={avatarRef} onclick={() => (open = !open)}>\n\t<Avatar name={user.name} />\n</button>\n\n<Popover bind:open ref={avatarRef} position=\"bottom\">\n\t<div class=\"p-4\">\n\t\t<h3>{user.name}</h3>\n\t\t<p>{user.email}</p>\n\t\t<Button fullWidth>View Profile</Button>\n\t</div>\n</Popover>\n```\n\n## State Management\n\nThe Popover component uses a `PopoverState` instance that is passed to all slot snippets. This state object provides:\n\n- **id**: string - Popover identifier\n- **size**: Size - Current popover size\n- **position**: Placement - Current popover position\n- **offset**: number - Current offset value\n- **open()**: () => void - Method to open the popover\n- **close()**: () => void - Method to close the popover\n- **toggle()**: () => void - Method to toggle the popover\n- **trigger**: attachment that makes any element the trigger (`{@attach popover.trigger}`): `reference` plus a click toggle (when `openOnClick`), and on a non-control `role=\"button\"`, `tabindex=\"0\"` and Enter/Space activation. The element's own handler wins: when it calls `open`, `close`, `toggle` or `setOpen` during the click, the default toggle stands down, so a kept `onclick={popover.toggle}` does not toggle twice\n- **reference**: attachment for a custom trigger element (`{@attach popover.reference}`); anchors the panel to it and keeps its `aria-haspopup`, `aria-expanded`, and `aria-controls` in sync, leaving clicks and keyboard to the element\n\n## Accessibility\n\n- The trigger carries `aria-haspopup` (from `haspopup`), `aria-expanded`, and `aria-controls` \u2014 automatically for the built-in Button and for a snippet trigger using `{@attach popover.trigger}` or `{@attach popover.reference}`\n- `focusOnOpen` decides where focus lands on open; focus returns to the trigger on close, whether by Escape or an outside press\n- Non-modal: Tab is not contained and the page is not made inert (use Dialog for that)\n- Escape closes only the topmost open layer; an outside press dismisses every layer stacked above the one pressed. Popovers, menus, and dialogs share one layer stack\n- Keyboard navigation support\n\n## Notes\n\n- Popover is positioned using floating-ui, except with `inline`, where the document lays the panel out\n- Automatically adjusts position to stay in viewport\n- Multiple popovers can be stacked\n- Scroll locking prevents background scroll (when enabled)\n- Transitions animate based on position direction\n\n## Theme Customization\n\nThe Popover component uses a theme object that can be customized using the `theme` prop or by setting a global theme.\n\n### Theme Structure\n\nThe theme object contains the following parts:\n- **motion**: Open/close transition preset, keyed by `mode` (a floating panel scales, the mobile\n sheet slides up). Takes `in` / `out` FSO params plus a `duration` / `easing` motion token;\n the `transition` prop wins over it\n- **popover**: Main popover container styles\n\n### Theme Type Definition\n\n```typescript\nimport type { PopoverThemeProps } from 'entasis/popover';\n\n// Example theme customization\nconst customTheme: PopoverThemeProps = {\n popover: {\n base: 'z-[+50] fixed bg-surface-floating text-neutral w-fit rounded-xl raised isolate h-fit',\n size: {\n small: 'max-w-3xs w-full p-2',\n normal: 'max-w-xs w-full p-3',\n large: 'max-w-sm w-full p-4'\n }\n }\n};\n```\n\n### Available Variants\n\n**popover**:\n- base: Base classes applied to all popovers\n- Variants:\n - size: 'small' | 'normal' | 'large' - Controls max-width, width, and padding\n - mode: 'floating' | 'inline' | 'mobileSheet' - Set from `inline` / `mobileSheet`; `root` positions the wrapper (fixed, in flow, or full-screen sheet)\n\n### Usage Examples\n\n**Basic Theme Override**:\n```svelte\n<Popover \n trigger={{ content: \"Click Me\" }}\n theme={{\n popover: {\n base: 'rounded-xl lift-5 border-2 border-primary',\n size: {\n normal: 'max-w-md p-4'\n }\n }\n }}\n>\n Custom styled popover content\n</Popover>\n```\n\n**Size Customization**:\n```svelte\n<Popover \n size=\"large\"\n trigger={{ content: \"Large Popover\" }}\n theme={{\n popover: {\n size: {\n large: 'max-w-lg p-6'\n }\n }\n }}\n>\n Large popover with more padding\n</Popover>\n```\n\n**Global Theme Setting**:\n```svelte\n<script>\n import { setPopoverTheme } from './index.ts';\n \n setPopoverTheme({\n popover: {\n base: 'rounded-lg lift-5 backdrop-blur-sm bg-white/95',\n size: {\n normal: 'max-w-sm p-4'\n }\n }\n });\n</script>\n```\n";
|
|
@@ -42,8 +42,13 @@ The Popover component displays floating content positioned relative to a trigger
|
|
|
42
42
|
### Slot Props
|
|
43
43
|
- **children**: Snippet<[PopoverState]> - Popover content
|
|
44
44
|
- **trigger**: Snippet<[PopoverState]> | (ButtonProps & { content?: string }) | false - Trigger element
|
|
45
|
-
- Pass a snippet function for custom trigger: \`{#snippet trigger(popover)}...</snippet
|
|
46
|
-
|
|
45
|
+
- Pass a snippet function for custom trigger: \`{#snippet trigger(popover)}...</snippet>\`. Put
|
|
46
|
+
\`{@attach popover.trigger}\` on any element (a card, an avatar, a Button) and it opens the
|
|
47
|
+
panel on click and from the keyboard, with the ARIA state kept in sync. Type the parameter
|
|
48
|
+
with \`PopoverState\` from \`entasis/popover\`.
|
|
49
|
+
- Pass button props object for default button: \`trigger={{ content: "Click Me", color: "primary" }}\`.
|
|
50
|
+
\`children\` (a string or a snippet) replaces \`content\` for a richer body; \`prefix\` and
|
|
51
|
+
\`suffix\` work as on Button.
|
|
47
52
|
- Pass \`false\` to disable trigger (use with external ref)
|
|
48
53
|
|
|
49
54
|
### Interaction Props
|
|
@@ -107,17 +112,38 @@ The Popover component displays floating content positioned relative to a trigger
|
|
|
107
112
|
import { Button } from '../Button/index.ts';
|
|
108
113
|
</script>
|
|
109
114
|
|
|
110
|
-
<!-- {@attach popover.
|
|
115
|
+
<!-- {@attach popover.trigger} anchors the panel to the element, toggles it on click and keeps
|
|
111
116
|
aria-haspopup / aria-expanded / aria-controls in sync on it -->
|
|
112
117
|
<Popover>
|
|
113
118
|
{#snippet trigger(popover)}
|
|
114
|
-
<Button
|
|
119
|
+
<Button {@attach popover.trigger}>Open</Button>
|
|
115
120
|
{/snippet}
|
|
116
121
|
|
|
117
122
|
<p>This is a popover!</p>
|
|
118
123
|
</Popover>
|
|
119
124
|
\`\`\`
|
|
120
125
|
|
|
126
|
+
Any element works. One that is not a control gets \`role="button"\`, \`tabindex="0"\` and Enter/Space
|
|
127
|
+
activation:
|
|
128
|
+
|
|
129
|
+
\`\`\`svelte
|
|
130
|
+
<script lang="ts">
|
|
131
|
+
import { Popover, type PopoverState } from './index.ts';
|
|
132
|
+
import { Avatar } from '../Avatar/index.ts';
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<Popover>
|
|
136
|
+
{#snippet trigger(popover: PopoverState)}
|
|
137
|
+
<div class="flex items-center gap-sm" {@attach popover.trigger}>
|
|
138
|
+
<Avatar name="Ada Lovelace" />
|
|
139
|
+
<span>Ada Lovelace</span>
|
|
140
|
+
</div>
|
|
141
|
+
{/snippet}
|
|
142
|
+
|
|
143
|
+
<p>Profile details</p>
|
|
144
|
+
</Popover>
|
|
145
|
+
\`\`\`
|
|
146
|
+
|
|
121
147
|
### With button props
|
|
122
148
|
\`\`\`svelte
|
|
123
149
|
<script>
|
|
@@ -349,11 +375,12 @@ The Popover component uses a \`PopoverState\` instance that is passed to all slo
|
|
|
349
375
|
- **open()**: () => void - Method to open the popover
|
|
350
376
|
- **close()**: () => void - Method to close the popover
|
|
351
377
|
- **toggle()**: () => void - Method to toggle the popover
|
|
352
|
-
- **
|
|
378
|
+
- **trigger**: attachment that makes any element the trigger (\`{@attach popover.trigger}\`): \`reference\` plus a click toggle (when \`openOnClick\`), and on a non-control \`role="button"\`, \`tabindex="0"\` and Enter/Space activation. The element's own handler wins: when it calls \`open\`, \`close\`, \`toggle\` or \`setOpen\` during the click, the default toggle stands down, so a kept \`onclick={popover.toggle}\` does not toggle twice
|
|
379
|
+
- **reference**: attachment for a custom trigger element (\`{@attach popover.reference}\`); anchors the panel to it and keeps its \`aria-haspopup\`, \`aria-expanded\`, and \`aria-controls\` in sync, leaving clicks and keyboard to the element
|
|
353
380
|
|
|
354
381
|
## Accessibility
|
|
355
382
|
|
|
356
|
-
- The trigger carries \`aria-haspopup\` (from \`haspopup\`), \`aria-expanded\`, and \`aria-controls\` — automatically for the built-in Button and for a snippet trigger using \`{@attach popover.reference}\`
|
|
383
|
+
- The trigger carries \`aria-haspopup\` (from \`haspopup\`), \`aria-expanded\`, and \`aria-controls\` — automatically for the built-in Button and for a snippet trigger using \`{@attach popover.trigger}\` or \`{@attach popover.reference}\`
|
|
357
384
|
- \`focusOnOpen\` decides where focus lands on open; focus returns to the trigger on close, whether by Escape or an outside press
|
|
358
385
|
- Non-modal: Tab is not contained and the page is not made inert (use Dialog for that)
|
|
359
386
|
- Escape closes only the topmost open layer; an outside press dismisses every layer stacked above the one pressed. Popovers, menus, and dialogs share one layer stack
|
|
@@ -11,13 +11,14 @@ interface PopoverOptions extends MakeRequired<Pick<PopoverProps, 'id' | 'size' |
|
|
|
11
11
|
}
|
|
12
12
|
declare const PopoverOptionsBase: new () => PopoverOptions;
|
|
13
13
|
export declare class PopoverState extends PopoverOptionsBase {
|
|
14
|
+
#private;
|
|
14
15
|
triggerReference: HTMLElement | null;
|
|
15
16
|
referenceElement: HTMLElement | VirtualElement | null;
|
|
16
17
|
dialogElement: HTMLElement | null;
|
|
17
18
|
transformOrigin: string;
|
|
18
19
|
triggerWidth: number | null;
|
|
19
20
|
parent: PopoverState | null;
|
|
20
|
-
children: PopoverState[];
|
|
21
|
+
get children(): readonly PopoverState[];
|
|
21
22
|
hasChildOpen: boolean;
|
|
22
23
|
hasTransitioned: boolean;
|
|
23
24
|
theme: import("../Theme/theme.state.svelte.js").ThemeState;
|
|
@@ -64,6 +65,13 @@ export declare class PopoverState extends PopoverOptionsBase {
|
|
|
64
65
|
dialog: (node: HTMLDialogElement) => () => void;
|
|
65
66
|
panel: (node: HTMLElement) => () => void;
|
|
66
67
|
reference: (node: HTMLElement) => () => void;
|
|
68
|
+
/**
|
|
69
|
+
* Makes any element a complete trigger: it anchors the panel (as \`reference\` does), toggles
|
|
70
|
+
* it on click when \`openOnClick\` allows, and keeps \`aria-expanded\` and friends in sync.
|
|
71
|
+
* An element that is not already a control also gets \`role="button"\`, \`tabindex="0"\` and
|
|
72
|
+
* Enter/Space activation, so a card or an avatar opens the panel from the keyboard too.
|
|
73
|
+
*/
|
|
74
|
+
trigger: (node: HTMLElement) => () => void;
|
|
67
75
|
}
|
|
68
76
|
export declare const usePopoverContext: () => PopoverState | null;
|
|
69
77
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { bind } from '../../utils/state.svelte.js';
|
|
2
|
+
import { Registry } from '../../utils/registry.svelte.js';
|
|
2
3
|
import { getContext, hasContext, onMount, setContext, untrack } from 'svelte';
|
|
3
4
|
import { useTheme } from '../Theme/theme.state.svelte.js';
|
|
4
5
|
import { computePosition, autoUpdate, hide, offset, shift, flip } from '@floating-ui/dom';
|
|
@@ -14,6 +15,10 @@ const PopoverOptionsBase = class {
|
|
|
14
15
|
};
|
|
15
16
|
// The one place the trigger's semantic state becomes ARIA, for triggers the library cannot
|
|
16
17
|
// pass props to (a snippet trigger carrying `{@attach popover.reference}`).
|
|
18
|
+
/** Elements that may carry popup ARIA state. */
|
|
19
|
+
const POPUP_CONTROL = 'button, a[href], input, select, textarea, summary, [role="button"], [role="link"], [role="combobox"], [role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [role="tab"], [role="option"], [role="treeitem"], [role="gridcell"], [role="switch"], [role="checkbox"]';
|
|
20
|
+
/** Elements the browser already focuses and activates from the keyboard. */
|
|
21
|
+
const NATIVE_CONTROL = 'button, a[href], input, select, textarea, summary';
|
|
17
22
|
const TRIGGER_ARIA = {
|
|
18
23
|
haspopup: 'aria-haspopup',
|
|
19
24
|
expanded: 'aria-expanded',
|
|
@@ -29,7 +34,11 @@ export class PopoverState extends PopoverOptionsBase {
|
|
|
29
34
|
// Trigger-matched width (fitTrigger), applied to the panel.
|
|
30
35
|
triggerWidth = $state(null);
|
|
31
36
|
parent = getContext('popover');
|
|
32
|
-
|
|
37
|
+
// Nested popovers join while they mount and leave when torn down; see `Registry`.
|
|
38
|
+
#children = new Registry();
|
|
39
|
+
get children() {
|
|
40
|
+
return this.#children.items;
|
|
41
|
+
}
|
|
33
42
|
hasChildOpen = $derived(this.children.some((d) => d.isOpen));
|
|
34
43
|
hasTransitioned = $state(false);
|
|
35
44
|
theme = useTheme();
|
|
@@ -84,12 +93,7 @@ export class PopoverState extends PopoverOptionsBase {
|
|
|
84
93
|
},
|
|
85
94
|
delay: this.delay
|
|
86
95
|
});
|
|
87
|
-
addChild = (child) => () =>
|
|
88
|
-
this.children.push(child);
|
|
89
|
-
return () => {
|
|
90
|
-
this.children = this.children.filter((d) => d.id !== child.id);
|
|
91
|
-
};
|
|
92
|
-
};
|
|
96
|
+
addChild = (child) => () => this.#children.add(child);
|
|
93
97
|
constructor(options) {
|
|
94
98
|
super();
|
|
95
99
|
bind(this, options);
|
|
@@ -109,7 +113,10 @@ export class PopoverState extends PopoverOptionsBase {
|
|
|
109
113
|
close = () => {
|
|
110
114
|
this.setOpen(false);
|
|
111
115
|
};
|
|
116
|
+
/** Calls to `setOpen`, direct or through `open`, `close` and `toggle`, unchanged state included. */
|
|
117
|
+
#openRequests = 0;
|
|
112
118
|
setOpen = (nextOpen) => {
|
|
119
|
+
this.#openRequests += 1;
|
|
113
120
|
if (this.isOpen === nextOpen)
|
|
114
121
|
return;
|
|
115
122
|
this.isOpen = nextOpen;
|
|
@@ -236,7 +243,7 @@ export class PopoverState extends PopoverOptionsBase {
|
|
|
236
243
|
// Keep the trigger's ARIA in sync whichever way it was rendered (snippet or Button).
|
|
237
244
|
// Only real controls carry aria-haspopup/aria-expanded; a wrapper div or a
|
|
238
245
|
// presentational span used as the anchor must not (axe: aria-allowed-attr).
|
|
239
|
-
const canCarryPopupState = node.matches(
|
|
246
|
+
const canCarryPopupState = node.matches(POPUP_CONTROL);
|
|
240
247
|
$effect(() => {
|
|
241
248
|
if (!canCarryPopupState)
|
|
242
249
|
return;
|
|
@@ -261,6 +268,52 @@ export class PopoverState extends PopoverOptionsBase {
|
|
|
261
268
|
};
|
|
262
269
|
});
|
|
263
270
|
};
|
|
271
|
+
/**
|
|
272
|
+
* Makes any element a complete trigger: it anchors the panel (as \`reference\` does), toggles
|
|
273
|
+
* it on click when \`openOnClick\` allows, and keeps \`aria-expanded\` and friends in sync.
|
|
274
|
+
* An element that is not already a control also gets \`role="button"\`, \`tabindex="0"\` and
|
|
275
|
+
* Enter/Space activation, so a card or an avatar opens the panel from the keyboard too.
|
|
276
|
+
*/
|
|
277
|
+
trigger = (node) => {
|
|
278
|
+
const nativeControl = node.matches(NATIVE_CONTROL);
|
|
279
|
+
if (!node.matches(POPUP_CONTROL) && !node.hasAttribute('role')) {
|
|
280
|
+
node.setAttribute('role', 'button');
|
|
281
|
+
}
|
|
282
|
+
if (!nativeControl && !node.hasAttribute('tabindex'))
|
|
283
|
+
node.tabIndex = 0;
|
|
284
|
+
// The element's own handler wins. One that still opens, closes or toggles the popover
|
|
285
|
+
// (`onclick={popover.toggle}` kept from before this attachment existed) has acted by the
|
|
286
|
+
// time the event finishes dispatching, so the default toggle waits until then and runs only
|
|
287
|
+
// if nothing asked for an open state meanwhile; toggling regardless would undo that call.
|
|
288
|
+
let pending;
|
|
289
|
+
const activate = () => {
|
|
290
|
+
if (!this.openOnClick)
|
|
291
|
+
return;
|
|
292
|
+
const requests = this.#openRequests;
|
|
293
|
+
clearTimeout(pending);
|
|
294
|
+
pending = setTimeout(() => {
|
|
295
|
+
if (this.#openRequests === requests)
|
|
296
|
+
this.toggle();
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
const onKeydown = (event) => {
|
|
300
|
+
if (nativeControl || event.target !== node)
|
|
301
|
+
return;
|
|
302
|
+
if (event.key !== 'Enter' && event.key !== ' ')
|
|
303
|
+
return;
|
|
304
|
+
event.preventDefault();
|
|
305
|
+
activate();
|
|
306
|
+
};
|
|
307
|
+
node.addEventListener('click', activate);
|
|
308
|
+
node.addEventListener('keydown', onKeydown);
|
|
309
|
+
const offReference = this.reference(node);
|
|
310
|
+
return () => {
|
|
311
|
+
clearTimeout(pending);
|
|
312
|
+
node.removeEventListener('click', activate);
|
|
313
|
+
node.removeEventListener('keydown', onKeydown);
|
|
314
|
+
offReference?.();
|
|
315
|
+
};
|
|
316
|
+
};
|
|
264
317
|
}
|
|
265
318
|
// Use interface merging to add the properties
|
|
266
319
|
export const usePopoverContext = () => {
|
|
@@ -12,7 +12,7 @@ const defaultPopoverContainer = cva({
|
|
|
12
12
|
// `inline` keeps the panel in normal flow where the component sits: no portal, no
|
|
13
13
|
// fixed positioning, no floating-ui placement.
|
|
14
14
|
inline: 'static h-fit w-fit',
|
|
15
|
-
mobileSheet: 'inset-0 flex h-dvh w-dvw max-w-none items-end justify-center overflow-hidden bg-
|
|
15
|
+
mobileSheet: 'inset-0 flex h-dvh w-dvw max-w-none items-end justify-center overflow-hidden bg-black/40 backdrop-blur-xs dark:bg-black/60'
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
defaultVariants: {
|