@revenexx/studio-shared 0.3.3 → 0.4.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/module.d.mts +2 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +3 -1
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardConfirmDialog.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardControls.d.vue.ts +8 -7
- package/dist/runtime/components/Dashboard/DashboardControls.vue.d.ts +8 -7
- package/dist/runtime/components/Dashboard/DashboardGrid.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardGrid.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardNameDialog.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardNameDialog.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardPickerDialog.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardShareDialog.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardShareDialog.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardTopBar.d.vue.ts +9 -4
- package/dist/runtime/components/Dashboard/DashboardTopBar.vue.d.ts +9 -4
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.d.vue.ts +8 -6
- package/dist/runtime/components/Dashboard/DashboardWidgetConfigDialog.vue.d.ts +8 -6
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.d.vue.ts +5 -4
- package/dist/runtime/components/Dashboard/DashboardWidgetFrame.vue.d.ts +5 -4
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetHost.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetIcon.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetLibrary.vue.d.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.d.vue.ts +2 -1
- package/dist/runtime/components/Dashboard/DashboardWidgetPreview.vue.d.ts +2 -1
- package/dist/runtime/components/DataTable/ColumnsMenu.d.vue.ts +2 -1
- package/dist/runtime/components/DataTable/ColumnsMenu.vue.d.ts +2 -1
- package/dist/runtime/components/DataTable/DataTable.d.vue.ts +75 -31
- package/dist/runtime/components/DataTable/DataTable.vue +153 -13
- package/dist/runtime/components/DataTable/DataTable.vue.d.ts +75 -31
- package/dist/runtime/components/DataTable/FacetChips.d.vue.ts +2 -1
- package/dist/runtime/components/DataTable/FacetChips.vue.d.ts +2 -1
- package/dist/runtime/components/DataTable/FacetPanel.d.vue.ts +6 -5
- package/dist/runtime/components/DataTable/FacetPanel.vue.d.ts +6 -5
- package/dist/runtime/components/DataTable/FilterMenu.d.vue.ts +6 -5
- package/dist/runtime/components/DataTable/FilterMenu.vue.d.ts +6 -5
- package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +2 -1
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +2 -1
- package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +4 -2
- package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +4 -2
- package/dist/runtime/components/DataTable/ViewModeSwitch.d.vue.ts +4 -2
- package/dist/runtime/components/DataTable/ViewModeSwitch.vue.d.ts +4 -2
- package/dist/runtime/components/Form/FieldCell.d.vue.ts +32 -0
- package/dist/runtime/components/Form/FieldCell.vue +46 -0
- package/dist/runtime/components/Form/FieldCell.vue.d.ts +32 -0
- package/dist/runtime/components/Form/FieldGrid.d.vue.ts +13 -0
- package/dist/runtime/components/Form/FieldGrid.vue +11 -0
- package/dist/runtime/components/Form/FieldGrid.vue.d.ts +13 -0
- package/dist/runtime/components/Form/FormContextCard.d.vue.ts +33 -0
- package/dist/runtime/components/Form/FormContextCard.vue +56 -0
- package/dist/runtime/components/Form/FormContextCard.vue.d.ts +33 -0
- package/dist/runtime/components/Form/FormLayout.d.vue.ts +47 -0
- package/dist/runtime/components/Form/FormLayout.vue +169 -0
- package/dist/runtime/components/Form/FormLayout.vue.d.ts +47 -0
- package/dist/runtime/components/Form/FormSection.d.vue.ts +25 -0
- package/dist/runtime/components/Form/FormSection.vue +48 -0
- package/dist/runtime/components/Form/FormSection.vue.d.ts +25 -0
- package/dist/runtime/components/Form/SaveBar.d.vue.ts +44 -0
- package/dist/runtime/components/Form/SaveBar.vue +71 -0
- package/dist/runtime/components/Form/SaveBar.vue.d.ts +44 -0
- package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +2 -1
- package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +2 -1
- package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +2 -1
- package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +2 -1
- package/dist/runtime/components/Media/MediaUploader.d.vue.ts +2 -1
- package/dist/runtime/components/Media/MediaUploader.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/AppIcon.d.vue.ts +21 -0
- package/dist/runtime/components/Shared/AppIcon.vue +47 -0
- package/dist/runtime/components/Shared/AppIcon.vue.d.ts +21 -0
- package/dist/runtime/components/Shared/BuildLogsFlyout.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/BuildLogsFlyout.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/DetailSheet.d.vue.ts +60 -0
- package/dist/runtime/components/Shared/DetailSheet.vue +207 -0
- package/dist/runtime/components/Shared/DetailSheet.vue.d.ts +60 -0
- package/dist/runtime/components/Shared/FloatingPanel.d.vue.ts +7 -6
- package/dist/runtime/components/Shared/FloatingPanel.vue.d.ts +7 -6
- package/dist/runtime/components/Shared/GitDeploymentSettings.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/GitDeploymentSettings.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/KpiTile.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/KpiTile.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/ListSurface.d.vue.ts +3 -2
- package/dist/runtime/components/Shared/ListSurface.vue.d.ts +3 -2
- package/dist/runtime/components/Shared/PageHeader.d.vue.ts +7 -6
- package/dist/runtime/components/Shared/PageHeader.vue.d.ts +7 -6
- package/dist/runtime/components/Shared/RecordLayout.d.vue.ts +8 -7
- package/dist/runtime/components/Shared/RecordLayout.vue.d.ts +8 -7
- package/dist/runtime/components/Shared/RecordRailCard.d.vue.ts +3 -2
- package/dist/runtime/components/Shared/RecordRailCard.vue.d.ts +3 -2
- package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +3 -2
- package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +3 -2
- package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/SortHeader.d.vue.ts +3 -2
- package/dist/runtime/components/Shared/SortHeader.vue.d.ts +3 -2
- package/dist/runtime/components/Shared/StateCard.d.vue.ts +6 -5
- package/dist/runtime/components/Shared/StateCard.vue.d.ts +6 -5
- package/dist/runtime/components/Shared/StudioCard.d.vue.ts +5 -4
- package/dist/runtime/components/Shared/StudioCard.vue.d.ts +5 -4
- package/dist/runtime/components/Shared/TabBar.d.vue.ts +4 -21
- package/dist/runtime/components/Shared/TabBar.vue +24 -6
- package/dist/runtime/components/Shared/TabBar.vue.d.ts +4 -21
- package/dist/runtime/components/Shared/ViewTab.d.vue.ts +2 -1
- package/dist/runtime/components/Shared/ViewTab.vue.d.ts +2 -1
- package/dist/runtime/components/Shared/Wizard.d.vue.ts +10 -7
- package/dist/runtime/components/Shared/Wizard.vue.d.ts +10 -7
- package/dist/runtime/composables/useDetailSheet.d.ts +20 -0
- package/dist/runtime/composables/useDetailSheet.js +50 -0
- package/dist/runtime/composables/usePlatformTenant.d.ts +6 -0
- package/dist/runtime/lib/activeTenant.d.ts +58 -0
- package/dist/runtime/lib/activeTenant.js +45 -0
- package/dist/runtime/lib/appIcon.d.ts +48 -0
- package/dist/runtime/lib/appIcon.js +27 -0
- package/dist/runtime/lib/detailSheet.d.ts +43 -0
- package/dist/runtime/lib/detailSheet.js +34 -0
- package/dist/runtime/lib/formLayout.d.ts +94 -0
- package/dist/runtime/lib/formLayout.js +70 -0
- package/dist/runtime/lib/formLayoutContext.d.ts +26 -0
- package/dist/runtime/lib/formLayoutContext.js +1 -0
- package/dist/runtime/lib/rowMove.d.ts +38 -0
- package/dist/runtime/lib/rowMove.js +20 -0
- package/dist/runtime/lib/solarIconMap.d.ts +15 -0
- package/dist/runtime/lib/solarIconMap.js +280 -0
- package/dist/runtime/lib/solarIcons.d.ts +37 -0
- package/dist/runtime/lib/solarIcons.js +25 -0
- package/dist/types.d.mts +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an app's icon is, read from the one field the manifest documents (PE-560).
|
|
3
|
+
*
|
|
4
|
+
* `manifest.icon` is either a FILE or a SOLAR ICON NAME, told apart by shape:
|
|
5
|
+
*
|
|
6
|
+
* - a file contains a `/` or ends in `.svg`, `.png` or `.webp` — a URL, a
|
|
7
|
+
* `data:` URI, or a path the registry has resolved to one;
|
|
8
|
+
* - a Solar name is kebab-case (`money-bag`), the same token a `cockpit.json`
|
|
9
|
+
* navigation entry carries.
|
|
10
|
+
*
|
|
11
|
+
* Anything else is neither and is skipped. The Cockpit used to read the field
|
|
12
|
+
* three ways — a Solar name in the sidebar, an `<img src>` on the installed
|
|
13
|
+
* pages, not at all in the Marketplace — so no value rendered everywhere, and
|
|
14
|
+
* no app shipped one.
|
|
15
|
+
*
|
|
16
|
+
* Pure on purpose: the Solar registry lookup lives in `solarIcons.ts`, so this
|
|
17
|
+
* module has no imports and is testable without a Nuxt alias.
|
|
18
|
+
*/
|
|
19
|
+
export type AppIconSource = {
|
|
20
|
+
kind: 'file';
|
|
21
|
+
src: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'solar';
|
|
24
|
+
name: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* `money-bag` → `MoneyBag`, the export name in the Solar registry. Snake case
|
|
28
|
+
* (`card_2`) and the export name itself (`Layers`) read the same way.
|
|
29
|
+
* `scripts/icons.mjs` carries a copy (`exportName`) and must agree with it.
|
|
30
|
+
*/
|
|
31
|
+
export declare function solarExportName(name: string): string;
|
|
32
|
+
/** How one `manifest.icon` value reads, or null when it is neither shape. */
|
|
33
|
+
export declare function readAppIcon(value: string | null | undefined): AppIconSource | null;
|
|
34
|
+
/**
|
|
35
|
+
* The Solar name of the first `cockpit.json` navigation entry that has one —
|
|
36
|
+
* the glyph the app already shows in its host studio's sidebar, and the
|
|
37
|
+
* fallback for the apps that set no `manifest.icon`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function firstNavIcon(navigation: ReadonlyArray<{
|
|
40
|
+
icon?: string | null;
|
|
41
|
+
}> | null | undefined): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Every source to try, in order: `manifest.icon` (file or Solar name), then the
|
|
44
|
+
* navigation glyph. The renderer walks the list and draws the first that
|
|
45
|
+
* resolves — an image that fails to load, or a name the registry lacks, falls
|
|
46
|
+
* through to the next, and past the end to the generic glyph.
|
|
47
|
+
*/
|
|
48
|
+
export declare function appIconCandidates(icon: string | null | undefined, navIcon?: string | null): AppIconSource[];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const FILE_EXTENSION = /\.(?:svg|png|webp)$/i;
|
|
2
|
+
const SOLAR_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
3
|
+
export function solarExportName(name) {
|
|
4
|
+
return name.replace(/(^|[-_])([a-z0-9])/gi, (_m, _sep, ch) => ch.toUpperCase());
|
|
5
|
+
}
|
|
6
|
+
export function readAppIcon(value) {
|
|
7
|
+
const v = value?.trim();
|
|
8
|
+
if (!v) return null;
|
|
9
|
+
if (v.includes("/") || FILE_EXTENSION.test(v)) return { kind: "file", src: v };
|
|
10
|
+
if (SOLAR_NAME.test(v)) return { kind: "solar", name: v };
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
export function firstNavIcon(navigation) {
|
|
14
|
+
for (const item of navigation ?? []) {
|
|
15
|
+
const icon = item.icon?.trim();
|
|
16
|
+
if (icon) return icon;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
export function appIconCandidates(icon, navIcon) {
|
|
21
|
+
const out = [];
|
|
22
|
+
const own = readAppIcon(icon);
|
|
23
|
+
if (own) out.push(own);
|
|
24
|
+
const nav = navIcon?.trim();
|
|
25
|
+
if (nav && !(own?.kind === "solar" && own.name === nav)) out.push({ kind: "solar", name: nav });
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The parts of `DetailSheet` that are arithmetic rather than UI (PE-691).
|
|
3
|
+
*
|
|
4
|
+
* Kept out of the component and the composable so they can be tested without a
|
|
5
|
+
* router or a DOM: which record is next, what the query looks like with the
|
|
6
|
+
* panel open or shut, and whether a keypress belongs to the panel or to the
|
|
7
|
+
* control it landed on.
|
|
8
|
+
*/
|
|
9
|
+
export type QueryValue = string | null | (string | null)[] | undefined;
|
|
10
|
+
export type Query = Record<string, QueryValue>;
|
|
11
|
+
/** The open record id from a query value — the first one if a link repeats it. */
|
|
12
|
+
export declare function idFromQuery(value: QueryValue): string | null;
|
|
13
|
+
/** `query` with `key` set to `id`, or without `key` when `id` is null. */
|
|
14
|
+
export declare function queryWith(query: Query, key: string, id: string | null): Query;
|
|
15
|
+
/**
|
|
16
|
+
* The id `step` places away from `current` in `ids`, or null at either end.
|
|
17
|
+
*
|
|
18
|
+
* No wrap-around: arrowing past the last row and landing on the first reads as
|
|
19
|
+
* the list having jumped, and in a paginated list the first row is not even on
|
|
20
|
+
* the page. An id that is no longer in the list (deleted, filtered out) has no
|
|
21
|
+
* neighbours either — guessing one would open a record nobody pointed at.
|
|
22
|
+
*/
|
|
23
|
+
export declare function neighbourId(ids: readonly string[], current: string | null, step: -1 | 1): string | null;
|
|
24
|
+
interface KeyEvent {
|
|
25
|
+
key: string;
|
|
26
|
+
altKey?: boolean;
|
|
27
|
+
ctrlKey?: boolean;
|
|
28
|
+
metaKey?: boolean;
|
|
29
|
+
shiftKey?: boolean;
|
|
30
|
+
defaultPrevented?: boolean;
|
|
31
|
+
target?: unknown;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether the focused element handles arrows and Escape itself: a text field
|
|
35
|
+
* (caret, clearing), a select, a menu, a listbox, a slider. The panel's keys
|
|
36
|
+
* give way there — taking them would break the control to save one click.
|
|
37
|
+
*/
|
|
38
|
+
export declare function targetOwnsKeys(target: unknown): boolean;
|
|
39
|
+
/** Whether ArrowUp/ArrowDown should move to the neighbouring record, and which way. */
|
|
40
|
+
export declare function isRecordStepKey(ev: KeyEvent): -1 | 1 | null;
|
|
41
|
+
/** Whether Escape should close the panel rather than the control it landed on. */
|
|
42
|
+
export declare function isCloseKey(ev: KeyEvent): boolean;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function idFromQuery(value) {
|
|
2
|
+
const v = Array.isArray(value) ? value[0] : value;
|
|
3
|
+
return typeof v === "string" && v !== "" ? v : null;
|
|
4
|
+
}
|
|
5
|
+
export function queryWith(query, key, id) {
|
|
6
|
+
const next = { ...query };
|
|
7
|
+
if (id === null) delete next[key];
|
|
8
|
+
else next[key] = id;
|
|
9
|
+
return next;
|
|
10
|
+
}
|
|
11
|
+
export function neighbourId(ids, current, step) {
|
|
12
|
+
if (current === null) return null;
|
|
13
|
+
const i = ids.indexOf(current);
|
|
14
|
+
if (i < 0) return null;
|
|
15
|
+
return ids[i + step] ?? null;
|
|
16
|
+
}
|
|
17
|
+
export function targetOwnsKeys(target) {
|
|
18
|
+
const t = target;
|
|
19
|
+
if (!t) return false;
|
|
20
|
+
const tag = (t.tagName ?? "").toUpperCase();
|
|
21
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
|
|
22
|
+
if (t.isContentEditable) return true;
|
|
23
|
+
return !!t.closest?.('[role="menu"],[role="listbox"],[role="combobox"],[role="slider"],[role="spinbutton"],[role="radiogroup"],[role="tablist"]');
|
|
24
|
+
}
|
|
25
|
+
export function isRecordStepKey(ev) {
|
|
26
|
+
if (ev.defaultPrevented) return null;
|
|
27
|
+
if (ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey) return null;
|
|
28
|
+
const step = ev.key === "ArrowDown" ? 1 : ev.key === "ArrowUp" ? -1 : null;
|
|
29
|
+
if (step === null) return null;
|
|
30
|
+
return targetOwnsKeys(ev.target) ? null : step;
|
|
31
|
+
}
|
|
32
|
+
export function isCloseKey(ev) {
|
|
33
|
+
return ev.key === "Escape" && !ev.defaultPrevented && !targetOwnsKeys(ev.target);
|
|
34
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The decisions behind the shared form layout (PE-976), as pure functions.
|
|
3
|
+
*
|
|
4
|
+
* `FormLayout`, `FormSection`, `FieldGrid`, `FieldCell`, `FormContextCard` and
|
|
5
|
+
* `SaveBar` draw a form; this file decides the parts that have to agree across
|
|
6
|
+
* every surface that draws one — how wide a field is when nobody said, when the
|
|
7
|
+
* section navigation appears, which section the navigation marks while the
|
|
8
|
+
* page scrolls, and which grid tracks the layout gets. The Commerce renderer
|
|
9
|
+
* (mapping a manifest), hand-written cockpit forms and the manifest lint all
|
|
10
|
+
* read the same answers from here, so a phone number cannot be half width in
|
|
11
|
+
* one place and full width in another.
|
|
12
|
+
*
|
|
13
|
+
* The class strings live here as whole literals on purpose: Tailwind finds
|
|
14
|
+
* classes by scanning source text, so a class assembled at runtime from parts
|
|
15
|
+
* would never be generated. This directory is under the host's `@source`.
|
|
16
|
+
*/
|
|
17
|
+
/** How much of a two-column field grid a field takes. */
|
|
18
|
+
export type FieldSpan = 'half' | 'full';
|
|
19
|
+
/** The width a field of `fieldType` gets when nothing says otherwise. */
|
|
20
|
+
export declare function defaultSpan(fieldType: string | null | undefined): FieldSpan;
|
|
21
|
+
/** What `resolveSpan` reads — a manifest field fits, so does a hand-written one. */
|
|
22
|
+
export interface SpanSource {
|
|
23
|
+
type?: string | null;
|
|
24
|
+
/** `auto` (or absent) means "the type decides". */
|
|
25
|
+
span?: 'auto' | FieldSpan | string | null;
|
|
26
|
+
}
|
|
27
|
+
/** An explicit `half` / `full` wins; `auto`, absent or unknown falls back to the type. */
|
|
28
|
+
export declare function resolveSpan(field: SpanSource): FieldSpan;
|
|
29
|
+
/**
|
|
30
|
+
* The grid-item class for a span inside `FieldGrid`.
|
|
31
|
+
*
|
|
32
|
+
* The grid is one column until its OWN width reaches the `@md` container size
|
|
33
|
+
* (28rem) and two above it — a container query, not a viewport breakpoint, so
|
|
34
|
+
* the same grid is right in a 512 px dialog, beside a section nav, and in a
|
|
35
|
+
* full-width page. A full field spans both tracks once there are two.
|
|
36
|
+
*/
|
|
37
|
+
export declare function spanClass(span: FieldSpan): string;
|
|
38
|
+
/** Below this many sections a navigation is noise — the form fits in a glance. */
|
|
39
|
+
export declare const SECTION_NAV_MIN = 3;
|
|
40
|
+
/**
|
|
41
|
+
* Whether the left section navigation is drawn.
|
|
42
|
+
* `auto` = from {@link SECTION_NAV_MIN} sections on; a boolean forces it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function showSectionNav(mode: boolean | 'auto', sectionCount: number): boolean;
|
|
45
|
+
/** One section as the scroll spy sees it: its top edge, relative to the viewport of the scroller. */
|
|
46
|
+
export interface SectionOffset {
|
|
47
|
+
id: string;
|
|
48
|
+
top: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Which section the navigation marks.
|
|
52
|
+
*
|
|
53
|
+
* The last section whose top edge has scrolled up to `threshold` (the line just
|
|
54
|
+
* under the sticky header) is the one being read. Two edges need a rule of
|
|
55
|
+
* their own: before anything has scrolled the first section is current even if
|
|
56
|
+
* it starts lower than the line, and once the scroller cannot move further the
|
|
57
|
+
* LAST section is current — a short final section can never reach the line,
|
|
58
|
+
* and a navigation that can never mark it reads as broken.
|
|
59
|
+
*
|
|
60
|
+
* `sections` must be in document order. Returns `null` for no sections.
|
|
61
|
+
*/
|
|
62
|
+
export declare function activeSectionId(sections: readonly SectionOffset[], options: {
|
|
63
|
+
threshold: number;
|
|
64
|
+
atEnd?: boolean;
|
|
65
|
+
}): string | null;
|
|
66
|
+
/** A scroller has reached its end, within a pixel or two of subpixel rounding. */
|
|
67
|
+
export declare function isScrolledToEnd(scrollTop: number, clientHeight: number, scrollHeight: number): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The tracks of the layout grid, by which side columns are present.
|
|
70
|
+
*
|
|
71
|
+
* The form column is capped at 48rem (~760 px, the mockup's readable width) and
|
|
72
|
+
* centred; the navigation is 10rem, the context column 16rem. Each side column
|
|
73
|
+
* only exists from the container width it fits at — the nav from `@3xl`
|
|
74
|
+
* (48rem), the context column from `@5xl` (64rem) — and below that the layout
|
|
75
|
+
* is the bare form column. Container queries again, because the space a form
|
|
76
|
+
* gets is the content pane beside a 256 px studio sidebar, not the window.
|
|
77
|
+
*/
|
|
78
|
+
export declare function layoutGridClass(nav: boolean, context: boolean): string;
|
|
79
|
+
/** Visibility of the side columns, matching the breakpoints in {@link layoutGridClass}. */
|
|
80
|
+
export declare const NAV_COLUMN_CLASS = "hidden @3xl:block";
|
|
81
|
+
export declare const CONTEXT_COLUMN_CLASS = "hidden @5xl:block";
|
|
82
|
+
/** One fact in a `FormContextCard`. Empty values render as an em dash. */
|
|
83
|
+
export interface FormContextRow {
|
|
84
|
+
/** Stable key; the `value` slot receives the whole row. */
|
|
85
|
+
key: string;
|
|
86
|
+
label: string;
|
|
87
|
+
value?: string | number | null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Up to two initials for an avatar: first letters of the first and last word (split on spaces, `-`, `_`),
|
|
91
|
+
* or the first two letters of a single word. Letters only — `"ACME GmbH (Test)"`
|
|
92
|
+
* reads as `AT`, not `A(`. Empty for nothing usable.
|
|
93
|
+
*/
|
|
94
|
+
export declare function initialsOf(name: string | null | undefined): string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const FULL_WIDTH_TYPES = /* @__PURE__ */ new Set([
|
|
2
|
+
"textarea",
|
|
3
|
+
"json",
|
|
4
|
+
"key-value",
|
|
5
|
+
"multi-select",
|
|
6
|
+
"market-assignment",
|
|
7
|
+
"repeater",
|
|
8
|
+
"record-list",
|
|
9
|
+
"string-list",
|
|
10
|
+
"fieldset",
|
|
11
|
+
"rich-text",
|
|
12
|
+
"richtext",
|
|
13
|
+
"markdown",
|
|
14
|
+
"media",
|
|
15
|
+
"media-collection",
|
|
16
|
+
"image",
|
|
17
|
+
"localized-text",
|
|
18
|
+
"rules",
|
|
19
|
+
"record-view",
|
|
20
|
+
"address",
|
|
21
|
+
"tree-assign",
|
|
22
|
+
"line-selection"
|
|
23
|
+
]);
|
|
24
|
+
export function defaultSpan(fieldType) {
|
|
25
|
+
if (!fieldType) return "half";
|
|
26
|
+
return FULL_WIDTH_TYPES.has(fieldType.trim().toLowerCase()) ? "full" : "half";
|
|
27
|
+
}
|
|
28
|
+
export function resolveSpan(field) {
|
|
29
|
+
if (field.span === "half" || field.span === "full") return field.span;
|
|
30
|
+
return defaultSpan(field.type);
|
|
31
|
+
}
|
|
32
|
+
export function spanClass(span) {
|
|
33
|
+
return span === "full" ? "col-span-1 @md:col-span-2" : "col-span-1";
|
|
34
|
+
}
|
|
35
|
+
export const SECTION_NAV_MIN = 3;
|
|
36
|
+
export function showSectionNav(mode, sectionCount) {
|
|
37
|
+
if (mode === "auto") return sectionCount >= SECTION_NAV_MIN;
|
|
38
|
+
return mode && sectionCount > 0;
|
|
39
|
+
}
|
|
40
|
+
export function activeSectionId(sections, options) {
|
|
41
|
+
if (!sections.length) return null;
|
|
42
|
+
if (options.atEnd) return sections[sections.length - 1].id;
|
|
43
|
+
let active = sections[0].id;
|
|
44
|
+
for (const section of sections) {
|
|
45
|
+
if (section.top <= options.threshold) active = section.id;
|
|
46
|
+
else break;
|
|
47
|
+
}
|
|
48
|
+
return active;
|
|
49
|
+
}
|
|
50
|
+
export function isScrolledToEnd(scrollTop, clientHeight, scrollHeight) {
|
|
51
|
+
if (scrollHeight <= clientHeight + 2) return false;
|
|
52
|
+
return scrollTop + clientHeight >= scrollHeight - 2;
|
|
53
|
+
}
|
|
54
|
+
export function layoutGridClass(nav, context) {
|
|
55
|
+
if (nav && context)
|
|
56
|
+
return "grid grid-cols-[minmax(0,48rem)] justify-center gap-6 @3xl:grid-cols-[10rem_minmax(0,48rem)] @5xl:grid-cols-[10rem_minmax(0,48rem)_16rem]";
|
|
57
|
+
if (nav)
|
|
58
|
+
return "grid grid-cols-[minmax(0,48rem)] justify-center gap-6 @3xl:grid-cols-[10rem_minmax(0,48rem)]";
|
|
59
|
+
if (context)
|
|
60
|
+
return "grid grid-cols-[minmax(0,48rem)] justify-center gap-6 @5xl:grid-cols-[minmax(0,48rem)_16rem]";
|
|
61
|
+
return "grid grid-cols-[minmax(0,48rem)] justify-center gap-6";
|
|
62
|
+
}
|
|
63
|
+
export const NAV_COLUMN_CLASS = "hidden @3xl:block";
|
|
64
|
+
export const CONTEXT_COLUMN_CLASS = "hidden @5xl:block";
|
|
65
|
+
export function initialsOf(name) {
|
|
66
|
+
const words = (name ?? "").split(/[\s_-]+/).map((w) => w.replace(/[^\p{L}\p{N}]/gu, "")).filter(Boolean);
|
|
67
|
+
if (!words.length) return "";
|
|
68
|
+
if (words.length === 1) return words[0].slice(0, 2).toUpperCase();
|
|
69
|
+
return (words[0][0] + words[words.length - 1][0]).toUpperCase();
|
|
70
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `FormLayout` shares with the parts inside it (PE-976).
|
|
3
|
+
*
|
|
4
|
+
* `FormSection` registers itself so the navigation is built from the sections
|
|
5
|
+
* actually on the page — their ids and titles, in document order — instead of a
|
|
6
|
+
* second list the page has to keep in step. `SaveBar` reads the grid tracks so
|
|
7
|
+
* its buttons line up under the form column rather than under the nav.
|
|
8
|
+
*
|
|
9
|
+
* Kept out of `components/` so the module's component scan never mistakes it
|
|
10
|
+
* for a component.
|
|
11
|
+
*/
|
|
12
|
+
import type { ComputedRef, InjectionKey } from 'vue';
|
|
13
|
+
export interface FormSectionEntry {
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
el: () => HTMLElement | null;
|
|
17
|
+
}
|
|
18
|
+
export interface FormLayoutContext {
|
|
19
|
+
register: (entry: FormSectionEntry) => void;
|
|
20
|
+
unregister: (id: string) => void;
|
|
21
|
+
/** Grid classes of the layout row, reused by the save bar. */
|
|
22
|
+
gridClass: ComputedRef<string>;
|
|
23
|
+
navVisible: ComputedRef<boolean>;
|
|
24
|
+
contextVisible: ComputedRef<boolean>;
|
|
25
|
+
}
|
|
26
|
+
export declare const FORM_LAYOUT_KEY: InjectionKey<FormLayoutContext>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const FORM_LAYOUT_KEY = Symbol("studio-shared.form-layout");
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Moving a row by dragging it, as arithmetic the table does not have to own.
|
|
3
|
+
*
|
|
4
|
+
* The DataTable only knows rows and keys. What a move MEANS — which position
|
|
5
|
+
* column is written, which rows have to change — is the surface's business, so
|
|
6
|
+
* the table answers one question and stops: "the operator put row X here; what
|
|
7
|
+
* is the order now?". Kept apart from the component so the edge cases are a
|
|
8
|
+
* test rather than something found by dragging.
|
|
9
|
+
*/
|
|
10
|
+
/** Which half of the row under the pointer the dragged row is dropped on. */
|
|
11
|
+
export type DropEdge = 'before' | 'after';
|
|
12
|
+
/**
|
|
13
|
+
* The edge of a row a pointer is nearest to.
|
|
14
|
+
*
|
|
15
|
+
* The middle of the row, not an arithmetic row height: a row whose label wraps
|
|
16
|
+
* is taller than its neighbours, and an index derived from "pointer offset ÷ 40
|
|
17
|
+
* pixels" puts the row one slot off on every list that has a long value in it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function dropEdge(pointerY: number, rowTop: number, rowHeight: number): DropEdge;
|
|
20
|
+
/**
|
|
21
|
+
* Where a row dragged from `from` lands when dropped on the `edge` of the row
|
|
22
|
+
* at `target`, as an index into the list WITHOUT the dragged row taken out.
|
|
23
|
+
*
|
|
24
|
+
* Returns null when the drop changes nothing — onto itself, or onto the edge
|
|
25
|
+
* it already touches (the bottom of the row above, the top of the row below).
|
|
26
|
+
* A move that changes nothing must not write anything, and it is easy to send
|
|
27
|
+
* two position updates for a row the operator merely picked up and put back.
|
|
28
|
+
*/
|
|
29
|
+
export declare function dropTargetIndex(from: number, target: number, edge: DropEdge): number | null;
|
|
30
|
+
/** The keys in their new order, `from` moved to `to`. */
|
|
31
|
+
export declare function moveKey<K>(keys: readonly K[], from: number, to: number): K[];
|
|
32
|
+
/**
|
|
33
|
+
* The keyboard half of the same move: one step up or down, or null at an end.
|
|
34
|
+
*
|
|
35
|
+
* Dragging is a pointer gesture and cannot be the only way to reorder a list —
|
|
36
|
+
* the handle is a button, and the arrow keys on it move the row one place.
|
|
37
|
+
*/
|
|
38
|
+
export declare function stepTarget(from: number, delta: -1 | 1, length: number): number | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function dropEdge(pointerY, rowTop, rowHeight) {
|
|
2
|
+
return pointerY < rowTop + rowHeight / 2 ? "before" : "after";
|
|
3
|
+
}
|
|
4
|
+
export function dropTargetIndex(from, target, edge) {
|
|
5
|
+
if (from < 0 || target < 0) return null;
|
|
6
|
+
const insertAt = edge === "before" ? target : target + 1;
|
|
7
|
+
const to = insertAt > from ? insertAt - 1 : insertAt;
|
|
8
|
+
return to === from ? null : to;
|
|
9
|
+
}
|
|
10
|
+
export function moveKey(keys, from, to) {
|
|
11
|
+
if (from < 0 || from >= keys.length || to < 0 || to >= keys.length || from === to) return [...keys];
|
|
12
|
+
const next = [...keys];
|
|
13
|
+
const [moved] = next.splice(from, 1);
|
|
14
|
+
next.splice(to, 0, moved);
|
|
15
|
+
return next;
|
|
16
|
+
}
|
|
17
|
+
export function stepTarget(from, delta, length) {
|
|
18
|
+
const to = from + delta;
|
|
19
|
+
return to < 0 || to >= length ? null : to;
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by `node scripts/icons.mjs`. Do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* The closed set of Solar icons the cockpit resolves by name: every icon the
|
|
5
|
+
* app manifests, this repo's code and the curated picker set ask for. Named
|
|
6
|
+
* imports, so the bundle carries these 127 instead of all 1,413
|
|
7
|
+
* (PE-985). A name outside the set renders the fallback glyph; run
|
|
8
|
+
* `pnpm icons` after adding one, and `pnpm icons:check` to verify.
|
|
9
|
+
*/
|
|
10
|
+
import type { Component } from 'vue';
|
|
11
|
+
export declare const SOLAR_ICON_MAP: Readonly<Record<string, Component>>;
|
|
12
|
+
/** Manifest names Solar does not have → the glyph drawn for them. */
|
|
13
|
+
export declare const SOLAR_ICON_ALIASES: Readonly<Record<string, string>>;
|
|
14
|
+
/** Drawn for a name outside the map. */
|
|
15
|
+
export declare const SOLAR_FALLBACK_ICON: Component;
|