@simhub2/justflipit 4.0.0 → 4.0.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.1
4
+
5
+ - Enable 3D tilt on touch and reset it when the finger lifts or scrolling takes over.
6
+ - Keep tap-to-flip active across flip, fade and scale without immediately flipping back on touch release.
7
+ - Restrict axis rotation to the flip effect so fade also works with `FlipX: true`.
8
+
3
9
  ## 4.0.0
4
10
 
5
11
  - One SSR-safe, dependency-free core with real ESM/CommonJS/browser exports.
package/README.md CHANGED
@@ -1,168 +1,312 @@
1
- # justFlipIt
1
+ <p align="center">
2
+ <img src="img/readme-banner.svg" alt="justFlipIt v4 — A little motion. A new perspective." width="100%">
3
+ </p>
2
4
 
3
- Responsive 3D flip and tilt animations, with no runtime dependencies in the core.
4
- ES modules, CommonJS, browser scripts, TypeScript, React, Vue 3, and Svelte 4/5.
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/@simhub2/justflipit"><img src="https://img.shields.io/npm/v/%40simhub2%2Fjustflipit?style=flat-square&amp;color=b4df83" alt="npm version"></a>
7
+ <a href="https://github.com/SimHub/justFlipIt/actions/workflows/ci.yml"><img src="https://github.com/SimHub/justFlipIt/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status"></a>
8
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-d9ccff?style=flat-square" alt="MIT license"></a>
9
+ <a href="#frameworks"><img src="https://img.shields.io/badge/TypeScript-included-93c5fd?style=flat-square" alt="TypeScript declarations included"></a>
10
+ </p>
11
+
12
+ <p align="center">
13
+ Responsive flip and tilt animations for your UI.<br>
14
+ One dependency-free core. Native framework lifecycles. Model-independent agent tools.
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="#quick-start">Quick start</a> ·
19
+ <a href="#frameworks">React / Vue / Svelte</a> ·
20
+ <a href="#agent-tools">Agent tools</a> ·
21
+ <a href="#options">Options</a> ·
22
+ <a href="#upgrading-from-v3">Upgrade from v3</a>
23
+ </p>
24
+
25
+ ---
26
+
27
+ ## A card with more to say
28
+
29
+ Build product cards, reveal details, or let an agent control a card through explicit tool calls. Choose a 3D flip, a soft fade, or a scale transition, then add pointer-driven tilt.
30
+
31
+ ![justFlipIt demo showing flip cards, effect controls and an agent tool](img/demo.png)
32
+
33
+ **[Browse the demo source →](example/index.html)** · Run locally with `npm run build` and `python3 -m http.server 4173`, then open [localhost:4173/example/](http://localhost:4173/example/).
34
+
35
+ | Motion that responds | Fits your application | Control when you need it |
36
+ |---|---|---|
37
+ | Flip on either axis, fade, and scale | React hook, Vue directive, Svelte action | `flip`, `toggle`, `update`, and state inspection |
38
+ | Pointer tilt batched per animation frame | Existing framework-owned faces stay in place | Model-independent JSON-schema tool definition |
39
+ | Touch, keyboard, and reduced-motion support | Responsive layout and SSR-safe imports | Typed API and change events |
40
+
41
+ ## Quick start
5
42
 
6
43
  ```sh
7
44
  npm install @simhub2/justflipit
8
45
  ```
9
46
 
47
+ Add a container to your page, then initialize it after the element exists:
48
+
49
+ ```html
50
+ <div class="product-card">
51
+ <h2>A different perspective</h2>
52
+ <p>Click to discover the other side.</p>
53
+ </div>
54
+ ```
55
+
10
56
  ```js
11
57
  import justFlipIt from '@simhub2/justflipit';
12
58
 
13
- const card = justFlipIt(document.querySelector('.card'), {
59
+ const card = justFlipIt(document.querySelector('.product-card'), {
14
60
  Click: true,
15
- Template: '<p>Back of the card</p>', // trusted application HTML only
16
- Tilt: { max: 12, perspective: 1000, scale: 1.03 },
17
- Effect: 'flip',
18
- Duration: 450
61
+ Template: '<h2>There is more to see.</h2><p>Your details go here.</p>',
62
+ Tilt: { max: 10, perspective: 1000, scale: 1.03 },
63
+ Duration: 450,
19
64
  });
20
- card.flip();
21
- card.flip(false);
22
- card.toggle();
23
- card.update({ Effect: 'fade' });
24
- card.getState();
25
- card.destroy();
26
65
  ```
27
66
 
28
- Passing an element returns one controller; a selector or collection returns an array.
29
- Repeated initialization returns the existing controller. `justFlipIt.get(element)`
30
- retrieves it; `justFlipIt.destroy(target)` cleans up. Original nodes and their event
31
- listeners are preserved. The grid layout adapts to both faces without fixed dimensions.
32
- The card needs an HTML container, such as a `div`; do not initialize a void element.
67
+ The original node and its event listeners are preserved. Both faces participate in a responsive grid, so the card adapts to its content. Style the faces to match your design; the core supplies the motion and layout.
33
68
 
34
- For script tags, use `dist/justFlipIt.min.js`, which exposes `window.justFlipIt`.
35
- `/modern`, `/vanilla`, `/combined`, and their historical browser filenames are aliases
36
- of the same core in v4. There is no jQuery plugin in v4.
69
+ > `Template` accepts trusted application HTML. Use the agent bridge's `setText` action for model-generated text. With a framework, render content through the framework instead.
37
70
 
38
- ## Options
71
+ <details>
72
+ <summary><strong>Using a browser script or CommonJS?</strong></summary>
39
73
 
40
- | Option | Default | Meaning |
41
- |---|---|---|
42
- | `Click` | `false` | Hover; `true` for click; selector or selector array for delegated clicks |
43
- | `FlipX` | `false` | Rotate on X instead of Y |
44
- | `Template` | cloned original | Trusted HTML for the generated back face |
45
- | `Duration` | `400` | Non-negative milliseconds |
46
- | `Easing` | `cubic-bezier(.22,.61,.36,1)` | CSS easing |
47
- | `Effect` | `'flip'` | `'flip'`, `'fade'`, or `'scale'` |
48
- | `Tilt` | `false` | `true`, `'only'`, or `{ max, perspective, scale }` |
49
- | `ReducedMotion` | `true` | Respect live `prefers-reduced-motion` preferences |
50
- | `Faces` | omitted | Select two existing direct children: `{ front, back }` |
51
- | `preserveClasses` | `true` | Copy classes onto generated container |
52
- | `preserveStyles` | `true` | Copy inline styles onto generated container |
53
- | `Style` | `[]` | Array of `{ el: 'self' or selector, style: {...} }` |
74
+ **Browser script — no bundler:**
54
75
 
55
- Tilt pointer updates are batched into one animation frame; touch scrolling remains
56
- native. Hover mode also supports touch tap. Focus the card and press Enter/Space to
57
- toggle, Escape to reset. The hidden face is inert and marked `aria-hidden`.
58
- Listen for `justflipit:change` on the container; `event.detail` contains the state.
59
- CSS transitions reverse naturally during rapid input without accumulating animations.
76
+ ```html
77
+ <div class="card">Front of the card</div>
78
+ <script src="https://cdn.jsdelivr.net/npm/@simhub2/justflipit@4.0.1/dist/justFlipIt.min.js"></script>
79
+ <script>
80
+ justFlipIt('.card', { Click: true, Template: '<p>Back of the card</p>' });
81
+ </script>
82
+ ```
83
+
84
+ **CommonJS:**
85
+
86
+ ```js
87
+ const justFlipIt = require('@simhub2/justflipit');
88
+ ```
89
+
90
+ The browser bundle exposes `window.justFlipIt`. ESM and CommonJS imports include matching TypeScript declarations.
91
+
92
+ </details>
60
93
 
61
94
  ## Frameworks
62
95
 
63
- Framework integrations use existing faces. They do not move, clone, or replace framework
64
- nodes. Render front/back content through your framework, and do not pass `Template`.
65
- Keep `Faces` selectors stable for the lifetime of an instance. Direct DOM methods
66
- run only on the client; importing any entry point during SSR is safe.
96
+ Use two direct child elements as the front and back faces. The adapters attach behavior to those existing nodes, so reactive content remains owned by your framework.
97
+
98
+ | Framework | Import | Integration | Cleanup |
99
+ |---|---|---|---|
100
+ | React 18+ | `@simhub2/justflipit/react` | `useFlip()` hook | Effect cleanup; StrictMode compatible |
101
+ | Vue 3 | `@simhub2/justflipit/vue` | `vFlip` directive | `beforeUnmount` |
102
+ | Svelte 4/5 | `@simhub2/justflipit/svelte` | `flip` action | Action `destroy()` |
103
+ | Other frameworks | `@simhub2/justflipit` | Core with `Faces` selectors | Call `destroy()` before unmount |
67
104
 
68
- React uses an Effect with cleanup, including development StrictMode cycles, following
69
- [React's external-system lifecycle](https://react.dev/reference/react/useEffect).
105
+ <details open>
106
+ <summary><strong>React</strong> — hook, ref and controller</summary>
70
107
 
71
108
  ```jsx
72
109
  import { useFlip } from '@simhub2/justflipit/react';
73
110
 
74
- export function Card({ title }) {
111
+ export function ProductCard({ title, details }) {
75
112
  const { ref, controller } = useFlip({ Click: true, Tilt: true });
76
- return <div>
77
- <div ref={ref}>
78
- <div data-flip-front>{title}</div>
79
- <div data-flip-back>Details</div>
80
- </div>
81
- <button onClick={() => controller.current?.toggle()}>Flip</button>
82
- </div>;
113
+
114
+ return (
115
+ <section>
116
+ <div ref={ref}>
117
+ <div data-flip-front>{title}</div>
118
+ <div data-flip-back>{details}</div>
119
+ </div>
120
+ <button onClick={() => controller.current?.toggle()}>Flip card</button>
121
+ </section>
122
+ );
83
123
  }
84
124
  ```
85
125
 
86
- Vue 3 uses a [custom directive](https://vuejs.org/guide/reusability/custom-directives.html)
87
- with mount, update and teardown hooks.
126
+ In Next.js App Router, put this hook in a Client Component with `'use client'`.
127
+
128
+ </details>
129
+
130
+ <details>
131
+ <summary><strong>Vue 3</strong> — directive with reactive options</summary>
88
132
 
89
133
  ```vue
90
134
  <script setup>
91
135
  import { vFlip } from '@simhub2/justflipit/vue';
136
+
137
+ defineProps({ title: String, details: String });
92
138
  </script>
139
+
93
140
  <template>
94
141
  <div v-flip="{ Click: true, Effect: 'scale' }">
95
- <div data-flip-front>Front</div>
96
- <div data-flip-back>Back</div>
142
+ <div data-flip-front>{{ title }}</div>
143
+ <div data-flip-back>{{ details }}</div>
97
144
  </div>
98
145
  </template>
99
146
  ```
100
147
 
101
- Svelte uses an [action](https://svelte.dev/docs/svelte/use) with update and destroy hooks.
148
+ </details>
149
+
150
+ <details>
151
+ <summary><strong>Svelte</strong> — action with lifecycle updates</summary>
102
152
 
103
153
  ```svelte
104
154
  <script>
105
155
  import { flip } from '@simhub2/justflipit/svelte';
106
156
  </script>
157
+
107
158
  <div use:flip={{ Click: true, Tilt: true }}>
108
159
  <div data-flip-front>Front</div>
109
- <div data-flip-back>Back</div>
160
+ <div data-flip-back>Details</div>
110
161
  </div>
111
162
  ```
112
163
 
113
- Other frameworks can call the core on mount with `Faces`, update options when needed,
114
- and call `destroy()` before unmounting.
164
+ </details>
115
165
 
116
- ## Model-independent agent tools
166
+ **Lifecycle notes:** Imports are safe during server rendering; initialize DOM controllers on the client. Keep `Faces` selectors stable while mounted. Render framework content through application state and omit `Template`. Avoid simultaneously managing the plugin's style, class and accessibility attributes from your framework.
117
167
 
118
- The optional `/agent` entry exports a JSON-schema tool definition and a synchronous
119
- executor. Your application registers explicit card IDs, receives a tool call from its
120
- agent runtime, parses its JSON arguments, and passes the command to `execute`.
121
- No API key or model SDK is required by this package.
168
+ ## Agent tools
169
+
170
+ Connect any agent runtime that supports tool calls. Register the cards your agent may control, supply the exported schema to your runtime, and pass parsed tool arguments to the executor.
122
171
 
123
172
  ```js
173
+ import justFlipIt from '@simhub2/justflipit';
124
174
  import { createAgentBridge } from '@simhub2/justflipit/agent';
125
- const bridge = createAgentBridge({ product: card });
126
175
 
127
- // Adapt this definition to the tool envelope required by your agent runtime.
128
- const toolDefinition = bridge.tool;
176
+ const product = justFlipIt(document.querySelector('.product-card'), {
177
+ Click: true,
178
+ });
179
+ const bridge = createAgentBridge({ product });
180
+
181
+ // Wrap this definition in the tool format expected by your agent runtime.
182
+ const tool = bridge.tool;
183
+
184
+ // Execute the parsed arguments from a tool call.
185
+ bridge.execute({ id: 'product', action: 'setText', text: 'Selected for you.' });
129
186
  const result = bridge.execute({ id: 'product', action: 'flip' });
130
- bridge.execute({ id: 'product', action: 'setText', text: 'Agent-generated details' });
187
+ // { id: 'product', flipped: true, destroyed: false, effect: 'flip' }
188
+ ```
189
+
190
+ | Action | Result |
191
+ |---|---|
192
+ | `state` | Read the card's current state |
193
+ | `flip` | Show the back |
194
+ | `reset` | Show the front |
195
+ | `toggle` | Switch sides |
196
+ | `setText` | Replace the generated back face with plain text |
197
+
198
+ The bridge validates commands and accepts only registered card IDs. `setText` uses `textContent`, accepts up to 100,000 characters, and rejects framework-managed cards; update framework content through application state.
199
+
200
+ The executor runs in the browser alongside the DOM. Relay server-side tool calls through your application. The package provides a schema and executor; your application supplies the agent runtime and any model credentials.
201
+
202
+ ## Controller API
203
+
204
+ ```js
205
+ card.flip(); // Show back
206
+ card.flip(false); // Show front
207
+ card.toggle(); // Switch sides
208
+ card.update({ Effect: 'fade' }); // Change options in place
209
+ card.getState(); // { flipped, destroyed, effect }
210
+ card.destroy(); // Remove listeners and restore the element
211
+ ```
212
+
213
+ | Call | Returns |
214
+ |---|---|
215
+ | `justFlipIt(element, options)` | One controller |
216
+ | `justFlipIt(selectorOrCollection, options)` | An array of controllers |
217
+ | `justFlipIt.get(element)` | Existing controller, or `undefined` |
218
+ | `justFlipIt.destroy(target)` | Destroys matching instances |
219
+
220
+ Repeated initialization returns the existing instance. Use `update()` to change its options. `destroy()` is safe to call more than once. Initialize an HTML container, such as a `div`, rather than a void element such as `img`.
221
+
222
+ ```js
223
+ card.hoverPanel.addEventListener('justflipit:change', ({ detail }) => {
224
+ console.log(detail.flipped, detail.effect);
225
+ });
226
+ ```
227
+
228
+ ## Options
229
+
230
+ | Option | Default | What it controls |
231
+ |---|---|---|
232
+ | `Click` | `false` | Hover by default; `true` for click; a selector or selector array for delegated clicks |
233
+ | `FlipX` | `false` | X-axis rotation instead of Y-axis |
234
+ | `Template` | Clone of original | Trusted HTML for a generated back face |
235
+ | `Duration` | `400` | Non-negative duration in milliseconds |
236
+ | `Easing` | `cubic-bezier(.22,.61,.36,1)` | CSS easing function |
237
+ | `Effect` | `'flip'` | `'flip'`, `'fade'`, or `'scale'` |
238
+ | `Tilt` | `false` | `true`, `'only'`, or `{ max, perspective, scale }` |
239
+ | `ReducedMotion` | `true` | Respect live `prefers-reduced-motion` preferences |
240
+ | `Faces` | Omitted | Existing direct children: `{ front: selector, back: selector }` |
241
+ | `preserveClasses` | `true` | Copy original classes onto a generated container |
242
+ | `preserveStyles` | `true` | Copy original inline styles onto a generated container |
243
+ | `Style` | `[]` | Apply `{ el: 'self' or selector, style: {...} }` entries |
244
+
245
+ <details>
246
+ <summary><strong>Style existing faces and choose a different effect</strong></summary>
247
+
248
+ ```js
249
+ justFlipIt(document.querySelector('.card'), {
250
+ Faces: { front: '[data-front]', back: '[data-back]' },
251
+ Click: true,
252
+ FlipX: true,
253
+ Effect: 'scale',
254
+ Duration: 500,
255
+ Style: [
256
+ { el: '[data-front]', style: { background: '#243b2b', borderRadius: '20px' } },
257
+ { el: '[data-back]', style: { background: '#c1f384', borderRadius: '20px' } },
258
+ ],
259
+ });
131
260
  ```
132
261
 
133
- Actions: `state`, `flip`, `reset`, `toggle`, `setText`. Unknown IDs and actions are
134
- rejected. `setText` uses `textContent` and accepts at most 100000 characters; it never
135
- interprets model output as HTML. In framework mode update application state for
136
- content changes; `setText` is rejected there. This bridge runs alongside the DOM in
137
- the browser; a server-side agent must relay validated tool calls through your app.
262
+ </details>
263
+
264
+ ## Motion and accessibility
265
+
266
+ - **Reversible transitions:** Rapid input reverses the current CSS transition without accumulating animation objects.
267
+ - **Efficient tilt.** Mouse and finger movement share one animation frame. With `Tilt: true` or custom tilt settings, touch tilts the card until release or until the browser takes over a scroll gesture.
268
+ - **Keyboard:** Focus the card, then use Enter or Space to toggle and Escape to reset.
269
+ - **Touch.** Tap to switch sides with any effect. Hover mode distinguishes a tap from a drag, and click mode uses the browser’s native click. Use `Tilt: 'only'` for touch tilt without tap-to-flip.
270
+ - **Hidden faces:** The inactive face is inert and marked `aria-hidden`.
271
+ - **Reduced motion:** System preference changes disable transitions and tilt automatically. Set `ReducedMotion: false` only when your application intentionally overrides this preference.
272
+
273
+ Targets modern browsers with CSS Grid, Pointer Events, and native `inert` support. Add application-level polyfills when targeting older browsers. SSR safety covers importing the package; DOM initialization requires a browser.
274
+
275
+ ## Upgrading from v3
138
276
 
139
- ## Migration from 3.x
277
+ **v4 is a major release.** Check these changes before upgrading an existing integration:
140
278
 
141
- v4 is a major release. Real ESM and CommonJS exports replace the old UMD-only npm
142
- entry. Use a default ESM import or `const justFlipIt = require('@simhub2/justflipit')`.
143
- The optional jQuery plugin registration is removed. Generated faces use
144
- `[data-jfi-face="front"]` and `[data-jfi-face="back"]`; replace `.front`, `.backY`,
145
- and `._flipY_` custom CSS with these attributes and `[data-jfi-flipped="true"]`.
146
- Animation options keep their PascalCase names. Dimensions are responsive; give the
147
- original content explicit sizes if your design requires a fixed card size.
148
- `destroy()` restores plugin-owned attributes from their initial values; avoid managing
149
- those same style/class/accessibility attributes from a framework while mounted.
279
+ | v3 integration | v4 equivalent |
280
+ |---|---|
281
+ | UMD-only npm entry | Default ESM import or callable CommonJS export |
282
+ | Optional jQuery plugin registration | Call `justFlipIt(...)` directly |
283
+ | Separate modern / vanilla / combined implementations | One core; historical import paths and browser filenames remain aliases |
284
+ | `.front` / `.backY` generated face selectors | `[data-jfi-face="front"]` / `[data-jfi-face="back"]` |
285
+ | `._flipY_` / `._flip_` state classes | `[data-jfi-flipped="true"]` |
286
+ | Fixed measured dimensions | Responsive grid; add explicit sizes in your design where needed |
287
+ | Framework DOM replacement | Existing direct-child faces via an adapter or `Faces` |
150
288
 
151
- ## Development and releases
289
+ Option names retain PascalCase. `Faces` cannot change after initialization. Teardown restores plugin-owned attributes to their initial values.
152
290
 
153
- Node.js 22 or newer is used for development.
291
+ See the [changelog](CHANGELOG.md) for the release overview.
292
+
293
+ ## Development
294
+
295
+ Use Node.js 22 or newer.
154
296
 
155
297
  ```sh
156
298
  npm ci
157
- npm run check
158
- npm pack --dry-run
299
+ npm run check # Lifecycle tests and TypeScript consumer checks
300
+ npm run build # ESM, CommonJS, browser bundles, source maps, declarations
301
+ npm pack --dry-run # Inspect published files
159
302
  ```
160
303
 
161
- `npm run build` produces ESM, CommonJS, minified browser bundles, source maps, and
162
- TypeScript declarations from one core. CI runs tests and type checks. A GitHub release
163
- with a `v` version tag runs the npm publishing workflow; configure npm trusted
164
- publishing for this repository/workflow, or publish locally with an authenticated
165
- npm account using `npm publish --access public`. Publish only versions not already
166
- present in the registry.
304
+ Tests cover original node preservation, teardown, delegated events, agent command validation, reduced motion, tilt batching, React StrictMode, Vue updates, and a compiled Svelte 5 component.
305
+
306
+ See the [release guide](https://github.com/SimHub/justFlipIt/blob/main/RELEASING.md) for the GitHub and npm steps.
307
+
308
+ ---
167
309
 
168
- MIT — see [LICENSE](LICENSE).
310
+ <p align="center">
311
+ Built by <a href="https://github.com/SimHub">SimHub</a> · <a href="LICENSE">MIT license</a> · <a href="https://github.com/SimHub/justFlipIt/issues">Report an issue</a> · <a href="https://www.buymeacoffee.com/simhub">Buy me a coffee</a>
312
+ </p>
@@ -28,14 +28,16 @@ var JustFlipItModule = (() => {
28
28
  var css = `
29
29
  ._justFlipIt_panel{position:relative;display:grid;perspective:var(--jfi-perspective,1000px);isolation:isolate}
30
30
  ._justFlipIt_panel>[data-jfi-face]{grid-area:1/1;min-width:0;backface-visibility:hidden;-webkit-backface-visibility:hidden;transition:transform var(--jfi-duration) var(--jfi-easing),opacity var(--jfi-duration) var(--jfi-easing);transform-origin:center}
31
- ._justFlipIt_panel>[data-jfi-face=front]{transform:rotateY(0deg)}
32
- ._justFlipIt_panel>[data-jfi-face=back]{transform:rotateY(-180deg);pointer-events:none}
33
- ._justFlipIt_panel[data-jfi-flipped=true]>[data-jfi-face=front]{transform:rotateY(180deg);pointer-events:none}
34
- ._justFlipIt_panel[data-jfi-flipped=true]>[data-jfi-face=back]{transform:rotateY(0deg);pointer-events:auto}
35
- ._justFlipIt_panel[data-jfi-axis=x]>[data-jfi-face=back]{transform:rotateX(-180deg)}
36
- ._justFlipIt_panel[data-jfi-axis=x][data-jfi-flipped=true]>[data-jfi-face=front]{transform:rotateX(180deg)}
37
- ._justFlipIt_panel[data-jfi-axis=x][data-jfi-flipped=true]>[data-jfi-face=back]{transform:rotateX(0deg)}
38
- ._justFlipIt_panel[data-jfi-effect=fade]>[data-jfi-face],._justFlipIt_panel[data-jfi-effect=scale]>[data-jfi-face]{transform:none}
31
+ ._justFlipIt_panel>[data-jfi-face=back],._justFlipIt_panel[data-jfi-flipped=true]>[data-jfi-face=front]{pointer-events:none}
32
+ ._justFlipIt_panel[data-jfi-flipped=true]>[data-jfi-face=back]{pointer-events:auto}
33
+ ._justFlipIt_panel[data-jfi-effect=flip]>[data-jfi-face=front]{transform:rotateY(0deg)}
34
+ ._justFlipIt_panel[data-jfi-effect=flip]>[data-jfi-face=back]{transform:rotateY(-180deg)}
35
+ ._justFlipIt_panel[data-jfi-effect=flip][data-jfi-flipped=true]>[data-jfi-face=front]{transform:rotateY(180deg)}
36
+ ._justFlipIt_panel[data-jfi-effect=flip][data-jfi-flipped=true]>[data-jfi-face=back]{transform:rotateY(0deg)}
37
+ ._justFlipIt_panel[data-jfi-effect=flip][data-jfi-axis=x]>[data-jfi-face=front]{transform:rotateX(0deg)}
38
+ ._justFlipIt_panel[data-jfi-effect=flip][data-jfi-axis=x]>[data-jfi-face=back]{transform:rotateX(-180deg)}
39
+ ._justFlipIt_panel[data-jfi-effect=flip][data-jfi-axis=x][data-jfi-flipped=true]>[data-jfi-face=front]{transform:rotateX(180deg)}
40
+ ._justFlipIt_panel[data-jfi-effect=flip][data-jfi-axis=x][data-jfi-flipped=true]>[data-jfi-face=back]{transform:rotateX(0deg)}
39
41
  ._justFlipIt_panel[data-jfi-effect=fade]>[data-jfi-face=back],._justFlipIt_panel[data-jfi-effect=scale]>[data-jfi-face=back]{opacity:0}
40
42
  ._justFlipIt_panel[data-jfi-effect=fade][data-jfi-flipped=true]>[data-jfi-face=front],._justFlipIt_panel[data-jfi-effect=scale][data-jfi-flipped=true]>[data-jfi-face=front]{opacity:0}
41
43
  ._justFlipIt_panel[data-jfi-effect=fade][data-jfi-flipped=true]>[data-jfi-face=back],._justFlipIt_panel[data-jfi-effect=scale][data-jfi-flipped=true]>[data-jfi-face=back]{opacity:1}
@@ -163,33 +165,63 @@ var JustFlipItModule = (() => {
163
165
  this.listen("pointerenter", (event) => {
164
166
  if (!this.settings.Click && this.settings.Tilt !== "only" && event.pointerType !== "touch") this.flip(true);
165
167
  });
166
- this.listen("pointerleave", () => {
168
+ this.listen("pointerleave", (event) => {
169
+ if (event.pointerType === "touch") return;
167
170
  this.resetTilt();
168
171
  if (!this.settings.Click && this.settings.Tilt !== "only") this.flip(false);
169
172
  });
170
- this.listen("pointercancel", () => this.resetTilt());
171
- this.listen("pointerup", (event) => {
172
- if (event.pointerType === "touch") {
173
- this.resetTilt();
174
- if (!this.settings.Click && this.settings.Tilt !== "only") this.toggle();
175
- }
176
- });
173
+ this.listen("pointerdown", (event) => {
174
+ if (event.pointerType !== "touch" || event.isPrimary === false || this.touch) return;
175
+ this.resetTilt();
176
+ this.touch = { id: event.pointerId, x: event.clientX, y: event.clientY, moved: false };
177
+ this.queueTilt(event, this.touch);
178
+ }, { passive: true });
177
179
  this.listen("pointermove", (event) => {
178
- if (!this.settings.Tilt || this.reduced || event.pointerType === "touch") return;
179
- this.point = [event.clientX, event.clientY];
180
- if (this.frame) return;
181
- this.frame = this.win.requestAnimationFrame(() => {
182
- this.frame = 0;
183
- const rect = this.hoverPanel.getBoundingClientRect();
184
- if (!rect.width || !rect.height) return;
185
- const t = { max: 10, perspective: 1e3, scale: 1.03, ...typeof this.settings.Tilt === "object" ? this.settings.Tilt : {} };
186
- const x = Math.max(-1, Math.min(1, (this.point[0] - rect.left) / rect.width * 2 - 1));
187
- const y = Math.max(-1, Math.min(1, (this.point[1] - rect.top) / rect.height * 2 - 1));
188
- this.hoverPanel.style.transform = `perspective(${t.perspective}px) rotateX(${-y * t.max}deg) rotateY(${x * t.max}deg) scale(${t.scale})`;
189
- });
180
+ if (event.pointerType !== "touch") {
181
+ if (!this.touch) this.queueTilt(event);
182
+ return;
183
+ }
184
+ const touch = this.touch;
185
+ if (!touch || touch.id !== event.pointerId) return;
186
+ touch.moved || (touch.moved = this.touchMoved(event, touch));
187
+ this.queueTilt(event, touch);
190
188
  }, { passive: true });
189
+ this.listen("pointerup", (event) => {
190
+ const touch = this.finishTouch(event);
191
+ if (!touch || touch.moved || this.touchMoved(event, touch)) return;
192
+ if (!this.settings.Click && this.settings.Tilt !== "only") this.toggle();
193
+ });
194
+ this.listen("pointercancel", (event) => {
195
+ if (event.pointerType === "touch") this.finishTouch(event);
196
+ else this.resetTilt();
197
+ });
198
+ this.listen("lostpointercapture", (event) => this.finishTouch(event));
191
199
  this.baseTransform = this.hoverPanel.style.transform;
192
200
  }
201
+ touchMoved(event, touch) {
202
+ return Math.hypot(event.clientX - touch.x, event.clientY - touch.y) > 10;
203
+ }
204
+ finishTouch(event) {
205
+ const touch = this.touch;
206
+ if (event.pointerType !== "touch" || !touch || touch.id !== event.pointerId) return;
207
+ this.touch = null;
208
+ this.resetTilt();
209
+ return touch;
210
+ }
211
+ queueTilt(event, touch) {
212
+ if (!this.settings.Tilt || this.reduced) return;
213
+ this.point = [event.clientX, event.clientY];
214
+ if (this.frame) return;
215
+ this.frame = this.win.requestAnimationFrame(() => {
216
+ this.frame = 0;
217
+ const rect = touch ? touch.rect ?? (touch.rect = this.hoverPanel.getBoundingClientRect()) : this.hoverPanel.getBoundingClientRect();
218
+ if (!rect.width || !rect.height) return;
219
+ const { max = 10, perspective = 1e3, scale = 1.03 } = this.settings.Tilt;
220
+ const x = Math.max(-1, Math.min(1, (this.point[0] - rect.left) / rect.width * 2 - 1));
221
+ const y = Math.max(-1, Math.min(1, (this.point[1] - rect.top) / rect.height * 2 - 1));
222
+ this.hoverPanel.style.transform = `perspective(${perspective}px) rotateX(${-y * max}deg) rotateY(${x * max}deg) scale(${scale})`;
223
+ });
224
+ }
193
225
  resetTilt() {
194
226
  if (this.frame) this.win.cancelAnimationFrame(this.frame);
195
227
  this.frame = 0;
@@ -1,14 +1,16 @@
1
- var JustFlipItModule=(()=>{var p=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var b=Object.prototype.hasOwnProperty;var T=(i,t)=>{for(var e in t)p(i,e,{get:t[e],enumerable:!0})},w=(i,t,e,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of v(t))!b.call(i,s)&&s!==e&&p(i,s,{get:()=>t[s],enumerable:!(a=g(t,s))||a.enumerable});return i};var _=i=>w(p({},"__esModule",{value:!0}),i);var x={};T(x,{FlipController:()=>o,default:()=>c});var n=new WeakMap,k={Click:!1,FlipX:!1,Duration:400,Easing:"cubic-bezier(.22,.61,.36,1)",Tilt:!1,Effect:"flip",ReducedMotion:!0,preserveClasses:!0,preserveStyles:!0,Style:[]},E=`
1
+ var JustFlipItModule=(()=>{var d=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var b=(i,t)=>{for(var e in t)d(i,e,{get:t[e],enumerable:!0})},_=(i,t,e,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of T(t))!v.call(i,s)&&s!==e&&d(i,s,{get:()=>t[s],enumerable:!(a=g(t,s))||a.enumerable});return i};var w=i=>_(d({},"__esModule",{value:!0}),i);var I={};b(I,{FlipController:()=>c,default:()=>h});var n=new WeakMap,k={Click:!1,FlipX:!1,Duration:400,Easing:"cubic-bezier(.22,.61,.36,1)",Tilt:!1,Effect:"flip",ReducedMotion:!0,preserveClasses:!0,preserveStyles:!0,Style:[]},F=`
2
2
  ._justFlipIt_panel{position:relative;display:grid;perspective:var(--jfi-perspective,1000px);isolation:isolate}
3
3
  ._justFlipIt_panel>[data-jfi-face]{grid-area:1/1;min-width:0;backface-visibility:hidden;-webkit-backface-visibility:hidden;transition:transform var(--jfi-duration) var(--jfi-easing),opacity var(--jfi-duration) var(--jfi-easing);transform-origin:center}
4
- ._justFlipIt_panel>[data-jfi-face=front]{transform:rotateY(0deg)}
5
- ._justFlipIt_panel>[data-jfi-face=back]{transform:rotateY(-180deg);pointer-events:none}
6
- ._justFlipIt_panel[data-jfi-flipped=true]>[data-jfi-face=front]{transform:rotateY(180deg);pointer-events:none}
7
- ._justFlipIt_panel[data-jfi-flipped=true]>[data-jfi-face=back]{transform:rotateY(0deg);pointer-events:auto}
8
- ._justFlipIt_panel[data-jfi-axis=x]>[data-jfi-face=back]{transform:rotateX(-180deg)}
9
- ._justFlipIt_panel[data-jfi-axis=x][data-jfi-flipped=true]>[data-jfi-face=front]{transform:rotateX(180deg)}
10
- ._justFlipIt_panel[data-jfi-axis=x][data-jfi-flipped=true]>[data-jfi-face=back]{transform:rotateX(0deg)}
11
- ._justFlipIt_panel[data-jfi-effect=fade]>[data-jfi-face],._justFlipIt_panel[data-jfi-effect=scale]>[data-jfi-face]{transform:none}
4
+ ._justFlipIt_panel>[data-jfi-face=back],._justFlipIt_panel[data-jfi-flipped=true]>[data-jfi-face=front]{pointer-events:none}
5
+ ._justFlipIt_panel[data-jfi-flipped=true]>[data-jfi-face=back]{pointer-events:auto}
6
+ ._justFlipIt_panel[data-jfi-effect=flip]>[data-jfi-face=front]{transform:rotateY(0deg)}
7
+ ._justFlipIt_panel[data-jfi-effect=flip]>[data-jfi-face=back]{transform:rotateY(-180deg)}
8
+ ._justFlipIt_panel[data-jfi-effect=flip][data-jfi-flipped=true]>[data-jfi-face=front]{transform:rotateY(180deg)}
9
+ ._justFlipIt_panel[data-jfi-effect=flip][data-jfi-flipped=true]>[data-jfi-face=back]{transform:rotateY(0deg)}
10
+ ._justFlipIt_panel[data-jfi-effect=flip][data-jfi-axis=x]>[data-jfi-face=front]{transform:rotateX(0deg)}
11
+ ._justFlipIt_panel[data-jfi-effect=flip][data-jfi-axis=x]>[data-jfi-face=back]{transform:rotateX(-180deg)}
12
+ ._justFlipIt_panel[data-jfi-effect=flip][data-jfi-axis=x][data-jfi-flipped=true]>[data-jfi-face=front]{transform:rotateX(180deg)}
13
+ ._justFlipIt_panel[data-jfi-effect=flip][data-jfi-axis=x][data-jfi-flipped=true]>[data-jfi-face=back]{transform:rotateX(0deg)}
12
14
  ._justFlipIt_panel[data-jfi-effect=fade]>[data-jfi-face=back],._justFlipIt_panel[data-jfi-effect=scale]>[data-jfi-face=back]{opacity:0}
13
15
  ._justFlipIt_panel[data-jfi-effect=fade][data-jfi-flipped=true]>[data-jfi-face=front],._justFlipIt_panel[data-jfi-effect=scale][data-jfi-flipped=true]>[data-jfi-face=front]{opacity:0}
14
16
  ._justFlipIt_panel[data-jfi-effect=fade][data-jfi-flipped=true]>[data-jfi-face=back],._justFlipIt_panel[data-jfi-effect=scale][data-jfi-flipped=true]>[data-jfi-face=back]{opacity:1}
@@ -16,6 +18,6 @@ var JustFlipItModule=(()=>{var p=Object.defineProperty;var g=Object.getOwnProper
16
18
  ._justFlipIt_panel[data-jfi-effect=scale][data-jfi-flipped=true]>[data-jfi-face=back]{transform:scale(1)}
17
19
  ._justFlipIt_panel[data-jfi-reduced=true]>[data-jfi-face]{transition:none!important}
18
20
  @media(prefers-reduced-motion:reduce){._justFlipIt_panel:not([data-jfi-motion=allow])>[data-jfi-face]{transition:none!important}}
19
- `;function F(i){if(i.querySelector('style[data-justflipitstyle="v4"]'))return;let t=i.createElement("style");t.dataset.justflipitstyle="v4",t.textContent=E,i.head.append(t)}function u(i){if(i.Duration!==void 0&&(!Number.isFinite(i.Duration)||i.Duration<0))throw new TypeError("Duration must be a non-negative finite number");if(i.Effect!==void 0&&!["flip","fade","scale"].includes(i.Effect))throw new TypeError("Unknown Effect");if(i.Tilt&&typeof i.Tilt=="object"){for(let t of["max","perspective","scale"])if(i.Tilt[t]!==void 0&&(!Number.isFinite(i.Tilt[t])||i.Tilt[t]<0))throw new TypeError(`Invalid Tilt.${t}`)}}var o=class{constructor(t,e={}){if(u(e),!t||t.nodeType!==1)throw new TypeError("Expected an HTML element");this.element=t,this.settings={...k,...e},this.flipped=!1,this.destroyed=!1,this.cleanups=[],this.restores=[],this.win=t.ownerDocument.defaultView;let a=t.ownerDocument;if(F(a),this.managed=!!e.Faces,this.managed){if(this.hoverPanel=t,this.frontWrapper=t.querySelector(e.Faces.front),this.backWrapper=t.querySelector(e.Faces.back),!this.frontWrapper||!this.backWrapper||this.frontWrapper===this.backWrapper||this.frontWrapper.parentNode!==t||this.backWrapper.parentNode!==t)throw new TypeError("Faces must select two distinct direct children")}else{if(!t.parentNode)throw new TypeError("Element must have a parent");if(this.hoverPanel=a.createElement("div"),this.frontWrapper=a.createElement("div"),this.backWrapper=a.createElement("div"),this.settings.preserveClasses&&(this.hoverPanel.className=t.className),this.settings.preserveStyles&&(this.hoverPanel.style.cssText=t.style.cssText),t.before(this.hoverPanel),this.hoverPanel.append(this.frontWrapper,this.backWrapper),this.frontWrapper.append(t),e.Template!==void 0)this.backWrapper.innerHTML=e.Template;else{let r=t.cloneNode(!0);r.removeAttribute("id"),r.querySelectorAll("[id]").forEach(d=>d.removeAttribute("id")),this.backWrapper.append(r)}}let s=this.hoverPanel;for(let r of[s,this.frontWrapper,this.backWrapper]){let m=(r===s?["class","style","tabindex","data-jfi-axis","data-jfi-effect","data-jfi-motion","data-jfi-flipped","data-jfi-reduced"]:["style","data-jfi-face","aria-hidden","inert"]).map(l=>[l,r.getAttribute(l)]),y=r.inert||!1;this.restores.push(()=>{r.inert=y;for(let[l,h]of m)h===null?r.removeAttribute(l):r.setAttribute(l,h)})}s.classList.add("_justFlipIt_panel"),this.frontWrapper.dataset.jfiFace="front",this.backWrapper.dataset.jfiFace="back",this.motion=this.win.matchMedia?.("(prefers-reduced-motion: reduce)");let f=()=>{this.resetTilt(),this.render()};this.motion?.addEventListener("change",f),this.cleanups.push(()=>this.motion?.removeEventListener("change",f)),this.configure(),this.events(),this.render(),n.set(t,this),n.set(s,this)}listen(t,e,a){this.hoverPanel.addEventListener(t,e,a),this.cleanups.push(()=>this.hoverPanel.removeEventListener(t,e,a))}get reduced(){return this.settings.ReducedMotion!==!1&&!!this.motion?.matches}configure(){let t=this.settings,e=this.hoverPanel;e.style.setProperty("--jfi-duration",`${t.Duration}ms`),e.style.setProperty("--jfi-easing",t.Easing),e.dataset.jfiAxis=t.FlipX?"x":"y",e.dataset.jfiEffect=t.Effect,e.dataset.jfiMotion=t.ReducedMotion===!1?"allow":"respect";for(let a of t.Style||[])(a.el==="self"?[e]:e.querySelectorAll(a.el)).forEach(f=>Object.assign(f.style,a.style));t.Tilt!=="only"&&!e.hasAttribute("tabindex")&&(e.tabIndex=0)}events(){this.listen("click",t=>{if(this.settings.Tilt==="only")return;let e=this.settings.Click;(e===!0||e&&[].concat(e).some(a=>t.target.closest?.(a)&&this.hoverPanel.contains(t.target.closest(a))))&&this.toggle()}),this.listen("keydown",t=>{t.target!==this.hoverPanel||this.settings.Tilt==="only"||((t.key==="Enter"||t.key===" ")&&(t.preventDefault(),this.toggle()),t.key==="Escape"&&this.flip(!1))}),this.listen("pointerenter",t=>{!this.settings.Click&&this.settings.Tilt!=="only"&&t.pointerType!=="touch"&&this.flip(!0)}),this.listen("pointerleave",()=>{this.resetTilt(),!this.settings.Click&&this.settings.Tilt!=="only"&&this.flip(!1)}),this.listen("pointercancel",()=>this.resetTilt()),this.listen("pointerup",t=>{t.pointerType==="touch"&&(this.resetTilt(),!this.settings.Click&&this.settings.Tilt!=="only"&&this.toggle())}),this.listen("pointermove",t=>{!this.settings.Tilt||this.reduced||t.pointerType==="touch"||(this.point=[t.clientX,t.clientY],!this.frame&&(this.frame=this.win.requestAnimationFrame(()=>{this.frame=0;let e=this.hoverPanel.getBoundingClientRect();if(!e.width||!e.height)return;let a={max:10,perspective:1e3,scale:1.03,...typeof this.settings.Tilt=="object"?this.settings.Tilt:{}},s=Math.max(-1,Math.min(1,(this.point[0]-e.left)/e.width*2-1)),f=Math.max(-1,Math.min(1,(this.point[1]-e.top)/e.height*2-1));this.hoverPanel.style.transform=`perspective(${a.perspective}px) rotateX(${-f*a.max}deg) rotateY(${s*a.max}deg) scale(${a.scale})`})))},{passive:!0}),this.baseTransform=this.hoverPanel.style.transform}resetTilt(){this.frame&&this.win.cancelAnimationFrame(this.frame),this.frame=0,this.hoverPanel.style.transform=this.baseTransform||""}render(){this.hoverPanel.dataset.jfiFlipped=String(this.flipped),this.hoverPanel.dataset.jfiReduced=String(this.reduced);for(let[t,e]of[[this.frontWrapper,this.flipped],[this.backWrapper,!this.flipped]])t.setAttribute("aria-hidden",String(e)),t.inert=e}flip(t=!0){if(this.destroyed||this.flipped===!!t)return this;let e=this.element.ownerDocument.activeElement;return(t?this.frontWrapper:this.backWrapper).contains(e)&&this.hoverPanel.focus({preventScroll:!0}),this.flipped=!!t,this.render(),this.hoverPanel.dispatchEvent(new this.win.CustomEvent("justflipit:change",{bubbles:!0,detail:this.getState()})),this}toggle(){return this.flip(!this.flipped)}update(t={}){if(this.destroyed)return this;if("Faces"in t)throw new TypeError("Faces cannot change after initialization");if(u(t),this.managed&&"Template"in t)throw new TypeError("Render framework face content with the framework");if(this.settings={...this.settings,...t},"Template"in t){if(this.managed)throw new TypeError("Render framework face content with the framework");this.backWrapper.innerHTML=t.Template}return this.resetTilt(),this.configure(),this.render(),this}getState(){return{flipped:this.flipped,destroyed:this.destroyed,effect:this.settings.Effect}}destroy(){this.destroyed||(this.resetTilt(),this.cleanups.forEach(t=>t()),this.restores.forEach(t=>t()),this.managed||this.hoverPanel.replaceWith(this.element),n.delete(this.element),n.delete(this.hoverPanel),this.destroyed=!0)}};function j(i){return typeof i=="string"?typeof document>"u"?[]:Array.from(document.querySelectorAll(i)):i?.nodeType===1?[i]:i?Array.from(i):[]}function c(i,t){let e=j(i).map(a=>n.get(a)||new o(a,t));return i?.nodeType===1?e[0]:e}c.destroy=i=>j(i).forEach(t=>n.get(t)?.destroy());c.get=i=>n.get(i);return _(x);})();
21
+ `;function E(i){if(i.querySelector('style[data-justflipitstyle="v4"]'))return;let t=i.createElement("style");t.dataset.justflipitstyle="v4",t.textContent=F,i.head.append(t)}function j(i){if(i.Duration!==void 0&&(!Number.isFinite(i.Duration)||i.Duration<0))throw new TypeError("Duration must be a non-negative finite number");if(i.Effect!==void 0&&!["flip","fade","scale"].includes(i.Effect))throw new TypeError("Unknown Effect");if(i.Tilt&&typeof i.Tilt=="object"){for(let t of["max","perspective","scale"])if(i.Tilt[t]!==void 0&&(!Number.isFinite(i.Tilt[t])||i.Tilt[t]<0))throw new TypeError(`Invalid Tilt.${t}`)}}var c=class{constructor(t,e={}){if(j(e),!t||t.nodeType!==1)throw new TypeError("Expected an HTML element");this.element=t,this.settings={...k,...e},this.flipped=!1,this.destroyed=!1,this.cleanups=[],this.restores=[],this.win=t.ownerDocument.defaultView;let a=t.ownerDocument;if(E(a),this.managed=!!e.Faces,this.managed){if(this.hoverPanel=t,this.frontWrapper=t.querySelector(e.Faces.front),this.backWrapper=t.querySelector(e.Faces.back),!this.frontWrapper||!this.backWrapper||this.frontWrapper===this.backWrapper||this.frontWrapper.parentNode!==t||this.backWrapper.parentNode!==t)throw new TypeError("Faces must select two distinct direct children")}else{if(!t.parentNode)throw new TypeError("Element must have a parent");if(this.hoverPanel=a.createElement("div"),this.frontWrapper=a.createElement("div"),this.backWrapper=a.createElement("div"),this.settings.preserveClasses&&(this.hoverPanel.className=t.className),this.settings.preserveStyles&&(this.hoverPanel.style.cssText=t.style.cssText),t.before(this.hoverPanel),this.hoverPanel.append(this.frontWrapper,this.backWrapper),this.frontWrapper.append(t),e.Template!==void 0)this.backWrapper.innerHTML=e.Template;else{let r=t.cloneNode(!0);r.removeAttribute("id"),r.querySelectorAll("[id]").forEach(l=>l.removeAttribute("id")),this.backWrapper.append(r)}}let s=this.hoverPanel;for(let r of[s,this.frontWrapper,this.backWrapper]){let p=(r===s?["class","style","tabindex","data-jfi-axis","data-jfi-effect","data-jfi-motion","data-jfi-flipped","data-jfi-reduced"]:["style","data-jfi-face","aria-hidden","inert"]).map(o=>[o,r.getAttribute(o)]),y=r.inert||!1;this.restores.push(()=>{r.inert=y;for(let[o,u]of p)u===null?r.removeAttribute(o):r.setAttribute(o,u)})}s.classList.add("_justFlipIt_panel"),this.frontWrapper.dataset.jfiFace="front",this.backWrapper.dataset.jfiFace="back",this.motion=this.win.matchMedia?.("(prefers-reduced-motion: reduce)");let f=()=>{this.resetTilt(),this.render()};this.motion?.addEventListener("change",f),this.cleanups.push(()=>this.motion?.removeEventListener("change",f)),this.configure(),this.events(),this.render(),n.set(t,this),n.set(s,this)}listen(t,e,a){this.hoverPanel.addEventListener(t,e,a),this.cleanups.push(()=>this.hoverPanel.removeEventListener(t,e,a))}get reduced(){return this.settings.ReducedMotion!==!1&&!!this.motion?.matches}configure(){let t=this.settings,e=this.hoverPanel;e.style.setProperty("--jfi-duration",`${t.Duration}ms`),e.style.setProperty("--jfi-easing",t.Easing),e.dataset.jfiAxis=t.FlipX?"x":"y",e.dataset.jfiEffect=t.Effect,e.dataset.jfiMotion=t.ReducedMotion===!1?"allow":"respect";for(let a of t.Style||[])(a.el==="self"?[e]:e.querySelectorAll(a.el)).forEach(f=>Object.assign(f.style,a.style));t.Tilt!=="only"&&!e.hasAttribute("tabindex")&&(e.tabIndex=0)}events(){this.listen("click",t=>{if(this.settings.Tilt==="only")return;let e=this.settings.Click;(e===!0||e&&[].concat(e).some(a=>t.target.closest?.(a)&&this.hoverPanel.contains(t.target.closest(a))))&&this.toggle()}),this.listen("keydown",t=>{t.target!==this.hoverPanel||this.settings.Tilt==="only"||((t.key==="Enter"||t.key===" ")&&(t.preventDefault(),this.toggle()),t.key==="Escape"&&this.flip(!1))}),this.listen("pointerenter",t=>{!this.settings.Click&&this.settings.Tilt!=="only"&&t.pointerType!=="touch"&&this.flip(!0)}),this.listen("pointerleave",t=>{t.pointerType!=="touch"&&(this.resetTilt(),!this.settings.Click&&this.settings.Tilt!=="only"&&this.flip(!1))}),this.listen("pointerdown",t=>{t.pointerType!=="touch"||t.isPrimary===!1||this.touch||(this.resetTilt(),this.touch={id:t.pointerId,x:t.clientX,y:t.clientY,moved:!1},this.queueTilt(t,this.touch))},{passive:!0}),this.listen("pointermove",t=>{if(t.pointerType!=="touch"){this.touch||this.queueTilt(t);return}let e=this.touch;!e||e.id!==t.pointerId||(e.moved||(e.moved=this.touchMoved(t,e)),this.queueTilt(t,e))},{passive:!0}),this.listen("pointerup",t=>{let e=this.finishTouch(t);!e||e.moved||this.touchMoved(t,e)||!this.settings.Click&&this.settings.Tilt!=="only"&&this.toggle()}),this.listen("pointercancel",t=>{t.pointerType==="touch"?this.finishTouch(t):this.resetTilt()}),this.listen("lostpointercapture",t=>this.finishTouch(t)),this.baseTransform=this.hoverPanel.style.transform}touchMoved(t,e){return Math.hypot(t.clientX-e.x,t.clientY-e.y)>10}finishTouch(t){let e=this.touch;if(!(t.pointerType!=="touch"||!e||e.id!==t.pointerId))return this.touch=null,this.resetTilt(),e}queueTilt(t,e){!this.settings.Tilt||this.reduced||(this.point=[t.clientX,t.clientY],!this.frame&&(this.frame=this.win.requestAnimationFrame(()=>{this.frame=0;let a=e?e.rect??(e.rect=this.hoverPanel.getBoundingClientRect()):this.hoverPanel.getBoundingClientRect();if(!a.width||!a.height)return;let{max:s=10,perspective:f=1e3,scale:r=1.03}=this.settings.Tilt,l=Math.max(-1,Math.min(1,(this.point[0]-a.left)/a.width*2-1)),p=Math.max(-1,Math.min(1,(this.point[1]-a.top)/a.height*2-1));this.hoverPanel.style.transform=`perspective(${f}px) rotateX(${-p*s}deg) rotateY(${l*s}deg) scale(${r})`})))}resetTilt(){this.frame&&this.win.cancelAnimationFrame(this.frame),this.frame=0,this.hoverPanel.style.transform=this.baseTransform||""}render(){this.hoverPanel.dataset.jfiFlipped=String(this.flipped),this.hoverPanel.dataset.jfiReduced=String(this.reduced);for(let[t,e]of[[this.frontWrapper,this.flipped],[this.backWrapper,!this.flipped]])t.setAttribute("aria-hidden",String(e)),t.inert=e}flip(t=!0){if(this.destroyed||this.flipped===!!t)return this;let e=this.element.ownerDocument.activeElement;return(t?this.frontWrapper:this.backWrapper).contains(e)&&this.hoverPanel.focus({preventScroll:!0}),this.flipped=!!t,this.render(),this.hoverPanel.dispatchEvent(new this.win.CustomEvent("justflipit:change",{bubbles:!0,detail:this.getState()})),this}toggle(){return this.flip(!this.flipped)}update(t={}){if(this.destroyed)return this;if("Faces"in t)throw new TypeError("Faces cannot change after initialization");if(j(t),this.managed&&"Template"in t)throw new TypeError("Render framework face content with the framework");if(this.settings={...this.settings,...t},"Template"in t){if(this.managed)throw new TypeError("Render framework face content with the framework");this.backWrapper.innerHTML=t.Template}return this.resetTilt(),this.configure(),this.render(),this}getState(){return{flipped:this.flipped,destroyed:this.destroyed,effect:this.settings.Effect}}destroy(){this.destroyed||(this.resetTilt(),this.cleanups.forEach(t=>t()),this.restores.forEach(t=>t()),this.managed||this.hoverPanel.replaceWith(this.element),n.delete(this.element),n.delete(this.hoverPanel),this.destroyed=!0)}};function m(i){return typeof i=="string"?typeof document>"u"?[]:Array.from(document.querySelectorAll(i)):i?.nodeType===1?[i]:i?Array.from(i):[]}function h(i,t){let e=m(i).map(a=>n.get(a)||new c(a,t));return i?.nodeType===1?e[0]:e}h.destroy=i=>m(i).forEach(t=>n.get(t)?.destroy());h.get=i=>n.get(i);return w(I);})();
20
22
  globalThis.justFlipIt = JustFlipItModule.default;
21
23
  //# sourceMappingURL=justFlipIt.min.js.map