@unlk/keymaster 1.4.5 → 1.4.6

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Unlock Keymaster v1.4.5 (https://keymaster.unlock.com)
2
+ * Unlock Keymaster v1.4.6 (https://keymaster.unlock.com)
3
3
  * Copyright 2022-2026 Unlk Developers
4
4
  */
5
5
  (function (global, factory) {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * Unlock Keymaster v1.4.5 (https://keymaster.unlock.com)
2
+ * Unlock Keymaster v1.4.6 (https://keymaster.unlock.com)
3
3
  * Copyright 2022-2026 Unlk Developers
4
4
  */
5
5
  !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).keymaster={})}(this,function(e){"use strict";function t(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function n(e){if(Object.prototype.hasOwnProperty.call(e,"__esModule"))return e;var t=e.default;if("function"==typeof t){var n=function e(){var n=!1;try{n=this instanceof e}catch{}return n?Reflect.construct(t,arguments,this.constructor):t.apply(this,arguments)};n.prototype=t.prototype}else n={};return Object.defineProperty(n,"__esModule",{value:!0}),Object.keys(e).forEach(function(t){var i=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(n,t,i.get?i:{enumerable:!0,get:function(){return e[t]}})}),n}var i,s={exports:{}},o={exports:{}},r={exports:{}};function a(){return i||(i=1,function(e){e.exports=function(){const e=new Map,t={set(t,n,i){e.has(t)||e.set(t,new Map);const s=e.get(t);(s.has(n)||0===s.size)&&s.set(n,i)},get:(t,n)=>e.has(t)&&e.get(t).get(n)||null,remove(t,n){if(!e.has(t))return;const i=e.get(t);i.delete(n),0===i.size&&e.delete(t)}};return t}()}(r)),r.exports}var l,c={exports:{}},u={exports:{}};function h(){return l||(l=1,function(e){const t=1e6,n=1e3,i="transitionend",s=e=>(e&&window.CSS&&window.CSS.escape&&(e=e.replace(/#([^\s"#']+)/g,(e,t)=>`#${CSS.escape(t)}`)),e),o=e=>null==e?`${e}`:Object.prototype.toString.call(e).match(/\s([a-z]+)/i)[1].toLowerCase(),r=e=>{do{e+=Math.floor(Math.random()*t)}while(document.getElementById(e));return e},a=e=>{if(!e)return 0;let{transitionDuration:t,transitionDelay:i}=window.getComputedStyle(e);const s=Number.parseFloat(t),o=Number.parseFloat(i);return s||o?(t=t.split(",")[0],i=i.split(",")[0],(Number.parseFloat(t)+Number.parseFloat(i))*n):0},l=e=>{e.dispatchEvent(new Event(i))},c=e=>!(!e||"object"!=typeof e)&&(void 0!==e.jquery&&(e=e[0]),void 0!==e.nodeType),u=e=>c(e)?e.jquery?e[0]:e:"string"==typeof e&&e.length>0?document.querySelector(s(e)):null,h=e=>{if(!c(e)||0===e.getClientRects().length)return!1;const t="visible"===getComputedStyle(e).getPropertyValue("visibility"),n=e.closest("details:not([open])");if(!n)return t;if(n!==e){const t=e.closest("summary");if(t&&t.parentNode!==n)return!1;if(null===t)return!1}return t},d=e=>!e||e.nodeType!==Node.ELEMENT_NODE||!!e.classList.contains("disabled")||(void 0!==e.disabled?e.disabled:e.hasAttribute("disabled")&&"false"!==e.getAttribute("disabled")),f=e=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof e.getRootNode){const t=e.getRootNode();return t instanceof ShadowRoot?t:null}return e instanceof ShadowRoot?e:e.parentNode?f(e.parentNode):null},p=()=>{},g=e=>{e.offsetHeight},m=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,_=[],b=e=>{"loading"===document.readyState?(_.length||document.addEventListener("DOMContentLoaded",()=>{for(const e of _)e()}),_.push(e)):e()},v=()=>"rtl"===document.documentElement.dir,y=e=>{b(()=>{const t=m();if(t){const n=e.NAME,i=t.fn[n];t.fn[n]=e.jQueryInterface,t.fn[n].Constructor=e,t.fn[n].noConflict=()=>(t.fn[n]=i,e.jQueryInterface)}})},w=(e,t=[],n=e)=>"function"==typeof e?e.call(...t):n,E=(e,t,n=!0)=>{if(!n)return void w(e);const s=5,o=a(t)+s;let r=!1;const c=({target:n})=>{n===t&&(r=!0,t.removeEventListener(i,c),w(e))};t.addEventListener(i,c),setTimeout(()=>{r||l(t)},o)},A=(e,t,n,i)=>{const s=e.length;let o=e.indexOf(t);return-1===o?!n&&i?e[s-1]:e[0]:(o+=n?1:-1,i&&(o=(o+s)%s),e[Math.max(0,Math.min(o,s-1))])};e.defineJQueryPlugin=y,e.execute=w,e.executeAfterTransition=E,e.findShadowRoot=f,e.getElement=u,e.getNextActiveElement=A,e.getTransitionDurationFromElement=a,e.getUID=r,e.getjQuery=m,e.isDisabled=d,e.isElement=c,e.isRTL=v,e.isVisible=h,e.noop=p,e.onDOMContentLoaded=b,e.parseSelector=s,e.reflow=g,e.toType=o,e.triggerTransitionEnd=l,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})}(u.exports)),u.exports}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unlk/keymaster",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -28,10 +28,15 @@
28
28
  "dist-clean": "node build/dist-clean.js",
29
29
  "lint": "npm-run-all --aggregate-output --continue-on-error --parallel js-lint css-lint"
30
30
  },
31
+ "exports": {
32
+ "./*": "./*",
33
+ "./react": "./react/index.js"
34
+ },
31
35
  "files": [
32
36
  "dist",
33
37
  "scss",
34
38
  "js",
39
+ "react",
35
40
  "fonts",
36
41
  "README.md",
37
42
  "CHANGELOG.md"
@@ -51,7 +56,13 @@
51
56
  "homepage": "https://keymaster.unlock.com",
52
57
  "peerDependencies": {
53
58
  "@popperjs/core": "^2.11.0",
54
- "bootstrap": "^5.3.0"
59
+ "bootstrap": "^5.3.0",
60
+ "react": ">=18"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "react": {
64
+ "optional": true
65
+ }
55
66
  },
56
67
  "devDependencies": {
57
68
  "@babel/core": "^7.27.1",
package/react/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { useDatepicker } from './use-datepicker.js';
2
+ export { useCarouselCaption } from './use-carousel-caption.js';
3
+ export { useCarouselHeight } from './use-carousel-height.js';
4
+ export { useVideoModal } from './use-video-modal.js';
@@ -0,0 +1,24 @@
1
+ import { useState, useCallback } from 'react';
2
+
3
+ /**
4
+ * Replicates carousel-caption.js behaviour in React.
5
+ *
6
+ * Tracks the active slide index and returns the caption for that slide,
7
+ * replacing the Bootstrap slid.bs.carousel event listener.
8
+ *
9
+ * @param {Array<{ caption: string }>} slides - Array of slide data objects
10
+ *
11
+ * Usage:
12
+ * const { activeIndex, onSelect, caption } = useCarouselCaption(slides);
13
+ * <Carousel activeIndex={activeIndex} onSelect={onSelect}>...</Carousel>
14
+ * <div className="carousel-caption-container"><p>{caption}</p></div>
15
+ */
16
+ export function useCarouselCaption(slides) {
17
+ const [activeIndex, setActiveIndex] = useState(0);
18
+
19
+ const onSelect = useCallback((index) => setActiveIndex(index), []);
20
+
21
+ const caption = slides[activeIndex]?.caption ?? '';
22
+
23
+ return { activeIndex, onSelect, caption };
24
+ }
@@ -0,0 +1,39 @@
1
+ import { useRef, useCallback } from 'react';
2
+
3
+ /**
4
+ * Replicates carousel-height.js behaviour in React.
5
+ *
6
+ * Animates .carousel-inner height between slides of different heights,
7
+ * replacing the Bootstrap slide.bs.carousel / slid.bs.carousel listeners.
8
+ *
9
+ * Attach `ref` to the wrapping element or the <Carousel> element,
10
+ * and pass onSlide / onSlid to the <Carousel> component.
11
+ *
12
+ * Usage:
13
+ * const { ref, onSlide, onSlid } = useCarouselHeight();
14
+ * <Carousel ref={ref} onSlide={onSlide} onSlid={onSlid}>...</Carousel>
15
+ */
16
+ export function useCarouselHeight() {
17
+ const ref = useRef(null);
18
+
19
+ const onSlide = useCallback(() => {
20
+ const el = ref.current;
21
+ if (!el) return;
22
+ const inner = el.querySelector('.carousel-inner');
23
+ const next = el.querySelector('.carousel-item-next, .carousel-item-prev');
24
+ if (!inner || !next) return;
25
+ inner.style.height = `${inner.offsetHeight}px`;
26
+ requestAnimationFrame(() => {
27
+ inner.style.height = `${next.offsetHeight}px`;
28
+ });
29
+ }, []);
30
+
31
+ const onSlid = useCallback(() => {
32
+ const el = ref.current;
33
+ if (!el) return;
34
+ const inner = el.querySelector('.carousel-inner');
35
+ if (inner) inner.style.height = '';
36
+ }, []);
37
+
38
+ return { ref, onSlide, onSlid };
39
+ }
@@ -0,0 +1,33 @@
1
+ import { useState, useCallback } from 'react';
2
+
3
+ /**
4
+ * Replicates datepicker.js behaviour in React.
5
+ *
6
+ * Renders as type="text" (so the floating label placeholder shows),
7
+ * switches to type="date" on focus (opens the native date picker),
8
+ * and reverts on blur if no value is selected.
9
+ *
10
+ * Usage:
11
+ * const dp = useDatepicker();
12
+ * <input type={dp.type} className={dp.hasValue ? 'has-value' : ''}
13
+ * onFocus={dp.onFocus} onBlur={dp.onBlur} onChange={dp.onChange} />
14
+ */
15
+ export function useDatepicker() {
16
+ const [type, setType] = useState('text');
17
+ const [hasValue, setHasValue] = useState(false);
18
+
19
+ const onFocus = useCallback(() => setType('date'), []);
20
+
21
+ const onBlur = useCallback((e) => {
22
+ if (!e.target.value) {
23
+ setType('text');
24
+ setHasValue(false);
25
+ }
26
+ }, []);
27
+
28
+ const onChange = useCallback((e) => {
29
+ setHasValue(Boolean(e.target.value));
30
+ }, []);
31
+
32
+ return { type, hasValue, onFocus, onBlur, onChange };
33
+ }
@@ -0,0 +1,132 @@
1
+ import { useRef, useCallback } from 'react';
2
+
3
+ // Module-level promises so the API scripts are only ever loaded once
4
+ let ytAPIPromise = null;
5
+ let vimeoAPIPromise = null;
6
+
7
+ function loadYouTubeAPI() {
8
+ if (ytAPIPromise) return ytAPIPromise;
9
+
10
+ ytAPIPromise = new Promise((resolve) => {
11
+ if (globalThis.YT?.Player) {
12
+ resolve();
13
+
14
+ return;
15
+ }
16
+
17
+ const prev = globalThis.onYouTubeIframeAPIReady;
18
+
19
+ globalThis.onYouTubeIframeAPIReady = () => {
20
+ try {
21
+ if (typeof prev === 'function') prev();
22
+ } catch {}
23
+
24
+ resolve();
25
+ };
26
+
27
+ const s = document.createElement('script');
28
+
29
+ s.src = 'https://www.youtube.com/iframe_api';
30
+ document.head.append(s);
31
+ });
32
+
33
+ return ytAPIPromise;
34
+ }
35
+
36
+ function loadVimeoAPI() {
37
+ if (vimeoAPIPromise) return vimeoAPIPromise;
38
+
39
+ vimeoAPIPromise = new Promise((resolve) => {
40
+ if (globalThis.Vimeo?.Player) {
41
+ resolve();
42
+
43
+ return;
44
+ }
45
+
46
+ const s = document.createElement('script');
47
+
48
+ s.src = 'https://player.vimeo.com/api/player.js';
49
+ s.addEventListener('load', resolve);
50
+ document.head.append(s);
51
+ });
52
+
53
+ return vimeoAPIPromise;
54
+ }
55
+
56
+ function getYouTubeId(url) {
57
+ const m = url.match(/(?:youtube\.com\/(?:watch\?v=|embed\/|v\/)|youtu\.be\/)([\w-]{11})/);
58
+
59
+ return m?.[1] ?? '';
60
+ }
61
+
62
+ function getVimeoId(url) {
63
+ const m = url.match(/vimeo\.com\/(\d+)/);
64
+
65
+ return m?.[1] ?? '';
66
+ }
67
+
68
+ /**
69
+ * Replicates video-modal.js behaviour in React.
70
+ *
71
+ * Auto-initialises a YouTube or Vimeo player when the modal opens and
72
+ * destroys it when the modal closes, replacing the Bootstrap
73
+ * show.bs.modal / hidden.bs.modal event listeners.
74
+ *
75
+ * @param {object} options
76
+ * @param {string} [options.youtubeSrc] - YouTube video URL
77
+ * @param {string} [options.vimeoUrl] - Vimeo video URL
78
+ *
79
+ * Usage:
80
+ * const { containerRef, onShow, onHide } = useVideoModal({ youtubeSrc: '...' });
81
+ * <Modal onShow={onShow} onHide={onHide}>
82
+ * <div className="ratio ratio-16x9" ref={containerRef}>
83
+ * <div data-yt-player /> ← YouTube mounts here
84
+ * </div>
85
+ * </Modal>
86
+ */
87
+ export function useVideoModal({ youtubeSrc, vimeoUrl } = {}) {
88
+ const containerRef = useRef(null);
89
+ const players = useRef({ youtube: null, vimeo: null });
90
+
91
+ const onShow = useCallback(async () => {
92
+ const container = containerRef.current;
93
+
94
+ if (!container) return;
95
+
96
+ if (vimeoUrl) {
97
+ await loadVimeoAPI();
98
+
99
+ players.current.vimeo = new globalThis.Vimeo.Player(container, {
100
+ id: getVimeoId(vimeoUrl),
101
+ autoplay: true,
102
+ });
103
+
104
+ return;
105
+ }
106
+
107
+ if (youtubeSrc) {
108
+ await loadYouTubeAPI();
109
+
110
+ const playerDiv = container.querySelector('[data-yt-player]');
111
+
112
+ players.current.youtube = new globalThis.YT.Player(playerDiv, {
113
+ videoId: getYouTubeId(youtubeSrc),
114
+ playerVars: { autoplay: 1, rel: 0 },
115
+ });
116
+ }
117
+ }, [youtubeSrc, vimeoUrl]);
118
+
119
+ const onHide = useCallback(() => {
120
+ if (players.current.vimeo) {
121
+ players.current.vimeo.unload().catch(() => {});
122
+ players.current.vimeo = null;
123
+ }
124
+
125
+ if (players.current.youtube) {
126
+ players.current.youtube.destroy();
127
+ players.current.youtube = null;
128
+ }
129
+ }, []);
130
+
131
+ return { containerRef, onShow, onHide };
132
+ }
@@ -26,19 +26,19 @@
26
26
  @extend .mt-3;
27
27
  display: flex;
28
28
  justify-content: end;
29
- }
30
- .carousel-control-prev,
31
- .carousel-control-next {
32
- position: unset;
33
- width: fit-content;
34
- padding: rfs-value(6px);
35
- }
36
- .carousel-indicators {
37
- position: unset;
38
- margin-bottom: 0;
39
- margin-left: 0;
40
- @include margin-right($spacer * 1.5);
41
- [data-bs-target] {
42
- @extend .rounded-circle;
29
+ .carousel-control-prev,
30
+ .carousel-control-next {
31
+ position: unset;
32
+ width: fit-content;
33
+ padding: rfs-value(6px);
34
+ }
35
+ .carousel-indicators {
36
+ position: unset;
37
+ margin-bottom: 0;
38
+ margin-left: 0;
39
+ @include margin-right($spacer * 1.5);
40
+ [data-bs-target] {
41
+ @extend .rounded-circle;
42
+ }
43
43
  }
44
44
  }
@@ -1,2 +1,2 @@
1
1
  // GENERATED FILE – do not edit manually
2
- $km-version: "1.4.5" !default;
2
+ $km-version: "1.4.6" !default;