@modufolio/panel 0.1.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/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/Builder/ImageNodeView.d.ts +33 -0
- package/dist/Builder/document.d.ts +17 -0
- package/dist/Builder/dragHandle.d.ts +13 -0
- package/dist/Builder/editing.d.ts +53 -0
- package/dist/Builder/plugins.d.ts +27 -0
- package/dist/Builder/schema.d.ts +20 -0
- package/dist/Components/Actions/Action.vue.d.ts +116 -0
- package/dist/Components/Actions/ActionGroup.vue.d.ts +25 -0
- package/dist/Components/Actions/ActionGroupItem.vue.d.ts +48 -0
- package/dist/Components/Auth/ForgotPassword.vue.d.ts +17 -0
- package/dist/Components/Auth/Login.vue.d.ts +33 -0
- package/dist/Components/Auth/ResetPassword.vue.d.ts +27 -0
- package/dist/Components/Auth/TwoFactor.vue.d.ts +32 -0
- package/dist/Components/Auth/TwoFactorVerify.vue.d.ts +31 -0
- package/dist/Components/Columns/BadgeColumn.vue.d.ts +34 -0
- package/dist/Components/Columns/BooleanColumn.vue.d.ts +78 -0
- package/dist/Components/Columns/CellActions.vue.d.ts +34 -0
- package/dist/Components/Columns/ColorColumn.vue.d.ts +84 -0
- package/dist/Components/Columns/CopyButton.vue.d.ts +15 -0
- package/dist/Components/Columns/DateColumn.vue.d.ts +60 -0
- package/dist/Components/Columns/IconColumn.vue.d.ts +56 -0
- package/dist/Components/Columns/ImageColumn.vue.d.ts +46 -0
- package/dist/Components/Columns/SelectColumn.vue.d.ts +71 -0
- package/dist/Components/Columns/TextColumn.vue.d.ts +69 -0
- package/dist/Components/Columns/TextInputColumn.vue.d.ts +75 -0
- package/dist/Components/Columns/ToggleColumn.vue.d.ts +91 -0
- package/dist/Components/Columns/TreeColumn.vue.d.ts +60 -0
- package/dist/Components/Columns/columnRegistry.d.ts +6 -0
- package/dist/Components/Composables/useInlineEdit.d.ts +27 -0
- package/dist/Components/Composables/useRelationship.d.ts +38 -0
- package/dist/Components/Core/Badge.vue.d.ts +47 -0
- package/dist/Components/Core/Button.vue.d.ts +131 -0
- package/dist/Components/Core/Dropdown.vue.d.ts +24 -0
- package/dist/Components/Core/Empty.vue.d.ts +54 -0
- package/dist/Components/Core/ErrorBoundary.vue.d.ts +24 -0
- package/dist/Components/Core/FlashMessages.vue.d.ts +7 -0
- package/dist/Components/Core/Icon.vue.d.ts +23 -0
- package/dist/Components/Core/Icons/DocumentLinesIcon.vue.d.ts +3 -0
- package/dist/Components/Core/Icons/SitemapIcon.vue.d.ts +3 -0
- package/dist/Components/Core/Label.vue.d.ts +52 -0
- package/dist/Components/Core/LoadingButton.vue.d.ts +18 -0
- package/dist/Components/Core/Modal.vue.d.ts +26 -0
- package/dist/Components/Core/Pagination.vue.d.ts +10 -0
- package/dist/Components/Core/SelectInput.vue.d.ts +26 -0
- package/dist/Components/Core/Tag.vue.d.ts +65 -0
- package/dist/Components/Core/TextInput.vue.d.ts +25 -0
- package/dist/Components/Core/iconRegistry.d.ts +3 -0
- package/dist/Components/Dialogs/ConfirmDialog.vue.d.ts +16 -0
- package/dist/Components/Dialogs/DeleteConfirmDialog.vue.d.ts +30 -0
- package/dist/Components/Dialogs/Dialog.vue.d.ts +81 -0
- package/dist/Components/Drawer/Drawer.vue.d.ts +130 -0
- package/dist/Components/Drawer/DrawerFieldGrid.vue.d.ts +39 -0
- package/dist/Components/Drawer/DrawerLink.vue.d.ts +96 -0
- package/dist/Components/Drawer/DrawerRelationList.vue.d.ts +81 -0
- package/dist/Components/Drawer/DrawerStack.vue.d.ts +93 -0
- package/dist/Components/Drawer/DrawerTabs.vue.d.ts +42 -0
- package/dist/Components/Drawer/NestedDrawerForm.vue.d.ts +55 -0
- package/dist/Components/Drawer/drawerFieldGrid.d.ts +25 -0
- package/dist/Components/Drawer/drawerTabs.d.ts +16 -0
- package/dist/Components/Drawer/useDrawerPage.d.ts +34 -0
- package/dist/Components/Drawer/useDrawerStack.d.ts +93 -0
- package/dist/Components/Drawer/useFocusedStackRow.d.ts +22 -0
- package/dist/Components/Drawer/useIsDrawer.d.ts +66 -0
- package/dist/Components/Drawer/visitDrawer.d.ts +48 -0
- package/dist/Components/Fields/BelongsToSelect.vue.d.ts +247 -0
- package/dist/Components/Fields/BlueprintForm.vue.d.ts +96 -0
- package/dist/Components/Fields/Calendar/CalendarPanel.vue.d.ts +63 -0
- package/dist/Components/Fields/Calendar/MonthGrid.vue.d.ts +82 -0
- package/dist/Components/Fields/CheckboxField.vue.d.ts +92 -0
- package/dist/Components/Fields/ColorPickerField.vue.d.ts +105 -0
- package/dist/Components/Fields/DatePickerField.vue.d.ts +102 -0
- package/dist/Components/Fields/DateRangePickerField.vue.d.ts +138 -0
- package/dist/Components/Fields/DateTimePickerField.vue.d.ts +134 -0
- package/dist/Components/Fields/FieldGrid.vue.d.ts +13 -0
- package/dist/Components/Fields/FileUploadField.vue.d.ts +117 -0
- package/dist/Components/Fields/MultiSelectField.vue.d.ts +129 -0
- package/dist/Components/Fields/ProseMirrorBuilderField.vue.d.ts +71 -0
- package/dist/Components/Fields/RangeField.vue.d.ts +122 -0
- package/dist/Components/Fields/RepeaterField.vue.d.ts +174 -0
- package/dist/Components/Fields/RichTextEditorField.vue.d.ts +118 -0
- package/dist/Components/Fields/SelectField.vue.d.ts +102 -0
- package/dist/Components/Fields/TagsField.vue.d.ts +102 -0
- package/dist/Components/Fields/TextField.vue.d.ts +138 -0
- package/dist/Components/Fields/TextareaField.vue.d.ts +111 -0
- package/dist/Components/Fields/TimePickerField.vue.d.ts +115 -0
- package/dist/Components/Fields/ToggleButtonsField.vue.d.ts +93 -0
- package/dist/Components/Fields/ToggleField.vue.d.ts +95 -0
- package/dist/Components/Fields/fieldRegistry.d.ts +25 -0
- package/dist/Components/Fields/fieldsFromSpec.d.ts +10 -0
- package/dist/Components/Fields/useBlueprint.d.ts +94 -0
- package/dist/Components/Fields/useFieldWidth.d.ts +10 -0
- package/dist/Components/Fields/validation.d.ts +43 -0
- package/dist/Components/Filters/DateRangeFilter.vue.d.ts +92 -0
- package/dist/Components/Filters/FacetedFilter.vue.d.ts +41 -0
- package/dist/Components/Filters/FilterIndicators.vue.d.ts +40 -0
- package/dist/Components/Filters/FilterPopover.vue.d.ts +38 -0
- package/dist/Components/Filters/MultiSelectFilter.vue.d.ts +51 -0
- package/dist/Components/Filters/NumberFilter.vue.d.ts +61 -0
- package/dist/Components/Filters/QueryBuilder.vue.d.ts +32 -0
- package/dist/Components/Filters/SelectFilter.vue.d.ts +61 -0
- package/dist/Components/Filters/TernaryFilter.vue.d.ts +73 -0
- package/dist/Components/Layout/AppLayout.vue.d.ts +137 -0
- package/dist/Components/Layout/Breadcrumbs.vue.d.ts +44 -0
- package/dist/Components/Layout/Cluster.vue.d.ts +88 -0
- package/dist/Components/Layout/Container.vue.d.ts +69 -0
- package/dist/Components/Layout/Grid.vue.d.ts +110 -0
- package/dist/Components/Layout/PageHeader.vue.d.ts +62 -0
- package/dist/Components/Layout/Sidebar.vue.d.ts +59 -0
- package/dist/Components/Layout/Stack.vue.d.ts +69 -0
- package/dist/Components/Layout/TopNavigation.vue.d.ts +124 -0
- package/dist/Components/Media/MediaPickerDialog.vue.d.ts +20 -0
- package/dist/Components/Media/mediaEndpoints.d.ts +18 -0
- package/dist/Components/Notifications/Toast.vue.d.ts +17 -0
- package/dist/Components/Notifications/useToast.d.ts +43 -0
- package/dist/Components/Relations/RelationManager.vue.d.ts +232 -0
- package/dist/Components/Sections/FieldsSection.vue.d.ts +56 -0
- package/dist/Components/Sections/FilesSection.vue.d.ts +120 -0
- package/dist/Components/Sections/FormSection.vue.d.ts +111 -0
- package/dist/Components/Sections/InfoSection.vue.d.ts +77 -0
- package/dist/Components/Sections/Section.vue.d.ts +67 -0
- package/dist/Components/Table/ColumnToggle.vue.d.ts +42 -0
- package/dist/Components/Table/ExportButton.vue.d.ts +93 -0
- package/dist/Components/Table/SchemaCell.d.ts +50 -0
- package/dist/Components/Table/SchemaFilterPanel.vue.d.ts +64 -0
- package/dist/Components/Table/SchemaTable.vue.d.ts +255 -0
- package/dist/Components/Table/Table.vue.d.ts +232 -0
- package/dist/Components/Table/TableEmptyState.vue.d.ts +24 -0
- package/dist/Components/Table/TablePagination.vue.d.ts +71 -0
- package/dist/Components/Table/TableSkeletonRows.vue.d.ts +52 -0
- package/dist/Components/Table/TableSortButton.vue.d.ts +38 -0
- package/dist/Components/Table/TableToolbar.vue.d.ts +62 -0
- package/dist/Components/Table/schemaLinks.d.ts +16 -0
- package/dist/Components/Table/tableSchema.d.ts +243 -0
- package/dist/Components/Table/tableTypes.d.ts +13 -0
- package/dist/Components/Table/useSchemaActions.d.ts +82 -0
- package/dist/Components/Table/useSchemaFilters.d.ts +28 -0
- package/dist/Components/Table/useTableExport.d.ts +16 -0
- package/dist/Components/Table/useTableGrouping.d.ts +9 -0
- package/dist/Components/Table/useTableRowFocus.d.ts +27 -0
- package/dist/Components/Table/useTableSelection.d.ts +15 -0
- package/dist/Components/Table/useTableTree.d.ts +23 -0
- package/dist/Components/Widgets/StatCard.vue.d.ts +81 -0
- package/dist/Components/Widgets/StatsWidget.vue.d.ts +27 -0
- package/dist/Components/Wizard/Wizard.vue.d.ts +99 -0
- package/dist/Composables/useAsyncData.d.ts +48 -0
- package/dist/Composables/useDeleteConfirmation.d.ts +86 -0
- package/dist/Composables/useDragReorder.d.ts +12 -0
- package/dist/Composables/useFieldSaver.d.ts +15 -0
- package/dist/Composables/useFocusTrap.d.ts +5 -0
- package/dist/Composables/useLocalStoragePersistence.d.ts +18 -0
- package/dist/Composables/useNestedDrawerForm.d.ts +58 -0
- package/dist/Composables/usePagination.d.ts +23 -0
- package/dist/Composables/usePendingKeys.d.ts +30 -0
- package/dist/Composables/useQuery.d.ts +61 -0
- package/dist/Composables/useReconciled.d.ts +22 -0
- package/dist/Composables/useUnsavedChangesWarning.d.ts +16 -0
- package/dist/Primitives/hideOthers.d.ts +12 -0
- package/dist/Primitives/teleportTarget.d.ts +2 -0
- package/dist/Primitives/useAnchoredPosition.d.ts +32 -0
- package/dist/Primitives/useArrowNavigation.d.ts +30 -0
- package/dist/Primitives/useBodyScrollLock.d.ts +9 -0
- package/dist/Primitives/useDismissableLayer.d.ts +46 -0
- package/dist/Primitives/useId.d.ts +13 -0
- package/dist/Primitives/useTypeahead.d.ts +11 -0
- package/dist/Utils/apiFetch.d.ts +28 -0
- package/dist/Utils/csrf.d.ts +2 -0
- package/dist/Utils/dates.d.ts +50 -0
- package/dist/Utils/moduleSingleton.d.ts +21 -0
- package/dist/Utils/optimistic.d.ts +33 -0
- package/dist/Utils/reconcile.d.ts +17 -0
- package/dist/Utils/tagsChanged.d.ts +16 -0
- package/dist/Utils/url.d.ts +40 -0
- package/dist/Utils/writeGate.d.ts +82 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +157 -0
- package/dist/index.js +27193 -0
- package/dist/index.js.map +1 -0
- package/dist/injectionKeys.d.ts +10 -0
- package/dist/plugin.d.ts +36 -0
- package/dist/types/menu.d.ts +11 -0
- package/docs/async-writes.md +159 -0
- package/docs/custom-fields.md +71 -0
- package/docs/drawer-protocol.md +130 -0
- package/docs/overlays.md +106 -0
- package/docs/relation-fields.md +112 -0
- package/docs/roadmap.md +38 -0
- package/docs/table-schema.md +371 -0
- package/package.json +150 -0
- package/styles/buttons.css +31 -0
- package/styles/form.css +74 -0
- package/styles/index.css +15 -0
- package/styles/tokens.css +79 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Warn before a navigation would discard unsaved edits.
|
|
3
|
+
*
|
|
4
|
+
* Hooks Inertia's `before` event, so it covers every ordinary visit —
|
|
5
|
+
* including Logout, which is a POST visit like any other — plus
|
|
6
|
+
* `beforeunload` for tab closes and hard refreshes.
|
|
7
|
+
*
|
|
8
|
+
* Takes Inertia's form object, or a getter for anything else that knows
|
|
9
|
+
* whether it is dirty (the nested drawer form's computed, say). Read at
|
|
10
|
+
* event time either way, never captured.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useUnsavedChangesWarning(form: {
|
|
13
|
+
isDirty: boolean;
|
|
14
|
+
} | (() => boolean)): {
|
|
15
|
+
allowNextNavigation: () => void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Take everything outside `target` out of the accessibility tree, walking up to
|
|
3
|
+
* the body and treating each level's siblings. Returns the undo.
|
|
4
|
+
*
|
|
5
|
+
* @param exempt Elements to leave alone — the other open overlays. They are
|
|
6
|
+
* siblings of `target` under the teleport root, and hiding a dropdown that
|
|
7
|
+
* belongs to the dialog on top of it would silence the very control the user
|
|
8
|
+
* just opened.
|
|
9
|
+
*/
|
|
10
|
+
export declare function hideOthers(target: Element, exempt?: Element[]): () => void;
|
|
11
|
+
/** Whether `element` sits in a subtree an overlay has taken out of the page. */
|
|
12
|
+
export declare function isHiddenBehindOverlay(element: HTMLElement): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Placement } from '@floating-ui/vue';
|
|
2
|
+
import { CSSProperties, Ref } from 'vue';
|
|
3
|
+
export interface AnchoredPositionOptions {
|
|
4
|
+
/** Preferred side; flipped automatically when it does not fit. */
|
|
5
|
+
placement?: Placement;
|
|
6
|
+
/** Gap between anchor and panel, in pixels. */
|
|
7
|
+
offset?: number;
|
|
8
|
+
/** Keep this much clearance from the viewport edge. */
|
|
9
|
+
padding?: number;
|
|
10
|
+
/** Size the panel to the anchor — what a combobox listbox wants. */
|
|
11
|
+
matchWidth?: boolean;
|
|
12
|
+
/** Cap the panel's height to the space actually available. */
|
|
13
|
+
fitViewport?: boolean;
|
|
14
|
+
/** Never shrink the panel below this, even in a cramped viewport. */
|
|
15
|
+
minHeight?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface AnchoredPosition {
|
|
18
|
+
/** Bind to the panel's `style`. */
|
|
19
|
+
floatingStyles: Readonly<Ref<CSSProperties>>;
|
|
20
|
+
/** The side finally used, after collision handling. */
|
|
21
|
+
placement: Readonly<Ref<Placement>>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Position a floating panel against its trigger.
|
|
25
|
+
*
|
|
26
|
+
* Replaces the hand-rolled `getBoundingClientRect()` + clamp that each dropdown
|
|
27
|
+
* used to carry: flipping when the panel would run off the bottom, shifting it
|
|
28
|
+
* back inside the viewport, capping its height, and — via `autoUpdate` —
|
|
29
|
+
* following the anchor through scrolls, resizes and layout changes without
|
|
30
|
+
* every component registering its own scroll and resize listeners.
|
|
31
|
+
*/
|
|
32
|
+
export declare function useAnchoredPosition(anchor: Ref<HTMLElement | null>, panel: Ref<HTMLElement | null>, open: Ref<boolean>, options?: AnchoredPositionOptions): AnchoredPosition;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
export type NavigationOrientation = 'vertical' | 'horizontal' | 'both';
|
|
3
|
+
export interface ArrowNavigationOptions {
|
|
4
|
+
/** Which arrow keys move the selection. @default 'vertical' */
|
|
5
|
+
orientation?: NavigationOrientation;
|
|
6
|
+
/** Wrap around at the ends. @default true */
|
|
7
|
+
loop?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The index an arrow / Home / End press should move to, or -1 when the key is
|
|
11
|
+
* not a navigation key and the caller should let it through.
|
|
12
|
+
*
|
|
13
|
+
* Pure on purpose: a listbox driven by a highlighted index (a combobox) and a
|
|
14
|
+
* menu driven by DOM focus want the same key model but move different things.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveNavigationIndex(key: string, currentIndex: number, count: number, options?: ArrowNavigationOptions): number;
|
|
17
|
+
/**
|
|
18
|
+
* Roving focus over the items inside a container — the WAI-ARIA menu keyboard
|
|
19
|
+
* model: arrows move focus between items, Home/End jump to the ends, and typing
|
|
20
|
+
* jumps to an item by name.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useArrowNavigation(container: Ref<HTMLElement | null>, options?: ArrowNavigationOptions & {
|
|
23
|
+
itemSelector?: string;
|
|
24
|
+
}): {
|
|
25
|
+
items: () => HTMLElement[];
|
|
26
|
+
focusFirst: () => void;
|
|
27
|
+
focusIndex: (index: number) => void;
|
|
28
|
+
onKeydown: (event: KeyboardEvent) => boolean;
|
|
29
|
+
resetTypeahead: () => void;
|
|
30
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* Lock page scroll while an overlay is open.
|
|
4
|
+
*
|
|
5
|
+
* Returns a writable ref: set it to `true` to hold the lock, `false` to release.
|
|
6
|
+
* The page only scrolls again once *every* holder has released, so stacked
|
|
7
|
+
* overlays can each manage their own lock without fighting over `body.overflow`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useBodyScrollLock(initialState?: boolean): Ref<boolean>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { MaybeRefOrGetter } from 'vue';
|
|
2
|
+
export type DismissReason = 'escape' | 'pointer-outside';
|
|
3
|
+
export interface DismissableLayerOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Everything that counts as "inside" this layer. A teleported panel and the
|
|
6
|
+
* trigger that opened it live in different parts of the DOM, and a press on
|
|
7
|
+
* the trigger must not read as an outside interaction — the trigger's own
|
|
8
|
+
* click handler is what closes the layer in that case.
|
|
9
|
+
*/
|
|
10
|
+
elements: () => Array<HTMLElement | null | undefined>;
|
|
11
|
+
/**
|
|
12
|
+
* Called when this layer is the top of the stack and the user pressed Escape
|
|
13
|
+
* or interacted outside it. The layer decides what to do: a non-closable
|
|
14
|
+
* dialog can ignore it, and Escape still will not reach the layer beneath.
|
|
15
|
+
*/
|
|
16
|
+
onDismiss: (reason: DismissReason) => void;
|
|
17
|
+
/** Dismiss on a press outside the layer. @default true */
|
|
18
|
+
dismissOnOutsidePointer?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The panel that owns the screen while this layer is on top. Supplying it
|
|
21
|
+
* marks the layer modal: the rest of the page is hidden from assistive
|
|
22
|
+
* technology, since `aria-modal="true"` is not reliably honoured on its own.
|
|
23
|
+
* Only the highest modal layer applies, so stacked overlays cannot each hide
|
|
24
|
+
* the other and leave the page with nothing announced.
|
|
25
|
+
*/
|
|
26
|
+
modalElement?: () => HTMLElement | null;
|
|
27
|
+
}
|
|
28
|
+
/** Whether any modal overlay is open. */
|
|
29
|
+
export declare function hasModalLayer(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Whether `element` is (or is inside) the overlay that owns focus.
|
|
32
|
+
*
|
|
33
|
+
* A focus trap has to ask: an overlay teleported next to an open drawer sits
|
|
34
|
+
* outside the drawer's subtree, so the drawer's trap would otherwise drag focus
|
|
35
|
+
* straight back out of whatever opened on top of it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isTopmostModalLayer(element: HTMLElement | null): boolean;
|
|
38
|
+
/** `Esc` is what older browsers report, and what Vue's `.esc` modifier sends. */
|
|
39
|
+
export declare function isEscapeKey(event: KeyboardEvent): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Register an overlay in the global layer stack for as long as `active` holds.
|
|
42
|
+
*
|
|
43
|
+
* The stack is what makes nesting behave: Escape and outside presses go to the
|
|
44
|
+
* layer the user can actually see, and nothing reaches the layers beneath it.
|
|
45
|
+
*/
|
|
46
|
+
export declare function useDismissableLayer(active: MaybeRefOrGetter<boolean>, options: DismissableLayerOptions): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A DOM id for wiring ARIA relationships (`aria-labelledby`, `aria-controls`, …).
|
|
3
|
+
*
|
|
4
|
+
* Prefers Vue's own `useId()`, which is app-scoped and produces the same value
|
|
5
|
+
* on a server render and its hydration — `Math.random()` does not, and a panel
|
|
6
|
+
* page rendered on the server would hydrate with mismatched ids. Vue's version
|
|
7
|
+
* needs a component instance, so anything called outside one (a bare composable
|
|
8
|
+
* in a test) falls back to a module counter.
|
|
9
|
+
*
|
|
10
|
+
* @param deterministicId Caller-supplied id, returned untouched when present,
|
|
11
|
+
* so a component can accept an `id` prop and still have a default.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useId(deterministicId?: string | null, prefix?: string): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** A printable single character, i.e. something a user typed to search by. */
|
|
2
|
+
export declare function isTypeaheadKey(event: KeyboardEvent): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Jump to an item by typing its first letters — the keyboard behaviour every
|
|
5
|
+
* native menu and select has, and the reason a long list is usable without a
|
|
6
|
+
* mouse. Repeating one character cycles through the items starting with it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useTypeahead(): {
|
|
9
|
+
onTypeaheadKey: (key: string, values: string[], currentIndex: number) => number;
|
|
10
|
+
resetTypeahead: () => void;
|
|
11
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small wrapper around `fetch` for the panel's JSON API calls.
|
|
3
|
+
*
|
|
4
|
+
* It centralises the three things every hand-rolled `fetch` in the panel used
|
|
5
|
+
* to do (inconsistently): attach the CSRF token, send/parse JSON, and turn a
|
|
6
|
+
* non-2xx response into a thrown error carrying the parsed body. Prefer this
|
|
7
|
+
* over calling `fetch` directly so CSRF and error handling stay uniform.
|
|
8
|
+
*
|
|
9
|
+
* const data = await apiFetch(panelUrl('/api/media/bulk'), {
|
|
10
|
+
* method: 'DELETE',
|
|
11
|
+
* body: { media_ids: ids },
|
|
12
|
+
* })
|
|
13
|
+
*/
|
|
14
|
+
export interface ApiFetchOptions extends Omit<RequestInit, 'body'> {
|
|
15
|
+
/** Plain objects are JSON-encoded; strings/FormData/Blob are sent as-is. */
|
|
16
|
+
body?: unknown;
|
|
17
|
+
}
|
|
18
|
+
/** Thrown on a non-2xx response. `body` holds the parsed error payload. */
|
|
19
|
+
export declare class ApiError extends Error {
|
|
20
|
+
readonly status: number;
|
|
21
|
+
readonly body: unknown;
|
|
22
|
+
constructor(message: string, status: number, body: unknown);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Perform a JSON API request. Resolves with the parsed response body (or null
|
|
26
|
+
* for an empty/204 response) and rejects with an {@link ApiError} on non-2xx.
|
|
27
|
+
*/
|
|
28
|
+
export declare function apiFetch<T = unknown>(url: string, options?: ApiFetchOptions): Promise<T>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure date math for the date picker. No DOM, no timezones beyond "local".
|
|
3
|
+
*
|
|
4
|
+
* Every Date produced here is a local-time midnight. Dates are constructed
|
|
5
|
+
* through setters rather than string or numeric constructor arguments:
|
|
6
|
+
* `new Date('2026-03-05')` parses as UTC midnight (off by a day west of
|
|
7
|
+
* Greenwich) and `new Date(26, 2, 5)` maps year 26 to 1926.
|
|
8
|
+
*/
|
|
9
|
+
/** Build a local-midnight Date. Month is 0-based, like the Date API. */
|
|
10
|
+
export declare function makeDate(year: number, month: number, day: number): Date;
|
|
11
|
+
/** Strict `YYYY-MM-DD`. Rejects overflow like 2026-02-31. Null for anything else. */
|
|
12
|
+
export declare function parseISO(value: string): Date | null;
|
|
13
|
+
export declare function formatISO(date: Date): string;
|
|
14
|
+
/** Local midnight of the same calendar day. */
|
|
15
|
+
export declare function atMidnight(date: Date): Date;
|
|
16
|
+
export declare function todayMidnight(): Date;
|
|
17
|
+
/** Same calendar day, ignoring time. Null-safe: two nulls are not equal. */
|
|
18
|
+
export declare function dateEquals(a: Date | null, b: Date | null): boolean;
|
|
19
|
+
export declare function addDays(date: Date, days: number): Date;
|
|
20
|
+
/**
|
|
21
|
+
* Month arithmetic that clamps instead of overflowing: Jan 31 + 1 month is
|
|
22
|
+
* Feb 28/29, not Mar 3. `anchorDay` lets a caller keep the originally chosen
|
|
23
|
+
* day across repeated jumps, so Jan 31 → Feb 28 → Mar 31 rather than Mar 28.
|
|
24
|
+
*/
|
|
25
|
+
export declare function addMonths(date: Date, months: number, anchorDay?: number): Date;
|
|
26
|
+
export declare function startOfMonth(date: Date): Date;
|
|
27
|
+
export declare function daysInMonth(year: number, month: number): number;
|
|
28
|
+
/** Is `date` within [min, max] and not vetoed by the callback? */
|
|
29
|
+
export declare function dateAllowed(date: Date, min: Date | null, max: Date | null, isDisabled?: (date: Date) => boolean): boolean;
|
|
30
|
+
/** Nearest date inside [min, max]; the date itself when already inside. */
|
|
31
|
+
export declare function clampToRange(date: Date, min: Date | null, max: Date | null): Date;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve a 2-digit year to the century that lands it within ±50 years of
|
|
34
|
+
* the reference date: "1/1/40" means 2040 next to 2026, 1940 next to 1985.
|
|
35
|
+
*/
|
|
36
|
+
export declare function adjustTwoDigitYear(year: number, reference: Date): number;
|
|
37
|
+
/**
|
|
38
|
+
* The weeks of one month as rows of 7. Leading/trailing cells outside the
|
|
39
|
+
* month are null (rendered empty, not as adjacent-month days).
|
|
40
|
+
* `firstDayOfWeek`: 0 = Sunday, 1 = Monday.
|
|
41
|
+
*/
|
|
42
|
+
export declare function monthMatrix(year: number, month: number, firstDayOfWeek?: number): Array<Array<Date | null>>;
|
|
43
|
+
/**
|
|
44
|
+
* Lenient user-input parsing: d/m/y, d-m-y, d.m.y, with 2- or 4-digit years
|
|
45
|
+
* (2-digit resolved near `reference`), plus strict ISO. Day-first, matching
|
|
46
|
+
* the panel's display format. Null when the text is not a real date.
|
|
47
|
+
*/
|
|
48
|
+
export declare function parseUserInput(text: string, reference?: Date): Date | null;
|
|
49
|
+
/** Display format for the panel: `dd/mm/yyyy`. */
|
|
50
|
+
export declare function formatDisplay(date: Date): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module-level state that survives a duplicated copy of this package.
|
|
3
|
+
*
|
|
4
|
+
* A bundler can instantiate the same `.ts` module twice — Vite's dep
|
|
5
|
+
* pre-bundler, for example, inlines a package's TS modules while leaving its
|
|
6
|
+
* `.vue` SFCs as external raw-source imports, so a store module imported from
|
|
7
|
+
* both sides of that boundary splits: `useToast().success()` pushes onto one
|
|
8
|
+
* copy's array while `<Toast />` renders the other's, and no toast ever
|
|
9
|
+
* appears. It fails silently, which is what makes it expensive to find.
|
|
10
|
+
*
|
|
11
|
+
* Keying the state off `globalThis` with `Symbol.for` hands every copy the
|
|
12
|
+
* same object, whatever the module graph did. In a single-copy build the cost
|
|
13
|
+
* is one symbol lookup at module init.
|
|
14
|
+
*
|
|
15
|
+
* Only for state that must be process-wide. Anything scoped to a component or
|
|
16
|
+
* an app instance belongs in provide/inject instead — a global would leak
|
|
17
|
+
* across SSR requests.
|
|
18
|
+
*
|
|
19
|
+
* (Pattern from frappe-ui's moduleSingleton, which exists for the same bug.)
|
|
20
|
+
*/
|
|
21
|
+
export declare function moduleSingleton<T>(key: string, create: () => T): T;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run an optimistic mutation: apply the local change immediately, fire the
|
|
3
|
+
* request, and roll back if it rejects. This replaces the hand-rolled
|
|
4
|
+
* snapshot -> mutate -> catch/rollback pattern duplicated across the media
|
|
5
|
+
* mutation composables.
|
|
6
|
+
*
|
|
7
|
+
* const ok = await optimistic(
|
|
8
|
+
* () => {
|
|
9
|
+
* const previous = file.is_favorite
|
|
10
|
+
* file.is_favorite = !previous
|
|
11
|
+
* return () => { file.is_favorite = previous } // rollback
|
|
12
|
+
* },
|
|
13
|
+
* () => apiFetch(panelUrl(`/api/media/${file.id}`), { method: 'PATCH', body: {...} }),
|
|
14
|
+
* writeKey('media', file.id, 'is_favorite'),
|
|
15
|
+
* )
|
|
16
|
+
*
|
|
17
|
+
* `apply` performs the optimistic change and returns a rollback thunk.
|
|
18
|
+
* Resolves true on success, false if the request failed (rolled back).
|
|
19
|
+
*
|
|
20
|
+
* The `key` orders overlapping mutations of the same state: without it, two
|
|
21
|
+
* quick toggles interleave so that the FIRST one failing rolls state back to
|
|
22
|
+
* before either ran, erasing the second's applied change. With a key, each
|
|
23
|
+
* application is recorded on the writeGate at dispatch, and a failure only
|
|
24
|
+
* rolls back while its own write is still the newest for that key — a stale
|
|
25
|
+
* failure keeps its hands off state a later mutation now owns. (When every
|
|
26
|
+
* overlapping write fails, the newest rollback restores the previous
|
|
27
|
+
* optimistic value rather than the original — the older writes' truth is
|
|
28
|
+
* unknowable locally at that point, and their toasts already fired.)
|
|
29
|
+
*
|
|
30
|
+
* `key` is optional only so unrelated one-shot mutations don't have to mint
|
|
31
|
+
* one; any state that can be mutated twice concurrently should pass it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function optimistic(apply: () => () => void, request: () => Promise<unknown>, key?: string): Promise<boolean>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge a fresh server payload into existing reactive state *in place*,
|
|
3
|
+
* preserving object and array identity so only changed leaves update — the
|
|
4
|
+
* analog of Solid's `reconcile`.
|
|
5
|
+
*
|
|
6
|
+
* The panel's recurring pattern is `reactive({ ...props.media })` plus a watch
|
|
7
|
+
* that throws the local copy away and rebuilds it whenever Inertia re-sends the
|
|
8
|
+
* prop. That resets identity on every navigation (flicker, lost transient UI
|
|
9
|
+
* state, full re-render). `reconcile(local, next)` instead diffs `next` into
|
|
10
|
+
* `local`: matched objects are updated in place, arrays are keyed by `id` so
|
|
11
|
+
* unchanged rows keep their identity, and only genuinely new/removed entries
|
|
12
|
+
* move.
|
|
13
|
+
*
|
|
14
|
+
* Both arguments must be plain data (objects/arrays/primitives) — call it with
|
|
15
|
+
* a structured clone of the server data, not a live reference you also mutate.
|
|
16
|
+
*/
|
|
17
|
+
export declare function reconcile<T>(target: T, source: T, key?: string): T;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface TagsChangedPayload {
|
|
2
|
+
action?: 'attach' | 'detach';
|
|
3
|
+
tagId?: string | number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Whether a `tags-changed` event (emitted by MediaInspectorContainer after
|
|
7
|
+
* any tag attach/detach, single-media or bulk) should trigger a refetch on
|
|
8
|
+
* a page filtered to one specific tag (e.g. Media/Tags.vue).
|
|
9
|
+
*
|
|
10
|
+
* Only a detach of THAT exact tag can drop the current item out of such a
|
|
11
|
+
* view. Attaching a tag must never trigger it, and neither must detaching
|
|
12
|
+
* some other, unrelated tag from the same item — either would clear the
|
|
13
|
+
* current selection and kick the user out of whatever they're still
|
|
14
|
+
* editing, for no reason (this broke twice before landing on this rule).
|
|
15
|
+
*/
|
|
16
|
+
export declare function shouldRefetchOnTagsChanged(payload: TagsChangedPayload | undefined, currentTagId: string | number): boolean;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL and HTML-escaping helpers shared by the rich-text editors.
|
|
3
|
+
*
|
|
4
|
+
* The panel has several hand-rolled contenteditable editors that serialize
|
|
5
|
+
* their content to HTML strings by hand. These helpers give them one place to
|
|
6
|
+
* escape attribute values and to reject dangerous URL schemes so a typed or
|
|
7
|
+
* pasted `javascript:` link can't turn into stored XSS.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Escape a string for safe interpolation into a double-quoted HTML attribute
|
|
11
|
+
* value. Escaping `"` and `<`/`>`/`&` prevents attribute-injection breakouts.
|
|
12
|
+
*/
|
|
13
|
+
export declare function escapeHtml(value: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Return `url` if it is safe to use as an href, otherwise ''.
|
|
16
|
+
*
|
|
17
|
+
* Safe means one of: a scheme on the allowlist (http/https/mailto/tel), a
|
|
18
|
+
* relative or same-document reference, or no scheme at all (a bare host like
|
|
19
|
+
* `example.com`). Everything else — `javascript:`, `data:`, `vbscript:`, and
|
|
20
|
+
* equally anything we simply do not recognise — is rejected.
|
|
21
|
+
*
|
|
22
|
+
* Deliberately an allowlist. A denylist has to name every scheme that can
|
|
23
|
+
* execute and silently admits the one nobody thought of.
|
|
24
|
+
*/
|
|
25
|
+
export declare function sanitizeUrl(url: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Coerce user-typed link input into a safe href value:
|
|
28
|
+
* example.com → https://example.com
|
|
29
|
+
* /path, #hash → unchanged
|
|
30
|
+
* mailto:a@b.c → unchanged
|
|
31
|
+
* javascript:… → '' (rejected)
|
|
32
|
+
*/
|
|
33
|
+
export declare function normalizeUrl(input: string): string;
|
|
34
|
+
export declare function setPanelBaseUrl(base: string): void;
|
|
35
|
+
export declare function getPanelBaseUrl(): string;
|
|
36
|
+
/**
|
|
37
|
+
* Prefix a panel-relative path with the configured mount path:
|
|
38
|
+
* panelUrl('/api/media') → '/panel/api/media' (baseUrl '/panel')
|
|
39
|
+
*/
|
|
40
|
+
export declare function panelUrl(path: string): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stale-write gate: ordering for concurrent writes to the same record.
|
|
3
|
+
*
|
|
4
|
+
* Two concurrent writes to one record must leave local state on the newer
|
|
5
|
+
* one, not on whichever response settled last. The order that matters is the
|
|
6
|
+
* order the requests were DISPATCHED in: a request dispatched later carries
|
|
7
|
+
* the newer intent, whatever the network does with it afterwards.
|
|
8
|
+
*
|
|
9
|
+
* So every write takes a sequence number when it is dispatched, and presents
|
|
10
|
+
* that number to `admit` before it lands. A write whose sequence is older
|
|
11
|
+
* than the newest already applied for that key is rejected. The record is
|
|
12
|
+
* per key, so writes to different records never gate each other.
|
|
13
|
+
*
|
|
14
|
+
* A sequence counter, not a timestamp: clocks move, and two responses can
|
|
15
|
+
* share a millisecond. The counter is store-wide and only ever incremented —
|
|
16
|
+
* a per-key counter restarting at 0 would let a number minted for one key
|
|
17
|
+
* match a later slot's, which is how a deleted record comes back to life.
|
|
18
|
+
* Comparison is still per key, which is what "per-key sequence" buys.
|
|
19
|
+
*
|
|
20
|
+
* Design ported from frappe-ui's writeGate (src/data-fetching/writeGate.ts),
|
|
21
|
+
* which solves the same bug for its doc/list stores. Here the keys are
|
|
22
|
+
* caller-composed strings — `writeKey('media', file.id)`, optionally with a
|
|
23
|
+
* field suffix — because the panel has no doctype registry to derive them.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* What a dispatched write presents to the gate: the sequence number of the
|
|
27
|
+
* request that produced it, and whether the write may RECORD that number.
|
|
28
|
+
*
|
|
29
|
+
* Only a mutating request records. A later-dispatched request that failed
|
|
30
|
+
* wrote nothing on the server, so it must not make an older success stale;
|
|
31
|
+
* and a read is not an ordered write at all. A non-recording stamp is
|
|
32
|
+
* admitted on its sequence and records nothing — which also makes it the
|
|
33
|
+
* way to ask "is my write still the newest?" without moving the gate.
|
|
34
|
+
*/
|
|
35
|
+
export type DispatchStamp = {
|
|
36
|
+
sequence: number;
|
|
37
|
+
record: boolean;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A write that did not come from a dispatched request — seeding state in a
|
|
41
|
+
* test, a value that never went over the wire. There is no order to compare
|
|
42
|
+
* it against, so it is always admitted and records nothing.
|
|
43
|
+
*
|
|
44
|
+
* A value a caller has to name, not an argument they can omit: the
|
|
45
|
+
* un-ordered case is a decision at the call site rather than the silent
|
|
46
|
+
* default of a forgotten parameter.
|
|
47
|
+
*/
|
|
48
|
+
export declare const LOCAL_WRITE: unique symbol;
|
|
49
|
+
export type WriteStamp = DispatchStamp | typeof LOCAL_WRITE;
|
|
50
|
+
declare class WriteGate {
|
|
51
|
+
private counter;
|
|
52
|
+
private applied;
|
|
53
|
+
/**
|
|
54
|
+
* Take the next sequence number. Called when a request is dispatched — not
|
|
55
|
+
* when it settles — so the number reflects the order the writes were
|
|
56
|
+
* intended in.
|
|
57
|
+
*/
|
|
58
|
+
next(record: boolean): DispatchStamp;
|
|
59
|
+
/**
|
|
60
|
+
* Whether this write may land on this key.
|
|
61
|
+
*
|
|
62
|
+
* Asking is booking: an admitted recording write becomes the newest for
|
|
63
|
+
* the key, so a later call carrying an older sequence is rejected. An
|
|
64
|
+
* equal sequence passes — the two halves of one response share a number.
|
|
65
|
+
*/
|
|
66
|
+
admit(key: string, stamp: WriteStamp): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* The record is gone on the server: reject every write in flight for it.
|
|
69
|
+
*
|
|
70
|
+
* Stamped with a FRESH number rather than the delete's own, because a
|
|
71
|
+
* delete becomes true when it SETTLES. Any write still in flight at that
|
|
72
|
+
* moment must have committed before the delete to have succeeded, so its
|
|
73
|
+
* response is dead data and must not re-create the record. Anything
|
|
74
|
+
* dispatched after this point takes a higher number and is admitted again.
|
|
75
|
+
*/
|
|
76
|
+
seal(key: string): void;
|
|
77
|
+
clear(): void;
|
|
78
|
+
}
|
|
79
|
+
export declare const writeGate: WriteGate;
|
|
80
|
+
/** One spelling for gate keys, so call sites agree on what "the same record" means. */
|
|
81
|
+
export declare function writeKey(resource: string, id: string | number, field?: string): string;
|
|
82
|
+
export {};
|
package/dist/index.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.ui-toggle-button[data-v-5920d123]{display:inline-flex;align-items:center;justify-content:center}.pm-editor .ProseMirror{outline:none;min-height:8rem}.pm-editor .ProseMirror>*+*{margin-top:.75rem}.pm-editor .ProseMirror h2{font-size:1.25rem;font-weight:600}.pm-editor .ProseMirror h3{font-size:1.125rem;font-weight:600}.pm-editor .ProseMirror h4,.pm-editor .ProseMirror h5,.pm-editor .ProseMirror h6,.pm-editor .ProseMirror strong{font-weight:600}.pm-editor .ProseMirror em{font-style:italic}.pm-editor .ProseMirror a{text-decoration:underline;color:inherit}.pm-editor .ProseMirror code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.875em;background:#f3f4f6;padding:.1em .3em;border-radius:.25rem}.pm-editor .ProseMirror pre{background:#f3f4f6;padding:.75rem;border-radius:.375rem;overflow-x:auto}.pm-editor .ProseMirror pre code{background:none;padding:0}.pm-editor .ProseMirror blockquote{border-left:3px solid rgb(209 213 219);padding-left:.75rem;color:#4b5563}.pm-editor .ProseMirror ul{list-style:disc;padding-left:1.25rem}.pm-editor .ProseMirror ol{list-style:decimal;padding-left:1.25rem}.pm-editor .ProseMirror li>*+*{margin-top:.25rem}.pm-editor .pm-placeholder:before{content:attr(data-placeholder);color:#d1d5db;float:left;height:0;pointer-events:none}.pm-editor .ProseMirror-selectednode{outline:2px solid rgb(59 130 246);border-radius:.375rem}.pm-editor .pm-dropcursor{background:#3b82f6}.pm-drag-handle{position:absolute;left:.5rem;display:none;align-items:center;justify-content:center;width:1rem;padding:.125rem;border-radius:.25rem;color:#d1d5db;cursor:grab;-webkit-user-select:none;user-select:none}.pm-drag-handle:hover{color:#6b7280;background:#f3f4f6}.pm-drag-handle:active{cursor:grabbing}.pm-drag-handle svg{width:.75rem;height:1.25rem;pointer-events:none}
|