@silver-formily/grid 1.0.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/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @silver-formily/grid
2
+
3
+ > `@silver-formily/grid` 初始化包结构。
4
+
5
+ 当前仅包含 `src/demo.ts` 作为示例入口,后续可在此基础上扩展网格组件与工具函数。
@@ -0,0 +1,73 @@
1
+ //#region src/types.d.ts
2
+ type GridNode = {
3
+ index?: number;
4
+ visible?: boolean;
5
+ column?: number;
6
+ shadowColumn?: number;
7
+ row?: number;
8
+ shadowRow?: number;
9
+ span?: number;
10
+ originSpan?: number;
11
+ element?: HTMLElement;
12
+ };
13
+ interface IGridOptions {
14
+ maxRows?: number;
15
+ maxColumns?: number | number[];
16
+ minColumns?: number | number[];
17
+ maxWidth?: number | number[];
18
+ minWidth?: number | number[];
19
+ breakpoints?: number[];
20
+ columnGap?: number;
21
+ rowGap?: number;
22
+ colWrap?: boolean;
23
+ strictAutoFit?: boolean;
24
+ shouldVisible?: (node: GridNode, grid: Grid<HTMLElement>) => boolean;
25
+ onDigest?: (grid: Grid<HTMLElement>) => void;
26
+ onInitialized?: (grid: Grid<HTMLElement>) => void;
27
+ }
28
+ //#endregion
29
+ //#region src/index.d.ts
30
+ declare function createGrid<Container extends HTMLElement>(options?: IGridOptions): Grid<Container>;
31
+ declare class Grid<Container extends HTMLElement> {
32
+ options: IGridOptions;
33
+ width: number;
34
+ height: number;
35
+ container: Container;
36
+ children: GridNode[];
37
+ childTotalColumns: number;
38
+ shadowChildTotalColumns: number;
39
+ childOriginTotalColumns: number;
40
+ shadowChildOriginTotalColumns: number;
41
+ ready: boolean;
42
+ constructor(options?: IGridOptions);
43
+ set breakpoints(breakpoints: number[]);
44
+ get breakpoints(): number[];
45
+ get breakpoint(): number;
46
+ set maxWidth(maxWidth: number);
47
+ get maxWidth(): number;
48
+ set minWidth(minWidth: number);
49
+ get minWidth(): number;
50
+ set maxColumns(maxColumns: number);
51
+ get maxColumns(): number;
52
+ set maxRows(maxRows: number);
53
+ get maxRows(): number;
54
+ set minColumns(minColumns: number);
55
+ get minColumns(): number;
56
+ set rowGap(rowGap: number);
57
+ get rowGap(): number;
58
+ set columnGap(columnGap: number);
59
+ get columnGap(): number;
60
+ set colWrap(colWrap: boolean);
61
+ get colWrap(): boolean;
62
+ get columns(): number;
63
+ get rows(): number;
64
+ get shadowRows(): number;
65
+ get templateColumns(): string;
66
+ get gap(): string;
67
+ get childSize(): number;
68
+ get fullnessLastColumn(): boolean;
69
+ connect: (container: Container) => () => void;
70
+ static id: (options?: IGridOptions) => string;
71
+ }
72
+ //#endregion
73
+ export { Grid, type GridNode, type IGridOptions, createGrid };
package/dist/index.mjs ADDED
@@ -0,0 +1,422 @@
1
+ import { batch, define, markRaw, observable, reaction } from "@formily/reactive";
2
+
3
+ //#region src/observer.ts
4
+ const isHTMLElement$1 = (node) => node.nodeType === 1;
5
+ var ChildListMutationObserver = class {
6
+ observer;
7
+ callback;
8
+ childList = [];
9
+ init = {};
10
+ constructor(callback) {
11
+ this.callback = callback;
12
+ this.observer = new MutationObserver(this.handler);
13
+ }
14
+ observeChildList(element) {
15
+ Array.from(element.children).forEach((node) => {
16
+ if (isHTMLElement$1(node)) this.addObserver(node);
17
+ });
18
+ }
19
+ addObserver(element) {
20
+ if (this.childList.find((node) => node.element === element)) return;
21
+ const child = {
22
+ element,
23
+ observer: new MutationObserver(this.callback),
24
+ dispose: () => {
25
+ child.observer.disconnect();
26
+ this.childList = this.childList.filter((node) => node !== child);
27
+ }
28
+ };
29
+ child.observer.observe(child.element, {
30
+ ...this.init,
31
+ subtree: false,
32
+ childList: false,
33
+ characterData: false,
34
+ characterDataOldValue: false,
35
+ attributeOldValue: false
36
+ });
37
+ this.childList.push(child);
38
+ }
39
+ removeObserver(element) {
40
+ this.childList.find((node) => node.element === element)?.dispose();
41
+ }
42
+ handler = (mutations) => {
43
+ mutations.forEach((mutation) => {
44
+ if (mutation.type === "childList") {
45
+ mutation.addedNodes.forEach((node) => {
46
+ if (isHTMLElement$1(node)) this.addObserver(node);
47
+ });
48
+ mutation.removedNodes.forEach((node) => {
49
+ if (isHTMLElement$1(node)) this.removeObserver(node);
50
+ });
51
+ }
52
+ });
53
+ this.callback(mutations, this.observer);
54
+ };
55
+ observe = (element, init = {}) => {
56
+ this.init = init;
57
+ this.observeChildList(element);
58
+ this.observer.observe(element, {
59
+ ...this.init,
60
+ subtree: false,
61
+ childList: true,
62
+ characterData: false,
63
+ characterDataOldValue: false,
64
+ attributeOldValue: false
65
+ });
66
+ };
67
+ disconnect = () => {
68
+ this.observer.disconnect();
69
+ this.childList.forEach((node) => node.observer.disconnect());
70
+ this.childList = [];
71
+ };
72
+ };
73
+
74
+ //#endregion
75
+ //#region src/utils.ts
76
+ const SpanRegExp = /span\s*(\d+)/;
77
+ const isValid = (value) => value !== void 0 && value !== null;
78
+ function isHTMLElement(value) {
79
+ const HTMLElementCtor = globalThis.HTMLElement;
80
+ if (typeof HTMLElementCtor !== "function") return false;
81
+ return value instanceof HTMLElementCtor;
82
+ }
83
+ function calcBreakpointIndex(breakpoints, width) {
84
+ if (Array.isArray(breakpoints)) {
85
+ for (let i = 0; i < breakpoints.length; i++) if (width <= breakpoints[i]) return i;
86
+ }
87
+ return -1;
88
+ }
89
+ function calcFactor(value, breakpointIndex) {
90
+ if (Array.isArray(value)) {
91
+ if (breakpointIndex === -1) return value[0];
92
+ return value[breakpointIndex] ?? value[value.length - 1];
93
+ }
94
+ return value;
95
+ }
96
+ function parseSpan(gridColumnStart) {
97
+ return Number(String(gridColumnStart).match(SpanRegExp)?.[1] ?? 1);
98
+ }
99
+ function parseGridNode(elements) {
100
+ return Array.from(elements).reduce((buf, element, index) => {
101
+ if (!isHTMLElement(element)) return buf;
102
+ const style = getComputedStyle(element);
103
+ const visible = style.display !== "none";
104
+ const origin = element.getAttribute("data-grid-span");
105
+ const span = parseSpan(style.gridColumnStart) || 1;
106
+ const originSpan = Number(origin ?? span);
107
+ if (!origin) element.setAttribute("data-grid-span", String(span));
108
+ buf.push({
109
+ index,
110
+ visible,
111
+ span,
112
+ originSpan,
113
+ element,
114
+ column: 0,
115
+ shadowColumn: 0,
116
+ row: 0,
117
+ shadowRow: 0
118
+ });
119
+ return buf;
120
+ }, []);
121
+ }
122
+ function calcChildTotalColumns(nodes, shadow = false) {
123
+ return nodes.reduce((buf, node) => {
124
+ if (!shadow && !node.visible) return buf;
125
+ return buf + (node.span ?? 1);
126
+ }, 0);
127
+ }
128
+ function calcChildOriginTotalColumns(nodes, shadow = false) {
129
+ return nodes.reduce((buf, node) => {
130
+ const span = node.span ?? 1;
131
+ const originSpan = node.originSpan ?? span;
132
+ if (!shadow && !node.visible) return buf;
133
+ if (originSpan === -1) return buf + span;
134
+ return buf + originSpan;
135
+ }, 0);
136
+ }
137
+ function calcSatisfyColumns(width, maxColumns, minColumns, maxWidth, minWidth, gap) {
138
+ const results = [];
139
+ for (let columns = minColumns; columns <= maxColumns; columns++) {
140
+ const innerWidth = width - (columns - 1) * gap;
141
+ const columnWidth = innerWidth / columns;
142
+ if (columnWidth >= minWidth && columnWidth <= maxWidth) results.push(columns);
143
+ else if (columnWidth < minWidth) results.push(Math.min(Math.floor(innerWidth / minWidth), maxColumns));
144
+ else if (columnWidth > maxWidth) results.push(Math.min(Math.floor(innerWidth / maxWidth), maxColumns));
145
+ }
146
+ if (!results.length) return minColumns;
147
+ return Math.max(...results);
148
+ }
149
+ function factor(value, grid) {
150
+ if (!isValid(value)) return void 0;
151
+ return calcFactor(value, grid.breakpoint);
152
+ }
153
+ function resolveChildren(grid) {
154
+ if (!grid.ready) return;
155
+ let walked = 0;
156
+ let shadowWalked = 0;
157
+ let rowIndex = 0;
158
+ let shadowRowIndex = 0;
159
+ const shouldVisible = grid.options.shouldVisible;
160
+ grid.children = grid.children.map((node) => {
161
+ const element = node.element;
162
+ const columnIndex = walked % grid.columns;
163
+ const shadowColumnIndex = shadowWalked % grid.columns;
164
+ const remainColumns = grid.columns - columnIndex;
165
+ const originSpan = node.originSpan ?? node.span ?? 1;
166
+ const targetSpan = originSpan > grid.columns ? grid.columns : originSpan;
167
+ const span = grid.options.strictAutoFit ? targetSpan : targetSpan > remainColumns ? remainColumns : targetSpan;
168
+ const gridColumn = originSpan === -1 ? `span ${remainColumns} / -1` : `span ${span} / auto`;
169
+ if (element && element.style.gridColumn !== gridColumn) element.style.gridColumn = gridColumn;
170
+ if (node.visible) walked += span;
171
+ shadowWalked += span;
172
+ if (columnIndex === 0) rowIndex++;
173
+ if (shadowColumnIndex === 0) shadowRowIndex++;
174
+ node.shadowRow = shadowRowIndex;
175
+ node.shadowColumn = shadowColumnIndex + 1;
176
+ if (node.visible) {
177
+ node.row = rowIndex;
178
+ node.column = columnIndex + 1;
179
+ }
180
+ if (shouldVisible) if (!shouldVisible(node, grid)) {
181
+ if (node.visible && element) element.style.display = "none";
182
+ node.visible = false;
183
+ } else {
184
+ if (!node.visible && element) element.style.display = "";
185
+ node.visible = true;
186
+ }
187
+ return node;
188
+ });
189
+ }
190
+ const nextTick = (callback) => Promise.resolve(0).then(callback);
191
+
192
+ //#endregion
193
+ //#region src/index.ts
194
+ function createGrid(options) {
195
+ return markRaw(new Grid(options));
196
+ }
197
+ var Grid = class {
198
+ options;
199
+ width = 0;
200
+ height = 0;
201
+ container;
202
+ children = [];
203
+ childTotalColumns = 0;
204
+ shadowChildTotalColumns = 0;
205
+ childOriginTotalColumns = 0;
206
+ shadowChildOriginTotalColumns = 0;
207
+ ready = false;
208
+ constructor(options) {
209
+ Reflect.defineProperty(this, "__v_skip", {
210
+ value: true,
211
+ configurable: true
212
+ });
213
+ this.options = {
214
+ breakpoints: [
215
+ 720,
216
+ 1280,
217
+ 1920
218
+ ],
219
+ columnGap: 8,
220
+ rowGap: 4,
221
+ minWidth: 100,
222
+ colWrap: true,
223
+ strictAutoFit: false,
224
+ ...options
225
+ };
226
+ define(this, {
227
+ options: observable.shallow,
228
+ width: observable.ref,
229
+ height: observable.ref,
230
+ ready: observable.ref,
231
+ children: observable.ref,
232
+ childOriginTotalColumns: observable.ref,
233
+ shadowChildOriginTotalColumns: observable.ref,
234
+ shadowChildTotalColumns: observable.ref,
235
+ childTotalColumns: observable.ref,
236
+ columns: observable.computed,
237
+ templateColumns: observable.computed,
238
+ gap: observable.computed,
239
+ maxColumns: observable.computed,
240
+ minColumns: observable.computed,
241
+ maxWidth: observable.computed,
242
+ minWidth: observable.computed,
243
+ breakpoints: observable.computed,
244
+ breakpoint: observable.computed,
245
+ rowGap: observable.computed,
246
+ columnGap: observable.computed,
247
+ colWrap: observable.computed
248
+ });
249
+ }
250
+ set breakpoints(breakpoints) {
251
+ this.options.breakpoints = breakpoints;
252
+ }
253
+ get breakpoints() {
254
+ return this.options.breakpoints ?? [];
255
+ }
256
+ get breakpoint() {
257
+ const width = this.ready ? this.width : Number.POSITIVE_INFINITY;
258
+ const breakpoint = calcBreakpointIndex(this.breakpoints, width);
259
+ if (!this.ready && breakpoint === -1 && this.breakpoints.length > 0) return this.breakpoints.length - 1;
260
+ return breakpoint;
261
+ }
262
+ set maxWidth(maxWidth) {
263
+ this.options.maxWidth = maxWidth;
264
+ }
265
+ get maxWidth() {
266
+ return factor(this.options.maxWidth, this) ?? Infinity;
267
+ }
268
+ set minWidth(minWidth) {
269
+ this.options.minWidth = minWidth;
270
+ }
271
+ get minWidth() {
272
+ return factor(this.options.minWidth, this) ?? 100;
273
+ }
274
+ set maxColumns(maxColumns) {
275
+ this.options.maxColumns = maxColumns;
276
+ }
277
+ get maxColumns() {
278
+ return factor(this.options.maxColumns, this) ?? Infinity;
279
+ }
280
+ set maxRows(maxRows) {
281
+ this.options.maxRows = maxRows;
282
+ }
283
+ get maxRows() {
284
+ return this.options.maxRows ?? Infinity;
285
+ }
286
+ set minColumns(minColumns) {
287
+ this.options.minColumns = minColumns;
288
+ }
289
+ get minColumns() {
290
+ return factor(this.options.minColumns, this) ?? 1;
291
+ }
292
+ set rowGap(rowGap) {
293
+ this.options.rowGap = rowGap;
294
+ }
295
+ get rowGap() {
296
+ return factor(this.options.rowGap, this) ?? 5;
297
+ }
298
+ set columnGap(columnGap) {
299
+ this.options.columnGap = columnGap;
300
+ }
301
+ get columnGap() {
302
+ return factor(this.options.columnGap, this) ?? 10;
303
+ }
304
+ set colWrap(colWrap) {
305
+ this.options.colWrap = colWrap;
306
+ }
307
+ get colWrap() {
308
+ return factor(this.options.colWrap, this) ?? true;
309
+ }
310
+ get columns() {
311
+ if (!this.ready) {
312
+ const minColumns = Math.max(1, this.minColumns);
313
+ if (this.maxColumns === Infinity) return minColumns;
314
+ return Math.max(minColumns, Math.max(1, this.maxColumns));
315
+ }
316
+ const originTotalColumns = this.childOriginTotalColumns;
317
+ if (this.colWrap === false) return originTotalColumns;
318
+ const baseColumns = this.childSize;
319
+ const strictMaxWidthColumns = Math.round(this.width / (this.maxWidth + this.columnGap));
320
+ const looseMaxWidthColumns = Math.min(originTotalColumns, strictMaxWidthColumns);
321
+ const maxWidthColumns = this.options.strictAutoFit ? strictMaxWidthColumns : looseMaxWidthColumns;
322
+ const strictMinWidthColumns = Math.round(this.width / (this.minWidth + this.columnGap));
323
+ const looseMinWidthColumns = Math.min(originTotalColumns, strictMinWidthColumns);
324
+ const minWidthColumns = this.options.strictAutoFit ? strictMinWidthColumns : looseMinWidthColumns;
325
+ const minCalculatedColumns = Math.min(baseColumns, originTotalColumns, maxWidthColumns, minWidthColumns);
326
+ const maxCalculatedColumns = Math.max(baseColumns, originTotalColumns, maxWidthColumns, minWidthColumns);
327
+ const finalColumns = calcSatisfyColumns(this.width, maxCalculatedColumns, minCalculatedColumns, this.maxWidth, this.minWidth, this.columnGap);
328
+ if (finalColumns >= this.maxColumns) return this.maxColumns;
329
+ if (finalColumns <= this.minColumns) return this.minColumns;
330
+ return finalColumns;
331
+ }
332
+ get rows() {
333
+ return Math.ceil(this.childTotalColumns / this.columns);
334
+ }
335
+ get shadowRows() {
336
+ return Math.ceil(this.shadowChildTotalColumns / this.columns);
337
+ }
338
+ get templateColumns() {
339
+ if (!this.ready) return `repeat(${this.columns},minmax(0,1fr))`;
340
+ if (!this.width) return "";
341
+ if (this.maxWidth === Infinity) return `repeat(${this.columns},minmax(0,1fr))`;
342
+ if (this.options.strictAutoFit !== true) {
343
+ const columnWidth = (this.width - (this.columns - 1) * this.columnGap) / this.columns;
344
+ if (columnWidth < this.minWidth || columnWidth > this.maxWidth) return `repeat(${this.columns},minmax(0,1fr))`;
345
+ }
346
+ return `repeat(${this.columns},minmax(${this.minWidth}px,${this.maxWidth}px))`;
347
+ }
348
+ get gap() {
349
+ return `${this.rowGap}px ${this.columnGap}px`;
350
+ }
351
+ get childSize() {
352
+ return this.children.length;
353
+ }
354
+ get fullnessLastColumn() {
355
+ return this.columns === this.children[this.childSize - 1]?.span;
356
+ }
357
+ connect = (container) => {
358
+ if (!container) return () => {};
359
+ this.container = container;
360
+ const digest = batch.bound(() => {
361
+ this.children = parseGridNode(this.container.children);
362
+ this.childTotalColumns = calcChildTotalColumns(this.children);
363
+ this.shadowChildTotalColumns = calcChildTotalColumns(this.children, true);
364
+ this.childOriginTotalColumns = calcChildOriginTotalColumns(this.children);
365
+ this.shadowChildOriginTotalColumns = calcChildOriginTotalColumns(this.children, true);
366
+ const rect = this.container.getBoundingClientRect();
367
+ if (rect.width && rect.height) {
368
+ this.width = rect.width;
369
+ this.height = rect.height;
370
+ }
371
+ resolveChildren(this);
372
+ nextTick(() => {
373
+ this.options.onDigest?.(this);
374
+ });
375
+ if (!this.ready) nextTick(() => {
376
+ this.options.onInitialized?.(this);
377
+ });
378
+ });
379
+ const initialize = batch.bound(() => {
380
+ digest();
381
+ this.ready = true;
382
+ });
383
+ const mutationObserver = new ChildListMutationObserver(digest);
384
+ const smoothDigest = () => {
385
+ requestAnimationFrame(() => {
386
+ digest();
387
+ });
388
+ };
389
+ const resizeObserver = new ResizeObserver(smoothDigest);
390
+ const dispose = reaction(() => ({ ...this.options }), digest);
391
+ resizeObserver.observe(this.container);
392
+ mutationObserver.observe(this.container, {
393
+ attributeFilter: ["data-grid-span"],
394
+ attributes: true
395
+ });
396
+ initialize();
397
+ return () => {
398
+ resizeObserver.unobserve(this.container);
399
+ resizeObserver.disconnect();
400
+ mutationObserver.disconnect();
401
+ dispose();
402
+ this.children = [];
403
+ };
404
+ };
405
+ static id = (options = {}) => {
406
+ return JSON.stringify([
407
+ "maxRows",
408
+ "maxColumns",
409
+ "minColumns",
410
+ "maxWidth",
411
+ "minWidth",
412
+ "breakpoints",
413
+ "columnGap",
414
+ "rowGap",
415
+ "colWrap",
416
+ "strictAutoFit"
417
+ ].map((key) => options[key]));
418
+ };
419
+ };
420
+
421
+ //#endregion
422
+ export { Grid, createGrid };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@silver-formily/grid",
3
+ "type": "module",
4
+ "version": "1.0.0",
5
+ "description": "Grid package scaffold for silver-formily",
6
+ "author": "hezhengxu",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/hezhengxu2018/silver-formily",
9
+ "repository": {
10
+ "type": "github",
11
+ "url": "https://github.com/hezhengxu2018/silver-formily",
12
+ "directory": "packages/grid"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/hezhengxu2018/silver-formily/issues"
16
+ },
17
+ "exports": {
18
+ ".": "./dist/index.mjs",
19
+ "./package.json": "./package.json"
20
+ },
21
+ "main": "./dist/index.mjs",
22
+ "module": "./dist/index.mjs",
23
+ "types": "./dist/index.d.mts",
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "peerDependencies": {
31
+ "@formily/reactive": "^2"
32
+ },
33
+ "devDependencies": {
34
+ "@formily/reactive": "^2.3.7",
35
+ "@vitest/browser-playwright": "^4.0.18",
36
+ "@vitest/coverage-istanbul": "4.0.18",
37
+ "eslint": "^9.36.0",
38
+ "jsdom": "^26.1.0",
39
+ "playwright": "^1.58.2",
40
+ "tsdown": "^0.18.1",
41
+ "typescript": "5.9.2",
42
+ "vitest": "^4.0.16",
43
+ "@silver-formily/typescript-config": "0.0.0"
44
+ },
45
+ "scripts": {
46
+ "build": "tsdown",
47
+ "dev": "tsdown --watch",
48
+ "test": "vitest run",
49
+ "coverage": "vitest run --coverage --browser.headless",
50
+ "lint": "eslint .",
51
+ "format": "eslint . --fix",
52
+ "check-types": "tsc --noEmit",
53
+ "docs:dev": "pnpm --filter grid-docs dev",
54
+ "docs:build": "pnpm turbo run docs:build --filter=grid-docs",
55
+ "docs:preview": "pnpm --filter grid-docs preview"
56
+ }
57
+ }