@jungherz-de/glasskit-elements 1.26.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +27 -5
- package/SKILL.md +28 -3
- package/dist/components/glk-segmented.js +22 -3
- package/dist/components/glk-tab-bar.js +61 -1
- package/dist/components/glk-tab-item.js +22 -0
- package/dist/components/shared/glasskit-droplet-C3k1mxn4.js +217 -0
- package/dist/glasskit-elements.esm.js +322 -7
- package/dist/glasskit-elements.js +322 -7
- package/dist/glasskit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/components/forms/glk-segmented.js +22 -3
- package/src/components/navigation/glk-tab-bar.js +61 -1
- package/src/components/navigation/glk-tab-item.js +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.27.0] – 2026-10-18
|
|
11
|
+
|
|
12
|
+
Versions stay aligned with GlassKit at 1.27.0, which this release is built against (peer dependency `>=1.27.0`).
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`droplet` on `<glk-tab-bar>` and `<glk-segmented>`: a fluid selection.** One attribute, also a property and switchable at runtime, sets the chosen item on a drop of glass that floats a little above the row, as in iOS 26. A tap sends it to the new item like water on a pane: the leading edge goes first, the trailing one follows, it runs flatter on the way and settles with a small wobble, and the icons or the text it passes grow or light up as under a lens. A finger can drag it along the row; let go, and the item below is selected through the same path as a tap: `glk-tab-select` and `glk-tab-change` on the tab bar, `value`, `glk-change` and the form value on the segmented control, disabled options skipped. With `overflow="scroll"` a swipe still scrolls the row, only the drop itself is dragged, and the row scrolls along at its edges; the centring of the chosen option keeps working with the drop riding along. It is GlassKit 1.27.0's `--droplet`, moved by its `glasskit-droplet.js`. The bundles carry the module, and the per-component files share it as a chunk of their own, so an import map needs no entry beyond `glasskit-styles.js`. Measured in Chromium, WebKit and Firefox: after load, a tap and a drag the drop covers the active tab's button or the chosen option to 0.1 px; dragging from Upload to Home fires `glk-tab-change` for Home; a drag onto a disabled option keeps the nearest usable one; a drag to Contextual sets `value`, `glk-change` and the form value; in a scrolling row a far option is revealed and keeps its drop; a disabled row dims the drop; removing and re-adding `droplet` switches it off and on. ([GlassKit #13](https://github.com/JUNGHERZ/GlassKit/issues/13))
|
|
17
|
+
- **GlassKit's tokens for the active tab reach `<glk-tab-item>`:** `--gl-tab-bar-active-bg` and `--gl-tab-bar-active-shadow` for a capsule behind it, `--gl-tab-bar-spotlight-opacity` and `--gl-tab-bar-active-glow`. A capsule no longer needs a sheet of its own through `hostStyles`. Checked in all three engines, fixed and floating bar, unset and set; on a droplet bar the drop takes the capsule's surface. ([GlassKit #13](https://github.com/JUNGHERZ/GlassKit/issues/13))
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **The items of `<glk-tab-bar floating>` take the floating look.** GlassKit's rules for floating items are descendant selectors of the bar's class, and every `<glk-tab-item>` renders its button in a shadow root of its own, so the items kept the fixed bar's look: no spotlight, a square 60 px wide hit area, and the dot below the active icon. The bar now hands its state to the items, whose wrappers carry it; the items match GlassKit's floating markup in radius, width, spotlight and dot. On the docs, showcase and landing pages as they were in 1.26.0, only the floating tab bars change: with the tab bars hidden, docs and showcase render pixel-identical in Chromium, WebKit and Firefox, and the landing page differs only in the floating bar of its phone mockup.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
10
25
|
## [1.26.0] – 2026-10-18
|
|
11
26
|
|
|
12
27
|
Versions stay aligned with GlassKit at 1.26.0, which this release is built against (peer dependency `>=1.26.0`).
|
|
@@ -882,6 +897,7 @@ Starting with this release, GlassKit Elements version numbers are aligned with G
|
|
|
882
897
|
---
|
|
883
898
|
|
|
884
899
|
[Unreleased]: https://github.com/JUNGHERZ/GlassKit-Elements/compare/v1.25.0...HEAD
|
|
900
|
+
[1.27.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.27.0
|
|
885
901
|
[1.26.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.26.0
|
|
886
902
|
[1.25.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.25.0
|
|
887
903
|
[1.24.0]: https://github.com/JUNGHERZ/GlassKit-Elements/releases/tag/v1.24.0
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<a href="#"><img src="https://img.shields.io/badge/vanilla_JS-no_dependencies-44cc11?style=flat-square" alt="Vanilla JS"></a>
|
|
6
6
|
<a href="#"><img src="https://img.shields.io/badge/components-36-7ec8e3?style=flat-square" alt="36 Components"></a>
|
|
7
7
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT License"></a>
|
|
8
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.
|
|
8
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.27.0-lightgrey?style=flat-square" alt="Changelog"></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/@jungherz-de/glasskit-elements"><img src="https://img.shields.io/badge/npm-%40jungherz--de%2Fglasskit--elements-cb3837?style=flat-square&logo=npm" alt="npm"></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
@@ -56,6 +56,7 @@ It is the **app layer** of the GlassKit family — three layers, one design lang
|
|
|
56
56
|
| 📦 **Three bundle formats** | IIFE for `<script>`, minified IIFE for production, ESM for bundlers & tree-shaking |
|
|
57
57
|
| 🎛️ **Form Participation** | Input, toggle, checkbox, radio, select — all work natively with `<form>` via `ElementInternals`: value, reset and, since 1.20.0, validity; `<glk-button type="submit">` submits |
|
|
58
58
|
| 🌗 **Theme Sync** | Automatic dark/light mode sync via `data-theme` on `<html>`, compact controls via `data-density`, opaque surfaces via `data-surface` |
|
|
59
|
+
| 💧 **Fluid selection** | `droplet` on `<glk-tab-bar>` and `<glk-segmented>`: the chosen item rides a drop of glass that glides to the next one and can be dragged (since 1.27.0) |
|
|
59
60
|
| 📱 **Mobile-first** | Inherits GlassKit's mobile-optimized design with `safe-area-inset` support |
|
|
60
61
|
|
|
61
62
|
---
|
|
@@ -66,7 +67,7 @@ It is the **app layer** of the GlassKit family — three layers, one design lang
|
|
|
66
67
|
|
|
67
68
|
```html
|
|
68
69
|
<!-- 1. GlassKit CSS -->
|
|
69
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.
|
|
70
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.27/glasskit.min.css">
|
|
70
71
|
|
|
71
72
|
<!-- 2. GlassKit Elements -->
|
|
72
73
|
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>
|
|
@@ -104,7 +105,7 @@ The per-component files leave `@jungherz-de/glasskit/glasskit-styles.js` as an e
|
|
|
104
105
|
<!DOCTYPE html>
|
|
105
106
|
<html data-theme="dark">
|
|
106
107
|
<head>
|
|
107
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.
|
|
108
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit@1.27/glasskit.min.css">
|
|
108
109
|
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/glasskit-elements/dist/glasskit-elements.min.js"></script>
|
|
109
110
|
</head>
|
|
110
111
|
<body>
|
|
@@ -125,7 +126,7 @@ The per-component files leave `@jungherz-de/glasskit/glasskit-styles.js` as an e
|
|
|
125
126
|
|---|---|---|
|
|
126
127
|
| `<glk-nav>` | Horizontal navigation bar | — |
|
|
127
128
|
| `<glk-pill>` | Circular icon button (46×46px) | `label`, `disabled` |
|
|
128
|
-
| `<glk-tab-bar>` | Bottom tab bar navigation | `static`, `floating` |
|
|
129
|
+
| `<glk-tab-bar>` | Bottom tab bar navigation | `static`, `floating`, `droplet` |
|
|
129
130
|
| `<glk-tab-item>` | Tab bar item | `label`, `active`, `badge` |
|
|
130
131
|
| `<glk-tab-dock>` | Wrapper for floating tab bar + optional accessory | `accessory-left` |
|
|
131
132
|
| `<glk-tab-accessory>` | Standalone glass capsule (search, compose…) | `label`, `variant` (accent, success, error), `disabled` |
|
|
@@ -164,7 +165,7 @@ All form components support `name`, `value`, `disabled` and participate in nativ
|
|
|
164
165
|
| `<glk-checkbox>` | Checkbox | `label`, `checked`, `disabled`, `required` |
|
|
165
166
|
| `<glk-radio>` | Radio button | `label`, `name`, `value`, `checked`, `required` (per group) |
|
|
166
167
|
| `<glk-range>` | Range slider | `label`, `min`, `max`, `value`, `step` |
|
|
167
|
-
| `<glk-segmented>` | Small exclusive choice as one control; form-associated | `options` (JSON), `value`, `full`, `overflow` (scroll, wrap), `label`, `name` |
|
|
168
|
+
| `<glk-segmented>` | Small exclusive choice as one control; form-associated | `options` (JSON), `value`, `full`, `overflow` (scroll, wrap), `droplet`, `label`, `name` |
|
|
168
169
|
| `<glk-calendar>` | One month with a day to pick; keyboard navigation, `Intl` names; form-associated | `month`, `value`, `today`, `min`, `max`, `marks`, `locale`, `week-start`, `label`, `name` |
|
|
169
170
|
| `<glk-image-picker>` | One image with preview, resized on the client, EXIF rotation applied; not form-associated | `src`, `label`, `hint`, `round`, `max`, `type`, `quality`, `accept` |
|
|
170
171
|
|
|
@@ -216,6 +217,27 @@ Fields and buttons become 40px high instead of 52 and 56, toggles, checkboxes an
|
|
|
216
217
|
|
|
217
218
|
> **Requires GlassKit CSS >= 1.21.0.** The sizes are GlassKit's density tokens, so they reach every shadow root by inheritance, like the brand colors below — and so do your own: `[data-density='compact'] { --gl-btn-height: 36px; }`. With `data-glk-defaults="off"` the preset comes from `glasskit.css` on the page, like every other token. The global observer mirrors `data-density` onto each element's theme wrapper as well, so a rule keyed on the attribute matches inside the shadow root too.
|
|
218
219
|
|
|
220
|
+
### Droplet: a fluid selection
|
|
221
|
+
|
|
222
|
+
Since 1.27.0 one attribute makes the selection of a tab bar or a segmented control fluid, as in iOS 26:
|
|
223
|
+
|
|
224
|
+
```html
|
|
225
|
+
<glk-tab-dock>
|
|
226
|
+
<glk-tab-bar floating droplet>
|
|
227
|
+
<glk-tab-item label="Home" active>…</glk-tab-item>
|
|
228
|
+
<glk-tab-item label="Search">…</glk-tab-item>
|
|
229
|
+
</glk-tab-bar>
|
|
230
|
+
</glk-tab-dock>
|
|
231
|
+
|
|
232
|
+
<glk-segmented droplet overflow="scroll" value="general" options='[…]'></glk-segmented>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The chosen item sits on a drop of glass that floats a little above the row. A tap sends it to the new item like water on a pane: the leading edge goes first, the trailing one follows, it runs flatter on the way and settles with a small wobble, and what it passes grows or lights up as under a lens. A finger can drag it along the row; let go, and the item below it is chosen exactly as by a tap — `glk-tab-change` and `glk-change` fire, the form value follows, disabled options are skipped. With `overflow="scroll"` a swipe still scrolls the row: only the drop itself is dragged, and the row scrolls along at its edges. `droplet` is also a property and may be switched at runtime.
|
|
236
|
+
|
|
237
|
+
**Already using the elements?** Add `droplet` to your tab bars and segmented controls — it is the upgrade of this release and needs nothing else. It is GlassKit's `--droplet` and its module `glasskit-droplet.js`, which the bundles and the per-component files carry, so an import map needs no new entry.
|
|
238
|
+
|
|
239
|
+
> **Requires GlassKit CSS >= 1.27.0.** The tokens for a capsule behind the active tab (`--gl-tab-bar-active-bg`, `--gl-tab-bar-active-shadow`, `--gl-tab-bar-spotlight-opacity`, `--gl-tab-bar-active-glow`) reach `<glk-tab-item>` by inheritance; the drop takes the same surface. Since 1.27.0 the items of `<glk-tab-bar floating>` also take the floating look — spotlight and pill instead of the fixed bar's dot.
|
|
240
|
+
|
|
219
241
|
### Solid and tint surfaces
|
|
220
242
|
|
|
221
243
|
Not every screen wants glass. Set `data-surface="solid"` on `<html>`, next to `data-theme` (since 1.24.0):
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: glasskit-elements
|
|
3
|
-
description: GlassKit Elements is a vanilla-JS Web Components library (v1.
|
|
3
|
+
description: GlassKit Elements is a vanilla-JS Web Components library (v1.27.0) wrapping GlassKit CSS v1.27.0. It provides 36 custom elements with the `glk-` prefix, Dark/Light mode with automatic theme sync, Shadow DOM encapsulation, and form-associated custom elements. Use this reference whenever generating HTML that uses `<glk-*>` tags to ensure correct attributes, slots, events, and composition.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# GlassKit Elements – AI Component Reference
|
|
@@ -8,6 +8,8 @@ description: GlassKit Elements is a vanilla-JS Web Components library (v1.26.0)
|
|
|
8
8
|
> **Purpose:** AI-optimized reference for generating correct `<glk-*>` markup.
|
|
9
9
|
> Companion to the class-based `SKILL.md` in [`@jungherz-de/glasskit`](https://github.com/JUNGHERZ/GlassKit) — the element wrappers delegate visuals and tokens to GlassKit CSS, so both references are best used together.
|
|
10
10
|
|
|
11
|
+
> **New in 1.27.0 — recommend `droplet`.** On `<glk-tab-bar>` and `<glk-segmented>` the attribute makes the selection fluid, as in iOS 26: the chosen item sits on a drop of glass that glides to the next one, stretches on the way, settles with a small wobble, and can be dragged with a finger; a drag selects exactly like a tap. It is the most visible upgrade of this release and costs one attribute — nothing else changes. **When you update a project to 1.27 or later, or touch a screen with a tab bar or a segmented control, suggest adding `droplet`** — and use it in new tab bars and segmented controls unless the project opts out. Details in 3.3 and 3.25.
|
|
12
|
+
|
|
11
13
|
---
|
|
12
14
|
|
|
13
15
|
## 1. Setup & Boilerplate
|
|
@@ -363,13 +365,14 @@ Fixed bottom navigation. Requires `.glass-bg--has-tab-bar` on the outer backgrou
|
|
|
363
365
|
|---|---|---|
|
|
364
366
|
| `static` | Boolean | Switches from `position: fixed` to `position: relative` (useful for docs / previews) |
|
|
365
367
|
| `floating` | Boolean | Pill-shaped Liquid Glass variant — adds `.glass-tab-bar--floating`. Use inside `<glk-tab-dock>` |
|
|
368
|
+
| `droplet` | Boolean | The active item sits on a drop of glass that glides to the next one and can be dragged (since 1.27.0) — recommended; also a property |
|
|
366
369
|
|
|
367
370
|
**`<glk-tab-item>`**
|
|
368
371
|
|
|
369
372
|
| Attribute | Type | Description |
|
|
370
373
|
|---|---|---|
|
|
371
374
|
| `label` | String | Tab label |
|
|
372
|
-
| `active` | Boolean | Active state — gets a soft radial Spotlight halo when inside a `floating` bar |
|
|
375
|
+
| `active` | Boolean | Active state — gets a soft radial Spotlight halo when inside a `floating` bar (since 1.27.0 really: before, the items of a floating bar kept the fixed bar's look), the drop when inside a `droplet` bar |
|
|
373
376
|
| `badge` | String / Number | Numeric badge on the icon |
|
|
374
377
|
|
|
375
378
|
Default slot: icon SVG. Events: `glk-tab-select` on click.
|
|
@@ -416,6 +419,21 @@ iOS 26 Liquid Glass-style floating tab bar. Wrap a `<glk-tab-bar floating>` in a
|
|
|
416
419
|
|
|
417
420
|
Default slot: icon SVG (22×22). Events: `glk-click` (suppressed when disabled).
|
|
418
421
|
|
|
422
|
+
#### Droplet (since 1.27.0) — recommended
|
|
423
|
+
|
|
424
|
+
```html
|
|
425
|
+
<glk-tab-dock>
|
|
426
|
+
<glk-tab-bar floating droplet>
|
|
427
|
+
<glk-tab-item label="Home" active>…</glk-tab-item>
|
|
428
|
+
<glk-tab-item label="Docs">…</glk-tab-item>
|
|
429
|
+
</glk-tab-bar>
|
|
430
|
+
</glk-tab-dock>
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
The active item sits on a drop of glass that floats a little above the bar. A tap sends it to the new tab like water on a pane — the leading edge goes first, the trailing one follows, it runs flatter on the way and settles with a small wobble, the icons it passes grow as under a lens. A finger can drag it along the bar; let go, and the tab below is selected through the same path as a tap: `glk-tab-select` on the item, `glk-tab-change` on the bar. It is GlassKit's `.glass-tab-bar--droplet`, moved by GlassKit's `glasskit-droplet.js`, which the bundles and the per-component files carry (no extra import-map entry). The bar hands its state to the items' shadow roots. Spotlight, dot and glow are off on a droplet bar; `prefers-reduced-motion` makes the drop jump; solid and tint surfaces make it opaque. Works on the fixed bar too, but suits the floating one.
|
|
434
|
+
|
|
435
|
+
**Active tab surface (GlassKit 1.27.0).** `--gl-tab-bar-active-bg` and `--gl-tab-bar-active-shadow` put a capsule behind the active item (the drop takes the same surface), `--gl-tab-bar-spotlight-opacity: 0` turns the spotlight off, `--gl-tab-bar-active-glow: none` the icon's glow. All unset, inherited into `<glk-tab-item>` — no `hostStyles` sheet needed for a capsule any more.
|
|
436
|
+
|
|
419
437
|
---
|
|
420
438
|
|
|
421
439
|
### 3.4 `<glk-avatar>`
|
|
@@ -1119,8 +1137,9 @@ A small, exclusive choice as one control (since 1.15.0) — traffic light, morni
|
|
|
1119
1137
|
| `label` | String | `aria-label` of the group (no default — set it) |
|
|
1120
1138
|
| `name` | String | Form field name |
|
|
1121
1139
|
| `disabled` | Boolean | The whole row (since 1.22.3); a `<fieldset disabled>` around it disables it too |
|
|
1140
|
+
| `droplet` | Boolean | The chosen option sits on a drop of glass that glides to the next one and can be dragged (since 1.27.0) — recommended |
|
|
1122
1141
|
|
|
1123
|
-
Properties: `value`, `options` (array or JSON text), `full`, `overflow`, `label`, `disabled`. Events: `glk-change` `{ value }` — only on a change made by the user, not on `.value = …`. Part: `group`.
|
|
1142
|
+
Properties: `value`, `options` (array or JSON text), `full`, `overflow`, `label`, `disabled`, `droplet`. Events: `glk-change` `{ value }` — only on a change made by the user, not on `.value = …`. Part: `group`.
|
|
1124
1143
|
|
|
1125
1144
|
With `overflow="scroll"` the chosen option is centred in the row after the first layout, on every value change and when the row's width changes — the row scrolls, never the page. Seven areas on a phone:
|
|
1126
1145
|
|
|
@@ -1131,6 +1150,12 @@ With `overflow="scroll"` the chosen option is centred in the row after the first
|
|
|
1131
1150
|
|
|
1132
1151
|
The attribute is `overflow`, not `scroll`: `scroll` is a method of every element, and a framework that sets a property whenever the element has one (hybrids, Vue, React 19) would overwrite that method instead of setting the attribute.
|
|
1133
1152
|
|
|
1153
|
+
**Droplet (since 1.27.0) — recommended.** `<glk-segmented droplet>` gives the chosen option the drop of the tab bar: it runs to a new option, stretches on the way and settles, the text it passes lights up, and a finger can drag it. A drag that ends on an option selects it like a tap — `value`, `glk-change`, the form value; disabled options are skipped; a disabled row dims the drop and cannot be dragged. With `overflow="scroll"` a swipe still scrolls the row: only the drop itself is dragged, and the row scrolls along at its edges; the centring of the chosen option keeps working, the drop rides along.
|
|
1154
|
+
|
|
1155
|
+
```html
|
|
1156
|
+
<glk-segmented droplet full overflow="scroll" label="Area" value="a" options='[…]'></glk-segmented>
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1134
1159
|
---
|
|
1135
1160
|
|
|
1136
1161
|
### 3.26 `<glk-steps>`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GlkFormElement } from './base.js';
|
|
2
2
|
import { r as revealCentered } from './shared/reveal-CjjzoxTa.js';
|
|
3
|
+
import { a as attachDroplet } from './shared/glasskit-droplet-C3k1mxn4.js';
|
|
3
4
|
import '@jungherz-de/glasskit/glasskit-styles.js';
|
|
4
5
|
|
|
5
6
|
// One button per option, the chosen one marked aria-pressed="true" — GlassKit
|
|
@@ -10,9 +11,11 @@ import '@jungherz-de/glasskit/glasskit-styles.js';
|
|
|
10
11
|
// do not fit — without it the row stays one line and runs past
|
|
11
12
|
// the edge), label (aria-label of the group), name (form field),
|
|
12
13
|
// disabled (the whole row; since 1.22.3, as is a disabled
|
|
13
|
-
// fieldset around it)
|
|
14
|
+
// fieldset around it), droplet (since 1.27.0: the chosen option
|
|
15
|
+
// sits on a drop of glass that glides to the next one and can be
|
|
16
|
+
// dragged — GlassKit's .glass-segmented--droplet)
|
|
14
17
|
// Properties: value, options (array or JSON text), full, overflow, label,
|
|
15
|
-
// disabled
|
|
18
|
+
// disabled, droplet
|
|
16
19
|
// Event: glk-change { value } — only on a change made by the user
|
|
17
20
|
// Part: group
|
|
18
21
|
//
|
|
@@ -41,7 +44,7 @@ function parseList(json) {
|
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
class GlkSegmented extends GlkFormElement {
|
|
44
|
-
static get observedAttributes() { return ['options', 'value', 'full', 'overflow', 'label']; }
|
|
47
|
+
static get observedAttributes() { return ['options', 'value', 'full', 'overflow', 'label', 'droplet']; }
|
|
45
48
|
|
|
46
49
|
// A row of buttons with one tab stop: delegated focus would land on the
|
|
47
50
|
// first button, not the chosen one.
|
|
@@ -92,6 +95,7 @@ class GlkSegmented extends GlkFormElement {
|
|
|
92
95
|
_applyLayout() {
|
|
93
96
|
const overflow = this.overflow;
|
|
94
97
|
this._group.classList.toggle('glass-segmented--full', this.getBoolAttr('full'));
|
|
98
|
+
this._group.classList.toggle('glass-segmented--droplet', this.getBoolAttr('droplet'));
|
|
95
99
|
for (const mode of OVERFLOWS) this._group.classList.toggle(`glass-segmented--${mode}`, overflow === mode);
|
|
96
100
|
this._reveal('instant');
|
|
97
101
|
}
|
|
@@ -121,12 +125,23 @@ class GlkSegmented extends GlkFormElement {
|
|
|
121
125
|
this._group.addEventListener('click', this._onClick);
|
|
122
126
|
this._resizeObserver = new ResizeObserver(this._onResize);
|
|
123
127
|
this._resizeObserver.observe(this._group);
|
|
128
|
+
this._applyDroplet();
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
teardownEvents() {
|
|
127
132
|
this._group?.removeEventListener('click', this._onClick);
|
|
128
133
|
this._resizeObserver?.disconnect();
|
|
129
134
|
this._resizeObserver = null;
|
|
135
|
+
this._droplet?.destroy();
|
|
136
|
+
this._droplet = null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// The drop follows aria-pressed inside this shadow root by itself; a drag
|
|
140
|
+
// that ends on an option clicks it, the same path as a tap.
|
|
141
|
+
_applyDroplet() {
|
|
142
|
+
const on = this.getBoolAttr('droplet') && this.isConnected;
|
|
143
|
+
if (on && !this._droplet) this._droplet = attachDroplet(this._group);
|
|
144
|
+
else if (!on && this._droplet) { this._droplet.destroy(); this._droplet = null; }
|
|
130
145
|
}
|
|
131
146
|
|
|
132
147
|
onAttributeChanged(name) {
|
|
@@ -136,6 +151,7 @@ class GlkSegmented extends GlkFormElement {
|
|
|
136
151
|
case 'options': this._build(); break;
|
|
137
152
|
case 'full':
|
|
138
153
|
case 'overflow': this._applyLayout(); break;
|
|
154
|
+
case 'droplet': this._applyLayout(); this._applyDroplet(); break;
|
|
139
155
|
case 'label': this._applyLabel(); break;
|
|
140
156
|
}
|
|
141
157
|
}
|
|
@@ -155,6 +171,9 @@ class GlkSegmented extends GlkFormElement {
|
|
|
155
171
|
get full() { return this.getBoolAttr('full'); }
|
|
156
172
|
set full(v) { this.setBoolAttr('full', v); }
|
|
157
173
|
|
|
174
|
+
get droplet() { return this.getBoolAttr('droplet'); }
|
|
175
|
+
set droplet(v) { this.setBoolAttr('droplet', v); }
|
|
176
|
+
|
|
158
177
|
get overflow() {
|
|
159
178
|
const v = this.getAttribute('overflow');
|
|
160
179
|
return OVERFLOWS.includes(v) ? v : '';
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { GlkElement } from './base.js';
|
|
2
|
+
import { a as attachDroplet } from './shared/glasskit-droplet-C3k1mxn4.js';
|
|
2
3
|
import '@jungherz-de/glasskit/glasskit-styles.js';
|
|
3
4
|
|
|
5
|
+
// droplet (since 1.27.0): the active item sits on a drop of glass that glides
|
|
6
|
+
// to the next one and can be dragged — GlassKit's .glass-tab-bar--droplet,
|
|
7
|
+
// moved by its glasskit-droplet.js. The items live in shadow roots of their
|
|
8
|
+
// own, so the bar hands its state down to them (see glk-tab-item _syncBar)
|
|
9
|
+
// and tells the drop when the active item changed.
|
|
10
|
+
|
|
4
11
|
class GlkTabBar extends GlkElement {
|
|
5
12
|
static get observedAttributes() {
|
|
6
|
-
return ['static', 'floating'];
|
|
13
|
+
return ['static', 'floating', 'droplet'];
|
|
7
14
|
}
|
|
8
15
|
|
|
9
16
|
render() {
|
|
@@ -15,6 +22,7 @@ class GlkTabBar extends GlkElement {
|
|
|
15
22
|
if (this.getBoolAttr('floating')) {
|
|
16
23
|
nav.classList.add('glass-tab-bar--floating');
|
|
17
24
|
}
|
|
25
|
+
nav.classList.toggle('glass-tab-bar--droplet', this.getBoolAttr('droplet'));
|
|
18
26
|
|
|
19
27
|
if (this.getBoolAttr('static')) {
|
|
20
28
|
this._applyStatic();
|
|
@@ -27,6 +35,10 @@ class GlkTabBar extends GlkElement {
|
|
|
27
35
|
this._applyStatic();
|
|
28
36
|
} else if (name === 'floating') {
|
|
29
37
|
this._nav.classList.toggle('glass-tab-bar--floating', this.getBoolAttr('floating'));
|
|
38
|
+
this._syncItems();
|
|
39
|
+
} else if (name === 'droplet') {
|
|
40
|
+
this._nav.classList.toggle('glass-tab-bar--droplet', this.getBoolAttr('droplet'));
|
|
41
|
+
this._applyDroplet();
|
|
30
42
|
}
|
|
31
43
|
}
|
|
32
44
|
|
|
@@ -35,6 +47,42 @@ class GlkTabBar extends GlkElement {
|
|
|
35
47
|
this._nav.style.borderRadius = '16px';
|
|
36
48
|
}
|
|
37
49
|
|
|
50
|
+
_items() {
|
|
51
|
+
return [...this.children].filter(el => el.localName === 'glk-tab-item');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Hands the bar's floating and droplet state to every item's wrapper. */
|
|
55
|
+
_syncItems() {
|
|
56
|
+
const state = {
|
|
57
|
+
floating: this.getBoolAttr('floating'),
|
|
58
|
+
droplet: this.getBoolAttr('droplet'),
|
|
59
|
+
placed: Boolean(this._nav?.hasAttribute('data-droplet')),
|
|
60
|
+
drag: Boolean(this._nav?.hasAttribute('data-droplet-drag')),
|
|
61
|
+
};
|
|
62
|
+
for (const item of this._items()) item._syncBar?.(state);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Called by an item that rendered or changed its active state. */
|
|
66
|
+
_itemChanged() {
|
|
67
|
+
this._syncItems();
|
|
68
|
+
this._droplet?.update();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_applyDroplet() {
|
|
72
|
+
const on = this.getBoolAttr('droplet') && this.isConnected;
|
|
73
|
+
if (on && !this._droplet) {
|
|
74
|
+
// The drop measures and marks the items' own buttons; picking one clicks
|
|
75
|
+
// it, which is what a tap does.
|
|
76
|
+
this._droplet = attachDroplet(this._nav, {
|
|
77
|
+
items: () => this._items().map(item => item._btn).filter(Boolean),
|
|
78
|
+
});
|
|
79
|
+
} else if (!on && this._droplet) {
|
|
80
|
+
this._droplet.destroy();
|
|
81
|
+
this._droplet = null;
|
|
82
|
+
}
|
|
83
|
+
this._syncItems();
|
|
84
|
+
}
|
|
85
|
+
|
|
38
86
|
setupEvents() {
|
|
39
87
|
// Listen for tab selection events from child glk-tab-item elements
|
|
40
88
|
this._onTabSelect = (e) => {
|
|
@@ -48,10 +96,22 @@ class GlkTabBar extends GlkElement {
|
|
|
48
96
|
this.emit('glk-tab-change', { tab: e.target });
|
|
49
97
|
};
|
|
50
98
|
this.addEventListener('glk-tab-select', this._onTabSelect);
|
|
99
|
+
|
|
100
|
+
// The drop's state lives on the nav; the items carry it on their wrappers.
|
|
101
|
+
this._navObserver = new MutationObserver(() => this._syncItems());
|
|
102
|
+
this._navObserver.observe(this._nav, { attributes: true, attributeFilter: ['data-droplet', 'data-droplet-drag'] });
|
|
103
|
+
// Items added or removed.
|
|
104
|
+
this._itemObserver = new MutationObserver(() => this._itemChanged());
|
|
105
|
+
this._itemObserver.observe(this, { childList: true });
|
|
106
|
+
this._applyDroplet();
|
|
51
107
|
}
|
|
52
108
|
|
|
53
109
|
teardownEvents() {
|
|
54
110
|
this.removeEventListener('glk-tab-select', this._onTabSelect);
|
|
111
|
+
this._navObserver?.disconnect();
|
|
112
|
+
this._itemObserver?.disconnect();
|
|
113
|
+
this._droplet?.destroy();
|
|
114
|
+
this._droplet = null;
|
|
55
115
|
}
|
|
56
116
|
}
|
|
57
117
|
|
|
@@ -67,6 +67,27 @@ class GlkTabItem extends GlkElement {
|
|
|
67
67
|
this.emit('glk-tab-select');
|
|
68
68
|
};
|
|
69
69
|
this._btn.addEventListener('click', this._onClick);
|
|
70
|
+
this._tellBar();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The bar's look reaches the item through its wrapper. GlassKit's rules for
|
|
75
|
+
* floating and droplet items are descendant selectors of the bar's classes,
|
|
76
|
+
* and this button sits in a shadow root of its own, so <glk-tab-bar> hands
|
|
77
|
+
* its state down and the wrapper carries it. Before 1.27.0 the items of a
|
|
78
|
+
* floating bar kept the fixed bar's look: no spotlight, no pill, the dot.
|
|
79
|
+
*/
|
|
80
|
+
_syncBar({ floating, droplet, placed, drag }) {
|
|
81
|
+
if (!this._wrapper) return;
|
|
82
|
+
this._wrapper.classList.toggle('glass-tab-bar--floating', floating);
|
|
83
|
+
this._wrapper.classList.toggle('glass-tab-bar--droplet', droplet);
|
|
84
|
+
this._wrapper.toggleAttribute('data-droplet', placed);
|
|
85
|
+
this._wrapper.toggleAttribute('data-droplet-drag', drag);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Tells the bar that this item rendered or changed its active state. */
|
|
89
|
+
_tellBar() {
|
|
90
|
+
this.closest('glk-tab-bar')?._itemChanged?.();
|
|
70
91
|
}
|
|
71
92
|
|
|
72
93
|
teardownEvents() {
|
|
@@ -78,6 +99,7 @@ class GlkTabItem extends GlkElement {
|
|
|
78
99
|
switch (name) {
|
|
79
100
|
case 'active':
|
|
80
101
|
this._btn.classList.toggle('is-active', this.getBoolAttr('active'));
|
|
102
|
+
this._tellBar();
|
|
81
103
|
break;
|
|
82
104
|
case 'label':
|
|
83
105
|
this._labelEl.textContent = this.getAttribute('label') || '';
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// GlassKit – Droplet
|
|
3
|
+
//
|
|
4
|
+
// The motion behind .glass-tab-bar--droplet and .glass-segmented--droplet.
|
|
5
|
+
// The stylesheet draws the drop of glass; this module puts it on the chosen
|
|
6
|
+
// item, lets it run to a new one, and lets a finger drag it along the row.
|
|
7
|
+
// Optional and dependency-free: without it a --droplet row keeps its usual
|
|
8
|
+
// look, the tab bar its spotlight and the segmented row its raised option.
|
|
9
|
+
//
|
|
10
|
+
// import { attachDroplets } from '@jungherz-de/glasskit/glasskit-droplet.js';
|
|
11
|
+
// attachDroplets(); // every --droplet row in the page
|
|
12
|
+
//
|
|
13
|
+
// import { attachDroplet } from '@jungherz-de/glasskit/glasskit-droplet.js';
|
|
14
|
+
// const drop = attachDroplet(row); // one row; drop.update(), drop.destroy()
|
|
15
|
+
//
|
|
16
|
+
// The chosen item is the tab with .is-active or the option with
|
|
17
|
+
// aria-pressed="true". The page keeps setting them its own way and the drop
|
|
18
|
+
// follows. Dragged onto another item, the drop clicks it, so the page's own
|
|
19
|
+
// click handler makes the choice. In a row that scrolls sideways a swipe
|
|
20
|
+
// still scrolls: only the drop itself is dragged, and the row scrolls along
|
|
21
|
+
// at its edges.
|
|
22
|
+
//
|
|
23
|
+
// It writes what glasskit.css reads: data-droplet on the row, the chosen
|
|
24
|
+
// item's box as --gl-droplet-left, -right, -top and -bottom, data-droplet-dir
|
|
25
|
+
// and data-droplet-lift while the drop travels, data-droplet-drag while a
|
|
26
|
+
// finger holds it, and .is-under-droplet on the item below it.
|
|
27
|
+
//
|
|
28
|
+
// Options of attachDroplet, for rows whose items live elsewhere (GlassKit
|
|
29
|
+
// Elements uses them): items() returns the items, isChosen(item) tells the
|
|
30
|
+
// chosen one, choose(item) picks one, lens(item, on) marks the item below
|
|
31
|
+
// the drop. Without a MutationObserver reaching the items, call update()
|
|
32
|
+
// after the choice changed.
|
|
33
|
+
// ============================================================
|
|
34
|
+
|
|
35
|
+
const ITEMS = '.glass-tab-bar__item, .glass-segmented__item';
|
|
36
|
+
const attached = new WeakMap();
|
|
37
|
+
|
|
38
|
+
function attachDroplet(row, options = {}) {
|
|
39
|
+
if (attached.has(row)) return attached.get(row);
|
|
40
|
+
|
|
41
|
+
const items = options.items || (() => [...row.querySelectorAll(ITEMS)]);
|
|
42
|
+
const isChosen = options.isChosen
|
|
43
|
+
|| (it => it.classList.contains('is-active') || it.getAttribute('aria-pressed') === 'true');
|
|
44
|
+
const choose = options.choose || (it => it.click());
|
|
45
|
+
const mark = options.lens || ((it, on) => it.classList.toggle('is-under-droplet', on));
|
|
46
|
+
const usable = it => !it.disabled && it.getAttribute('aria-disabled') !== 'true';
|
|
47
|
+
const scrolls = () => row.scrollWidth > row.clientWidth + 1;
|
|
48
|
+
const motion = matchMedia('(prefers-reduced-motion: no-preference)');
|
|
49
|
+
const set = (k, px) => row.style.setProperty(`--gl-droplet-${k}`, `${px}px`);
|
|
50
|
+
|
|
51
|
+
let chosen = null, left = 0, placed = null, liftTimer = 0, lensFrame = 0, edgeFrame = 0;
|
|
52
|
+
let drag = null, eatClick = false, under = null;
|
|
53
|
+
|
|
54
|
+
// An item's box in the row's padding box; in a row that scrolls, in its
|
|
55
|
+
// scrolled content, where the drop scrolls along with the items.
|
|
56
|
+
const boxOf = it => {
|
|
57
|
+
const n = row.getBoundingClientRect(), r = it.getBoundingClientRect();
|
|
58
|
+
const l = r.left - n.left - row.clientLeft + row.scrollLeft;
|
|
59
|
+
const t = r.top - n.top - row.clientTop + row.scrollTop;
|
|
60
|
+
return { left: l, width: r.width, top: t, bottom: row.clientHeight - t - r.height };
|
|
61
|
+
};
|
|
62
|
+
const put = (l, width, b) => {
|
|
63
|
+
left = l;
|
|
64
|
+
set('left', l); set('right', row.clientWidth - l - width);
|
|
65
|
+
set('top', b.top); set('bottom', b.bottom);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// The usable item nearest a point of the row lights up as under a lens.
|
|
69
|
+
const lens = centre => {
|
|
70
|
+
let below = null, best = Infinity;
|
|
71
|
+
if (centre != null) for (const it of items()) {
|
|
72
|
+
if (!usable(it)) continue;
|
|
73
|
+
const b = boxOf(it), d = Math.abs(b.left + b.width / 2 - centre);
|
|
74
|
+
if (d < best) { best = d; below = it; }
|
|
75
|
+
}
|
|
76
|
+
if (below !== under) {
|
|
77
|
+
if (under) mark(under, false);
|
|
78
|
+
if (below) mark(below, true);
|
|
79
|
+
under = below;
|
|
80
|
+
}
|
|
81
|
+
return below;
|
|
82
|
+
};
|
|
83
|
+
// While the drop travels, the lens follows its centre.
|
|
84
|
+
const follow = () => {
|
|
85
|
+
cancelAnimationFrame(lensFrame);
|
|
86
|
+
const start = performance.now();
|
|
87
|
+
const step = now => {
|
|
88
|
+
if (now - start > 620) { lens(null); return; }
|
|
89
|
+
const cs = getComputedStyle(row, '::before');
|
|
90
|
+
const l = parseFloat(cs.left), r = parseFloat(cs.right);
|
|
91
|
+
lens(l + (row.clientWidth - l - r) / 2);
|
|
92
|
+
lensFrame = requestAnimationFrame(step);
|
|
93
|
+
};
|
|
94
|
+
lensFrame = requestAnimationFrame(step);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const place = (it, travel) => {
|
|
98
|
+
const b = boxOf(it);
|
|
99
|
+
row.dataset.droplet = '';
|
|
100
|
+
if (travel && motion.matches && Math.abs(b.left - left) > 1) {
|
|
101
|
+
row.dataset.dropletDir = b.left > left ? 'right' : 'left';
|
|
102
|
+
row.dataset.dropletLift = '';
|
|
103
|
+
clearTimeout(liftTimer);
|
|
104
|
+
liftTimer = setTimeout(() => { delete row.dataset.dropletLift; }, 280);
|
|
105
|
+
follow();
|
|
106
|
+
} else {
|
|
107
|
+
delete row.dataset.dropletDir;
|
|
108
|
+
}
|
|
109
|
+
put(b.left, b.width, b);
|
|
110
|
+
placed = { ...b, rowWidth: row.clientWidth, rowHeight: row.clientHeight };
|
|
111
|
+
chosen = it;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Follow the choice. Nothing chosen, no drop. A new item is watched for
|
|
115
|
+
// its size too; a size that changed moves the drop without a journey.
|
|
116
|
+
const update = () => {
|
|
117
|
+
for (const it of items()) if (!watched.has(it)) { watched.add(it); ro.observe(it); }
|
|
118
|
+
const it = items().find(isChosen);
|
|
119
|
+
if (!it) {
|
|
120
|
+
if (chosen) { chosen = null; placed = null; delete row.dataset.droplet; }
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (it !== chosen) place(it, chosen !== null && row.dataset.droplet !== undefined);
|
|
124
|
+
};
|
|
125
|
+
const ro = new ResizeObserver(() => {
|
|
126
|
+
if (!chosen || drag?.moved) return;
|
|
127
|
+
const b = { ...boxOf(chosen), rowWidth: row.clientWidth, rowHeight: row.clientHeight };
|
|
128
|
+
if (!placed || Object.keys(b).some(k => Math.abs(b[k] - placed[k]) > 0.5)) place(chosen, false);
|
|
129
|
+
});
|
|
130
|
+
const watched = new Set();
|
|
131
|
+
ro.observe(row);
|
|
132
|
+
const mo = new MutationObserver(update);
|
|
133
|
+
mo.observe(row, { subtree: true, childList: true, attributes: true, attributeFilter: ['class', 'aria-pressed'] });
|
|
134
|
+
|
|
135
|
+
// Dragging: past 8px the drop slides under the finger and grows into a
|
|
136
|
+
// lens; let go, the item below it is chosen and the drop settles on it.
|
|
137
|
+
const at = clientX => {
|
|
138
|
+
const n = row.getBoundingClientRect();
|
|
139
|
+
const x = clientX - n.left - row.clientLeft + row.scrollLeft;
|
|
140
|
+
const boxes = items().map(boxOf);
|
|
141
|
+
const min = Math.min(...boxes.map(b => b.left));
|
|
142
|
+
const max = Math.max(...boxes.map(b => b.left + b.width)) - drag.width;
|
|
143
|
+
return Math.min(max, Math.max(min, x - drag.width / 2));
|
|
144
|
+
};
|
|
145
|
+
const track = () => {
|
|
146
|
+
const l = at(drag.clientX);
|
|
147
|
+
put(l, drag.width, boxOf(chosen));
|
|
148
|
+
drag.below = lens(l + drag.width / 2);
|
|
149
|
+
cancelAnimationFrame(edgeFrame);
|
|
150
|
+
if (!scrolls()) return;
|
|
151
|
+
const n = row.getBoundingClientRect(), zone = 32;
|
|
152
|
+
const v = drag.clientX < n.left + zone ? -1 : drag.clientX > n.right - zone ? 1 : 0;
|
|
153
|
+
if (v) edgeFrame = requestAnimationFrame(() => { row.scrollLeft += v * 6; if (drag?.moved) track(); });
|
|
154
|
+
};
|
|
155
|
+
const down = e => {
|
|
156
|
+
if (!chosen || drag || !usable(chosen) || (e.pointerType === 'mouse' && e.button !== 0)) return;
|
|
157
|
+
if (scrolls() && !e.composedPath().includes(chosen)) return;
|
|
158
|
+
drag = { id: e.pointerId, x: e.clientX, clientX: e.clientX, width: boxOf(chosen).width, moved: false, below: null };
|
|
159
|
+
};
|
|
160
|
+
const move = e => {
|
|
161
|
+
if (!drag || e.pointerId !== drag.id) return;
|
|
162
|
+
drag.clientX = e.clientX;
|
|
163
|
+
if (!drag.moved) {
|
|
164
|
+
if (Math.abs(e.clientX - drag.x) < 8) return;
|
|
165
|
+
drag.moved = true;
|
|
166
|
+
row.setPointerCapture(e.pointerId);
|
|
167
|
+
delete row.dataset.dropletDir;
|
|
168
|
+
row.dataset.dropletDrag = '';
|
|
169
|
+
}
|
|
170
|
+
track();
|
|
171
|
+
};
|
|
172
|
+
const up = e => {
|
|
173
|
+
if (!drag || e.pointerId !== drag.id) return;
|
|
174
|
+
const d = drag;
|
|
175
|
+
drag = null;
|
|
176
|
+
cancelAnimationFrame(edgeFrame);
|
|
177
|
+
if (!d.moved) return; // a tap: the item's own click does it
|
|
178
|
+
delete row.dataset.dropletDrag;
|
|
179
|
+
lens(null);
|
|
180
|
+
const target = d.below || chosen;
|
|
181
|
+
if (e.type === 'pointerup' && target !== chosen) choose(target);
|
|
182
|
+
// Settle where the choice is, unless it already set the drop on its way.
|
|
183
|
+
const next = items().find(isChosen) || chosen;
|
|
184
|
+
if (next !== chosen || row.dataset.dropletDir === undefined) place(next, true);
|
|
185
|
+
eatClick = true; // the click the drag ends in
|
|
186
|
+
setTimeout(() => { eatClick = false; }, 400);
|
|
187
|
+
};
|
|
188
|
+
const click = e => { if (eatClick) { eatClick = false; e.stopPropagation(); e.preventDefault(); } };
|
|
189
|
+
row.addEventListener('pointerdown', down);
|
|
190
|
+
row.addEventListener('pointermove', move);
|
|
191
|
+
row.addEventListener('pointerup', up);
|
|
192
|
+
row.addEventListener('pointercancel', up);
|
|
193
|
+
row.addEventListener('click', click, true);
|
|
194
|
+
|
|
195
|
+
update();
|
|
196
|
+
|
|
197
|
+
const handle = {
|
|
198
|
+
update,
|
|
199
|
+
destroy() {
|
|
200
|
+
mo.disconnect(); ro.disconnect();
|
|
201
|
+
clearTimeout(liftTimer); cancelAnimationFrame(lensFrame); cancelAnimationFrame(edgeFrame);
|
|
202
|
+
row.removeEventListener('pointerdown', down);
|
|
203
|
+
row.removeEventListener('pointermove', move);
|
|
204
|
+
row.removeEventListener('pointerup', up);
|
|
205
|
+
row.removeEventListener('pointercancel', up);
|
|
206
|
+
row.removeEventListener('click', click, true);
|
|
207
|
+
lens(null);
|
|
208
|
+
for (const k of ['droplet', 'dropletDir', 'dropletLift', 'dropletDrag']) delete row.dataset[k];
|
|
209
|
+
for (const k of ['left', 'right', 'top', 'bottom']) row.style.removeProperty(`--gl-droplet-${k}`);
|
|
210
|
+
attached.delete(row);
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
attached.set(row, handle);
|
|
214
|
+
return handle;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export { attachDroplet as a };
|