@jini-ai/admin 0.4.0 → 0.4.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.1 — 2026-10-04
4
+
5
+ - Media cards show the original file size and upload date (`formatUploadDate` exported from the media barrel); a null size is hidden.
6
+ - Menu ports accept entry targets with an optional `lastKnownHref`.
7
+ - Internal `@jini-ai/*` dependencies are caret ranges (`workspace:^`) instead of exact pins, so a host on a newer patch resolves a single copy without an override.
8
+
3
9
  ## 0.4.0 — 2026-10-02
4
10
 
5
11
  ### BREAKING
@@ -115,6 +115,9 @@ export type NavTargetKind = "entryRef" | "termRef" | "url" | "route" | (string &
115
115
  export interface AdminMenuEntryTarget {
116
116
  readonly kind: "entryRef";
117
117
  readonly entryId: string;
118
+ readonly entryType?: string;
119
+ /** Last URL known to the author; the host resolves entryId first and preserves availability checks. */
120
+ readonly lastKnownHref?: string;
118
121
  }
119
122
  /** A link to a taxonomy term (category/tag archive, or a host's closest equivalent grouping). */
120
123
  export interface AdminMenuTermTarget {
@@ -3,7 +3,7 @@ export { mediaApiToken, mediaProvidersToken } from './ports.js';
3
3
  export type { MediaApiPort, MediaProvidersPort, MediaEventsPort } from './ports.js';
4
4
  export type { MediaAsset, MediaQuery, UploadInput, MediaMetadataPatch, MediaRequestOptions, MediaProvider, MediaProviderOption, MediaProviderConfiguration, MediaProviderConfigurationMap, MediaTabId, MediaContentTabId, MediaOrderBy, } from './models.js';
5
5
  export { mediaMessagesEn } from './messages.en.js';
6
- export { filterMediaByTab, resolveActiveTab, queryMedia, diffMediaMetadata, acceptsMedia, safeMediaUrl, canReplaceMedia, canRestoreMedia, isRetryableMediaError, parseOptionalPixelSize, formatByteSize, countMediaTabs, mediaEmbedSnippet, mediaRowHandles, } from './rules.js';
6
+ export { filterMediaByTab, resolveActiveTab, queryMedia, diffMediaMetadata, acceptsMedia, safeMediaUrl, canReplaceMedia, canRestoreMedia, isRetryableMediaError, parseOptionalPixelSize, formatByteSize, formatUploadDate, countMediaTabs, mediaEmbedSnippet, mediaRowHandles, } from './rules.js';
7
7
  export { createLibraryController } from './controllers/library.controller.js';
8
8
  export type { LibraryState } from './controllers/library.controller.js';
9
9
  export { createEditMediaController } from './controllers/edit-media.controller.js';
@@ -1,7 +1,7 @@
1
1
  export { mediaModule } from './media.module.js';
2
2
  export { mediaApiToken, mediaProvidersToken } from './ports.js';
3
3
  export { mediaMessagesEn } from './messages.en.js';
4
- export { filterMediaByTab, resolveActiveTab, queryMedia, diffMediaMetadata, acceptsMedia, safeMediaUrl, canReplaceMedia, canRestoreMedia, isRetryableMediaError, parseOptionalPixelSize, formatByteSize, countMediaTabs, mediaEmbedSnippet, mediaRowHandles, } from './rules.js';
4
+ export { filterMediaByTab, resolveActiveTab, queryMedia, diffMediaMetadata, acceptsMedia, safeMediaUrl, canReplaceMedia, canRestoreMedia, isRetryableMediaError, parseOptionalPixelSize, formatByteSize, formatUploadDate, countMediaTabs, mediaEmbedSnippet, mediaRowHandles, } from './rules.js';
5
5
  export { createLibraryController } from './controllers/library.controller.js';
6
6
  export { createEditMediaController } from './controllers/edit-media.controller.js';
7
7
  export { createProvidersController } from './controllers/providers.controller.js';
@@ -12,6 +12,6 @@ export function MediaPreview(props, _optional = {}) {
12
12
  }
13
13
  export function MediaCard(props, _optional = {}) {
14
14
  const vm = useMediaCard(props);
15
- return _jsxs("article", { className: "jini-media-card", "data-jini-part": "media.card", "data-media-id": vm.item.id, tabIndex: -1, children: [_jsx("div", { className: "jini-media-card-preview", "data-jini-part": "media.preview.frame", children: _jsx(PreviewMarkup, { ...vm }) }), _jsxs("div", { className: "jini-media-card-body", children: [_jsx("h3", { className: "jini-media-card-title", title: vm.item.title, children: vm.item.title }), _jsxs("div", { className: "jini-media-card-meta", children: [_jsx(Badge, { className: vm.statusClassName, attrs: { 'data-jini-part': 'media.status' }, tone: vm.statusTone, children: vm.statusLabel }), vm.menuItems.length > 0 && _jsx(Menu, { label: vm.menuLabel, attrs: vm.menuAttrs, items: vm.menuItems, disabled: vm.busy })] }), vm.byteSize && _jsx("p", { className: "jini-media-card-size", hidden: true, children: vm.byteSize }), vm.onChoose && _jsx(Button, { attrs: { 'data-jini-part': 'media.choose', 'data-agent-element': `media-choose-${vm.item.id}` }, onPress: vm.onChoose, children: "Choose" })] })] });
15
+ return _jsxs("article", { className: "jini-media-card", "data-jini-part": "media.card", "data-media-id": vm.item.id, tabIndex: -1, children: [_jsx("div", { className: "jini-media-card-preview", "data-jini-part": "media.preview.frame", children: _jsx(PreviewMarkup, { ...vm }) }), _jsxs("div", { className: "jini-media-card-body", children: [_jsx("h3", { className: "jini-media-card-title", title: vm.item.title, children: vm.item.title }), _jsxs("div", { className: "jini-media-card-meta", children: [_jsx(Badge, { className: vm.statusClassName, attrs: { 'data-jini-part': 'media.status' }, tone: vm.statusTone, children: vm.statusLabel }), vm.menuItems.length > 0 && _jsx(Menu, { label: vm.menuLabel, attrs: vm.menuAttrs, items: vm.menuItems, disabled: vm.busy })] }), _jsxs("p", { className: "jini-media-card-size", children: [_jsx("span", { children: vm.byteSize }), vm.metadataSeparator, _jsx("time", { dateTime: vm.item.createdAt, children: vm.uploadDate })] }), vm.onChoose && _jsx(Button, { attrs: { 'data-jini-part': 'media.choose', 'data-agent-element': `media-choose-${vm.item.id}` }, onPress: vm.onChoose, children: "Choose" })] })] });
16
16
  }
17
17
  //# sourceMappingURL=MediaCard.js.map
@@ -3,6 +3,7 @@ import type { MediaApiPort } from '../../ports.js';
3
3
  export interface MediaCardProps {
4
4
  readonly item: MediaAsset;
5
5
  readonly handleBase?: string;
6
+ readonly locale?: string;
6
7
  readonly api: MediaApiPort;
7
8
  readonly busy?: boolean;
8
9
  readonly onEdit?: () => void;
@@ -15,6 +16,8 @@ export interface MediaCardProps {
15
16
  export declare function useMediaCard(props: MediaCardProps, _optional?: Record<string, never>): {
16
17
  busy: boolean;
17
18
  byteSize: string | null;
19
+ uploadDate: string | null;
20
+ metadataSeparator: string;
18
21
  preview: () => void | undefined;
19
22
  canPreview: boolean;
20
23
  imagePreview: boolean;
@@ -68,6 +71,7 @@ export declare function useMediaCard(props: MediaCardProps, _optional?: Record<s
68
71
  trashed: boolean;
69
72
  item: MediaAsset;
70
73
  handleBase?: string;
74
+ locale?: string;
71
75
  api: MediaApiPort;
72
76
  onEdit?: () => void;
73
77
  onPreview?: () => void;
@@ -1,5 +1,5 @@
1
1
  import { useState } from 'react';
2
- import { safeMediaUrl, formatByteSize, mediaRowHandles } from '../../rules.js';
2
+ import { safeMediaUrl, formatByteSize, formatUploadDate, mediaRowHandles } from '../../rules.js';
3
3
  export function useMediaCard(props, _optional = {}) {
4
4
  /** Historical preview rationale, retained with the ported fallback chain:
5
5
  * The hard problem this rewrite had to solve: **the media list response carried no content type**
@@ -25,10 +25,13 @@ export function useMediaCard(props, _optional = {}) {
25
25
  // immediately when the versioned URL changes, including in an already-open lightbox.
26
26
  const stage = probe.src === src ? probe.stage : 'image';
27
27
  const handleBase = props.handleBase ?? mediaRowHandles({ media: [props.item] })[0];
28
+ const byteSize = props.item.byteSize === undefined ? null : formatByteSize({ bytes: props.item.byteSize }, { locale: props.locale ?? 'en-US' });
29
+ const uploadDate = formatUploadDate({ createdAt: props.item.createdAt }, { locale: props.locale ?? 'en-US' });
28
30
  return {
29
31
  ...props,
30
32
  busy: props.busy === true,
31
- byteSize: props.item.byteSize === undefined ? null : formatByteSize({ bytes: props.item.byteSize }),
33
+ byteSize, uploadDate,
34
+ metadataSeparator: byteSize && uploadDate ? ' · ' : '',
32
35
  preview: () => props.onPreview?.(),
33
36
  canPreview: !!props.onPreview && !!src && stage !== 'unsupported',
34
37
  imagePreview: !!props.onPreview && !!src && stage === 'image',
@@ -12,6 +12,7 @@ export declare function useMediaGrid(props: MediaGridProps, _optional?: Record<s
12
12
  handleBase: string;
13
13
  busy?: boolean;
14
14
  item: import("../../models.js").MediaAsset;
15
+ locale?: string;
15
16
  onEdit?: () => void;
16
17
  onPreview?: () => void;
17
18
  onTrash?: () => void;
@@ -77,4 +77,10 @@ export declare function mediaEmbedSnippet({ slug }: {
77
77
  export declare function mediaRowHandles({ media }: {
78
78
  media: readonly Pick<MediaAsset, 'id'>[];
79
79
  }, _optional?: Record<string, never>): readonly string[];
80
+ /** A calendar date in UTC, so cards do not shift an upload to yesterday across host timezones. */
81
+ export declare function formatUploadDate({ createdAt }: {
82
+ createdAt: string;
83
+ }, { locale }?: {
84
+ locale?: string;
85
+ }): string | null;
80
86
  //# sourceMappingURL=rules.d.ts.map
@@ -124,4 +124,11 @@ export function mediaRowHandles({ media }, _optional = {}) {
124
124
  return handle;
125
125
  });
126
126
  }
127
+ /** A calendar date in UTC, so cards do not shift an upload to yesterday across host timezones. */
128
+ export function formatUploadDate({ createdAt }, { locale = 'en-US' } = {}) {
129
+ const date = new Date(createdAt);
130
+ if (!Number.isFinite(date.getTime()))
131
+ return null;
132
+ return new Intl.DateTimeFormat(locale, { month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' }).format(date);
133
+ }
127
134
  //# sourceMappingURL=rules.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/admin",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Composable admin surface for Jini-hosted products: framework-free contracts, panel registry, and route model in `/core`; React shell and panels in `/react`. Import only the layers you need.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -282,10 +282,10 @@
282
282
  "react-dom": "^19.2.0"
283
283
  },
284
284
  "dependencies": {
285
- "@jini-ai/ui": "0.4.0",
286
- "@jini-ai/agentic": "0.4.0",
287
- "@jini-ai/agent-plugins": "0.4.0",
288
- "@jini-ai/ui-kit": "0.1.0"
285
+ "@jini-ai/agentic": "^0.4.1",
286
+ "@jini-ai/ui": "^0.4.2",
287
+ "@jini-ai/ui-kit": "^0.1.0",
288
+ "@jini-ai/agent-plugins": "^0.4.1"
289
289
  },
290
290
  "scripts": {
291
291
  "build": "tsc -p tsconfig.json",
@@ -1,13 +0,0 @@
1
- import type { ReactAdminKit } from './types.js';
2
- export declare const KitContext: import("react").Context<ReactAdminKit>;
3
- export declare function useKitComponent<K extends keyof ReactAdminKit>({ name }: {
4
- name: K;
5
- }, _optional?: Record<string, never>): ReactAdminKit[K];
6
- export declare function useKitProvider({ kit }: {
7
- kit?: Partial<ReactAdminKit>;
8
- }, _optional?: Record<string, never>): {
9
- Button: import("react").ComponentType<import("./types.js").ButtonProps>;
10
- Dialog: import("react").ComponentType<import("./types.js").DialogProps>;
11
- TextField: import("react").ComponentType<import("./types.js").TextFieldProps>;
12
- };
13
- //# sourceMappingURL=Kit.hooks.d.ts.map
@@ -1,10 +0,0 @@
1
- import { createContext, useContext, useMemo } from 'react';
2
- import { nativeKit } from './native.js';
3
- export const KitContext = createContext(nativeKit);
4
- export function useKitComponent({ name }, _optional = {}) {
5
- return useContext(KitContext)[name];
6
- }
7
- export function useKitProvider({ kit = {} }, _optional = {}) {
8
- return useMemo(() => ({ ...nativeKit, ...kit }), [kit]);
9
- }
10
- //# sourceMappingURL=Kit.hooks.js.map
@@ -1,38 +0,0 @@
1
- import type { ChangeEvent, MouseEvent, SyntheticEvent } from 'react';
2
- import type { ButtonProps, DialogProps, TextFieldProps } from './types.js';
3
- export declare function useNativeButton(props: ButtonProps, _optional?: Record<string, never>): {
4
- disabled: boolean;
5
- onClick: () => void;
6
- attrs: import("./types.js").KitAttrs;
7
- children: import("react").ReactNode;
8
- onPress?: () => void;
9
- pending?: boolean;
10
- variant?: "primary" | "secondary" | "danger";
11
- type?: "button" | "submit";
12
- ref?: import("react").Ref<HTMLButtonElement>;
13
- };
14
- export declare function useNativeTextField(props: TextFieldProps, _optional?: Record<string, never>): {
15
- id: string;
16
- onChange: (event: ChangeEvent<HTMLInputElement>) => void;
17
- attrs: import("./types.js").KitAttrs;
18
- label: string;
19
- value: string;
20
- onValueChange: (value: string) => void;
21
- disabled?: boolean;
22
- type?: "text" | "password" | "search";
23
- ref?: import("react").Ref<HTMLInputElement>;
24
- };
25
- export declare function useNativeDialog(props: DialogProps, _optional?: Record<string, never>): {
26
- dialogRef: import("react").RefObject<HTMLDialogElement | null>;
27
- titleId: string;
28
- onCancel: (event: SyntheticEvent<HTMLDialogElement>) => void;
29
- onClick: (event: MouseEvent<HTMLDialogElement>) => void;
30
- attrs: import("./types.js").KitAttrs;
31
- title: string;
32
- open: boolean;
33
- children: import("react").ReactNode;
34
- onClose: () => void;
35
- pending?: boolean;
36
- ref?: import("react").Ref<HTMLDialogElement>;
37
- };
38
- //# sourceMappingURL=Native.hooks.d.ts.map
@@ -1,65 +0,0 @@
1
- import { useEffect, useRef, useId, useImperativeHandle } from 'react';
2
- export function useNativeButton(props, _optional = {}) {
3
- return {
4
- ...props,
5
- disabled: props.disabled === true || props.pending === true,
6
- onClick: () => {
7
- if (!props.disabled && !props.pending)
8
- props.onPress?.();
9
- },
10
- };
11
- }
12
- export function useNativeTextField(props, _optional = {}) {
13
- const generated = useId();
14
- const id = props.attrs.id ?? generated;
15
- return {
16
- ...props,
17
- id,
18
- onChange: (event) => props.onValueChange(event.currentTarget.value),
19
- };
20
- }
21
- export function useNativeDialog(props, _optional = {}) {
22
- const dialogRef = useRef(null), trigger = useRef(null), titleId = useId();
23
- useImperativeHandle(props.ref, () => dialogRef.current, []);
24
- useEffect(() => {
25
- const dialog = dialogRef.current;
26
- if (!dialog || !props.open)
27
- return;
28
- trigger.current = document.activeElement;
29
- // <dialog> may not support showModal() in tests/older browsers. Guard already-open
30
- // calls, which throw InvalidStateError in real browsers, and fall back to open.
31
- if (typeof dialog.showModal === 'function') {
32
- if (!dialog.open)
33
- dialog.showModal();
34
- }
35
- else
36
- dialog.setAttribute('open', '');
37
- // Focus Cancel/Close, the always-available action, rather than a destructive control.
38
- const focus = dialog.querySelector('[data-jini-autofocus]') ?? dialog;
39
- focus.focus();
40
- return () => {
41
- if (typeof dialog.close === 'function') {
42
- if (dialog.open)
43
- dialog.close();
44
- }
45
- else
46
- dialog.removeAttribute('open');
47
- if (trigger.current instanceof HTMLElement)
48
- trigger.current.focus();
49
- };
50
- // Deliberately keyed on open, not content: navigation while already open must
51
- // not reopen the dialog or capture a different trigger for focus restoration.
52
- }, [props.open]);
53
- function onCancel(event) {
54
- // Always prevented so the effect stays the single source of truth for open/closed.
55
- event.preventDefault();
56
- if (!props.pending)
57
- props.onClose();
58
- }
59
- function onClick(event) {
60
- if (!props.pending && event.target === event.currentTarget)
61
- props.onClose();
62
- }
63
- return { ...props, dialogRef, titleId, onCancel, onClick };
64
- }
65
- //# sourceMappingURL=Native.hooks.js.map
@@ -1,12 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import type { ReactAdminKit, ButtonProps, TextFieldProps, DialogProps } from './types.js';
3
- export type { ReactAdminKit, ButtonProps, TextFieldProps, DialogProps, KitAttrs } from './types.js';
4
- export { KIT_CONTRACT_VERSION } from './types.js';
5
- export declare function KitProvider(props: {
6
- kit?: Partial<ReactAdminKit>;
7
- children: ReactNode;
8
- }, _optional?: Record<string, never>): import("react").JSX.Element;
9
- export declare const Button: import("react").ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
10
- export declare const TextField: import("react").ForwardRefExoticComponent<Omit<TextFieldProps, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
11
- export declare const Dialog: import("react").ForwardRefExoticComponent<Omit<DialogProps, "ref"> & import("react").RefAttributes<HTMLDialogElement>>;
12
- //# sourceMappingURL=index.d.ts.map
@@ -1,21 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { forwardRef } from 'react';
3
- import { KitContext, useKitComponent, useKitProvider } from './Kit.hooks.js';
4
- export { KIT_CONTRACT_VERSION } from './types.js';
5
- export function KitProvider(props, _optional = {}) {
6
- const kit = useKitProvider(props);
7
- return _jsx(KitContext.Provider, { value: kit, children: props.children });
8
- }
9
- export const Button = forwardRef(function Button(props, ref) {
10
- const Component = useKitComponent({ name: 'Button' });
11
- return _jsx(Component, { ...props, ref: ref });
12
- });
13
- export const TextField = forwardRef(function TextField(props, ref) {
14
- const Component = useKitComponent({ name: 'TextField' });
15
- return _jsx(Component, { ...props, ref: ref });
16
- });
17
- export const Dialog = forwardRef(function Dialog(props, ref) {
18
- const Component = useKitComponent({ name: 'Dialog' });
19
- return _jsx(Component, { ...props, ref: ref });
20
- });
21
- //# sourceMappingURL=index.js.map
@@ -1,3 +0,0 @@
1
- import type { ReactAdminKit } from './types.js';
2
- export declare const nativeKit: ReactAdminKit;
3
- //# sourceMappingURL=native.d.ts.map
@@ -1,21 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { forwardRef } from 'react';
3
- import { useNativeButton, useNativeDialog, useNativeTextField } from './Native.hooks.js';
4
- const NativeButton = forwardRef(function NativeButton(props, ref) {
5
- const vm = useNativeButton({ ...props, ref });
6
- return (_jsx("button", { ...vm.attrs, ref: vm.ref, type: vm.type ?? 'button', disabled: vm.disabled, "aria-busy": vm.pending, "data-jini-variant": vm.variant, onClick: vm.onClick, children: vm.children }));
7
- });
8
- const NativeTextField = forwardRef(function NativeTextField(props, ref) {
9
- const vm = useNativeTextField({ ...props, ref });
10
- return (_jsxs("label", { htmlFor: vm.id, children: [vm.label, _jsx("input", { ...vm.attrs, id: vm.id, ref: vm.ref, type: vm.type ?? 'text', value: vm.value, disabled: vm.disabled, onChange: vm.onChange })] }));
11
- });
12
- const NativeDialog = forwardRef(function NativeDialog(props, ref) {
13
- const vm = useNativeDialog({ ...props, ref });
14
- return (_jsxs("dialog", { ...vm.attrs, ref: vm.dialogRef, tabIndex: -1, "aria-labelledby": vm.titleId, "aria-busy": vm.pending, onCancel: vm.onCancel, onClick: vm.onClick, children: [_jsx("h2", { id: vm.titleId, children: vm.title }), vm.children] }));
15
- });
16
- export const nativeKit = Object.freeze({
17
- Button: NativeButton,
18
- Dialog: NativeDialog,
19
- TextField: NativeTextField,
20
- });
21
- //# sourceMappingURL=native.js.map
@@ -1,40 +0,0 @@
1
- import type { ComponentType, ReactNode, Ref } from 'react';
2
- export type KitAttrs = {
3
- readonly 'data-jini-part': string;
4
- readonly id?: string;
5
- } & Readonly<Partial<Record<`data-${string}` | `aria-${string}`, string | number | boolean>>>;
6
- export interface ButtonProps {
7
- readonly attrs: KitAttrs;
8
- readonly children: ReactNode;
9
- readonly onPress?: () => void;
10
- readonly disabled?: boolean;
11
- readonly pending?: boolean;
12
- readonly variant?: 'primary' | 'secondary' | 'danger';
13
- readonly type?: 'button' | 'submit';
14
- readonly ref?: Ref<HTMLButtonElement>;
15
- }
16
- export interface TextFieldProps {
17
- readonly attrs: KitAttrs;
18
- readonly label: string;
19
- readonly value: string;
20
- readonly onValueChange: (value: string) => void;
21
- readonly disabled?: boolean;
22
- readonly type?: 'text' | 'password' | 'search';
23
- readonly ref?: Ref<HTMLInputElement>;
24
- }
25
- export interface DialogProps {
26
- readonly attrs: KitAttrs;
27
- readonly title: string;
28
- readonly open: boolean;
29
- readonly children: ReactNode;
30
- readonly onClose: () => void;
31
- readonly pending?: boolean;
32
- readonly ref?: Ref<HTMLDialogElement>;
33
- }
34
- export interface ReactAdminKit {
35
- readonly Button: ComponentType<ButtonProps>;
36
- readonly Dialog: ComponentType<DialogProps>;
37
- readonly TextField: ComponentType<TextFieldProps>;
38
- }
39
- export declare const KIT_CONTRACT_VERSION = "1.0.0";
40
- //# sourceMappingURL=types.d.ts.map
@@ -1,2 +0,0 @@
1
- export const KIT_CONTRACT_VERSION = '1.0.0';
2
- //# sourceMappingURL=types.js.map
@@ -1,36 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- export interface OverlayEntry {
3
- readonly id: number;
4
- readonly render: () => ReactNode;
5
- }
6
- export interface OverlayPort {
7
- open<T>(required: {
8
- render: (required: {
9
- resolve: (value: T | null) => void;
10
- cancel: () => void;
11
- }) => ReactNode;
12
- }, optional?: {
13
- signal?: AbortSignal;
14
- }): Promise<T | null>;
15
- }
16
- /** One active overlay per host. A second request rejects instead of stealing the first
17
- * caller's promise; abort/dispose settle null and release every event listener. */
18
- export declare function createOverlayController(_required?: Record<string, never>, _optional?: Record<string, never>): {
19
- getSnapshot: () => Readonly<{
20
- entries: readonly OverlayEntry[];
21
- }>;
22
- subscribe: ({ listener }: {
23
- listener: () => void;
24
- }) => () => void;
25
- open<T>({ render }: {
26
- render: (required: {
27
- resolve: (value: T | null) => void;
28
- cancel: () => void;
29
- }) => ReactNode;
30
- }, { signal }?: {
31
- signal?: AbortSignal;
32
- }): Promise<T | null>;
33
- cancel(_required?: Record<string, never>, _optional?: Record<string, never>): void;
34
- dispose(_required?: Record<string, never>, _optional?: Record<string, never>): void;
35
- };
36
- //# sourceMappingURL=overlay.d.ts.map
@@ -1,35 +0,0 @@
1
- import { createControllerStore } from '../core/module/controller-store.js';
2
- /** One active overlay per host. A second request rejects instead of stealing the first
3
- * caller's promise; abort/dispose settle null and release every event listener. */
4
- export function createOverlayController(_required = {}, _optional = {}) {
5
- const store = createControllerStore({ initial: { entries: [] } });
6
- let sequence = 0, cancelActive = null;
7
- return {
8
- getSnapshot: store.getSnapshot, subscribe: store.subscribe,
9
- open({ render }, { signal } = {}) {
10
- if (store.signal.aborted || signal?.aborted)
11
- return Promise.resolve(null);
12
- if (cancelActive)
13
- return Promise.reject(new Error('An overlay is already open'));
14
- return new Promise(resolve => {
15
- let settled = false;
16
- const finish = (value) => {
17
- if (settled)
18
- return;
19
- settled = true;
20
- signal?.removeEventListener('abort', cancel);
21
- cancelActive = null;
22
- store.set({ patch: { entries: [] } });
23
- resolve(value);
24
- };
25
- const cancel = () => finish(null);
26
- cancelActive = cancel;
27
- signal?.addEventListener('abort', cancel, { once: true });
28
- store.set({ patch: { entries: [{ id: ++sequence, render: () => render({ resolve: finish, cancel }) }] } });
29
- });
30
- },
31
- cancel(_required = {}, _optional = {}) { cancelActive?.(); },
32
- dispose(_required = {}, _optional = {}) { cancelActive?.(); store.dispose(); },
33
- };
34
- }
35
- //# sourceMappingURL=overlay.js.map