@unlk/keymaster 1.4.6 → 1.4.7

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.
@@ -0,0 +1,43 @@
1
+ import BaseComponent from 'bootstrap/js/src/base-component.js';
2
+ import EventHandler from 'bootstrap/js/src/dom/event-handler.js';
3
+ import SelectorEngine from 'bootstrap/js/src/dom/selector-engine.js';
4
+ import { defineJQueryPlugin } from 'bootstrap/js/src/util/index.js';
5
+
6
+ const NAME = 'accordionScroll';
7
+ const EVENT_SHOWN = 'shown.bs.collapse';
8
+ const SELECTOR_ACCORDION = '.accordion';
9
+ const SELECTOR_ITEM = '.accordion-item';
10
+ const SELECTOR_HEADER = '.accordion-header';
11
+
12
+ class AccordionScroll extends BaseComponent {
13
+ _onShown(e) {
14
+ const item = e.target.closest(SELECTOR_ITEM);
15
+ if (!item) return;
16
+
17
+ const header = SelectorEngine.findOne(SELECTOR_HEADER, item);
18
+ if (!header) return;
19
+
20
+ const rect = header.getBoundingClientRect();
21
+ if (rect.top < 0) {
22
+ header.scrollIntoView({ behavior: 'smooth', block: 'start' });
23
+ }
24
+ }
25
+
26
+ static get NAME() {
27
+ return NAME;
28
+ }
29
+
30
+ static jQueryInterface(config) {
31
+ return this.each(function () {
32
+ AccordionScroll.getOrCreateInstance(this, config);
33
+ });
34
+ }
35
+ }
36
+
37
+ EventHandler.on(document, EVENT_SHOWN, SELECTOR_ACCORDION, function (e) {
38
+ AccordionScroll.getOrCreateInstance(this)._onShown(e);
39
+ });
40
+
41
+ defineJQueryPlugin(AccordionScroll);
42
+
43
+ export default AccordionScroll;
package/js/bootstrap.js CHANGED
@@ -14,3 +14,4 @@ export { default as Datepicker } from './datepicker';
14
14
  export { default as CarouselCaption } from './carousel-caption';
15
15
  export { default as CarouselHeight } from './carousel-height';
16
16
  export { default as VideoModal } from './video-modal';
17
+ export { default as AccordionScroll } from './accordion-scroll';
package/js/video-modal.js CHANGED
@@ -84,6 +84,13 @@ class VideoModal extends BaseComponent {
84
84
  this._element.dataset.vimeoId = String(vimeoId);
85
85
  this._element.__unlkVimeoPlayer = this._vimeoPlayer;
86
86
 
87
+ this._element.dispatchEvent(
88
+ new CustomEvent('videomodal.ready', {
89
+ bubbles: true,
90
+ detail: { provider: 'vimeo', player: this._vimeoPlayer, videoId: vimeoId }
91
+ })
92
+ );
93
+
87
94
  return;
88
95
  }
89
96
 
@@ -99,6 +106,13 @@ class VideoModal extends BaseComponent {
99
106
 
100
107
  this._element.dataset.ytId = youtubeId;
101
108
  this._element.__unlkYtPlayer = this._youtubePlayer;
109
+
110
+ this._element.dispatchEvent(
111
+ new CustomEvent('videomodal.ready', {
112
+ bubbles: true,
113
+ detail: { provider: 'youtube', player: this._youtubePlayer, videoId: youtubeId }
114
+ })
115
+ );
102
116
  }
103
117
  }
104
118
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unlk/keymaster",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/react/index.js CHANGED
@@ -2,3 +2,4 @@ export { useDatepicker } from './use-datepicker.js';
2
2
  export { useCarouselCaption } from './use-carousel-caption.js';
3
3
  export { useCarouselHeight } from './use-carousel-height.js';
4
4
  export { useVideoModal } from './use-video-modal.js';
