Просмотр исходного кода

Add resize plugin: `x-resize` (#4304)

* implementation

* Add docs
Caleb Porzio 10 месяцев назад
Родитель
Сommit
10997a7f3c

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 99 - 0
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.
+
+<a name="installation"></a>
+## Installation
+
+You can use this plugin by either including it from a `<script>` tag or installing it via NPM:
+
+### Via CDN
+
+You can include the CDN build of this plugin as a `<script>` tag, just make sure to include it BEFORE Alpine's core JS file.
+
+```alpine
+<!-- Alpine Plugins -->
+<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/resize@3.x.x/dist/cdn.min.js"></script>
+
+<!-- Alpine Core -->
+<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
+```
+
+### 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)
+
+...
+```
+
+<a name="x-resize"></a>
+## 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
+<div
+    x-data="{ width: 0, height: 0 }"
+    x-resize="width = $width; height = $height"
+>
+    <p x-text="'Width: ' + width + 'px'"></p>
+    <p x-text="'Height: ' + height + 'px'"></p>
+</div>
+```
+
+<!-- START_VERBATIM -->
+<div class="demo">
+    <div x-data="{ width: 0, height: 0 }" x-resize="width = $width; height = $height">
+        <i>Resize your browser window to see the width and height values change.</i>
+        <br><br>
+        <p x-text="'Width: ' + width + 'px'"></p>
+        <p x-text="'Height: ' + height + 'px'"></p>
+    </div>
+</div>
+<!-- END_VERBATIM -->
+
+<a name="modifiers"></a>
+## Modifiers
+
+<a name="document"></a>
+### .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
+<div x-resize.document="...">
+```
+
+<!-- START_VERBATIM -->
+<div class="demo">
+    <div x-data="{ width: 0, height: 0 }" x-resize.document="width = $width; height = $height">
+        <i>Resize your browser window to see the document width and height values change.</i>
+        <br><br>
+        <p x-text="'Width: ' + width + 'px'"></p>
+        <p x-text="'Height: ' + height + 'px'"></p>
+    </div>
+</div>
+<!-- END_VERBATIM -->

+ 1 - 1
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

+ 5 - 0
packages/resize/builds/cdn.js

@@ -0,0 +1,5 @@
+import resize from '../src/index.js'
+
+document.addEventListener('alpine:init', () => {
+    window.Alpine.plugin(resize)
+})

+ 5 - 0
packages/resize/builds/module.js

@@ -0,0 +1,5 @@
+import resize from './../src/index.js'
+
+export default resize
+
+export { resize }

+ 17 - 0
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": {}
+}

+ 59 - 0
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]
+}

+ 2 - 1
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',

+ 6 - 0
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')
 

+ 48 - 0
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`
+    <div x-data="{ width: 0, height: 0 }">
+        <h1 x-text="width"></h1>
+        <h2 x-text="height"></h2>
+
+        <div x-ref="target" x-resize="width = $width; height = $height" style="width: 100px; height: 100px; background: red">
+        </div>
+
+        <button id="1" x-on:click="$refs.target.style.width = 50 + 'px'">resize width</button>
+        <button id="2" x-on:click="$refs.target.style.height = 50 + 'px'">resize height</button>
+    </div>
+    `],
+    ({ 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`
+    <div x-data="{ width: 0, height: 0 }">
+        <h1 x-text="width"></h1>
+        <h2 x-text="height"></h2>
+
+        <div x-ref="target" x-resize.document="width = $width; height = $height" style="width: 100px; height: 100px; background: red">
+    </div>
+    `],
+    ({ 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'))
+    },
+)

+ 1 - 0
tests/cypress/spec.html

@@ -12,6 +12,7 @@
     <script src="/../../packages/intersect/dist/cdn.js"></script>
     <script src="/../../packages/collapse/dist/cdn.js"></script>
     <script src="/../../packages/anchor/dist/cdn.js"></script>
+    <script src="/../../packages/resize/dist/cdn.js"></script>
     <script src="/../../packages/mask/dist/cdn.js"></script>
     <script src="/../../packages/sort/dist/cdn.js"></script>
     <script src="/../../packages/ui/dist/cdn.js"></script>