@woylie/doggo 0.15.0 → 0.16.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/README.md +8 -1
- package/assets/js/hooks/carousel.js +40 -15
- package/assets/js/hooks/menu.js +47 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ The package can be installed by adding `doggo` to your list of dependencies in
|
|
|
21
21
|
```elixir
|
|
22
22
|
def deps do
|
|
23
23
|
[
|
|
24
|
-
{:doggo, "~> 0.
|
|
24
|
+
{:doggo, "~> 0.16.0"}
|
|
25
25
|
]
|
|
26
26
|
end
|
|
27
27
|
```
|
|
@@ -419,3 +419,10 @@ If you encounter any issues with a component, have suggestions for improvements,
|
|
|
419
419
|
or need a component for a specific use case that isn't currently available,
|
|
420
420
|
please don't hesitate to open a
|
|
421
421
|
[Github issue](https://github.com/woylie/doggo/issues).
|
|
422
|
+
|
|
423
|
+
## Related libraries
|
|
424
|
+
|
|
425
|
+
- [ex_icon](https://github.com/woylie/ex_icon) - Generic icon library for
|
|
426
|
+
Phoenix LiveView.
|
|
427
|
+
- [flop_phoenix](https://github.com/woylie/flop_phoenix) - Pagination, sortable
|
|
428
|
+
table and filter components for Flop.
|
|
@@ -83,11 +83,17 @@ export function initCarousel(carousel) {
|
|
|
83
83
|
|
|
84
84
|
const syncActiveState = () => setActiveIdx(getActiveIdx());
|
|
85
85
|
|
|
86
|
+
const SCROLL_SETTLE_MS = 100;
|
|
87
|
+
|
|
88
|
+
let pendingIdx = null;
|
|
89
|
+
let settleTimer = null;
|
|
90
|
+
|
|
86
91
|
const scrollToIdx = (idx) => {
|
|
87
92
|
const item = getItems()[idx];
|
|
88
93
|
|
|
89
94
|
if (!item) return;
|
|
90
95
|
|
|
96
|
+
pendingIdx = idx;
|
|
91
97
|
setActiveIdx(idx);
|
|
92
98
|
|
|
93
99
|
const itemsContainer = getItemsContainer();
|
|
@@ -106,6 +112,8 @@ export function initCarousel(carousel) {
|
|
|
106
112
|
|
|
107
113
|
// Auto rotation
|
|
108
114
|
let isPaused = false;
|
|
115
|
+
let isHovered = false;
|
|
116
|
+
let isFocused = false;
|
|
109
117
|
|
|
110
118
|
// Stop auto rotation if the last items does not wrap.
|
|
111
119
|
const hasShowEnded = () =>
|
|
@@ -117,8 +125,11 @@ export function initCarousel(carousel) {
|
|
|
117
125
|
syncPauseControl();
|
|
118
126
|
};
|
|
119
127
|
|
|
128
|
+
const canRotate = () =>
|
|
129
|
+
isAutoRotationEnabled() && !isPaused && !isHovered && !isFocused;
|
|
130
|
+
|
|
120
131
|
const startAutoRotation = () => {
|
|
121
|
-
if (!
|
|
132
|
+
if (!canRotate()) return;
|
|
122
133
|
if (autoRotationTimer != null) return;
|
|
123
134
|
|
|
124
135
|
if (hasShowEnded()) {
|
|
@@ -190,7 +201,15 @@ export function initCarousel(carousel) {
|
|
|
190
201
|
carousel.addEventListener(
|
|
191
202
|
"scroll",
|
|
192
203
|
(e) => {
|
|
193
|
-
if (e.target
|
|
204
|
+
if (e.target !== getItemsContainer()) return;
|
|
205
|
+
|
|
206
|
+
clearTimeout(settleTimer);
|
|
207
|
+
settleTimer = setTimeout(() => {
|
|
208
|
+
pendingIdx = null;
|
|
209
|
+
syncActiveState();
|
|
210
|
+
}, SCROLL_SETTLE_MS);
|
|
211
|
+
|
|
212
|
+
if (pendingIdx === null) syncActiveState();
|
|
194
213
|
},
|
|
195
214
|
true,
|
|
196
215
|
);
|
|
@@ -246,12 +265,8 @@ export function initCarousel(carousel) {
|
|
|
246
265
|
// Pause when hovering or focusing, resume when leaving, unless
|
|
247
266
|
// rotation is paused with the pause button.
|
|
248
267
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const resume = () => {
|
|
253
|
-
if (!isHovered && !isFocused) startAutoRotation();
|
|
254
|
-
};
|
|
268
|
+
const isPauseControl = (el) =>
|
|
269
|
+
el != null && el.closest?.(`.${baseClass}-pause`) != null;
|
|
255
270
|
|
|
256
271
|
carousel.addEventListener("pointerenter", () => {
|
|
257
272
|
isHovered = true;
|
|
@@ -260,12 +275,17 @@ export function initCarousel(carousel) {
|
|
|
260
275
|
|
|
261
276
|
carousel.addEventListener("pointerleave", () => {
|
|
262
277
|
isHovered = false;
|
|
263
|
-
|
|
278
|
+
startAutoRotation();
|
|
264
279
|
});
|
|
265
280
|
|
|
266
|
-
carousel.addEventListener("focusin", () => {
|
|
267
|
-
isFocused =
|
|
268
|
-
|
|
281
|
+
carousel.addEventListener("focusin", (e) => {
|
|
282
|
+
isFocused = !isPauseControl(e.target);
|
|
283
|
+
|
|
284
|
+
if (isFocused) {
|
|
285
|
+
stopAutoRotation();
|
|
286
|
+
} else {
|
|
287
|
+
startAutoRotation();
|
|
288
|
+
}
|
|
269
289
|
});
|
|
270
290
|
|
|
271
291
|
carousel.addEventListener("focusout", (e) => {
|
|
@@ -274,7 +294,7 @@ export function initCarousel(carousel) {
|
|
|
274
294
|
if (carousel.contains(e.relatedTarget)) return;
|
|
275
295
|
|
|
276
296
|
isFocused = false;
|
|
277
|
-
|
|
297
|
+
startAutoRotation();
|
|
278
298
|
});
|
|
279
299
|
|
|
280
300
|
// Initialize
|
|
@@ -289,13 +309,18 @@ export function initCarousel(carousel) {
|
|
|
289
309
|
// A patch can add or remove the pause button, and with it the
|
|
290
310
|
// rotation. Starting is a no-op while it runs or is paused.
|
|
291
311
|
if (isAutoRotationEnabled()) {
|
|
292
|
-
|
|
312
|
+
startAutoRotation();
|
|
293
313
|
} else {
|
|
294
314
|
stopAutoRotation();
|
|
295
315
|
}
|
|
296
316
|
};
|
|
297
317
|
|
|
298
|
-
|
|
318
|
+
const destroy = () => {
|
|
319
|
+
stopAutoRotation();
|
|
320
|
+
clearTimeout(settleTimer);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
return { update: syncAfterUpdate, destroy };
|
|
299
324
|
}
|
|
300
325
|
|
|
301
326
|
export default {
|
package/assets/js/hooks/menu.js
CHANGED
|
@@ -64,20 +64,52 @@ export function initMenu(menu) {
|
|
|
64
64
|
// through the `aria-controls` pointing at it. Compared rather than put in a
|
|
65
65
|
// selector, because the id is the caller's and may hold characters that
|
|
66
66
|
// would change what the selector matches.
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
67
|
+
const getButton = () =>
|
|
68
|
+
Array.from(document.querySelectorAll("[aria-controls]")).find(
|
|
69
|
+
(el) => el.getAttribute("aria-controls") === menu.id,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const close = ({ focusButton } = { focusButton: true }) => {
|
|
73
|
+
const button = getButton();
|
|
71
74
|
|
|
72
75
|
if (!button) return false;
|
|
73
76
|
|
|
74
77
|
menu.setAttribute("hidden", "");
|
|
75
78
|
button.setAttribute("aria-expanded", "false");
|
|
76
|
-
|
|
79
|
+
|
|
80
|
+
if (focusButton) button.focus();
|
|
77
81
|
|
|
78
82
|
return true;
|
|
79
83
|
};
|
|
80
84
|
|
|
85
|
+
const closeOnClickOutside = (e) => {
|
|
86
|
+
if (menu.hidden) return;
|
|
87
|
+
|
|
88
|
+
const button = getButton();
|
|
89
|
+
|
|
90
|
+
if (!button) return;
|
|
91
|
+
if (menu.contains(e.target) || button.contains(e.target)) return;
|
|
92
|
+
|
|
93
|
+
close({ focusButton: false });
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
document.addEventListener("click", closeOnClickOutside);
|
|
97
|
+
|
|
98
|
+
const closeOnButtonEscape = (e) => {
|
|
99
|
+
if (e.key !== "Escape" || menu.hidden) return;
|
|
100
|
+
|
|
101
|
+
const button = getButton();
|
|
102
|
+
|
|
103
|
+
if (!button || !button.contains(e.target)) return;
|
|
104
|
+
|
|
105
|
+
if (close()) {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
document.addEventListener("keydown", closeOnButtonEscape, true);
|
|
112
|
+
|
|
81
113
|
menu.addEventListener("keydown", (e) => {
|
|
82
114
|
const items = getItems();
|
|
83
115
|
const currentIdx = items.indexOf(e.target.closest(ITEMS));
|
|
@@ -124,7 +156,12 @@ export function initMenu(menu) {
|
|
|
124
156
|
|
|
125
157
|
restoreTabStop();
|
|
126
158
|
|
|
127
|
-
|
|
159
|
+
const destroy = () => {
|
|
160
|
+
document.removeEventListener("click", closeOnClickOutside);
|
|
161
|
+
document.removeEventListener("keydown", closeOnButtonEscape, true);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return { update: restoreTabStop, destroy };
|
|
128
165
|
}
|
|
129
166
|
|
|
130
167
|
export default {
|
|
@@ -135,4 +172,8 @@ export default {
|
|
|
135
172
|
updated() {
|
|
136
173
|
this.instance.update();
|
|
137
174
|
},
|
|
175
|
+
|
|
176
|
+
destroyed() {
|
|
177
|
+
this.instance.destroy();
|
|
178
|
+
},
|
|
138
179
|
};
|