@woylie/doggo 0.15.1 → 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 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.15.1"}
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.
@@ -112,6 +112,8 @@ export function initCarousel(carousel) {
112
112
 
113
113
  // Auto rotation
114
114
  let isPaused = false;
115
+ let isHovered = false;
116
+ let isFocused = false;
115
117
 
116
118
  // Stop auto rotation if the last items does not wrap.
117
119
  const hasShowEnded = () =>
@@ -123,8 +125,11 @@ export function initCarousel(carousel) {
123
125
  syncPauseControl();
124
126
  };
125
127
 
128
+ const canRotate = () =>
129
+ isAutoRotationEnabled() && !isPaused && !isHovered && !isFocused;
130
+
126
131
  const startAutoRotation = () => {
127
- if (!isAutoRotationEnabled() || isPaused) return;
132
+ if (!canRotate()) return;
128
133
  if (autoRotationTimer != null) return;
129
134
 
130
135
  if (hasShowEnded()) {
@@ -260,12 +265,8 @@ export function initCarousel(carousel) {
260
265
  // Pause when hovering or focusing, resume when leaving, unless
261
266
  // rotation is paused with the pause button.
262
267
 
263
- let isHovered = false;
264
- let isFocused = false;
265
-
266
- const resume = () => {
267
- if (!isHovered && !isFocused) startAutoRotation();
268
- };
268
+ const isPauseControl = (el) =>
269
+ el != null && el.closest?.(`.${baseClass}-pause`) != null;
269
270
 
270
271
  carousel.addEventListener("pointerenter", () => {
271
272
  isHovered = true;
@@ -274,12 +275,17 @@ export function initCarousel(carousel) {
274
275
 
275
276
  carousel.addEventListener("pointerleave", () => {
276
277
  isHovered = false;
277
- resume();
278
+ startAutoRotation();
278
279
  });
279
280
 
280
- carousel.addEventListener("focusin", () => {
281
- isFocused = true;
282
- stopAutoRotation();
281
+ carousel.addEventListener("focusin", (e) => {
282
+ isFocused = !isPauseControl(e.target);
283
+
284
+ if (isFocused) {
285
+ stopAutoRotation();
286
+ } else {
287
+ startAutoRotation();
288
+ }
283
289
  });
284
290
 
285
291
  carousel.addEventListener("focusout", (e) => {
@@ -288,7 +294,7 @@ export function initCarousel(carousel) {
288
294
  if (carousel.contains(e.relatedTarget)) return;
289
295
 
290
296
  isFocused = false;
291
- resume();
297
+ startAutoRotation();
292
298
  });
293
299
 
294
300
  // Initialize
@@ -303,7 +309,7 @@ export function initCarousel(carousel) {
303
309
  // A patch can add or remove the pause button, and with it the
304
310
  // rotation. Starting is a no-op while it runs or is paused.
305
311
  if (isAutoRotationEnabled()) {
306
- resume();
312
+ startAutoRotation();
307
313
  } else {
308
314
  stopAutoRotation();
309
315
  }
@@ -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 close = () => {
68
- const button = Array.from(
69
- document.querySelectorAll("[aria-controls]"),
70
- ).find((el) => el.getAttribute("aria-controls") === menu.id);
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
- button.focus();
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
- return { update: restoreTabStop };
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@woylie/doggo",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "JavaScript hooks for the Doggo component library",
5
5
  "keywords": [
6
6
  "phoenix",