From 10997a7f3c4a5bc76510977c9a87b9fb179a44c1 Mon Sep 17 00:00:00 2001 From: Caleb Porzio Date: Mon, 15 Jul 2024 13:32:44 -0400 Subject: [PATCH] Add resize plugin: `x-resize` (#4304) * implementation * Add docs --- packages/docs/src/en/plugins/anchor.md | 2 +- packages/docs/src/en/plugins/collapse.md | 2 +- packages/docs/src/en/plugins/focus.md | 2 +- packages/docs/src/en/plugins/morph.md | 2 +- packages/docs/src/en/plugins/persist.md | 2 +- packages/docs/src/en/plugins/resize.md | 99 +++++++++++++++++++ packages/docs/src/en/plugins/sort.md | 2 +- packages/resize/builds/cdn.js | 5 + packages/resize/builds/module.js | 5 + packages/resize/package.json | 17 ++++ packages/resize/src/index.js | 59 +++++++++++ scripts/build.js | 3 +- scripts/release.js | 6 ++ .../integration/plugins/resize.spec.js | 48 +++++++++ tests/cypress/spec.html | 1 + 15 files changed, 248 insertions(+), 7 deletions(-) create mode 100644 packages/docs/src/en/plugins/resize.md create mode 100644 packages/resize/builds/cdn.js create mode 100644 packages/resize/builds/module.js create mode 100644 packages/resize/package.json create mode 100644 packages/resize/src/index.js create mode 100644 tests/cypress/integration/plugins/resize.spec.js diff --git a/packages/docs/src/en/plugins/anchor.md b/packages/docs/src/en/plugins/anchor.md index 05cad10d1..b7c3b94c7 100644 --- a/packages/docs/src/en/plugins/anchor.md +++ b/packages/docs/src/en/plugins/anchor.md @@ -1,5 +1,5 @@ --- -order: 5 +order: 7 title: Anchor description: Anchor an element's positioning to another element on the page graph_image: https://alpinejs.dev/social_anchor.jpg diff --git a/packages/docs/src/en/plugins/collapse.md b/packages/docs/src/en/plugins/collapse.md index de9472f8c..063caec54 100644 --- a/packages/docs/src/en/plugins/collapse.md +++ b/packages/docs/src/en/plugins/collapse.md @@ -1,5 +1,5 @@ --- -order: 4 +order: 6 title: Collapse description: Collapse and expand elements with robust animations graph_image: https://alpinejs.dev/social_collapse.jpg diff --git a/packages/docs/src/en/plugins/focus.md b/packages/docs/src/en/plugins/focus.md index 57c9f92fc..90cc728d3 100644 --- a/packages/docs/src/en/plugins/focus.md +++ b/packages/docs/src/en/plugins/focus.md @@ -1,5 +1,5 @@ --- -order: 3 +order: 5 title: Focus description: Easily manage focus within the page graph_image: https://alpinejs.dev/social_focus.jpg diff --git a/packages/docs/src/en/plugins/morph.md b/packages/docs/src/en/plugins/morph.md index b8a8769af..cc60d4a79 100644 --- a/packages/docs/src/en/plugins/morph.md +++ b/packages/docs/src/en/plugins/morph.md @@ -1,5 +1,5 @@ --- -order: 6 +order: 8 title: Morph description: Morph an element into the provided HTML graph_image: https://alpinejs.dev/social_morph.jpg diff --git a/packages/docs/src/en/plugins/persist.md b/packages/docs/src/en/plugins/persist.md index f8850194c..4c6fb665a 100644 --- a/packages/docs/src/en/plugins/persist.md +++ b/packages/docs/src/en/plugins/persist.md @@ -1,5 +1,5 @@ --- -order: 2 +order: 4 title: Persist description: Easily persist data across page loads using localStorage graph_image: https://alpinejs.dev/social_persist.jpg diff --git a/packages/docs/src/en/plugins/resize.md b/packages/docs/src/en/plugins/resize.md new file mode 100644 index 000000000..cf9b27e6c --- /dev/null +++ b/packages/docs/src/en/plugins/resize.md @@ -0,0 +1,99 @@ +--- +order: 3 +title: Resize +description: An Alpine convenience wrapper for the Resize Observer API that allows you to easily react when an element is resized. +graph_image: https://alpinejs.dev/social_resize.jpg +--- + +# Resize Plugin + +Alpine's Resize plugin is a convenience wrapper for the [Resize Observer](https://developer.mozilla.org/en-US/docs/Web/API/Resize_Observer_API) that allows you to easily react when an element changes size. + +This is useful for: custom size-based animations, intelligent sticky positioning, conditionally adding attributes based on the element's size, etc. + + +## Installation + +You can use this plugin by either including it from a ` + + + +``` + +### Via NPM + +You can install Resize from NPM for use inside your bundle like so: + +```shell +npm install @alpinejs/resize +``` + +Then initialize it from your bundle: + +```js +import Alpine from 'alpinejs' +import resize from '@alpinejs/resize' + +Alpine.plugin(resize) + +... +``` + + +## x-resize + +The primary API for using this plugin is `x-resize`. You can add `x-resize` to any element within an Alpine component, and when that element is resized for any reason, the provided expression will execute with two magic properties: `$width` and `$height`. + +For example, here's a simple example of using `x-resize` do display the width and height of an element as it changes size. + +```alpine +
+

+

+
+``` + + +
+
+ Resize your browser window to see the width and height values change. +

+

+

+
+
+ + + +## Modifiers + + +### .document + +It's often useful to observer the entire document's size, rather than a specific element. To do this, you can add the `.document` modifier to `x-resize`: + +```alpine +
+``` + + +
+
+ Resize your browser window to see the document width and height values change. +

+

+

+
+
+ diff --git a/packages/docs/src/en/plugins/sort.md b/packages/docs/src/en/plugins/sort.md index 33ef2217a..3595c9173 100644 --- a/packages/docs/src/en/plugins/sort.md +++ b/packages/docs/src/en/plugins/sort.md @@ -1,5 +1,5 @@ --- -order: 6 +order: 9 title: Sort description: Easily re-order elements by dragging them with your mouse graph_image: https://alpinejs.dev/social_sort.jpg diff --git a/packages/resize/builds/cdn.js b/packages/resize/builds/cdn.js new file mode 100644 index 000000000..b8a3f5b76 --- /dev/null +++ b/packages/resize/builds/cdn.js @@ -0,0 +1,5 @@ +import resize from '../src/index.js' + +document.addEventListener('alpine:init', () => { + window.Alpine.plugin(resize) +}) diff --git a/packages/resize/builds/module.js b/packages/resize/builds/module.js new file mode 100644 index 000000000..fc370a076 --- /dev/null +++ b/packages/resize/builds/module.js @@ -0,0 +1,5 @@ +import resize from './../src/index.js' + +export default resize + +export { resize } diff --git a/packages/resize/package.json b/packages/resize/package.json new file mode 100644 index 000000000..f0dd6d313 --- /dev/null +++ b/packages/resize/package.json @@ -0,0 +1,17 @@ +{ + "name": "@alpinejs/resize", + "version": "3.14.1", + "description": "Trigger JavaScript when an element is resized on the page", + "homepage": "https://alpinejs.dev/plugins/intersect", + "repository": { + "type": "git", + "url": "https://github.com/alpinejs/alpine.git", + "directory": "packages/resize" + }, + "author": "Caleb Porzio", + "license": "MIT", + "main": "dist/module.cjs.js", + "module": "dist/module.esm.js", + "unpkg": "dist/cdn.min.js", + "dependencies": {} +} diff --git a/packages/resize/src/index.js b/packages/resize/src/index.js new file mode 100644 index 000000000..24d647595 --- /dev/null +++ b/packages/resize/src/index.js @@ -0,0 +1,59 @@ +export default function (Alpine) { + Alpine.directive('resize', Alpine.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater, cleanup }) => { + let evaluator = evaluateLater(expression) + + let evaluate = (width, height) => { + evaluator(() => {}, { scope: { '$width': width, '$height': height }}) + } + + let off = modifiers.includes('document') + ? onDocumentResize(evaluate) + : onElResize(el, evaluate) + + cleanup(() => off()) + })) +} + +function onElResize(el, callback) { + let observer = new ResizeObserver((entries) => { + let [width, height] = dimensions(entries) + + callback(width, height) + }) + + observer.observe(el) + + return () => observer.disconnect() +} + +let documentResizeObserver +let documentResizeObserverCallbacks = new Set + +function onDocumentResize(callback) { + documentResizeObserverCallbacks.add(callback) + + if (! documentResizeObserver) { + documentResizeObserver = new ResizeObserver((entries) => { + let [width, height] = dimensions(entries) + + documentResizeObserverCallbacks.forEach(i => i(width, height)) + }) + + documentResizeObserver.observe(document.documentElement) + } + + return () => { + documentResizeObserverCallbacks.delete(callback) + } +} + +function dimensions(entries) { + let width, height + + for (let entry of entries) { + width = entry.borderBoxSize[0].inlineSize + height = entry.borderBoxSize[0].blockSize + } + + return [width, height] +} diff --git a/scripts/build.js b/scripts/build.js index e02d07733..c7421ab44 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -9,8 +9,9 @@ let zlib = require('zlib'); // 'history', - removed because this plugin has been moved to livewire/livewire until it's stable... // 'navigate', - remove because this plugin has been moved to livewire/livewire until it's stable... 'intersect', - 'persist', 'collapse', + 'persist', + 'resize', 'anchor', 'morph', 'focus', diff --git a/scripts/release.js b/scripts/release.js index a9624d1e0..a94624aaa 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -45,6 +45,9 @@ function writeNewAlpineVersion() { writeToPackageDotJson('intersect', 'version', version) console.log('Bumping @alpinejs/intersect package.json: '+version) + writeToPackageDotJson('resize', 'version', version) + console.log('Bumping @alpinejs/resize package.json: '+version) + writeToPackageDotJson('persist', 'version', version) console.log('Bumping @alpinejs/persist package.json: '+version) @@ -92,6 +95,9 @@ function publish() { console.log('Publishing @alpinejs/intersect on NPM...'); runFromPackage('intersect', 'npm publish --access public') + console.log('Publishing @alpinejs/resize on NPM...'); + runFromPackage('resize', 'npm publish --access public') + console.log('Publishing @alpinejs/persist on NPM...'); runFromPackage('persist', 'npm publish --access public') diff --git a/tests/cypress/integration/plugins/resize.spec.js b/tests/cypress/integration/plugins/resize.spec.js new file mode 100644 index 000000000..6004ef31b --- /dev/null +++ b/tests/cypress/integration/plugins/resize.spec.js @@ -0,0 +1,48 @@ +import { haveText, test, html, notHaveText } from '../../utils' + +test('can react to the resizing of an element', + [html` +
+

+

+ +
+
+ + + +
+ `], + ({ get }) => { + get('h1').should(haveText('100')) + get('h2').should(haveText('100')) + get('button#1').click() + get('h1').should(haveText('50')) + get('h2').should(haveText('100')) + get('button#2').click() + get('h1').should(haveText('50')) + get('h2').should(haveText('50')) + }, +) + +test('can react to the resizing of the document', + [html` +
+

+

+ +
+
+ `], + ({ get }) => { + get('h1').should(notHaveText('0')) + get('h2').should(notHaveText('0')) + get('h1').should(notHaveText('100')) + get('h2').should(notHaveText('100')) + + cy.viewport(550, 750) + + get('h1').should(haveText('550')) + get('h2').should(haveText('750')) + }, +) diff --git a/tests/cypress/spec.html b/tests/cypress/spec.html index 5c939f48d..c61eb2d8b 100644 --- a/tests/cypress/spec.html +++ b/tests/cypress/spec.html @@ -12,6 +12,7 @@ +