5
+ export { useAccordionScroll } from './use-accordion-scroll.js';
@@ -0,0 +1,36 @@
1
+ import { useCallback } from 'react';
2
+
3
+ /**
4
+ * Replicates accordion-scroll.js behaviour in React.
5
+ *
6
+ * After an accordion panel finishes opening, scrolls its header into view
7
+ * if it has been pushed above the viewport, replacing the Bootstrap
8
+ * shown.bs.collapse event listener.
9
+ *
10
+ * Usage:
11
+ * const { onEntered } = useAccordionScroll();
12
+ * <Accordion>
13
+ * <Accordion.Item eventKey="0">
14
+ * <Accordion.Header>Header</Accordion.Header>
15
+ * <Accordion.Collapse eventKey="0" onEntered={onEntered}>
16
+ * <Accordion.Body>Content</Accordion.Body>
17
+ * </Accordion.Collapse>
18
+ * </Accordion.Item>
19
+ * </Accordion>
20
+ */
21
+ export function useAccordionScroll() {
22
+ const onEntered = useCallback((node) => {
23
+ const item = node.closest('.accordion-item');
24
+ if (!item) return;
25
+
26
+ const header = item.querySelector('.accordion-header');
27
+ if (!header) return;
28
+
29
+ const rect = header.getBoundingClientRect();
30
+ if (rect.top < 0) {
31
+ header.scrollIntoView({ behavior: 'smooth', block: 'start' });
32
+ }
33
+ }, []);
34
+
35
+ return { onEntered };
36
+ }
@@ -73,8 +73,9 @@ function getVimeoId(url) {
73
73
  * show.bs.modal / hidden.bs.modal event listeners.
74
74
  *
75
75
  * @param {object} options
76
- * @param {string} [options.youtubeSrc] - YouTube video URL
77
- * @param {string} [options.vimeoUrl] - Vimeo video URL
76
+ * @param {string} [options.youtubeSrc] - YouTube video URL
77
+ * @param {string} [options.vimeoUrl] - Vimeo video URL
78
+ * @param {function} [options.onReady] - Called with { provider, player, videoId } when the player is ready
78
79
  *
79
80
  * Usage:
80
81
  * const { containerRef, onShow, onHide } = useVideoModal({ youtubeSrc: '...' });
@@ -84,7 +85,7 @@ function getVimeoId(url) {
84
85
  * </div>
85
86
  * </Modal>
86
87
  */
87
- export function useVideoModal({ youtubeSrc, vimeoUrl } = {}) {
88
+ export function useVideoModal({ youtubeSrc, vimeoUrl, onReady } = {}) {
88
89
  const containerRef = useRef(null);
89
90
  const players = useRef({ youtube: null, vimeo: null });
90
91
 
@@ -95,26 +96,36 @@ export function useVideoModal({ youtubeSrc, vimeoUrl } = {}) {
95
96
 
96
97
  if (vimeoUrl) {
97
98
  await loadVimeoAPI();
99
+ const vimeoId = getVimeoId(vimeoUrl);
98
100
 
99
101
  players.current.vimeo = new globalThis.Vimeo.Player(container, {
100
- id: getVimeoId(vimeoUrl),
102
+ id: vimeoId,
101
103
  autoplay: true,
102
104
  });
103
105
 
106
+ if (onReady) {
107
+ onReady({ provider: 'vimeo', player: players.current.vimeo, videoId: vimeoId });
108
+ }
109
+
104
110
  return;
105
111
  }
106
112
 
107
113
  if (youtubeSrc) {
108
114
  await loadYouTubeAPI();
115
+ const youtubeId = getYouTubeId(youtubeSrc);
109
116
 
110
117
  const playerDiv = container.querySelector('[data-yt-player]');
111
118
 
112
119
  players.current.youtube = new globalThis.YT.Player(playerDiv, {
113
- videoId: getYouTubeId(youtubeSrc),
120
+ videoId: youtubeId,
114
121
  playerVars: { autoplay: 1, rel: 0 },
115
122
  });
123
+
124
+ if (onReady) {
125
+ onReady({ provider: 'youtube', player: players.current.youtube, videoId: youtubeId });
126
+ }
116
127
  }
117
- }, [youtubeSrc, vimeoUrl]);
128
+ }, [youtubeSrc, vimeoUrl, onReady]);
118
129
 
119
130
  const onHide = useCallback(() => {
120
131
  if (players.current.vimeo) {
@@ -1,2 +1,2 @@
1
1
  // GENERATED FILE – do not edit manually
2
- $km-version: "1.4.6" !default;
2
+ $km-version: "1.4.7" !default;