@bsuite/page-builder 1.1.0 → 2.1.0-next.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 +28 -0
- package/dist/CanvasCard.d.ts.map +1 -1
- package/dist/DraggableCardPage.d.ts +1 -1
- package/dist/PageGridLayout.js +1 -1
- package/dist/canvasCardLayout.d.ts +1 -1
- package/dist/canvasCardLayout.d.ts.map +1 -1
- package/dist/canvasCardLayout.js +12 -2
- package/dist/cardSurfaces.stories.d.ts +2 -2
- package/dist/cardSurfaces.stories.js +2 -2
- package/dist/scanner/cardHeadingScanner.d.ts +91 -0
- package/dist/scanner/cardHeadingScanner.d.ts.map +1 -0
- package/dist/scanner/cardHeadingScanner.js +278 -0
- package/dist/scanner/cardSurfaceScanner.d.ts +1 -1
- package/dist/scanner/index.d.ts +2 -0
- package/dist/scanner/index.d.ts.map +1 -1
- package/dist/scanner/index.js +1 -0
- package/dist/types.d.ts +2 -2
- package/dist/usePageGridLayout.d.ts.map +1 -1
- package/dist/usePageGridLayout.js +29 -5
- package/package.json +1 -1
package/dist/CanvasCard.d.ts
CHANGED
|
@@ -35,6 +35,34 @@ export interface CanvasCardProps {
|
|
|
35
35
|
* and was swept. Do not reintroduce it on a plain card or table.
|
|
36
36
|
*/
|
|
37
37
|
autoHeight?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Whether the GRID ITEM paints a card around this slot — border, radius,
|
|
40
|
+
* background, shadow. Threaded through to {@link GridLayoutItem.chrome} by
|
|
41
|
+
* `buildCanvasCardLayout`, and per-item always beats the page-level
|
|
42
|
+
* `itemChrome` prop in either direction.
|
|
43
|
+
*
|
|
44
|
+
* Leave it undefined and the app's `itemChrome` decides. Say `chrome={false}`
|
|
45
|
+
* on a slot whose content is NOT a card — a marketing section, a page
|
|
46
|
+
* heading, a chart that supplies its own surface — in an app that has set
|
|
47
|
+
* `itemChrome` true; say `chrome` on a BARE slot in an app that has not.
|
|
48
|
+
*
|
|
49
|
+
* WHY IT EXISTS AT ALL (2.1.0). 2.0.0 inverted the grid-item chrome default
|
|
50
|
+
* and offered `GridLayoutItem.chrome` as the per-slot escape hatch. That
|
|
51
|
+
* hatch was real for the handful of pages that hand-author a layout array,
|
|
52
|
+
* and UNREACHABLE for the ~1,700 CanvasCard usages that are how BSuite pages
|
|
53
|
+
* are actually written: `CanvasCardProps` did not declare the prop and
|
|
54
|
+
* `buildCanvasCardLayout` destructured a fixed set of fields, so a `chrome`
|
|
55
|
+
* written on a CanvasCard was dropped before it could reach a layout item.
|
|
56
|
+
* crm7's 2.0.0 adoption shipped a comment promising the opt-out and zero
|
|
57
|
+
* call sites able to use it. Declaring it here is what makes the promise
|
|
58
|
+
* true.
|
|
59
|
+
*
|
|
60
|
+
* Unlike `autoHeight`, this is not a hint the grid may override: a slot that
|
|
61
|
+
* says false is never framed. It also survives a drag — see the `chrome`
|
|
62
|
+
* restore in `stripAutoHeightRows`, which exists because react-grid-layout
|
|
63
|
+
* does not round-trip custom item props.
|
|
64
|
+
*/
|
|
65
|
+
chrome?: boolean;
|
|
38
66
|
/** Card body — typically a single card or page-header element. */
|
|
39
67
|
children: ReactNode;
|
|
40
68
|
}
|
package/dist/CanvasCard.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CanvasCard.d.ts","sourceRoot":"","sources":["../src/CanvasCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;IAC3B,qEAAqE;IACrE,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;IAC9B,4FAA4F;IAC5F,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kEAAkE;IAClE,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAExD;yBAFe,UAAU"}
|
|
1
|
+
{"version":3,"file":"CanvasCard.d.ts","sourceRoot":"","sources":["../src/CanvasCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;IAC3B,qEAAqE;IACrE,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;IAC9B,4FAA4F;IAC5F,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;OAgBG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kEAAkE;IAClE,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAExD;yBAFe,UAAU"}
|
|
@@ -109,7 +109,7 @@ export type DraggableCardPageProps = Omit<PageGridLayoutProps, 'widgets' | 'defa
|
|
|
109
109
|
* Width, in grid columns, for cards that do not set `w` themselves.
|
|
110
110
|
*
|
|
111
111
|
* Defaults to `DEFAULT_CANVAS_CARD_WIDTH` (6 — half the grid, two cards per
|
|
112
|
-
* row) as of
|
|
112
|
+
* row) as of 2.0.0. Pass 12 to restore the pre-2.0.0 full-width stack while
|
|
113
113
|
* an app migrates its pages.
|
|
114
114
|
*/
|
|
115
115
|
defaultCardWidth?: number;
|
package/dist/PageGridLayout.js
CHANGED
|
@@ -236,7 +236,7 @@ export function PageGridLayout({ pageKey, defaultLayouts, defaultCols, layoutVer
|
|
|
236
236
|
isResizable = true, resizeHandles = DEFAULT_RESIZE_HANDLES, tenantId, defaultAutoHeight,
|
|
237
237
|
// CHROME OFF BY DEFAULT — the inversion. See `GridItemProps.chrome`.
|
|
238
238
|
// An app that is not ready to migrate its own card surfaces passes
|
|
239
|
-
// `itemChrome` to get the pre-
|
|
239
|
+
// `itemChrome` to get the pre-2.0.0 behaviour back for every slot, and
|
|
240
240
|
// migrates page by page with the per-item `chrome` flag.
|
|
241
241
|
itemChrome = false, addEntityWidgetEventNames = DEFAULT_ADD_ENTITY_WIDGET_EVENT_NAMES, createEntityWidget, onRegisterEntityWidget, relationshipCatalog, addRelationshipWidgetEventNames = DEFAULT_ADD_RELATIONSHIP_WIDGET_EVENT_NAMES, createRelationshipWidget, onRegisterRelationshipWidget, onRelationshipWidgetRejected, }) {
|
|
242
242
|
const { currentLayouts, layoutCols, isEditing, setIsEditing, activeCols, activeCompactor, onLayoutChange, handleColumnChange, handleBreakpointChange, handleCompact, handleReset, addWidget, moveWidget, setWidgetLocked, applyAutoHeightRows, autoHeightRows, resetConfirmOpen, setResetConfirmOpen, containerRef, containerWidth, } = usePageGridLayout({
|
|
@@ -15,7 +15,7 @@ export declare const CANVAS_GRID_COLUMNS = 12;
|
|
|
15
15
|
/**
|
|
16
16
|
* The width a `CanvasCard` gets when its author does not say.
|
|
17
17
|
*
|
|
18
|
-
* SIX, i.e. half the grid — changed from twelve in
|
|
18
|
+
* SIX, i.e. half the grid — changed from twelve in 2.0.0. Twelve on a
|
|
19
19
|
* twelve-column grid means every card that omits `w` renders full width and
|
|
20
20
|
* the page becomes a single vertical stack. Measured across the estate on
|
|
21
21
|
* 2026-08-25: **1,068 of 1,729 CanvasCard usages omit `w`** (BSU and conduit
|
|
@@ -1 +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;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C,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,EACnB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,GACtC,sBAAsB,
|
|
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;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C,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,EACnB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,GACtC,sBAAsB,CAsDxB"}
|
package/dist/canvasCardLayout.js
CHANGED
|
@@ -14,7 +14,7 @@ export const CANVAS_GRID_COLUMNS = 12;
|
|
|
14
14
|
/**
|
|
15
15
|
* The width a `CanvasCard` gets when its author does not say.
|
|
16
16
|
*
|
|
17
|
-
* SIX, i.e. half the grid — changed from twelve in
|
|
17
|
+
* SIX, i.e. half the grid — changed from twelve in 2.0.0. Twelve on a
|
|
18
18
|
* twelve-column grid means every card that omits `w` renders full width and
|
|
19
19
|
* the page becomes a single vertical stack. Measured across the estate on
|
|
20
20
|
* 2026-08-25: **1,068 of 1,729 CanvasCard usages omit `w`** (BSU and conduit
|
|
@@ -115,7 +115,7 @@ export function buildCanvasCardLayout(children, options = {}) {
|
|
|
115
115
|
for (const child of cards) {
|
|
116
116
|
// autoHeight defaults to TRUE — cards track measured content height unless
|
|
117
117
|
// a card explicitly opts out for a virtualized list. See CanvasCardProps.
|
|
118
|
-
const { cardKey, h = 6, minH = 2, minW = 4, autoHeight = true, children: body, } = child.props;
|
|
118
|
+
const { cardKey, h = 6, minH = 2, minW = 4, autoHeight = true, chrome, children: body, } = child.props;
|
|
119
119
|
const normalizedMinW = clampColumns(minW);
|
|
120
120
|
const width = clampColumns(child.props.w ?? defaultWidth, normalizedMinW);
|
|
121
121
|
if (x + width > CANVAS_GRID_COLUMNS) {
|
|
@@ -133,6 +133,16 @@ export function buildCanvasCardLayout(children, options = {}) {
|
|
|
133
133
|
minW: normalizedMinW,
|
|
134
134
|
minH,
|
|
135
135
|
...(autoHeight ? { autoHeight: true } : {}),
|
|
136
|
+
// `chrome` is THREE-VALUED and only two of those values are the card's
|
|
137
|
+
// own: true and false both have to reach the layout item, and undefined
|
|
138
|
+
// has to stay absent so `layoutItem.chrome ?? itemChrome` can fall
|
|
139
|
+
// through to the app default. Writing `chrome: chrome` unconditionally
|
|
140
|
+
// would put an explicit `undefined` on every item, which is still absent
|
|
141
|
+
// to `??` but is NOT absent to a structural comparison — and
|
|
142
|
+
// `stripAutoHeightRows` compares items across a gesture. Spreading only
|
|
143
|
+
// when the author said something keeps the item shape identical to what
|
|
144
|
+
// 2.0.0 produced for the 1,729 cards that say nothing.
|
|
145
|
+
...(chrome === undefined ? {} : { chrome }),
|
|
136
146
|
});
|
|
137
147
|
x += width;
|
|
138
148
|
rowHeight = Math.max(rowHeight, h);
|
|
@@ -8,7 +8,7 @@ import type { ReactNode } from 'react';
|
|
|
8
8
|
* inversion has something to be checked against that is not a number.
|
|
9
9
|
*
|
|
10
10
|
* Read them in pairs: the story named "…(the defect)" is what shipped up to
|
|
11
|
-
* 1.0.7, and the one beside it is what
|
|
11
|
+
* 1.0.7, and the one beside it is what 2.0.0 renders.
|
|
12
12
|
*/
|
|
13
13
|
/**
|
|
14
14
|
* Plain CSF, no `Meta`/`StoryObj` type imports.
|
|
@@ -35,7 +35,7 @@ export default _default;
|
|
|
35
35
|
* two radii, two 1px borders, two backgrounds. 316+ files render this.
|
|
36
36
|
*/
|
|
37
37
|
export declare const NestedCardDoubleFrame: Story;
|
|
38
|
-
/** The same content on
|
|
38
|
+
/** The same content on 2.0.0 defaults: ONE border, ONE radius. */
|
|
39
39
|
export declare const NestedCardSingleFrame: Story;
|
|
40
40
|
/** Bare content with chrome OFF — the regression the inversion could cause. */
|
|
41
41
|
export declare const BareContentNoChrome: Story;
|
|
@@ -21,9 +21,9 @@ export const NestedCardDoubleFrame = {
|
|
|
21
21
|
name: 'Nested card — DOUBLE FRAME (the defect)',
|
|
22
22
|
render: () => (_jsx("div", { className: "p-8", children: _jsx(PageGridLayout, { pageKey: "sb/nested-defect", defaultLayouts: oneSlot, itemChrome: true, widgets: { a: _jsx(AppCard, { title: "Quick Actions", children: "Grid item chrome ON + app card." }) } }) })),
|
|
23
23
|
};
|
|
24
|
-
/** The same content on
|
|
24
|
+
/** The same content on 2.0.0 defaults: ONE border, ONE radius. */
|
|
25
25
|
export const NestedCardSingleFrame = {
|
|
26
|
-
name: 'Nested card — ONE frame (
|
|
26
|
+
name: 'Nested card — ONE frame (2.0.0 default)',
|
|
27
27
|
render: () => (_jsx("div", { className: "p-8", children: _jsx(PageGridLayout, { pageKey: "sb/nested-fixed", defaultLayouts: oneSlot, widgets: { a: _jsx(AppCard, { title: "Quick Actions", children: "Grid item chrome OFF; the app card is the only surface." }) } }) })),
|
|
28
28
|
};
|
|
29
29
|
/** Bare content with chrome OFF — the regression the inversion could cause. */
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which headings are CARD headings, and do they carry the gradient?
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. The estate's heading gradient was implemented as a
|
|
5
|
+
* PAGE-TITLE convention and shipped: 183 of 232 crm7 `<h1>` page titles carry
|
|
6
|
+
* `.text-gradient-accent`. Card headings were never in scope, and the numbers
|
|
7
|
+
* say so plainly — 0 of 304 crm7 `<h2>`/`<h3>`, 15 of 734 estate-wide. The work
|
|
8
|
+
* was done, declared complete, and covered a DIFFERENT ELEMENT than the one the
|
|
9
|
+
* operator keeps pointing at. On the very dashboard he screenshotted,
|
|
10
|
+
* "Dashboard" (h1) has the gradient and "Quick Actions", "Communication Center"
|
|
11
|
+
* and "Pipeline Overview" (h2/h3) do not.
|
|
12
|
+
*
|
|
13
|
+
* SO WHY NOT JUST GREP `<h2`? Because `<h2>` is not the same thing as "card
|
|
14
|
+
* heading", and a sweep that treats them as equal gradients dialog titles,
|
|
15
|
+
* empty-state titles, section labels and table captions along with the cards.
|
|
16
|
+
* The operator asked for card headings. A detector that cannot tell a card
|
|
17
|
+
* heading from any other heading would deliver 734 changes to satisfy a request
|
|
18
|
+
* about roughly 600 of them, and the excess is what gets noticed.
|
|
19
|
+
*
|
|
20
|
+
* A heading is a CARD heading here when an enclosing element paints card chrome
|
|
21
|
+
* — a card component, or a border+radius+background surface — which is the same
|
|
22
|
+
* definition `cardSurfaceScanner` already uses for nested chrome. One definition
|
|
23
|
+
* of "card", two scanners.
|
|
24
|
+
*
|
|
25
|
+
* FAIL CLOSED. A file whose JSX tags do not balance is UNKNOWN, not clean: a
|
|
26
|
+
* tag stack that has lost its place cannot say what encloses what, and
|
|
27
|
+
* "checked nothing" must not share an exit code with "found nothing" (D-92).
|
|
28
|
+
*
|
|
29
|
+
* Node-only: filesystem access, exported from `@bsuite/page-builder/scanner`.
|
|
30
|
+
*/
|
|
31
|
+
export interface CardHeadingFinding {
|
|
32
|
+
file: string;
|
|
33
|
+
/** `h2` or `h3`. */
|
|
34
|
+
tag: string;
|
|
35
|
+
/** 1-based line of the heading's opening tag. */
|
|
36
|
+
line: number;
|
|
37
|
+
/** The chrome element that makes this a card heading. */
|
|
38
|
+
enclosingSurface: string;
|
|
39
|
+
/** Does it already carry a gradient class? */
|
|
40
|
+
hasGradient: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Classes that fight `width: fit-content`.
|
|
43
|
+
*
|
|
44
|
+
* `.text-gradient-accent` sets `width: fit-content`, and that is load-bearing
|
|
45
|
+
* — `background-clip: text` paints across the ELEMENT BOX, so a block-level
|
|
46
|
+
* heading spanning its container samples only the first slice of the gradient
|
|
47
|
+
* and renders flat, which is indistinguishable from having no gradient at
|
|
48
|
+
* all. But a heading that is `truncate`, `flex-1`, `w-full` or `line-clamp-*`
|
|
49
|
+
* DEPENDS on filling its box, and shrink-wrapping it changes the layout.
|
|
50
|
+
* Those headings need a decision, not a codemod.
|
|
51
|
+
*/
|
|
52
|
+
widthDependentClasses: string[];
|
|
53
|
+
}
|
|
54
|
+
export interface CardHeadingScanResult {
|
|
55
|
+
findings: CardHeadingFinding[];
|
|
56
|
+
filesScanned: number;
|
|
57
|
+
scanRootsResolved: string[];
|
|
58
|
+
/** Files whose JSX could not be walked. UNKNOWN is not clean. */
|
|
59
|
+
unknownFiles: string[];
|
|
60
|
+
summary: string;
|
|
61
|
+
}
|
|
62
|
+
export interface CardHeadingScannerConfig {
|
|
63
|
+
projectRoot: string;
|
|
64
|
+
scanRoots: string[];
|
|
65
|
+
/** Component names that count as card chrome. */
|
|
66
|
+
cardTags: string[];
|
|
67
|
+
/** Extra chrome components, as in `cardSurfaceScanner`. */
|
|
68
|
+
nestedChromeTags?: string[];
|
|
69
|
+
/** Heading tags to consider. Defaults to h2 and h3 — h1 is the page title. */
|
|
70
|
+
headingTags?: string[];
|
|
71
|
+
/** Classes that already paint a heading gradient. */
|
|
72
|
+
gradientClasses?: string[];
|
|
73
|
+
extensions?: string[];
|
|
74
|
+
}
|
|
75
|
+
interface JsxTag {
|
|
76
|
+
kind: 'open' | 'close' | 'self';
|
|
77
|
+
tag: string;
|
|
78
|
+
attrs: string;
|
|
79
|
+
index: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Tokenise JSX tags with their kind and offset.
|
|
83
|
+
*
|
|
84
|
+
* `openingTags` in the sibling module is deliberately kind-blind — it only has
|
|
85
|
+
* to find chrome inside a known block. Ancestry needs a stack, and a stack
|
|
86
|
+
* needs to know which tags close.
|
|
87
|
+
*/
|
|
88
|
+
export declare function jsxTags(source: string): JsxTag[];
|
|
89
|
+
export declare function scanCardHeadings(config: CardHeadingScannerConfig): CardHeadingScanResult;
|
|
90
|
+
export {};
|
|
91
|
+
//# sourceMappingURL=cardHeadingScanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cardHeadingScanner.d.ts","sourceRoot":"","sources":["../../src/scanner/cardHeadingScanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,gBAAgB,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,WAAW,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;OAUG;IACH,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,iEAAiE;IACjE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,qDAAqD;IACrD,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAkDD,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAqDhD;AAsCD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,wBAAwB,GAAG,qBAAqB,CA+FxF"}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which headings are CARD headings, and do they carry the gradient?
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. The estate's heading gradient was implemented as a
|
|
5
|
+
* PAGE-TITLE convention and shipped: 183 of 232 crm7 `<h1>` page titles carry
|
|
6
|
+
* `.text-gradient-accent`. Card headings were never in scope, and the numbers
|
|
7
|
+
* say so plainly — 0 of 304 crm7 `<h2>`/`<h3>`, 15 of 734 estate-wide. The work
|
|
8
|
+
* was done, declared complete, and covered a DIFFERENT ELEMENT than the one the
|
|
9
|
+
* operator keeps pointing at. On the very dashboard he screenshotted,
|
|
10
|
+
* "Dashboard" (h1) has the gradient and "Quick Actions", "Communication Center"
|
|
11
|
+
* and "Pipeline Overview" (h2/h3) do not.
|
|
12
|
+
*
|
|
13
|
+
* SO WHY NOT JUST GREP `<h2`? Because `<h2>` is not the same thing as "card
|
|
14
|
+
* heading", and a sweep that treats them as equal gradients dialog titles,
|
|
15
|
+
* empty-state titles, section labels and table captions along with the cards.
|
|
16
|
+
* The operator asked for card headings. A detector that cannot tell a card
|
|
17
|
+
* heading from any other heading would deliver 734 changes to satisfy a request
|
|
18
|
+
* about roughly 600 of them, and the excess is what gets noticed.
|
|
19
|
+
*
|
|
20
|
+
* A heading is a CARD heading here when an enclosing element paints card chrome
|
|
21
|
+
* — a card component, or a border+radius+background surface — which is the same
|
|
22
|
+
* definition `cardSurfaceScanner` already uses for nested chrome. One definition
|
|
23
|
+
* of "card", two scanners.
|
|
24
|
+
*
|
|
25
|
+
* FAIL CLOSED. A file whose JSX tags do not balance is UNKNOWN, not clean: a
|
|
26
|
+
* tag stack that has lost its place cannot say what encloses what, and
|
|
27
|
+
* "checked nothing" must not share an exit code with "found nothing" (D-92).
|
|
28
|
+
*
|
|
29
|
+
* Node-only: filesystem access, exported from `@bsuite/page-builder/scanner`.
|
|
30
|
+
*/
|
|
31
|
+
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
|
|
32
|
+
import { join, relative } from 'node:path';
|
|
33
|
+
import { stripComments, elementDeclaresCardChrome } from './cardSurfaceScanner.js';
|
|
34
|
+
const DEFAULT_HEADINGS = ['h2', 'h3'];
|
|
35
|
+
const DEFAULT_GRADIENT_CLASSES = ['text-gradient-accent', 'gradient-text'];
|
|
36
|
+
/**
|
|
37
|
+
* Classes whose effect DEPENDS on the heading filling its box.
|
|
38
|
+
*
|
|
39
|
+
* `.text-gradient-accent` sets `width: fit-content`, and that is load-bearing —
|
|
40
|
+
* `background-clip: text` paints across the ELEMENT BOX, so a block-level
|
|
41
|
+
* heading spanning its container samples only the first slice of the gradient
|
|
42
|
+
* and renders flat, indistinguishable from having no gradient at all. Shrinking
|
|
43
|
+
* the box is therefore not optional; it is the effect.
|
|
44
|
+
*
|
|
45
|
+
* But shrinking the box is VISIBLE wherever the box itself is visible or
|
|
46
|
+
* positioned. Three families, and the first was the only one measured on the
|
|
47
|
+
* first pass:
|
|
48
|
+
*
|
|
49
|
+
* 1. SIZE — `truncate`, `flex-1`, `w-full`, `line-clamp-*`. Fit-content never
|
|
50
|
+
* truncates and never fills.
|
|
51
|
+
* 2. POSITION — `text-center`, `mx-auto`. A centred heading stops centring the
|
|
52
|
+
* moment its box hugs the glyphs, because there is nothing left to centre
|
|
53
|
+
* the text inside.
|
|
54
|
+
* 3. PAINT — the heading's OWN `border-*` or `bg-*`. A rule or a tinted strip
|
|
55
|
+
* that used to span the card suddenly ends at the last letter. This is the
|
|
56
|
+
* most visible of the three and the easiest to miss in a diff.
|
|
57
|
+
*
|
|
58
|
+
* A first, loose measurement over a three-line window put family 1 at 2 and
|
|
59
|
+
* implied the sweep was nearly free. Reading the heading's OWN attributes puts
|
|
60
|
+
* the full hazard set an order of magnitude higher. The loose number was the
|
|
61
|
+
* kind that makes a codemod look safe.
|
|
62
|
+
*/
|
|
63
|
+
const WIDTH_DEPENDENT = [
|
|
64
|
+
'truncate',
|
|
65
|
+
'flex-1',
|
|
66
|
+
'w-full',
|
|
67
|
+
'grow',
|
|
68
|
+
'flex-grow',
|
|
69
|
+
'text-center',
|
|
70
|
+
'mx-auto',
|
|
71
|
+
];
|
|
72
|
+
const WIDTH_DEPENDENT_PREFIXES = ['line-clamp-', 'basis-', 'w-', 'min-w-', 'max-w-'];
|
|
73
|
+
/** The heading paints its own box — a border or a background of its own. */
|
|
74
|
+
const OWN_PAINT = /(?:^|\s)(?:border(?:-[a-z0-9[\]().\-/]+)?|bg-[a-z0-9[\]().\-/]+)(?=$|\s)/;
|
|
75
|
+
/** Void HTML elements never carry a closing tag. */
|
|
76
|
+
const VOID_TAGS = new Set([
|
|
77
|
+
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
|
78
|
+
'link', 'meta', 'param', 'source', 'track', 'wbr',
|
|
79
|
+
]);
|
|
80
|
+
/**
|
|
81
|
+
* Tokenise JSX tags with their kind and offset.
|
|
82
|
+
*
|
|
83
|
+
* `openingTags` in the sibling module is deliberately kind-blind — it only has
|
|
84
|
+
* to find chrome inside a known block. Ancestry needs a stack, and a stack
|
|
85
|
+
* needs to know which tags close.
|
|
86
|
+
*/
|
|
87
|
+
export function jsxTags(source) {
|
|
88
|
+
const out = [];
|
|
89
|
+
for (let i = 0; i < source.length; i++) {
|
|
90
|
+
if (source[i] !== '<')
|
|
91
|
+
continue;
|
|
92
|
+
const close = /^<\/([A-Za-z][A-Za-z0-9._-]*)\s*>/.exec(source.slice(i, i + 80));
|
|
93
|
+
if (close) {
|
|
94
|
+
out.push({ kind: 'close', tag: close[1], attrs: '', index: i });
|
|
95
|
+
i += close[0].length - 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const open = /^<([A-Za-z][A-Za-z0-9._-]*)/.exec(source.slice(i, i + 64));
|
|
99
|
+
if (!open)
|
|
100
|
+
continue;
|
|
101
|
+
// KNOWN LIMITATION, measured rather than papered over. A TypeScript type
|
|
102
|
+
// argument presents as a tag to any matcher this cheap:
|
|
103
|
+
// `row.getValue<string>('code')` pushes `<string>`, and every ancestry
|
|
104
|
+
// answer after it in that file is fiction. The file is therefore reported
|
|
105
|
+
// UNKNOWN by the stack check below — 23 files across six apps, 0.8% of the
|
|
106
|
+
// tree, named individually rather than absorbed.
|
|
107
|
+
//
|
|
108
|
+
// A GUARD WAS TRIED AND REVERTED, and the measurement is why. Skipping a
|
|
109
|
+
// tag whose `<` is preceded by an identifier character catches the generics
|
|
110
|
+
// — and also catches JSX that follows TEXT (`Total<span>`), which is
|
|
111
|
+
// ordinary markup. Skipping an OPENING tag while its CLOSING tag is still
|
|
112
|
+
// counted unbalances the stack outright, so UNKNOWN went from 23 to 106.
|
|
113
|
+
// An over-eager filter feeding a stack is strictly worse than no filter,
|
|
114
|
+
// and a real parser is the fix if 23 ever stops being acceptable.
|
|
115
|
+
let j = i + open[0].length;
|
|
116
|
+
let depth = 0;
|
|
117
|
+
let quote = null;
|
|
118
|
+
let closed = false;
|
|
119
|
+
for (; j < source.length; j++) {
|
|
120
|
+
const c = source[j];
|
|
121
|
+
if (quote) {
|
|
122
|
+
if (c === quote && source[j - 1] !== '\\')
|
|
123
|
+
quote = null;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
127
|
+
quote = c;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (c === '{')
|
|
131
|
+
depth++;
|
|
132
|
+
else if (c === '}')
|
|
133
|
+
depth--;
|
|
134
|
+
else if (c === '>' && depth === 0) {
|
|
135
|
+
closed = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!closed)
|
|
140
|
+
continue;
|
|
141
|
+
const attrs = source.slice(i + open[0].length, j);
|
|
142
|
+
const selfClosing = /\/\s*$/.test(attrs) || VOID_TAGS.has(open[1]);
|
|
143
|
+
out.push({
|
|
144
|
+
kind: selfClosing ? 'self' : 'open',
|
|
145
|
+
tag: open[1],
|
|
146
|
+
attrs: attrs.replace(/\/\s*$/, ''),
|
|
147
|
+
index: i,
|
|
148
|
+
});
|
|
149
|
+
i = j;
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
function classText(attrs) {
|
|
154
|
+
const m = /class(?:Name)?\s*=\s*(?:"([^"]*)"|'([^']*)'|\{([\s\S]*)\})/.exec(attrs);
|
|
155
|
+
if (!m)
|
|
156
|
+
return '';
|
|
157
|
+
if (m[1] !== undefined)
|
|
158
|
+
return m[1];
|
|
159
|
+
if (m[2] !== undefined)
|
|
160
|
+
return m[2];
|
|
161
|
+
const lit = /(?:"([^"]*)"|'([^']*)'|`([^`]*)`)/g;
|
|
162
|
+
const parts = [];
|
|
163
|
+
let l;
|
|
164
|
+
while ((l = lit.exec(m[3] ?? '')) !== null)
|
|
165
|
+
parts.push(l[1] ?? l[2] ?? l[3] ?? '');
|
|
166
|
+
return parts.join(' ');
|
|
167
|
+
}
|
|
168
|
+
function widthDependent(classes) {
|
|
169
|
+
const tokens = classes.split(/\s+/).filter(Boolean);
|
|
170
|
+
const hits = tokens.filter((t) => WIDTH_DEPENDENT.includes(t) ||
|
|
171
|
+
WIDTH_DEPENDENT_PREFIXES.some((p) => t.startsWith(p)));
|
|
172
|
+
// A heading that paints its own box: shrinking the box moves the paint.
|
|
173
|
+
for (const t of tokens)
|
|
174
|
+
if (OWN_PAINT.test(` ${t} `) && !hits.includes(t))
|
|
175
|
+
hits.push(t);
|
|
176
|
+
return hits;
|
|
177
|
+
}
|
|
178
|
+
function walk(dir, extensions, acc) {
|
|
179
|
+
for (const entry of readdirSync(dir)) {
|
|
180
|
+
if (entry === 'node_modules' || entry === 'dist' || entry === '.git')
|
|
181
|
+
continue;
|
|
182
|
+
const full = join(dir, entry);
|
|
183
|
+
const st = statSync(full);
|
|
184
|
+
if (st.isDirectory())
|
|
185
|
+
walk(full, extensions, acc);
|
|
186
|
+
else if (extensions.some((e) => entry.endsWith(e)) && !/\.(test|spec)\./.test(entry))
|
|
187
|
+
acc.push(full);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export function scanCardHeadings(config) {
|
|
191
|
+
const extensions = config.extensions ?? ['.tsx'];
|
|
192
|
+
const headings = new Set(config.headingTags ?? DEFAULT_HEADINGS);
|
|
193
|
+
const gradientClasses = config.gradientClasses ?? DEFAULT_GRADIENT_CLASSES;
|
|
194
|
+
const chromeTags = new Set([...config.cardTags, ...(config.nestedChromeTags ?? [])]);
|
|
195
|
+
const findings = [];
|
|
196
|
+
const unknownFiles = [];
|
|
197
|
+
const files = [];
|
|
198
|
+
const scanRootsResolved = [];
|
|
199
|
+
// D-92: assert the inputs before reporting a verdict.
|
|
200
|
+
for (const root of config.scanRoots) {
|
|
201
|
+
const abs = join(config.projectRoot, root);
|
|
202
|
+
if (!existsSync(abs))
|
|
203
|
+
throw new Error(`[cardHeadingScanner] scan root "${root}" does not exist at ${abs}. ` +
|
|
204
|
+
`A scanner that silently skips a missing root reports a false pass.`);
|
|
205
|
+
const before = files.length;
|
|
206
|
+
walk(abs, extensions, files);
|
|
207
|
+
if (files.length === before)
|
|
208
|
+
throw new Error(`[cardHeadingScanner] scan root "${root}" matched ZERO files. ` +
|
|
209
|
+
`Refusing to report a verdict from an empty scan.`);
|
|
210
|
+
scanRootsResolved.push(root);
|
|
211
|
+
}
|
|
212
|
+
for (const file of files) {
|
|
213
|
+
const rel = relative(config.projectRoot, file);
|
|
214
|
+
let src;
|
|
215
|
+
try {
|
|
216
|
+
src = stripComments(readFileSync(file, 'utf8'));
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
unknownFiles.push(rel);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (!/<h[1-6][\s/>]/.test(src))
|
|
223
|
+
continue;
|
|
224
|
+
const tags = jsxTags(src);
|
|
225
|
+
const stack = [];
|
|
226
|
+
let lostPlace = false;
|
|
227
|
+
for (const t of tags) {
|
|
228
|
+
if (t.kind === 'self')
|
|
229
|
+
continue;
|
|
230
|
+
if (t.kind === 'close') {
|
|
231
|
+
// A close with nothing to close, or closing something else, means the
|
|
232
|
+
// stack no longer describes the tree. Ancestry answers become fiction
|
|
233
|
+
// from here, so stop and report UNKNOWN rather than guess.
|
|
234
|
+
const top = stack[stack.length - 1];
|
|
235
|
+
if (!top || top.tag !== t.tag) {
|
|
236
|
+
lostPlace = true;
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
stack.pop();
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (headings.has(t.tag)) {
|
|
243
|
+
const surface = stack
|
|
244
|
+
.slice()
|
|
245
|
+
.reverse()
|
|
246
|
+
.map((a) => elementDeclaresCardChrome(a, chromeTags))
|
|
247
|
+
.find((hit) => Boolean(hit));
|
|
248
|
+
if (surface) {
|
|
249
|
+
const classes = classText(t.attrs);
|
|
250
|
+
findings.push({
|
|
251
|
+
file: rel,
|
|
252
|
+
tag: t.tag,
|
|
253
|
+
line: src.slice(0, t.index).split('\n').length,
|
|
254
|
+
enclosingSurface: surface,
|
|
255
|
+
hasGradient: gradientClasses.some((g) => new RegExp(`(?:^|\\s)${g}(?:$|\\s)`).test(classes)),
|
|
256
|
+
widthDependentClasses: widthDependent(classes),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
stack.push({ tag: t.tag, attrs: t.attrs });
|
|
261
|
+
}
|
|
262
|
+
if (lostPlace) {
|
|
263
|
+
unknownFiles.push(rel);
|
|
264
|
+
// Drop this file's findings — an ancestry answer from a broken stack is
|
|
265
|
+
// worse than no answer, because it looks like a measurement.
|
|
266
|
+
for (let i = findings.length - 1; i >= 0; i--)
|
|
267
|
+
if (findings[i].file === rel)
|
|
268
|
+
findings.splice(i, 1);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const missing = findings.filter((f) => !f.hasGradient);
|
|
272
|
+
const risky = missing.filter((f) => f.widthDependentClasses.length > 0);
|
|
273
|
+
const summary = `scanned ${files.length} files across ${scanRootsResolved.length} root(s) [${scanRootsResolved.join(', ')}]; ` +
|
|
274
|
+
`${findings.length} card heading(s), ${findings.length - missing.length} already gradient, ` +
|
|
275
|
+
`${missing.length} without — of which ${risky.length} carry a width-dependent class and need a ` +
|
|
276
|
+
`decision rather than a codemod; ${unknownFiles.length} UNKNOWN file(s) — an UNKNOWN is not a clean file.`;
|
|
277
|
+
return { findings, filesScanned: files.length, scanRootsResolved, unknownFiles, summary };
|
|
278
|
+
}
|
|
@@ -125,7 +125,7 @@ export interface CardSurfaceScannerConfig {
|
|
|
125
125
|
* no way to turn it off. On those versions a nested card is ALWAYS a double
|
|
126
126
|
* frame, so every nesting slot is a finding.
|
|
127
127
|
*
|
|
128
|
-
* FALSE from
|
|
128
|
+
* FALSE from 2.0.0, where chrome is opt-in. A nested card then becomes the
|
|
129
129
|
* CORRECT shape — it is the only surface — and the defect narrows to a slot
|
|
130
130
|
* that opts back INTO chrome (`itemChrome`, or `chrome` on the item or the
|
|
131
131
|
* CanvasCard) AND still nests a card.
|
package/dist/scanner/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { scanCardSurfaces, stripComments, openingTags, elementDeclaresCardChrome, findNestedChrome, parseConfidence, slotOptsIntoChrome, } from './cardSurfaceScanner.js';
|
|
2
2
|
export type { CardSurfaceScannerConfig, CardSurfaceScanResult, CardSurfaceFinding, CardSurfaceIdiom, } from './cardSurfaceScanner.js';
|
|
3
|
+
export { scanCardHeadings, jsxTags } from './cardHeadingScanner.js';
|
|
4
|
+
export type { CardHeadingScannerConfig, CardHeadingScanResult, CardHeadingFinding, } from './cardHeadingScanner.js';
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scanner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,yBAAyB,EACzB,gBAAgB,EAChB,eAAe,EACf,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,wBAAwB,EACxB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scanner/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,yBAAyB,EACzB,gBAAgB,EAChB,eAAe,EACf,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,wBAAwB,EACxB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACpE,YAAY,EACV,wBAAwB,EACxB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC"}
|
package/dist/scanner/index.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -161,9 +161,9 @@ export interface PageGridLayoutProps extends UsePageGridLayoutOptions {
|
|
|
161
161
|
/**
|
|
162
162
|
* Paint every grid item as a card unless the item says otherwise.
|
|
163
163
|
*
|
|
164
|
-
* DEFAULT FALSE. This is the app-level migration lever for the
|
|
164
|
+
* DEFAULT FALSE. This is the app-level migration lever for the 2.0.0 chrome
|
|
165
165
|
* inversion: an app that has not yet migrated its bare slots passes
|
|
166
|
-
* `itemChrome` and gets the pre-
|
|
166
|
+
* `itemChrome` and gets the pre-2.0.0 look back exactly, then flips slot by
|
|
167
167
|
* slot with `GridLayoutItem.chrome`. Per-item always wins.
|
|
168
168
|
*/
|
|
169
169
|
itemChrome?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePageGridLayout.d.ts","sourceRoot":"","sources":["../src/usePageGridLayout.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAGV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,0BAA0B,2IAM7B,CAAC;AAEX;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,6BAA6B,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAiBzC,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,cAAc,EACd,WAAgB,EAChB,aAAiB,EACjB,WAAkB,EAClB,gBAA6C,EAC7C,iBAA4C,EAC5C,iBAAiB,GAClB,EAAE,wBAAwB,GAAG,uBAAuB,
|
|
1
|
+
{"version":3,"file":"usePageGridLayout.d.ts","sourceRoot":"","sources":["../src/usePageGridLayout.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAGV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,0BAA0B,2IAM7B,CAAC;AAEX;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,6BAA6B,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAiBzC,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,cAAc,EACd,WAAgB,EAChB,aAAiB,EACjB,WAAkB,EAClB,gBAA6C,EAC7C,iBAA4C,EAC5C,iBAAiB,GAClB,EAAE,wBAAwB,GAAG,uBAAuB,CAsuBpD"}
|
|
@@ -330,8 +330,32 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
330
330
|
const baseByKey = new Map(baseItems.map((item) => [item.i, item]));
|
|
331
331
|
result[bp] = (layouts[bp] ?? []).map((item) => {
|
|
332
332
|
const baseItem = baseByKey.get(item.i);
|
|
333
|
+
/**
|
|
334
|
+
* Carry `chrome` back onto EVERY item, autoHeight or not.
|
|
335
|
+
*
|
|
336
|
+
* react-grid-layout hands `onLayoutChange` its own item objects, which
|
|
337
|
+
* carry only the fields it models (`i/x/y/w/h/minW/minH/…`). Every
|
|
338
|
+
* custom field is gone. The pre-existing `autoHeight`/`hUserSet`
|
|
339
|
+
* restore below is the same mechanism — it just lived inside the
|
|
340
|
+
* `autoHeight` branch, so it only ever protected autoHeight items.
|
|
341
|
+
*
|
|
342
|
+
* `chrome` cannot live in that branch. It is meaningful on items that
|
|
343
|
+
* are NOT autoHeight, and losing it is not a cosmetic regression that
|
|
344
|
+
* heals on reload: `rawLayouts` prefers a saved item over the authored
|
|
345
|
+
* default for every key it already has, so the first drag on a page
|
|
346
|
+
* writes a chrome-less layout to user_preferences and the slot falls
|
|
347
|
+
* back to the app's `itemChrome` permanently, for that user only. A
|
|
348
|
+
* marketing page deliberately set frameless would silently re-frame
|
|
349
|
+
* itself the first time someone nudged a card, and nothing would ever
|
|
350
|
+
* put it back.
|
|
351
|
+
*
|
|
352
|
+
* Three-valued on purpose: `undefined` must stay ABSENT so
|
|
353
|
+
* `layoutItem.chrome ?? itemChrome` still falls through to the app
|
|
354
|
+
* default. Only an explicit true/false is restored.
|
|
355
|
+
*/
|
|
356
|
+
const withChrome = (next) => baseItem?.chrome === undefined ? next : { ...next, chrome: baseItem.chrome };
|
|
333
357
|
if (!baseItem?.autoHeight)
|
|
334
|
-
return item;
|
|
358
|
+
return withChrome(item);
|
|
335
359
|
const measuredRows = measured[item.i];
|
|
336
360
|
// MUST mirror `activeLayouts` in PageGridLayout.tsx exactly, including
|
|
337
361
|
// its `hUserSet` test. This predicts what the grid was RENDERED with;
|
|
@@ -350,25 +374,25 @@ export function usePageGridLayout({ pageKey, defaultLayouts, defaultCols = 12, l
|
|
|
350
374
|
// for the same reason `autoHeight` is: react-grid-layout does not
|
|
351
375
|
// round-trip custom item props, so anything not restored here is
|
|
352
376
|
// silently dropped on the next commit.
|
|
353
|
-
return {
|
|
377
|
+
return withChrome({
|
|
354
378
|
...item,
|
|
355
379
|
autoHeight: true,
|
|
356
380
|
h: baseItem.h,
|
|
357
381
|
minH: baseItem.minH,
|
|
358
382
|
...(baseItem.hUserSet ? { hUserSet: true } : {}),
|
|
359
|
-
};
|
|
383
|
+
});
|
|
360
384
|
}
|
|
361
385
|
// A deliberate resize (bigger or smaller than the base) — keep it,
|
|
362
386
|
// still floored by the measured content height. This is the only
|
|
363
387
|
// moment in the system where a height is known to be a CHOICE rather
|
|
364
388
|
// than an authored seed, so it is where that fact gets recorded.
|
|
365
|
-
return {
|
|
389
|
+
return withChrome({
|
|
366
390
|
...item,
|
|
367
391
|
autoHeight: true,
|
|
368
392
|
hUserSet: true,
|
|
369
393
|
h: measuredRows === undefined ? item.h : Math.max(item.h, measuredRows),
|
|
370
394
|
minH: measuredRows ?? baseItem.minH,
|
|
371
|
-
};
|
|
395
|
+
});
|
|
372
396
|
});
|
|
373
397
|
}
|
|
374
398
|
return result;
|