@bsuite/page-builder 0.7.0 → 0.9.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/CanvasCard.d.ts +52 -0
- package/dist/CanvasCard.d.ts.map +1 -0
- package/dist/CanvasCard.js +18 -0
- package/dist/DraggableCardPage.d.ts +118 -0
- package/dist/DraggableCardPage.d.ts.map +1 -0
- package/dist/DraggableCardPage.js +110 -0
- package/dist/PageGridLayout.d.ts +1 -1
- package/dist/PageGridLayout.d.ts.map +1 -1
- package/dist/PageGridLayout.js +102 -19
- package/dist/buildResponsiveLayouts.d.ts +61 -4
- package/dist/buildResponsiveLayouts.d.ts.map +1 -1
- package/dist/buildResponsiveLayouts.js +77 -5
- package/dist/canvasCardLayout.d.ts +62 -0
- package/dist/canvasCardLayout.d.ts.map +1 -0
- package/dist/canvasCardLayout.js +120 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/scanner/cardSurfaceScanner.d.ts +120 -0
- package/dist/scanner/cardSurfaceScanner.d.ts.map +1 -0
- package/dist/scanner/cardSurfaceScanner.js +289 -0
- package/dist/scanner/index.d.ts +3 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/usePageGridLayout.d.ts +22 -1
- package/dist/usePageGridLayout.d.ts.map +1 -1
- package/dist/usePageGridLayout.js +197 -66
- package/package.json +8 -3
|
@@ -1,4 +1,71 @@
|
|
|
1
1
|
export const RESPONSIVE_BREAKPOINTS = ['lg', 'md', 'sm', 'xs', 'xxs'];
|
|
2
|
+
/**
|
|
3
|
+
* Breakpoints whose layout MIRRORS `lg` verbatim.
|
|
4
|
+
*
|
|
5
|
+
* D-75 root cause (operator directive 2026-08-13: "columns available to move
|
|
6
|
+
* cards into do not respect the columns slider").
|
|
7
|
+
* ---------------------------------------------------------------------------
|
|
8
|
+
* react-grid-layout picks its breakpoint from the **container** width, not the
|
|
9
|
+
* viewport — `<Responsive width={containerWidth}>` in `PageGridLayout`. Inside
|
|
10
|
+
* an app shell with a sidebar and page padding, a 1280px laptop presents a
|
|
11
|
+
* container around 950px and a 1440px desktop around 1100px. Both land BELOW
|
|
12
|
+
* the `md: 996` / `lg: 1200` thresholds, so the great majority of real desktop
|
|
13
|
+
* sessions render at `sm` or `md`, not `lg`.
|
|
14
|
+
*
|
|
15
|
+
* `sm` used to be derived by `stackToSingleColumn`, which sets `w: cols` on
|
|
16
|
+
* every item — one card per row at full width, whatever the column count. So
|
|
17
|
+
* on an ordinary laptop the columns slider changed `activeCols` while every
|
|
18
|
+
* card stayed stretched across all of them: the control provably did nothing,
|
|
19
|
+
* and the SE resize handle could not widen a card that was already at the grid
|
|
20
|
+
* bound nor narrow one whose `minW` had been clamped up to `cols`.
|
|
21
|
+
*
|
|
22
|
+
* `md` and `sm` therefore mirror `lg`: at a container width of 768px and above
|
|
23
|
+
* there is room for the arrangement the user chose, and honouring their choice
|
|
24
|
+
* is the whole point of the control. Genuine phone widths (`xs` < 768px
|
|
25
|
+
* container, `xxs` < 480px) still stack, which is what that code was for.
|
|
26
|
+
*/
|
|
27
|
+
export const MIRRORED_BREAKPOINTS = ['md', 'sm'];
|
|
28
|
+
/**
|
|
29
|
+
* Breakpoints derived as a single full-width stack — narrow enough that a
|
|
30
|
+
* multi-column arrangement cannot be read.
|
|
31
|
+
*/
|
|
32
|
+
export const STACKED_BREAKPOINTS = ['xs', 'xxs'];
|
|
33
|
+
/**
|
|
34
|
+
* Every breakpoint this module DERIVES from `lg`.
|
|
35
|
+
*
|
|
36
|
+
* A derived breakpoint is a render-time projection, never a stored fact. It
|
|
37
|
+
* must not reach the preference adapter: react-grid-layout echoes ALL
|
|
38
|
+
* breakpoints back through `onLayoutChange` on every gesture, so persisting
|
|
39
|
+
* them verbatim froze the derivation — a layout derived once at 12 columns was
|
|
40
|
+
* then "consumer-supplied" forever and stopped tracking `lg`. See
|
|
41
|
+
* `canonicaliseLayoutForPersist` in `usePageGridLayout.ts`.
|
|
42
|
+
*/
|
|
43
|
+
export const DERIVED_BREAKPOINTS = [
|
|
44
|
+
...MIRRORED_BREAKPOINTS,
|
|
45
|
+
...STACKED_BREAKPOINTS,
|
|
46
|
+
];
|
|
47
|
+
export function isDerivedBreakpoint(breakpoint) {
|
|
48
|
+
return DERIVED_BREAKPOINTS.includes(breakpoint);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* True when a gesture at `breakpoint` can be written straight back onto `lg`.
|
|
52
|
+
*
|
|
53
|
+
* `lg`, `md` and `sm` all render the same array against the same column count,
|
|
54
|
+
* so an edit made at any of them is expressible in `lg` with no loss. `xs` and
|
|
55
|
+
* `xxs` render a full-width stack, where an edit carries only a vertical order
|
|
56
|
+
* and would flatten a multi-column desktop arrangement if applied to `lg`.
|
|
57
|
+
*/
|
|
58
|
+
export function isCanonicalisableBreakpoint(breakpoint) {
|
|
59
|
+
return breakpoint === 'lg' || MIRRORED_BREAKPOINTS.includes(breakpoint);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Stack items into one full-width column, preserving reading order.
|
|
63
|
+
*
|
|
64
|
+
* `w: cols` is deliberate at the breakpoints this is used for (`xs`/`xxs`):
|
|
65
|
+
* the container is narrower than 768px, so a card occupies the whole grid
|
|
66
|
+
* whatever the column count. Do NOT reach for this at wider breakpoints — that
|
|
67
|
+
* was D-75.
|
|
68
|
+
*/
|
|
2
69
|
function stackToSingleColumn(items, cols) {
|
|
3
70
|
const sorted = [...items].sort((a, b) => (a.y !== b.y ? a.y - b.y : a.x - b.x));
|
|
4
71
|
let nextY = 0;
|
|
@@ -16,10 +83,15 @@ function stackToSingleColumn(items, cols) {
|
|
|
16
83
|
}
|
|
17
84
|
/**
|
|
18
85
|
* Ensure a GridLayouts object has entries for all responsive breakpoints
|
|
19
|
-
* (lg, md, sm, xs, xxs).
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
86
|
+
* (lg, md, sm, xs, xxs).
|
|
87
|
+
*
|
|
88
|
+
* - `md` and `sm` mirror `lg` so the user's chosen column arrangement survives
|
|
89
|
+
* every desktop and tablet container width (see {@link MIRRORED_BREAKPOINTS}).
|
|
90
|
+
* - `xs` and `xxs` are derived as a single-column stack so a phone viewport
|
|
91
|
+
* never squashes cards into unreadable slivers.
|
|
92
|
+
* - Consumer-supplied breakpoints are preserved verbatim. Supplying one opts
|
|
93
|
+
* that breakpoint OUT of tracking `lg`, including out of the columns slider —
|
|
94
|
+
* prefer not to.
|
|
23
95
|
*/
|
|
24
96
|
export function buildResponsiveLayouts(layouts, { cols = 12 } = {}) {
|
|
25
97
|
const lg = layouts.lg ?? [];
|
|
@@ -32,7 +104,7 @@ export function buildResponsiveLayouts(layouts, { cols = 12 } = {}) {
|
|
|
32
104
|
result[bp] = supplied;
|
|
33
105
|
continue;
|
|
34
106
|
}
|
|
35
|
-
if (bp
|
|
107
|
+
if (MIRRORED_BREAKPOINTS.includes(bp)) {
|
|
36
108
|
result[bp] = lg;
|
|
37
109
|
continue;
|
|
38
110
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CanvasCard → grid-layout algorithm, extracted from the component so it
|
|
3
|
+
* can be unit-tested and reused without rendering anything.
|
|
4
|
+
*
|
|
5
|
+
* This is the half of `DraggableCardPage` that was copied verbatim into three
|
|
6
|
+
* apps (crm7, braden, throughput) and was absent from two more (BSU, conduit).
|
|
7
|
+
* It has no app dependencies, no permission model and no adapter — those are
|
|
8
|
+
* the parts that legitimately differ per app and stay there.
|
|
9
|
+
*/
|
|
10
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
11
|
+
import type { CanvasCardProps } from './CanvasCard.js';
|
|
12
|
+
import type { GridLayouts } from './types.js';
|
|
13
|
+
/** The 12-column grid every BSuite page canvas is laid out against. */
|
|
14
|
+
export declare const CANVAS_GRID_COLUMNS = 12;
|
|
15
|
+
export declare function isCanvasCardElement(node: ReactNode): node is ReactElement<CanvasCardProps>;
|
|
16
|
+
/**
|
|
17
|
+
* Best-effort display name for a node that is about to be discarded.
|
|
18
|
+
* "Something was dropped" is not actionable across hundreds of pages; the
|
|
19
|
+
* component's own name is what makes it findable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function describeNode(node: ReactNode): string;
|
|
22
|
+
/**
|
|
23
|
+
* Depth-first flatten: CanvasCards kept, Fragments expanded, everything else
|
|
24
|
+
* dropped — and RECORDED rather than vanishing.
|
|
25
|
+
*
|
|
26
|
+
* The dropping itself is correct and deliberate: this maps children onto grid
|
|
27
|
+
* slots, and a node with no `cardKey` has no slot to occupy. What was wrong is
|
|
28
|
+
* that it happened in total silence, so a page could render with a confirm
|
|
29
|
+
* dialog missing and look completely fine — the button appears, the click
|
|
30
|
+
* handler runs, state flips, and the element it toggles was never mounted.
|
|
31
|
+
*
|
|
32
|
+
* Fragments must be expanded explicitly because `Children.forEach` is shallow:
|
|
33
|
+
* it sees one Fragment node, not the CanvasCards inside it. A Fragment-wrapped
|
|
34
|
+
* conditional branch used to drop every nested card silently (the crm7 burn-7
|
|
35
|
+
* CI class, `leave/[id]`).
|
|
36
|
+
*/
|
|
37
|
+
export declare function flattenCanvasCards(nodes: ReactNode): {
|
|
38
|
+
cards: ReactElement<CanvasCardProps>[];
|
|
39
|
+
dropped: string[];
|
|
40
|
+
};
|
|
41
|
+
export declare function clampColumns(value: number, min?: number, max?: number): number;
|
|
42
|
+
export interface CanvasCardLayoutResult {
|
|
43
|
+
/** Widget dict keyed by `cardKey`, ready for `PageGridLayout.widgets`. */
|
|
44
|
+
widgets: Record<string, ReactNode>;
|
|
45
|
+
/** `lg` breakpoint layout, ready for `PageGridLayout.defaultLayouts`. */
|
|
46
|
+
layouts: GridLayouts;
|
|
47
|
+
/** Display names of children that occupied no grid slot and were discarded. */
|
|
48
|
+
dropped: string[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build the `widgets` dict and `defaultLayouts.lg` array from CanvasCard
|
|
52
|
+
* children.
|
|
53
|
+
*
|
|
54
|
+
* Cards flow left-to-right and wrap to a new row when the next card would
|
|
55
|
+
* overflow the 12-column grid. Because the dict is built FROM THE CHILDREN AT
|
|
56
|
+
* RENDER TIME, a `.map()` producing N cards is fully supported and needs no
|
|
57
|
+
* registration mechanism — each mapped card becomes its own independent grid
|
|
58
|
+
* item. (This is the fact that disproves the "dynamic card lists cannot be
|
|
59
|
+
* split into static widget keys" objection recorded in several app ledgers.)
|
|
60
|
+
*/
|
|
61
|
+
export declare function buildCanvasCardLayout(children: ReactNode): CanvasCardLayoutResult;
|
|
62
|
+
//# sourceMappingURL=canvasCardLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvasCardLayout.d.ts","sourceRoot":"","sources":["../src/canvasCardLayout.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,SAAS,GACd,IAAI,IAAI,YAAY,CAAC,eAAe,CAAC,CAKvC;AAQD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAQpD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG;IACpD,KAAK,EAAE,YAAY,CAAC,eAAe,CAAC,EAAE,CAAC;IACvC,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAqBA;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAI,EAAE,GAAG,SAAsB,UAE7E;AAED,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACnC,yEAAyE;IACzE,OAAO,EAAE,WAAW,CAAC;IACrB,+EAA+E;IAC/E,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,SAAS,GAClB,sBAAsB,CA0CxB"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The CanvasCard → grid-layout algorithm, extracted from the component so it
|
|
4
|
+
* can be unit-tested and reused without rendering anything.
|
|
5
|
+
*
|
|
6
|
+
* This is the half of `DraggableCardPage` that was copied verbatim into three
|
|
7
|
+
* apps (crm7, braden, throughput) and was absent from two more (BSU, conduit).
|
|
8
|
+
* It has no app dependencies, no permission model and no adapter — those are
|
|
9
|
+
* the parts that legitimately differ per app and stay there.
|
|
10
|
+
*/
|
|
11
|
+
import { Children, Fragment, isValidElement, } from 'react';
|
|
12
|
+
/** The 12-column grid every BSuite page canvas is laid out against. */
|
|
13
|
+
export const CANVAS_GRID_COLUMNS = 12;
|
|
14
|
+
export function isCanvasCardElement(node) {
|
|
15
|
+
return (isValidElement(node) &&
|
|
16
|
+
node.type.displayName === 'CanvasCard');
|
|
17
|
+
}
|
|
18
|
+
function isFragmentElement(node) {
|
|
19
|
+
return isValidElement(node) && node.type === Fragment;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Best-effort display name for a node that is about to be discarded.
|
|
23
|
+
* "Something was dropped" is not actionable across hundreds of pages; the
|
|
24
|
+
* component's own name is what makes it findable.
|
|
25
|
+
*/
|
|
26
|
+
export function describeNode(node) {
|
|
27
|
+
if (!isValidElement(node))
|
|
28
|
+
return typeof node === 'string'
|
|
29
|
+
? `text ${JSON.stringify(node)}`
|
|
30
|
+
: String(node);
|
|
31
|
+
const t = node.type;
|
|
32
|
+
if (typeof t === 'string')
|
|
33
|
+
return `<${t}>`;
|
|
34
|
+
return `<${t?.displayName ?? t?.name ?? 'Unknown'}>`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Depth-first flatten: CanvasCards kept, Fragments expanded, everything else
|
|
38
|
+
* dropped — and RECORDED rather than vanishing.
|
|
39
|
+
*
|
|
40
|
+
* The dropping itself is correct and deliberate: this maps children onto grid
|
|
41
|
+
* slots, and a node with no `cardKey` has no slot to occupy. What was wrong is
|
|
42
|
+
* that it happened in total silence, so a page could render with a confirm
|
|
43
|
+
* dialog missing and look completely fine — the button appears, the click
|
|
44
|
+
* handler runs, state flips, and the element it toggles was never mounted.
|
|
45
|
+
*
|
|
46
|
+
* Fragments must be expanded explicitly because `Children.forEach` is shallow:
|
|
47
|
+
* it sees one Fragment node, not the CanvasCards inside it. A Fragment-wrapped
|
|
48
|
+
* conditional branch used to drop every nested card silently (the crm7 burn-7
|
|
49
|
+
* CI class, `leave/[id]`).
|
|
50
|
+
*/
|
|
51
|
+
export function flattenCanvasCards(nodes) {
|
|
52
|
+
const cards = [];
|
|
53
|
+
const dropped = [];
|
|
54
|
+
Children.forEach(nodes, (child) => {
|
|
55
|
+
// Falsy entries are the documented conditional shape — `{cond && <CanvasCard/>}`
|
|
56
|
+
// collapses to `false`, which is intentional and must stay silent.
|
|
57
|
+
if (child == null || child === false || child === true || child === '')
|
|
58
|
+
return;
|
|
59
|
+
if (isCanvasCardElement(child)) {
|
|
60
|
+
cards.push(child);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (isFragmentElement(child)) {
|
|
64
|
+
const inner = flattenCanvasCards(child.props.children);
|
|
65
|
+
cards.push(...inner.cards);
|
|
66
|
+
dropped.push(...inner.dropped);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
dropped.push(describeNode(child));
|
|
70
|
+
});
|
|
71
|
+
return { cards, dropped };
|
|
72
|
+
}
|
|
73
|
+
export function clampColumns(value, min = 1, max = CANVAS_GRID_COLUMNS) {
|
|
74
|
+
return Math.min(Math.max(value, min), max);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build the `widgets` dict and `defaultLayouts.lg` array from CanvasCard
|
|
78
|
+
* children.
|
|
79
|
+
*
|
|
80
|
+
* Cards flow left-to-right and wrap to a new row when the next card would
|
|
81
|
+
* overflow the 12-column grid. Because the dict is built FROM THE CHILDREN AT
|
|
82
|
+
* RENDER TIME, a `.map()` producing N cards is fully supported and needs no
|
|
83
|
+
* registration mechanism — each mapped card becomes its own independent grid
|
|
84
|
+
* item. (This is the fact that disproves the "dynamic card lists cannot be
|
|
85
|
+
* split into static widget keys" objection recorded in several app ledgers.)
|
|
86
|
+
*/
|
|
87
|
+
export function buildCanvasCardLayout(children) {
|
|
88
|
+
const widgets = {};
|
|
89
|
+
const lg = [];
|
|
90
|
+
let x = 0;
|
|
91
|
+
let y = 0;
|
|
92
|
+
let rowHeight = 0;
|
|
93
|
+
const { cards, dropped } = flattenCanvasCards(children);
|
|
94
|
+
for (const child of cards) {
|
|
95
|
+
// autoHeight defaults to TRUE — cards track measured content height unless
|
|
96
|
+
// a card explicitly opts out for a virtualized list. See CanvasCardProps.
|
|
97
|
+
const { cardKey, h = 6, minH = 2, minW = 4, autoHeight = true, children: body, } = child.props;
|
|
98
|
+
const normalizedMinW = clampColumns(minW);
|
|
99
|
+
const width = clampColumns(child.props.w ?? CANVAS_GRID_COLUMNS, normalizedMinW);
|
|
100
|
+
if (x + width > CANVAS_GRID_COLUMNS) {
|
|
101
|
+
y += rowHeight;
|
|
102
|
+
x = 0;
|
|
103
|
+
rowHeight = 0;
|
|
104
|
+
}
|
|
105
|
+
widgets[cardKey] = (_jsx("div", { className: "relative h-full group/canvas-card", children: body }));
|
|
106
|
+
lg.push({
|
|
107
|
+
i: cardKey,
|
|
108
|
+
x,
|
|
109
|
+
y,
|
|
110
|
+
w: width,
|
|
111
|
+
h,
|
|
112
|
+
minW: normalizedMinW,
|
|
113
|
+
minH,
|
|
114
|
+
...(autoHeight ? { autoHeight: true } : {}),
|
|
115
|
+
});
|
|
116
|
+
x += width;
|
|
117
|
+
rowHeight = Math.max(rowHeight, h);
|
|
118
|
+
}
|
|
119
|
+
return { widgets, layouts: { lg }, dropped };
|
|
120
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export { PageGridLayout } from './PageGridLayout.js';
|
|
2
2
|
export { PageEditorLauncher } from './PageEditorLauncher.js';
|
|
3
|
+
export { CanvasCard } from './CanvasCard.js';
|
|
4
|
+
export type { CanvasCardProps } from './CanvasCard.js';
|
|
5
|
+
export { DraggableCardPage } from './DraggableCardPage.js';
|
|
6
|
+
export type { DraggableCardPageProps } from './DraggableCardPage.js';
|
|
7
|
+
export { buildCanvasCardLayout, flattenCanvasCards, isCanvasCardElement, describeNode, clampColumns, CANVAS_GRID_COLUMNS, } from './canvasCardLayout.js';
|
|
8
|
+
export type { CanvasCardLayoutResult } from './canvasCardLayout.js';
|
|
3
9
|
export type { WidgetConfig, PageEditorLauncherProps } from './PageEditorLauncher.js';
|
|
4
10
|
export { usePageGridLayout, DEFAULT_EDITOR_EVENT_NAMES, PAGE_GRID_EDITING_EVENT, } from './usePageGridLayout.js';
|
|
5
11
|
export type { PageGridEditingEventDetail } from './usePageGridLayout.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EACL,iBAAiB,EACjB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,YAAY,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,6BAA6B,EAC7B,oBAAoB,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,YAAY,EACV,kBAAkB,EAClB,0BAA0B,EAC1B,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,yBAAyB,EACzB,yBAAyB,EACzB,mBAAmB,EACnB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,gCAAgC,EAChC,wBAAwB,EACxB,uBAAuB,EACvB,UAAU,GACX,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,YAAY,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AACrF,OAAO,EACL,iBAAiB,EACjB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,YAAY,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,6BAA6B,EAC7B,oBAAoB,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,YAAY,EACV,kBAAkB,EAClB,0BAA0B,EAC1B,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,yBAAyB,EACzB,yBAAyB,EACzB,mBAAmB,EACnB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,gCAAgC,EAChC,wBAAwB,EACxB,uBAAuB,EACvB,UAAU,GACX,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { PageGridLayout } from './PageGridLayout.js';
|
|
2
2
|
export { PageEditorLauncher } from './PageEditorLauncher.js';
|
|
3
|
+
export { CanvasCard } from './CanvasCard.js';
|
|
4
|
+
export { DraggableCardPage } from './DraggableCardPage.js';
|
|
5
|
+
export { buildCanvasCardLayout, flattenCanvasCards, isCanvasCardElement, describeNode, clampColumns, CANVAS_GRID_COLUMNS, } from './canvasCardLayout.js';
|
|
3
6
|
export { usePageGridLayout, DEFAULT_EDITOR_EVENT_NAMES, PAGE_GRID_EDITING_EVENT, } from './usePageGridLayout.js';
|
|
4
7
|
export { rescaleLayout } from './rescaleLayout.js';
|
|
5
8
|
export { computeAutoHeightRows } from './autoHeight.js';
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The card-surface contract scanner — ONE implementation, shipped from the
|
|
3
|
+
* package, replacing five divergent hand-rolled copies.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists
|
|
6
|
+
* ---------------
|
|
7
|
+
* crm7 wrote a card-unglue contract test. BSU, conduit, throughput and braden
|
|
8
|
+
* each PORTED it, and each silently dropped checks during the port. The
|
|
9
|
+
* divergence was not cosmetic:
|
|
10
|
+
*
|
|
11
|
+
* - `findUngriddedMultiCard` — the check crm7's own comment calls "the actual
|
|
12
|
+
* root cause of the operator's platform-wide complaint" — was ABSENT from
|
|
13
|
+
* BSU, throughput and braden, and present only in a narrowed form in
|
|
14
|
+
* conduit. In throughput and braden it was absent structurally: their glue
|
|
15
|
+
* scan is gated on the file containing `CanvasCard` at all, so a page with
|
|
16
|
+
* two cards and no grid primitive is invisible by construction.
|
|
17
|
+
* - conduit's header documents THREE card idioms and its regexes detect TWO.
|
|
18
|
+
* The third (`rounded-lg border p-4` section panels) has no detector, which
|
|
19
|
+
* is how five known-glued conduit pages pass as an empty ledger.
|
|
20
|
+
* - BSU's scan only walks files containing the literal string
|
|
21
|
+
* `PageGridLayout`, so every BSU page that never adopted the grid is
|
|
22
|
+
* structurally invisible to BSU's own gate.
|
|
23
|
+
*
|
|
24
|
+
* A gate that cannot tell "checked nothing" from "found nothing" is not a gate
|
|
25
|
+
* (D-92). This module therefore ASSERTS ITS INPUTS: it fails closed if a scan
|
|
26
|
+
* root is missing or matches zero files, and it reports how it reached its
|
|
27
|
+
* verdict rather than only the verdict.
|
|
28
|
+
*
|
|
29
|
+
* What stays per-app
|
|
30
|
+
* ------------------
|
|
31
|
+
* Scan roots, card vocabulary, class-based surface patterns, and the exclusion
|
|
32
|
+
* ledgers. Those are operational truth about a specific app. Everything else —
|
|
33
|
+
* the detection logic — lives here so it can only be fixed once.
|
|
34
|
+
*
|
|
35
|
+
* Node-only: this module touches the filesystem and is exported from
|
|
36
|
+
* `@bsuite/page-builder/scanner`, NOT from the package root, so it never
|
|
37
|
+
* reaches a browser bundle.
|
|
38
|
+
*/
|
|
39
|
+
export type CardSurfaceIdiom =
|
|
40
|
+
/** 2+ card-like components packed inside ONE grid slot. */
|
|
41
|
+
'glued-widget'
|
|
42
|
+
/** 2+ card-like components on a page that never reaches a grid primitive. */
|
|
43
|
+
| 'ungridded-multi-card'
|
|
44
|
+
/** A retired single-widget primitive still present or still imported. */
|
|
45
|
+
| 'retired-primitive'
|
|
46
|
+
/** autoHeight opt-out on a card that is not a virtualized list. */
|
|
47
|
+
| 'autoheight-optout'
|
|
48
|
+
/** autoHeight={false} with a seed height so small the card must clip. */
|
|
49
|
+
| 'clipped-card';
|
|
50
|
+
export interface CardSurfaceFinding {
|
|
51
|
+
file: string;
|
|
52
|
+
idiom: CardSurfaceIdiom;
|
|
53
|
+
detail: string;
|
|
54
|
+
/** Number of card-like components involved, where meaningful. */
|
|
55
|
+
cardCount?: number;
|
|
56
|
+
}
|
|
57
|
+
export interface CardSurfaceScannerConfig {
|
|
58
|
+
/** Absolute path to the repo root. */
|
|
59
|
+
projectRoot: string;
|
|
60
|
+
/**
|
|
61
|
+
* Directories to walk, repo-relative. Every one MUST exist and MUST contain
|
|
62
|
+
* at least one scannable file, or the scan fails closed.
|
|
63
|
+
*/
|
|
64
|
+
scanRoots: string[];
|
|
65
|
+
/**
|
|
66
|
+
* Component names that count as a card. Apps differ materially here: crm7
|
|
67
|
+
* uses `Card`/`StatCard`/`SummaryCard`, BSU adds `ServiceCard`, conduit uses
|
|
68
|
+
* `SummaryCard`/`CounterCard`.
|
|
69
|
+
*/
|
|
70
|
+
cardTags: string[];
|
|
71
|
+
/**
|
|
72
|
+
* Class-based card surfaces — a card that is a styled `div`, not a
|
|
73
|
+
* component. This is the idiom conduit documented and never implemented, and
|
|
74
|
+
* the one BSU's `glass-card` tiles use. Supply e.g.
|
|
75
|
+
* `[/glass-card/, /rounded-lg border p-4/]`.
|
|
76
|
+
*/
|
|
77
|
+
classSurfaces?: RegExp[];
|
|
78
|
+
/** Identifiers that prove a file reached the grid. */
|
|
79
|
+
gridPrimitives?: string[];
|
|
80
|
+
/** Repo-relative path of a retired single-widget primitive that must not exist. */
|
|
81
|
+
retiredPrimitivePaths?: string[];
|
|
82
|
+
/** file -> reason. A glued widget listed here is allowed. */
|
|
83
|
+
gluedExclusions?: Record<string, string>;
|
|
84
|
+
/** file -> reason. An ungridded multi-card page listed here is allowed. */
|
|
85
|
+
ungriddedExclusions?: Record<string, string>;
|
|
86
|
+
/**
|
|
87
|
+
* Directories deliberately NOT scanned, each with a reason. Declaring a
|
|
88
|
+
* blind spot is required; having an undeclared one is the defect.
|
|
89
|
+
*/
|
|
90
|
+
blindSpots?: Record<string, string>;
|
|
91
|
+
/**
|
|
92
|
+
* If a file matches this, an `autoHeight={false}` in it is permitted — the
|
|
93
|
+
* opt-out is reserved for genuinely virtualized/windowed lists.
|
|
94
|
+
*/
|
|
95
|
+
autoHeightEscapeHatch?: RegExp;
|
|
96
|
+
/** File extensions to scan. Defaults to .tsx. */
|
|
97
|
+
extensions?: string[];
|
|
98
|
+
}
|
|
99
|
+
export interface CardSurfaceScanResult {
|
|
100
|
+
findings: CardSurfaceFinding[];
|
|
101
|
+
/** Files actually read. If this is 0 the scan proved nothing. */
|
|
102
|
+
filesScanned: number;
|
|
103
|
+
scanRootsResolved: string[];
|
|
104
|
+
/** Exclusion-ledger keys that no longer match any finding — stale entries. */
|
|
105
|
+
staleExclusions: string[];
|
|
106
|
+
/** Human-readable account of how the verdict was reached. */
|
|
107
|
+
summary: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Strip block comments, line comments and JSX comments before matching.
|
|
111
|
+
*
|
|
112
|
+
* Three of the five hand-rolled copies matched against raw file content, which
|
|
113
|
+
* produced two opposite errors at once: a commented-out `<Card/><Card/>`
|
|
114
|
+
* example false-POSITIVES as glue, and a file that merely MENTIONS
|
|
115
|
+
* `CanvasCard` in a TODO comment false-NEGATIVES as "already gridded" and is
|
|
116
|
+
* skipped entirely. Stripping first fixes both.
|
|
117
|
+
*/
|
|
118
|
+
export declare function stripComments(source: string): string;
|
|
119
|
+
export declare function scanCardSurfaces(config: CardSurfaceScannerConfig): CardSurfaceScanResult;
|
|
120
|
+
//# sourceMappingURL=cardSurfaceScanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cardSurfaceScanner.d.ts","sourceRoot":"","sources":["../../src/scanner/cardSurfaceScanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAKH,MAAM,MAAM,gBAAgB;AAC1B,2DAA2D;AACzD,cAAc;AAChB,6EAA6E;GAC3E,sBAAsB;AACxB,yEAAyE;GACvE,mBAAmB;AACrB,mEAAmE;GACjE,mBAAmB;AACrB,yEAAyE;GACvE,cAAc,CAAC;AAEnB,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,sCAAsC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,sDAAsD;IACtD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,mFAAmF;IACnF,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,2EAA2E;IAC3E,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,8EAA8E;IAC9E,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;CACjB;AASD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKpD;AAwHD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,wBAAwB,GAC/B,qBAAqB,CAwIvB"}
|