diff --git a/.github/workflows/sync-docs.yml b/.github/workflows/sync-docs.yml
new file mode 100644
index 000000000..31fe83318
--- /dev/null
+++ b/.github/workflows/sync-docs.yml
@@ -0,0 +1,113 @@
+name: Sync Documentation to gh-pages
+
+on:
+ push:
+ branches: [master, develop]
+ paths:
+ - 'doc/html/**'
+ - 'angular/doc/**'
+ - '.github/workflows/sync-docs.yml'
+ workflow_dispatch:
+
+jobs:
+ sync-docs:
+ runs-on: ubuntu-latest
+ if: github.repository == 'gridstack/gridstack.js'
+
+ steps:
+ - name: Checkout master branch
+ uses: actions/checkout@v4
+ with:
+ ref: master
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Configure Git
+ run: |
+ git config --global user.name 'github-actions[bot]'
+ git config --global user.email 'github-actions[bot]@users.noreply.github.com'
+
+ - name: Check if docs exist
+ id: check-docs
+ run: |
+ if [ -d "doc/html" ]; then
+ echo "main_docs=true" >> $GITHUB_OUTPUT
+ else
+ echo "main_docs=false" >> $GITHUB_OUTPUT
+ fi
+
+ if [ -d "angular/doc/api" ]; then
+ echo "angular_docs=true" >> $GITHUB_OUTPUT
+ else
+ echo "angular_docs=false" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Checkout gh-pages branch
+ if: steps.check-docs.outputs.main_docs == 'true' || steps.check-docs.outputs.angular_docs == 'true'
+ run: |
+ git fetch origin gh-pages
+ git checkout gh-pages
+
+ - name: Sync main library documentation
+ if: steps.check-docs.outputs.main_docs == 'true'
+ run: |
+ echo "Syncing main library documentation..."
+
+ # Remove existing docs directory if it exists
+ if [ -d "docs/html" ]; then
+ rm -rf docs/html
+ fi
+
+ # Extract docs from master branch using git archive
+ mkdir -p docs
+ git archive master doc/html | tar -xf -
+ mv doc/html docs/html
+ rm -rf doc
+
+ # Add changes
+ git add docs/html
+
+ - name: Sync Angular documentation
+ if: steps.check-docs.outputs.angular_docs == 'true'
+ run: |
+ echo "Syncing Angular library documentation..."
+
+ # Remove existing Angular docs if they exist
+ if [ -d "angular/doc" ]; then
+ rm -rf angular/doc
+ fi
+
+ # Extract Angular docs from master branch using git archive
+ git archive master angular/doc | tar -xf -
+
+ # Add changes
+ git add angular/doc
+
+ - name: Commit and push changes
+ if: steps.check-docs.outputs.main_docs == 'true' || steps.check-docs.outputs.angular_docs == 'true'
+ run: |
+ # Check if there are changes to commit
+ if git diff --staged --quiet; then
+ echo "No documentation changes to sync"
+ exit 0
+ fi
+
+ # Create commit message
+ COMMIT_MSG="📚 Auto-sync documentation from master"
+ if [ "${{ steps.check-docs.outputs.main_docs }}" == "true" ]; then
+ COMMIT_MSG="${COMMIT_MSG}%0A%0A- Updated main library HTML docs (docs/html/)"
+ fi
+ if [ "${{ steps.check-docs.outputs.angular_docs }}" == "true" ]; then
+ COMMIT_MSG="${COMMIT_MSG}%0A%0A- Updated Angular library docs (angular/doc/)"
+ fi
+
+ COMMIT_MSG="${COMMIT_MSG}%0A%0ASource: ${{ github.sha }}"
+
+ # Decode URL-encoded newlines for the commit message
+ COMMIT_MSG=$(echo -e "${COMMIT_MSG//%0A/\\n}")
+
+ # Commit and push
+ git commit -m "$COMMIT_MSG"
+ git push origin gh-pages
+
+ echo "✅ Documentation synced to gh-pages successfully!"
diff --git a/.vitestrc.coverage.ts b/.vitestrc.coverage.ts
new file mode 100644
index 000000000..4c8b203dd
--- /dev/null
+++ b/.vitestrc.coverage.ts
@@ -0,0 +1,131 @@
+///
Test dragging widgets outside the grid should not throw exceptions.
+ +Outside Grid Area - Try dragging items here
+New content
' }); - * + * * // Update multiple properties * grid.update('#my-widget', { * w: 4, @@ -1626,17 +1631,17 @@ export class GridStack { /** * Updates widget height to match the content height to avoid vertical scrollbars or dead space. * This automatically adjusts the widget height based on its content size. - * - * Note: This assumes only 1 child under resizeToContentParent='.grid-stack-item-content' + * + * Note: This assumes only 1 child under resizeToContentParent='.grid-stack-item-content' * (sized to gridItem minus padding) that represents the entire content size. - * + * * @param el the grid item element to resize - * + * * @example * // Resize a widget to fit its content * const widget = document.querySelector('.grid-stack-item'); * grid.resizeToContent(widget); - * + * * // This is commonly used with dynamic content: * widget.querySelector('.content').innerHTML = 'New longer content...'; * grid.resizeToContent(widget); @@ -1704,15 +1709,15 @@ export class GridStack { /** * Rotate widgets by swapping their width and height. This is typically called when the user presses 'r' during dragging. * The rotation swaps the w/h dimensions and adjusts min/max constraints accordingly. - * + * * @param els widget element(s) or selector to rotate * @param relative optional pixel coordinate relative to upper/left corner to rotate around (keeps that cell under cursor) * @returns the grid instance for chaining - * + * * @example * // Rotate a specific widget * grid.rotate('.my-widget'); - * + * * // Rotate with relative positioning during drag * grid.rotate(widget, { left: 50, top: 30 }); */ @@ -1739,13 +1744,13 @@ export class GridStack { /** * Updates the margins which will set all 4 sides at once - see `GridStackOptions.margin` for format options. * Supports CSS string format of 1, 2, or 4 values or a single number. - * + * * @param value margin value - can be: * - Single number: `10` (applies to all sides) * - Two values: `'10px 20px'` (top/bottom, left/right) * - Four values: `'10px 20px 5px 15px'` (top, right, bottom, left) * @returns the grid instance for chaining - * + * * @example * grid.margin(10); // 10px all sides * grid.margin('10px 20px'); // 10px top/bottom, 20px left/right @@ -1769,9 +1774,9 @@ export class GridStack { /** * Returns the current margin value as a number (undefined if the 4 sides don't match). * This only returns a number if all sides have the same margin value. - * + * * @returns the margin value in pixels, or undefined if sides have different values - * + * * @example * const margin = grid.getMargin(); * if (margin !== undefined) { @@ -2163,7 +2168,7 @@ export class GridStack { } /** @internal current version compiled in code */ - static GDRev = '12.3.0'; + static GDRev = '12.3.3'; /* =========================================================================================== * drag&drop methods that used to be stubbed out and implemented in dd-gridstack.ts @@ -2174,9 +2179,9 @@ export class GridStack { /** * Get the global drag & drop implementation instance. * This provides access to the underlying drag & drop functionality. - * + * * @returns the DDGridStack instance used for drag & drop operations - * + * * @example * const dd = GridStack.getDD(); * // Access drag & drop functionality @@ -2208,20 +2213,20 @@ export class GridStack { } /** - * Enables/Disables dragging by the user for specific grid elements. + * Enables/Disables dragging by the user for specific grid elements. * For all items and future items, use enableMove() instead. No-op for static grids. - * - * Note: If you want to prevent an item from moving due to being pushed around by another + * + * Note: If you want to prevent an item from moving due to being pushed around by another * during collision, use the 'locked' property instead. - * + * * @param els widget element(s) or selector to modify * @param val if true widget will be draggable, assuming the parent grid isn't noMove or static * @returns the grid instance for chaining - * + * * @example * // Make specific widgets draggable * grid.movable('.my-widget', true); - * + * * // Disable dragging for specific widgets * grid.movable('#fixed-widget', false); */ @@ -2239,15 +2244,15 @@ export class GridStack { /** * Enables/Disables user resizing for specific grid elements. * For all items and future items, use enableResize() instead. No-op for static grids. - * + * * @param els widget element(s) or selector to modify * @param val if true widget will be resizable, assuming the parent grid isn't noResize or static * @returns the grid instance for chaining - * + * * @example * // Make specific widgets resizable * grid.resizable('.my-widget', true); - * + * * // Disable resizing for specific widgets * grid.resizable('#fixed-size-widget', false); */ @@ -2265,22 +2270,22 @@ export class GridStack { /** * Temporarily disables widgets moving/resizing. * If you want a more permanent way (which freezes up resources) use `setStatic(true)` instead. - * + * * Note: This is a no-op for static grids. - * + * * This is a shortcut for: * ```typescript * grid.enableMove(false); * grid.enableResize(false); * ``` - * + * * @param recurse if true (default), sub-grids also get updated * @returns the grid instance for chaining - * + * * @example * // Disable all interactions * grid.disable(); - * + * * // Disable only this grid, not sub-grids * grid.disable(false); */ @@ -2294,20 +2299,20 @@ export class GridStack { /** * Re-enables widgets moving/resizing - see disable(). * Note: This is a no-op for static grids. - * + * * This is a shortcut for: * ```typescript * grid.enableMove(true); * grid.enableResize(true); * ``` - * + * * @param recurse if true (default), sub-grids also get updated * @returns the grid instance for chaining - * + * * @example * // Re-enable all interactions * grid.enable(); - * + * * // Enable only this grid, not sub-grids * grid.enable(false); */ @@ -2322,18 +2327,18 @@ export class GridStack { /** * Enables/disables widget moving for all widgets. No-op for static grids. * Note: locally defined items (with noMove property) still override this setting. - * + * * @param doEnable if true widgets will be movable, if false moving is disabled * @param recurse if true (default), sub-grids also get updated * @returns the grid instance for chaining - * + * * @example * // Enable moving for all widgets * grid.enableMove(true); - * + * * // Disable moving for all widgets * grid.enableMove(false); - * + * * // Enable only this grid, not sub-grids * grid.enableMove(true, false); */ @@ -2350,18 +2355,18 @@ export class GridStack { /** * Enables/disables widget resizing for all widgets. No-op for static grids. * Note: locally defined items (with noResize property) still override this setting. - * + * * @param doEnable if true widgets will be resizable, if false resizing is disabled - * @param recurse if true (default), sub-grids also get updated + * @param recurse if true (default), sub-grids also get updated * @returns the grid instance for chaining - * + * * @example * // Enable resizing for all widgets * grid.enableResize(true); - * + * * // Disable resizing for all widgets * grid.enableResize(false); - * + * * // Enable only this grid, not sub-grids * grid.enableResize(true, false); */ diff --git a/src/types.ts b/src/types.ts index 0d3f4407e..cd48102bb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,5 @@ /** - * types.ts 12.3.0 + * types.ts 12.3.3 * Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license */ @@ -43,10 +43,10 @@ export const gridDefaults: GridStackOptions = { /** * Different layout options when changing the number of columns. - * + * * These options control how widgets are repositioned when the grid column count changes. * Note: The new list may be partially filled if there's a cached layout for that size. - * + * * Options: * - `'list'`: Treat items as a sorted list, keeping them sequentially without resizing (unless too big) * - `'compact'`: Similar to list, but uses compact() method to fill empty slots by reordering @@ -109,7 +109,7 @@ export type GridStackEventHandlerCallback = GridStackEventHandler | GridStackEle /** * Optional callback function called during load() operations. * Allows custom handling of widget addition/removal for framework integration. - * + * * @param parent - The parent HTML element * @param w - The widget definition * @param add - True if adding, false if removing @@ -121,7 +121,7 @@ export type AddRemoveFcn = (parent: HTMLElement, w: GridStackWidget, add: boolea /** * Optional callback function called during save() operations. * Allows adding custom data to the saved widget structure. - * + * * @param node - The internal grid node * @param w - The widget structure being saved (can be modified) */ @@ -130,7 +130,7 @@ export type SaveFcn = (node: GridStackNode, w: GridStackWidget) => void; /** * Optional callback function for custom widget content rendering. * Called during load()/addWidget() to create custom content beyond plain text. - * + * * @param el - The widget's content container element * @param w - The widget definition with content and other properties */ @@ -139,14 +139,14 @@ export type RenderFcn = (el: HTMLElement, w: GridStackWidget) => void; /** * Optional callback function for custom resize-to-content behavior. * Called when a widget needs to resize to fit its content. - * + * * @param el - The grid item element to resize */ export type ResizeToContentFcn = (el: GridItemHTMLElement) => void; /** * Configuration for responsive grid behavior. - * + * * Defines how the grid responds to different screen sizes by changing column counts. * NOTE: Make sure to include the appropriate CSS (gridstack-extra.css) to support responsive behavior. */ @@ -188,19 +188,19 @@ export interface GridStackOptions { * - `false`: will not accept any external widgets * - string: explicit class name to accept instead of default * - function: callback called before an item will be accepted when entering a grid - * + * * @example * // Accept all grid items * acceptWidgets: true - * + * * // Accept only items with specific class * acceptWidgets: 'my-draggable-item' - * + * * // Custom validation function * acceptWidgets: (el) => { * return el.getAttribute('data-accept') === 'true'; * } - * + * * @see {@link http://gridstack.github.io/gridstack.js/demo/two.html} for complete example */ acceptWidgets?: boolean | string | ((element: Element) => boolean); @@ -225,20 +225,20 @@ export interface GridStackOptions { * - 0: library will not generate styles for rows (define your own CSS) * - 'auto': height calculated for square cells (width / column) and updated live on window resize * - 'initial': similar to 'auto' but stays fixed size during window resizing - * + * * Note: % values don't work correctly - see demo/cell-height.html - * + * * @example * // Fixed 100px height * cellHeight: 100 - * + * * // CSS units * cellHeight: '5rem' * cellHeight: '100px' - * + * * // Auto-sizing for square cells * cellHeight: 'auto' - * + * * // No CSS generation (custom styles) * cellHeight: 0 */ diff --git a/src/utils.ts b/src/utils.ts index 783e2a062..024dbafd2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ /** - * utils.ts 12.3.0 + * utils.ts 12.3.3 * Copyright (c) 2021-2025 Alain Dumesny - see GridStack root license */ @@ -20,7 +20,7 @@ export interface DragTransform { /** * @internal Checks for obsolete method names and provides deprecation warnings. * Creates a wrapper function that logs a deprecation warning when called. - * + * * @param self the object context to apply the function to * @param f the new function to call * @param oldName the deprecated method name @@ -42,7 +42,7 @@ export function obsolete(self, f, oldName: string, newName: string, rev: string) /** * @internal Checks for obsolete grid options and migrates them to new names. * Automatically copies old option values to new option names and shows deprecation warnings. - * + * * @param opts the options object to check and migrate * @param oldName the deprecated option name * @param newName the new option name to use instead @@ -59,7 +59,7 @@ export function obsoleteOpts(opts: GridStackOptions, oldName: string, newName: s /** * @internal Checks for obsolete grid options that have been completely removed. * Shows deprecation warnings for options that are no longer supported. - * + * * @param opts the options object to check * @param oldName the removed option name * @param rev the version when the option was removed @@ -74,7 +74,7 @@ export function obsoleteOptsDel(opts: GridStackOptions, oldName: string, rev: st /** * @internal Checks for obsolete HTML element attributes and migrates them. * Automatically copies old attribute values to new attribute names and shows deprecation warnings. - * + * * @param el the HTML element to check and migrate * @param oldName the deprecated attribute name * @param newName the new attribute name to use instead @@ -99,11 +99,11 @@ export class Utils { /** * Convert a potential selector into an actual list of HTML elements. * Supports CSS selectors, element references, and special ID handling. - * + * * @param els selector string, HTMLElement, or array of elements * @param root optional root element to search within (defaults to document, useful for shadow DOM) * @returns array of HTML elements matching the selector - * + * * @example * const elements = Utils.getElements('.grid-item'); * const byId = Utils.getElements('#myWidget'); @@ -134,11 +134,11 @@ export class Utils { /** * Convert a potential selector into a single HTML element. * Similar to getElements() but returns only the first match. - * + * * @param els selector string or HTMLElement * @param root optional root element to search within (defaults to document) * @returns the first HTML element matching the selector, or null if not found - * + * * @example * const element = Utils.getElement('#myWidget'); * const first = Utils.getElement('.grid-item'); @@ -170,10 +170,10 @@ export class Utils { /** * Check if a widget should be lazy loaded based on node or grid settings. - * + * * @param n the grid node to check * @returns true if the item should be lazy loaded - * + * * @example * if (Utils.lazyLoad(node)) { * // Set up intersection observer for lazy loading @@ -185,11 +185,11 @@ export class Utils { /** * Create a div element with the specified CSS classes. - * + * * @param classes array of CSS class names to add * @param parent optional parent element to append the div to * @returns the created div element - * + * * @example * const div = Utils.createDiv(['grid-item', 'draggable']); * const nested = Utils.createDiv(['content'], parentDiv); @@ -203,11 +203,11 @@ export class Utils { /** * Check if a widget should resize to fit its content. - * + * * @param n the grid node to check (can be undefined) * @param strict if true, only returns true for explicit sizeToContent:true (not numbers) * @returns true if the widget should resize to content - * + * * @example * if (Utils.shouldSizeToContent(node)) { * // Trigger content-based resizing @@ -221,11 +221,11 @@ export class Utils { /** * Check if two grid positions overlap/intersect. - * + * * @param a first position with x, y, w, h properties * @param b second position with x, y, w, h properties * @returns true if the positions overlap - * + * * @example * const overlaps = Utils.isIntercepted( * {x: 0, y: 0, w: 2, h: 1}, @@ -238,11 +238,11 @@ export class Utils { /** * Check if two grid positions are touching (edges or corners). - * + * * @param a first position * @param b second position * @returns true if the positions are touching - * + * * @example * const touching = Utils.isTouching( * {x: 0, y: 0, w: 2, h: 1}, @@ -255,11 +255,11 @@ export class Utils { /** * Calculate the overlapping area between two grid positions. - * + * * @param a first position * @param b second position * @returns the area of overlap (0 if no overlap) - * + * * @example * const overlap = Utils.areaIntercept( * {x: 0, y: 0, w: 3, h: 2}, @@ -278,10 +278,10 @@ export class Utils { /** * Calculate the total area of a grid position. - * + * * @param a position with width and height * @returns the total area (width * height) - * + * * @example * const area = Utils.area({x: 0, y: 0, w: 3, h: 2}); // returns 6 */ @@ -291,11 +291,11 @@ export class Utils { /** * Sort an array of grid nodes by position (y first, then x). - * + * * @param nodes array of nodes to sort * @param dir sort direction: 1 for ascending (top-left first), -1 for descending * @returns the sorted array (modifies original) - * + * * @example * const sorted = Utils.sort(nodes); // Sort top-left to bottom-right * const reverse = Utils.sort(nodes, -1); // Sort bottom-right to top-left @@ -311,11 +311,11 @@ export class Utils { /** * Find a grid node by its ID. - * + * * @param nodes array of nodes to search * @param id the ID to search for * @returns the node with matching ID, or undefined if not found - * + * * @example * const node = Utils.find(nodes, 'widget-1'); * if (node) console.log('Found node at:', node.x, node.y); @@ -327,10 +327,10 @@ export class Utils { /** * Convert various value types to boolean. * Handles strings like 'false', 'no', '0' as false. - * + * * @param v value to convert * @returns boolean representation - * + * * @example * Utils.toBool('true'); // true * Utils.toBool('false'); // false @@ -351,10 +351,10 @@ export class Utils { /** * Convert a string value to a number, handling null and empty strings. - * + * * @param value string or null value to convert * @returns number value, or undefined for null/empty strings - * + * * @example * Utils.toNumber('42'); // 42 * Utils.toNumber(''); // undefined @@ -367,10 +367,10 @@ export class Utils { /** * Parse a height value with units into numeric value and unit string. * Supports px, em, rem, vh, vw, %, cm, mm units. - * + * * @param val height value as number or string with units * @returns object with h (height) and unit properties - * + * * @example * Utils.parseHeight('100px'); // {h: 100, unit: 'px'} * Utils.parseHeight('2rem'); // {h: 2, unit: 'rem'} @@ -398,11 +398,11 @@ export class Utils { /** * Copy unset fields from source objects to target object (shallow merge with defaults). * Similar to Object.assign but only sets undefined/null fields. - * + * * @param target the object to copy defaults into * @param sources one or more source objects to copy defaults from * @returns the modified target object - * + * * @example * const config = { width: 100 }; * Utils.defaults(config, { width: 200, height: 50 }); @@ -429,11 +429,11 @@ export class Utils { /** * Compare two objects for equality (shallow comparison). * Checks if objects have the same fields and values at one level deep. - * + * * @param a first object to compare * @param b second object to compare * @returns true if objects have the same values - * + * * @example * Utils.same({x: 1, y: 2}, {x: 1, y: 2}); // true * Utils.same({x: 1}, {x: 1, y: 2}); // false @@ -452,12 +452,12 @@ export class Utils { /** * Copy position and size properties from one widget to another. * Copies x, y, w, h and optionally min/max constraints. - * + * * @param a target widget to copy to * @param b source widget to copy from * @param doMinMax if true, also copy min/max width/height constraints * @returns the target widget (a) - * + * * @example * Utils.copyPos(widget1, widget2); // Copy position/size * Utils.copyPos(widget1, widget2, true); // Also copy constraints @@ -493,6 +493,8 @@ export class Utils { /** removes field from the first object if same as the second objects (like diffing) and internal '_' for saving */ static removeInternalAndSame(a: unknown, b: unknown):void { if (typeof a !== 'object' || typeof b !== 'object') return; + // skip arrays as we don't know how to hydrate them (unlike object spread operator) + if (Array.isArray(a) || Array.isArray(b)) return; for (let key in a) { const aVal = a[key]; const bVal = b[key]; diff --git a/tsconfig.json b/tsconfig.json index 4b5c75da6..d0548e9d1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,16 +7,24 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "inlineSources": true, - "lib": [ "es6", "es2015", "dom" ], + "lib": [ "es2017", "es2015", "dom" ], "module": "ES2020", "noImplicitAny": false, "outDir": "./dist", "sourceMap": true, "strict": false, - "target": "ES2020" + "target": "ES2020", + "types": ["vitest/globals", "@testing-library/jest-dom", "node"], + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "moduleResolution": "node" }, "include": [ - "./src/**/*.ts" + "./src/**/*.ts", + "./spec/**/*.ts", + "./vitest.setup.ts", + "./vitest.config.ts" ], "typeroots": [ "./node_modules/@types" diff --git a/typedoc.html.json b/typedoc.html.json index 0626a7a57..46651938d 100644 --- a/typedoc.html.json +++ b/typedoc.html.json @@ -15,7 +15,7 @@ "excludePrivate": true, "excludeProtected": false, "excludeInternal": true, - "includeVersion": false, + "includeVersion": true, "sort": ["alphabetical"], "kindSortOrder": [ "Class", diff --git a/typedoc.json b/typedoc.json index d0af849cb..9b949f035 100644 --- a/typedoc.json +++ b/typedoc.json @@ -20,7 +20,7 @@ "excludePrivate": true, "excludeProtected": false, "excludeInternal": true, - "includeVersion": false, + "includeVersion": true, "sort": ["alphabetical"], "kindSortOrder": [ "Class", diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 000000000..7a5ca49c1 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,84 @@ +///