@gemboss/gem-framework 0.6.0 → 0.9.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 +63 -1
- package/dist/SOURCE.json +2 -2
- package/dist/gem-framework.css +206 -147
- package/dist/gem-runtime.js +93 -15
- package/dist/index.js +629 -178
- package/dist/plugins/before_after.d.ts +9 -0
- package/dist/plugins/before_after.js +37 -0
- package/dist/plugins/carousel.d.ts +9 -0
- package/dist/plugins/carousel.js +49 -0
- package/dist/plugins/cart-drawer.d.ts +9 -0
- package/dist/plugins/cart-drawer.js +69 -0
- package/dist/plugins/context-adapter.d.ts +9 -0
- package/dist/plugins/context-adapter.js +17 -0
- package/dist/plugins/countdown.d.ts +9 -0
- package/dist/plugins/countdown.js +164 -0
- package/dist/plugins/disclosure.d.ts +9 -0
- package/dist/plugins/disclosure.js +40 -0
- package/dist/plugins/hero-slideshow.d.ts +9 -0
- package/dist/plugins/hero-slideshow.js +57 -0
- package/dist/plugins/localization.d.ts +9 -0
- package/dist/plugins/localization.js +37 -0
- package/dist/plugins/media-gallery.d.ts +9 -0
- package/dist/plugins/media-gallery.js +57 -0
- package/dist/plugins/mobile-menu.d.ts +9 -0
- package/dist/plugins/mobile-menu.js +51 -0
- package/dist/plugins/offer-engine.d.ts +9 -0
- package/dist/plugins/offer-engine.js +57 -0
- package/dist/plugins/predictive-search.d.ts +9 -0
- package/dist/plugins/predictive-search.js +77 -0
- package/dist/plugins/promo-popup.d.ts +9 -0
- package/dist/plugins/promo-popup.js +117 -0
- package/dist/plugins/quantity-input.d.ts +9 -0
- package/dist/plugins/quantity-input.js +44 -0
- package/dist/plugins/quick-view.d.ts +9 -0
- package/dist/plugins/quick-view.js +69 -0
- package/dist/plugins/recently-viewed.d.ts +9 -0
- package/dist/plugins/recently-viewed.js +71 -0
- package/dist/plugins/reveal-on-scroll.d.ts +9 -0
- package/dist/plugins/reveal-on-scroll.js +37 -0
- package/dist/plugins/runtime-slot-loader.d.ts +9 -0
- package/dist/plugins/runtime-slot-loader.js +29 -0
- package/dist/plugins/seasonal-effects.d.ts +9 -0
- package/dist/plugins/seasonal-effects.js +17 -0
- package/dist/plugins/sticky-atc.d.ts +9 -0
- package/dist/plugins/sticky-atc.js +43 -0
- package/dist/plugins/tabs.d.ts +9 -0
- package/dist/plugins/tabs.js +50 -0
- package/dist/plugins/variant-selects.d.ts +9 -0
- package/dist/plugins/variant-selects.js +70 -0
- package/dist/popup-shell.css +91 -0
- package/dist/primitives.css +1430 -0
- package/dist/runtime-core.d.ts +51 -0
- package/dist/runtime-core.js +176 -0
- package/dist/section-gate.js +315 -86
- package/dist/sections.css +1116 -0
- package/dist/tokens.css +67 -0
- package/package.json +21 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/mobile-menu: types of the ES module next to this file. Generated; do not edit here.
|
|
2
|
+
import type { MountOptions, Unmount } from "../runtime-core.js";
|
|
3
|
+
|
|
4
|
+
export type { MountOptions, Unmount };
|
|
5
|
+
export declare const key: "mobile-menu";
|
|
6
|
+
export declare const readyEvent: "gem:menu:ready";
|
|
7
|
+
/** Bind every "mobile-menu" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
8
|
+
export declare function mount(root?: ParentNode | null, options?: MountOptions): Unmount;
|
|
9
|
+
export default mount;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/mobile-menu: the gem-runtime plugin "mobile-menu" as an ES module. Generated from
|
|
2
|
+
// packages/gem-framework/src/runtime.ts, the same lines gem-runtime.js carries; do not edit here.
|
|
3
|
+
import { mountPlugin } from "../runtime-core.js";
|
|
4
|
+
|
|
5
|
+
export const key = "mobile-menu";
|
|
6
|
+
export const readyEvent = "gem:menu:ready";
|
|
7
|
+
|
|
8
|
+
function install({ document, window, target, mark, canListen, listen: __gemListen, registerPlugin, registerNoopPlugin }) {
|
|
9
|
+
registerPlugin('mobile-menu', ({ api }) => {
|
|
10
|
+
const menus = document.querySelectorAll('[data-gem-mobile-menu], details.gem-shell-header__drawer');
|
|
11
|
+
let bound = 0;
|
|
12
|
+
menus.forEach((menu) => {
|
|
13
|
+
if (!mark(menu, 'mobile-menu')) return;
|
|
14
|
+
const setMenuA11y = () => {
|
|
15
|
+
menu.setAttribute && menu.setAttribute('aria-expanded', menu.open ? 'true' : 'false');
|
|
16
|
+
const panel = menu.querySelector && menu.querySelector('[data-gem-mobile-menu-panel], .gem-shell-header__drawer-panel');
|
|
17
|
+
if (panel && panel.setAttribute) panel.setAttribute('aria-hidden', menu.open ? 'false' : 'true');
|
|
18
|
+
};
|
|
19
|
+
menu.dataset.gemMenuState = menu.open ? 'open' : 'closed';
|
|
20
|
+
setMenuA11y();
|
|
21
|
+
if (!canListen(menu)) return;
|
|
22
|
+
__gemListen(menu, 'toggle', () => {
|
|
23
|
+
menu.dataset.gemMenuState = menu.open ? 'open' : 'closed';
|
|
24
|
+
setMenuA11y();
|
|
25
|
+
if (menu.open) {
|
|
26
|
+
const panel = menu.querySelector && menu.querySelector('[data-gem-mobile-menu-panel], .gem-shell-header__drawer-panel');
|
|
27
|
+
const firstFocusable = panel && panel.querySelector && panel.querySelector('a[href], button, select, input, textarea, summary, [tabindex]:not([tabindex="-1"])');
|
|
28
|
+
if (firstFocusable && firstFocusable.focus) firstFocusable.focus();
|
|
29
|
+
}
|
|
30
|
+
api.emit(menu.open ? 'gem:menu:open' : 'gem:menu:close', { source: 'gem-runtime', id: menu.id || undefined });
|
|
31
|
+
});
|
|
32
|
+
__gemListen(menu, 'keydown', (event) => {
|
|
33
|
+
if (event.key !== 'Escape' || !menu.open) return;
|
|
34
|
+
menu.open = false;
|
|
35
|
+
menu.dataset.gemMenuState = 'closed';
|
|
36
|
+
setMenuA11y();
|
|
37
|
+
const summary = menu.querySelector && menu.querySelector('summary');
|
|
38
|
+
if (summary && summary.focus) summary.focus();
|
|
39
|
+
});
|
|
40
|
+
bound += 1;
|
|
41
|
+
});
|
|
42
|
+
api.emit('gem:menu:ready', { source: 'gem-runtime', count: bound });
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Bind every "mobile-menu" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
47
|
+
export function mount(root, options) {
|
|
48
|
+
return mountPlugin(key, install, root, options);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default mount;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/offer-engine: types of the ES module next to this file. Generated; do not edit here.
|
|
2
|
+
import type { MountOptions, Unmount } from "../runtime-core.js";
|
|
3
|
+
|
|
4
|
+
export type { MountOptions, Unmount };
|
|
5
|
+
export declare const key: "offer-engine";
|
|
6
|
+
export declare const readyEvent: "gem:offer:ready";
|
|
7
|
+
/** Bind every "offer-engine" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
8
|
+
export declare function mount(root?: ParentNode | null, options?: MountOptions): Unmount;
|
|
9
|
+
export default mount;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/offer-engine: the gem-runtime plugin "offer-engine" as an ES module. Generated from
|
|
2
|
+
// packages/gem-framework/src/runtime.ts, the same lines gem-runtime.js carries; do not edit here.
|
|
3
|
+
import { mountPlugin } from "../runtime-core.js";
|
|
4
|
+
|
|
5
|
+
export const key = "offer-engine";
|
|
6
|
+
export const readyEvent = "gem:offer:ready";
|
|
7
|
+
|
|
8
|
+
function install({ document, window, target, mark, canListen, listen: __gemListen, registerPlugin, registerNoopPlugin }) {
|
|
9
|
+
registerPlugin('offer-engine', ({ api }) => {
|
|
10
|
+
(function(){
|
|
11
|
+
document.querySelectorAll('[data-gem-offer-engine]').forEach(function(root){
|
|
12
|
+
if(root.__gemOffer)return; root.__gemOffer=1;
|
|
13
|
+
var el=root.querySelector('[data-gem-offer-model]'); if(!el)return;
|
|
14
|
+
var model; try{model=JSON.parse(el.textContent);}catch(e){return;}
|
|
15
|
+
var dims=model.dimensions||{}, sel={};
|
|
16
|
+
Object.keys(dims).forEach(function(d){var o=(dims[d].options||[]),def=o.filter(function(x){return x.default;})[0]||o[0];sel[d]=(model.default_selection&&model.default_selection[d])||(def&&def.id);});
|
|
17
|
+
function m(w){return Object.keys(w).every(function(k){return sel[k]===w[k];});}
|
|
18
|
+
function sp(w){return Object.keys(w).length;}
|
|
19
|
+
function pick(list){return (list||[]).filter(function(r){return m(r.when);}).sort(function(a,b){return sp(b.when)-sp(a.when);})[0];}
|
|
20
|
+
function gift(g,t){if(!g||t==null)return null;var sym=g.currency_symbol||'$';var rem=Math.max(0,Math.round((g.threshold-t)*100)/100);var unl=rem<=0;var rs=sym+rem.toFixed(2);var pct=g.threshold>0?Math.max(0,Math.min(100,Math.round((t/g.threshold)*100))):100;var tpl=unl?(g.unlocked_text||'\uD83C\uDF81 {label} unlocked!'):(g.locked_text||'Add {remaining} more to unlock {label}');return {unlocked:unl,remaining:rs,remaining_amount:rem,percent:pct,text:tpl.replace('{remaining}',rs).replace('{label}',g.label)};}
|
|
21
|
+
function render(){
|
|
22
|
+
var row=pick(model.price_matrix)||{}, plan=pick(model.selling_plan_map);
|
|
23
|
+
var vis={}; Object.keys(dims).forEach(function(d){vis[d]=true;});
|
|
24
|
+
(model.conditions||[]).forEach(function(c){vis[c.show]=(sel[c.when_dim]===c.equals);});
|
|
25
|
+
(model.panels||[]).forEach(function(p){if(p.when)vis[p.id]=(sel[p.when.when_dim]===p.when.equals);});
|
|
26
|
+
var headline=row.total||row.price||'';
|
|
27
|
+
var gf=gift(model.gift,row.total_amount);
|
|
28
|
+
root.querySelectorAll('[data-gem-offer-out]').forEach(function(o){var k=o.getAttribute('data-gem-offer-out');
|
|
29
|
+
if(k==='price')o.textContent=row.price||'';
|
|
30
|
+
else if(k==='compare'){o.textContent=row.compare_at||'';o.style.display=row.compare_at?'':'none';}
|
|
31
|
+
else if(k==='save'){o.textContent=row.save_label||'';o.style.display=row.save_label?'':'none';}
|
|
32
|
+
else if(k==='gift'){o.textContent=gf?gf.text:'';if(o.classList)o.classList.toggle('unlocked',!!(gf&&gf.unlocked));}
|
|
33
|
+
else if(k==='cta')o.textContent=(model.cta_template||'Add to cart').replace('{price}',headline).replace('{total}',headline);});
|
|
34
|
+
root.querySelectorAll('[data-gem-offer-gift]').forEach(function(w){w.style.display=gf?'':'none';if(w.classList)w.classList.toggle('unlocked',!!(gf&&gf.unlocked));});
|
|
35
|
+
root.querySelectorAll('[data-gem-offer-gift-bar]').forEach(function(b){b.style.width=(gf?gf.percent:0)+'%';});
|
|
36
|
+
root.querySelectorAll('[data-gem-offer-group]').forEach(function(g){g.style.display=(vis[g.getAttribute('data-gem-offer-group')]===false)?'none':'';});
|
|
37
|
+
root.querySelectorAll('[data-gem-offer-dim]').forEach(function(b){var d=b.getAttribute('data-gem-offer-dim'),v=b.getAttribute('data-gem-offer-value');if(b.tagName==='OPTION'){b.selected=(sel[d]===v);}else{b.classList.toggle('sel',sel[d]===v);}});
|
|
38
|
+
var s=root.querySelector('[data-gem-offer-selling-plan]'); if(s)s.value=(plan&&plan.selling_plan_id)||'';
|
|
39
|
+
var ca=(plan&&plan.cart_attributes)||{};
|
|
40
|
+
root.querySelectorAll('[data-gem-offer-cart-field]').forEach(function(f){var n=f.getAttribute('data-gem-offer-cart-field');f.value=ca[n]||'';f.disabled=!(n in ca);});
|
|
41
|
+
root.dispatchEvent(new CustomEvent('gem:offer:change',{bubbles:true,detail:{selection:sel,row:row,selling_plan_id:(plan&&plan.selling_plan_id)||null,cart_attributes:ca,gift:gf}}));
|
|
42
|
+
}
|
|
43
|
+
__gemListen(root, 'click',function(e){var b=e.target.closest('[data-gem-offer-dim]');if(!b||b.tagName==='OPTION')return;sel[b.getAttribute('data-gem-offer-dim')]=b.getAttribute('data-gem-offer-value');render();});
|
|
44
|
+
__gemListen(root, 'change',function(e){var s=e.target.closest('select[data-gem-offer-select]');if(!s)return;var o=s.options[s.selectedIndex];sel[s.getAttribute('data-gem-offer-select')]=o.getAttribute('data-gem-offer-value');render();});
|
|
45
|
+
render();
|
|
46
|
+
});
|
|
47
|
+
})();
|
|
48
|
+
api.emit('gem:offer:ready', { source: 'gem-runtime' });
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Bind every "offer-engine" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
53
|
+
export function mount(root, options) {
|
|
54
|
+
return mountPlugin(key, install, root, options, {"selector":"[data-gem-offer-engine]","prop":"__gemOffer"});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default mount;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/predictive-search: types of the ES module next to this file. Generated; do not edit here.
|
|
2
|
+
import type { MountOptions, Unmount } from "../runtime-core.js";
|
|
3
|
+
|
|
4
|
+
export type { MountOptions, Unmount };
|
|
5
|
+
export declare const key: "predictive-search";
|
|
6
|
+
export declare const readyEvent: "gem:search:ready";
|
|
7
|
+
/** Bind every "predictive-search" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
8
|
+
export declare function mount(root?: ParentNode | null, options?: MountOptions): Unmount;
|
|
9
|
+
export default mount;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/predictive-search: the gem-runtime plugin "predictive-search" as an ES module. Generated from
|
|
2
|
+
// packages/gem-framework/src/runtime.ts, the same lines gem-runtime.js carries; do not edit here.
|
|
3
|
+
import { mountPlugin } from "../runtime-core.js";
|
|
4
|
+
|
|
5
|
+
export const key = "predictive-search";
|
|
6
|
+
export const readyEvent = "gem:search:ready";
|
|
7
|
+
|
|
8
|
+
function install({ document, window, target, mark, canListen, listen: __gemListen, registerPlugin, registerNoopPlugin }) {
|
|
9
|
+
registerPlugin('predictive-search', ({ api }) => {
|
|
10
|
+
const forms = document.querySelectorAll('[data-gem-predictive-search], [data-gem-search-canvas], form[action*="/search"], form[role="search"]');
|
|
11
|
+
const toggles = document.querySelectorAll('[data-gem-search-toggle]');
|
|
12
|
+
const canvas = document.querySelector('[data-gem-search-canvas]');
|
|
13
|
+
let bound = 0;
|
|
14
|
+
const setCanvasState = (state, reason = 'api') => {
|
|
15
|
+
if (!canvas || !canvas.dataset) return false;
|
|
16
|
+
canvas.dataset.gemSearchCanvasState = state;
|
|
17
|
+
canvas.setAttribute && canvas.setAttribute('aria-hidden', state === 'open' ? 'false' : 'true');
|
|
18
|
+
if (state === 'open') {
|
|
19
|
+
const input = canvas.querySelector && canvas.querySelector('input[type="search"], input[name="q"]');
|
|
20
|
+
if (input && input.focus) input.focus();
|
|
21
|
+
}
|
|
22
|
+
api.emit(state === 'open' ? 'gem:search:open' : 'gem:search:close', { source: 'gem-runtime', reason });
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
forms.forEach((form) => {
|
|
26
|
+
if (!mark(form, 'predictive-search')) return;
|
|
27
|
+
const input = form.querySelector && form.querySelector('input[type="search"], input[name="q"]');
|
|
28
|
+
const results = form.querySelector && form.querySelector('[data-gem-predictive-search-results]');
|
|
29
|
+
const setState = (state, value = '') => {
|
|
30
|
+
if (form.dataset) form.dataset.gemPredictiveSearchState = state;
|
|
31
|
+
if (results) {
|
|
32
|
+
results.hidden = state === 'closed';
|
|
33
|
+
if (results.dataset) results.dataset.gemPredictiveSearchState = state;
|
|
34
|
+
results.setAttribute && results.setAttribute('aria-hidden', state === 'closed' ? 'true' : 'false');
|
|
35
|
+
if (state === 'typing') results.textContent = value ? 'Search suggestions for ' + value : 'Start typing to search';
|
|
36
|
+
if (state === 'empty') results.textContent = 'Start typing to search';
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
if (results) {
|
|
40
|
+
results.hidden = true;
|
|
41
|
+
results.setAttribute && results.setAttribute('role', 'status');
|
|
42
|
+
results.setAttribute && results.setAttribute('aria-live', 'polite');
|
|
43
|
+
}
|
|
44
|
+
setState('closed');
|
|
45
|
+
if (canListen(input)) __gemListen(input, 'focus', () => { setState(input.value ? 'typing' : 'empty', input.value || ''); api.emit('gem:search:open', { source: 'gem-runtime', value: input.value || '' }); });
|
|
46
|
+
const closeOnEscape = (event) => { if (event.key === 'Escape') { event.preventDefault && event.preventDefault(); event.stopPropagation && event.stopPropagation(); setState('closed'); api.emit('gem:search:close', { source: 'gem-runtime', reason: 'escape' }); } };
|
|
47
|
+
if (canListen(input)) __gemListen(input, 'input', () => { setState(input.value ? 'typing' : 'empty', input.value || ''); api.emit('gem:search:input', { source: 'gem-runtime', value: input.value || '' }); });
|
|
48
|
+
if (canListen(input)) __gemListen(input, 'keydown', closeOnEscape);
|
|
49
|
+
if (canListen(form)) __gemListen(form, 'keydown', closeOnEscape);
|
|
50
|
+
if (canListen(form)) __gemListen(form, 'submit', () => { setState('submit', input && input.value || ''); api.emit('gem:search:submit', { source: 'gem-runtime', value: input && input.value || '' }); });
|
|
51
|
+
bound += 1;
|
|
52
|
+
});
|
|
53
|
+
if (canvas) {
|
|
54
|
+
canvas.dataset && (canvas.dataset.gemSearchCanvasState = 'closed');
|
|
55
|
+
canvas.setAttribute && canvas.setAttribute('aria-hidden', 'true');
|
|
56
|
+
if (mark(canvas, 'search-canvas')) bound += 1;
|
|
57
|
+
if (canListen(canvas)) __gemListen(canvas, 'keydown', (event) => { if (event.key === 'Escape') setCanvasState('closed', 'escape'); });
|
|
58
|
+
}
|
|
59
|
+
toggles.forEach((toggle) => {
|
|
60
|
+
if (!mark(toggle, 'search-toggle')) return;
|
|
61
|
+
if (!canListen(toggle)) return;
|
|
62
|
+
__gemListen(toggle, 'click', (event) => {
|
|
63
|
+
if (canvas) event.preventDefault && event.preventDefault();
|
|
64
|
+
setCanvasState('open', 'toggle');
|
|
65
|
+
});
|
|
66
|
+
bound += 1;
|
|
67
|
+
});
|
|
68
|
+
api.emit('gem:search:ready', { source: 'gem-runtime', count: bound });
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Bind every "predictive-search" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
73
|
+
export function mount(root, options) {
|
|
74
|
+
return mountPlugin(key, install, root, options);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default mount;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/promo-popup: types of the ES module next to this file. Generated; do not edit here.
|
|
2
|
+
import type { MountOptions, Unmount } from "../runtime-core.js";
|
|
3
|
+
|
|
4
|
+
export type { MountOptions, Unmount };
|
|
5
|
+
export declare const key: "promo-popup";
|
|
6
|
+
export declare const readyEvent: "gem:promo-popup:ready";
|
|
7
|
+
/** Bind every "promo-popup" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
8
|
+
export declare function mount(root?: ParentNode | null, options?: MountOptions): Unmount;
|
|
9
|
+
export default mount;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/promo-popup: the gem-runtime plugin "promo-popup" as an ES module. Generated from
|
|
2
|
+
// packages/gem-framework/src/runtime.ts, the same lines gem-runtime.js carries; do not edit here.
|
|
3
|
+
import { mountPlugin } from "../runtime-core.js";
|
|
4
|
+
|
|
5
|
+
export const key = "promo-popup";
|
|
6
|
+
export const readyEvent = "gem:promo-popup:ready";
|
|
7
|
+
|
|
8
|
+
function install({ document, window, target, mark, canListen, listen: __gemListen, registerPlugin, registerNoopPlugin }) {
|
|
9
|
+
registerPlugin('promo-popup', ({ api }) => {
|
|
10
|
+
const popups = document.querySelectorAll('[data-gem-promo-popup]');
|
|
11
|
+
let bound = 0;
|
|
12
|
+
const shown = [];
|
|
13
|
+
const storagePrefix = 'gem:promo-popup-dismissed:';
|
|
14
|
+
const now = () => Date.now ? Date.now() : new Date().getTime();
|
|
15
|
+
const readDismissal = (key) => {
|
|
16
|
+
try { const value = Number(target.localStorage && target.localStorage.getItem(storagePrefix + key) || '0'); return Number.isFinite(value) && value > 0 ? value : 0; }
|
|
17
|
+
catch { return 0; }
|
|
18
|
+
};
|
|
19
|
+
const writeDismissedAt = (key, timestamp) => {
|
|
20
|
+
try { if (target.localStorage) target.localStorage.setItem(storagePrefix + key, String(timestamp)); }
|
|
21
|
+
catch { /* storage can be unavailable in private contexts */ }
|
|
22
|
+
};
|
|
23
|
+
const isDismissed = (key, windowMs) => {
|
|
24
|
+
const stored = readDismissal(key);
|
|
25
|
+
if (!stored) return false;
|
|
26
|
+
const at = now();
|
|
27
|
+
if (stored > at) { if (stored - windowMs <= at) writeDismissedAt(key, stored - windowMs); return true; }
|
|
28
|
+
return at - stored < windowMs;
|
|
29
|
+
};
|
|
30
|
+
popups.forEach((popup, index) => {
|
|
31
|
+
if (!mark(popup, 'promo-popup')) return;
|
|
32
|
+
const key = popup.id || popup.getAttribute && popup.getAttribute('data-gem-section-library-id') || String(index);
|
|
33
|
+
const delaySeconds = Number(popup.getAttribute && (popup.getAttribute('data-gem-promo-delay') || '0'));
|
|
34
|
+
const frequencyDays = Number(popup.getAttribute && (popup.getAttribute('data-gem-promo-frequency-days') || '7'));
|
|
35
|
+
const windowMs = (Number.isFinite(frequencyDays) ? Math.max(1, frequencyDays) : 7) * 86400000;
|
|
36
|
+
const dialog = popup.querySelector && popup.querySelector('[data-gem-promo-popup-dialog]');
|
|
37
|
+
const overlay = popup.querySelector && popup.querySelector('[data-gem-promo-popup-overlay]');
|
|
38
|
+
const show = (reason) => {
|
|
39
|
+
popup.hidden = false;
|
|
40
|
+
if (dialog) dialog.hidden = false;
|
|
41
|
+
if (overlay) overlay.hidden = false;
|
|
42
|
+
popup.dataset.gemPromoPopupState = 'open';
|
|
43
|
+
api.emit('gem:promo-popup:open', { source: 'gem-runtime', id: popup.id || undefined, reason });
|
|
44
|
+
};
|
|
45
|
+
const open = (reason = 'timer') => {
|
|
46
|
+
if (isDismissed(key, windowMs)) { popup.dataset.gemPromoPopupState = 'dismissed'; return false; }
|
|
47
|
+
show(reason);
|
|
48
|
+
return true;
|
|
49
|
+
};
|
|
50
|
+
const close = (reason = 'dismiss') => {
|
|
51
|
+
writeDismissedAt(key, now());
|
|
52
|
+
if (dialog) dialog.hidden = true;
|
|
53
|
+
if (overlay) overlay.hidden = true;
|
|
54
|
+
popup.dataset.gemPromoPopupState = 'closed';
|
|
55
|
+
api.emit('gem:promo-popup:close', { source: 'gem-runtime', id: popup.id || undefined, reason });
|
|
56
|
+
};
|
|
57
|
+
popup.querySelectorAll && popup.querySelectorAll('[data-gem-promo-popup-dismiss]').forEach((button) => {
|
|
58
|
+
if (!canListen(button)) return;
|
|
59
|
+
__gemListen(button, 'click', (event) => { event.preventDefault && event.preventDefault(); close('dismiss'); });
|
|
60
|
+
});
|
|
61
|
+
if (canListen(overlay)) __gemListen(overlay, 'click', () => close('overlay'));
|
|
62
|
+
popup.dataset.gemPromoPopupState = 'ready';
|
|
63
|
+
const delay = Math.max(0, Number.isFinite(delaySeconds) ? delaySeconds : 0) * 1000;
|
|
64
|
+
const trigger = String(popup.getAttribute && popup.getAttribute('data-gem-promo-trigger') || 'delay').trim();
|
|
65
|
+
const rawPercent = String(popup.getAttribute && popup.getAttribute('data-gem-promo-scroll-percent') || '').trim();
|
|
66
|
+
const parsedPercent = rawPercent ? Number(rawPercent) : NaN;
|
|
67
|
+
const scrollPercent = Number.isFinite(parsedPercent) ? Math.min(90, Math.max(10, parsedPercent)) : 50;
|
|
68
|
+
const canHover = () => { try { return Boolean(target.matchMedia && target.matchMedia('(hover: hover) and (pointer: fine)').matches); } catch { return false; } };
|
|
69
|
+
const armed = trigger === 'exit_intent' && canHover() ? 'exit_intent' : trigger === 'scroll' ? 'scroll' : 'delay';
|
|
70
|
+
if (popup.setAttribute) popup.setAttribute('data-gem-promo-armed', armed);
|
|
71
|
+
let opened = false;
|
|
72
|
+
const disarm = [];
|
|
73
|
+
const openOnce = (reason) => { if (opened) return; opened = true; disarm.splice(0).forEach((off) => off()); open(reason); };
|
|
74
|
+
shown.push((reason) => { opened = true; disarm.splice(0).forEach((off) => off()); if (popup.dataset.gemPromoPopupState !== 'open') show(reason); });
|
|
75
|
+
if (armed === 'exit_intent') {
|
|
76
|
+
const onMouseOut = (event) => { if (event.relatedTarget || event.clientY >= 10) return; openOnce('exit_intent'); };
|
|
77
|
+
if (canListen(document)) {
|
|
78
|
+
__gemListen(document, 'mouseout', onMouseOut);
|
|
79
|
+
disarm.push(() => document.removeEventListener && document.removeEventListener('mouseout', onMouseOut));
|
|
80
|
+
}
|
|
81
|
+
} else if (armed === 'scroll') {
|
|
82
|
+
const room = () => Number(document.documentElement && document.documentElement.scrollHeight || 0) - Number(target.innerHeight || 0);
|
|
83
|
+
const check = () => { const r = room(); if (r > 0 && (Number(target.scrollY || target.pageYOffset || 0) / r) * 100 >= scrollPercent) openOnce('scroll'); };
|
|
84
|
+
let ticking = false;
|
|
85
|
+
const onScroll = () => {
|
|
86
|
+
if (ticking) return;
|
|
87
|
+
ticking = true;
|
|
88
|
+
const frame = target.requestAnimationFrame ? (fn) => target.requestAnimationFrame(fn) : (fn) => target.setTimeout && target.setTimeout(fn, 16);
|
|
89
|
+
frame(() => { ticking = false; check(); });
|
|
90
|
+
};
|
|
91
|
+
if (canListen(target)) {
|
|
92
|
+
__gemListen(target, 'scroll', onScroll, { passive: true });
|
|
93
|
+
__gemListen(target, 'resize', check);
|
|
94
|
+
__gemListen(target, 'load', check);
|
|
95
|
+
disarm.push(() => { if (!target.removeEventListener) return; target.removeEventListener('scroll', onScroll); target.removeEventListener('resize', check); target.removeEventListener('load', check); });
|
|
96
|
+
}
|
|
97
|
+
if (target.setTimeout) {
|
|
98
|
+
const fallback = target.setTimeout(() => { if (room() <= 0) openOnce('timer'); }, delay);
|
|
99
|
+
disarm.push(() => target.clearTimeout && target.clearTimeout(fallback));
|
|
100
|
+
}
|
|
101
|
+
check();
|
|
102
|
+
} else {
|
|
103
|
+
target.setTimeout ? target.setTimeout(() => openOnce('timer'), delay) : openOnce('timer');
|
|
104
|
+
}
|
|
105
|
+
bound += 1;
|
|
106
|
+
});
|
|
107
|
+
api.promoPopup = { open: () => shown.forEach((show) => show('api')) };
|
|
108
|
+
api.emit('gem:promo-popup:ready', { source: 'gem-runtime', count: bound });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Bind every "promo-popup" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
113
|
+
export function mount(root, options) {
|
|
114
|
+
return mountPlugin(key, install, root, options);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default mount;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/quantity-input: types of the ES module next to this file. Generated; do not edit here.
|
|
2
|
+
import type { MountOptions, Unmount } from "../runtime-core.js";
|
|
3
|
+
|
|
4
|
+
export type { MountOptions, Unmount };
|
|
5
|
+
export declare const key: "quantity-input";
|
|
6
|
+
export declare const readyEvent: "gem:quantity:ready";
|
|
7
|
+
/** Bind every "quantity-input" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
8
|
+
export declare function mount(root?: ParentNode | null, options?: MountOptions): Unmount;
|
|
9
|
+
export default mount;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/quantity-input: the gem-runtime plugin "quantity-input" as an ES module. Generated from
|
|
2
|
+
// packages/gem-framework/src/runtime.ts, the same lines gem-runtime.js carries; do not edit here.
|
|
3
|
+
import { mountPlugin } from "../runtime-core.js";
|
|
4
|
+
|
|
5
|
+
export const key = "quantity-input";
|
|
6
|
+
export const readyEvent = "gem:quantity:ready";
|
|
7
|
+
|
|
8
|
+
function install({ document, window, target, mark, canListen, listen: __gemListen, registerPlugin, registerNoopPlugin }) {
|
|
9
|
+
registerPlugin('quantity-input', ({ api }) => {
|
|
10
|
+
const controls = document.querySelectorAll('[data-gem-quantity-input], .gem-quantity-input');
|
|
11
|
+
let bound = 0;
|
|
12
|
+
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
13
|
+
controls.forEach((control) => {
|
|
14
|
+
if (!mark(control, 'quantity-input')) return;
|
|
15
|
+
const input = control.querySelector && control.querySelector('input[type="number"]');
|
|
16
|
+
if (!input) return;
|
|
17
|
+
const setValue = (nextValue, reason) => {
|
|
18
|
+
const min = Number.isFinite(Number(input.min)) ? Number(input.min) : 0;
|
|
19
|
+
const max = Number.isFinite(Number(input.max)) && input.max !== '' ? Number(input.max) : Number.MAX_SAFE_INTEGER;
|
|
20
|
+
const value = clamp(Number(nextValue) || min, min, max);
|
|
21
|
+
input.value = String(value);
|
|
22
|
+
api.emit('gem:quantity:change', { source: 'gem-runtime', value, reason, name: input.name || undefined });
|
|
23
|
+
};
|
|
24
|
+
if (!canListen(control) || !canListen(input)) return;
|
|
25
|
+
__gemListen(control, 'click', (event) => {
|
|
26
|
+
const button = event.target && event.target.closest ? event.target.closest('[data-gem-quantity-change]') : null;
|
|
27
|
+
if (!button) return;
|
|
28
|
+
event.preventDefault && event.preventDefault();
|
|
29
|
+
const delta = Number(button.getAttribute('data-gem-quantity-change') || '0');
|
|
30
|
+
setValue(Number(input.value || 0) + delta, delta > 0 ? 'increase' : 'decrease');
|
|
31
|
+
});
|
|
32
|
+
__gemListen(input, 'change', () => setValue(input.value, 'input'));
|
|
33
|
+
bound += 1;
|
|
34
|
+
});
|
|
35
|
+
api.emit('gem:quantity:ready', { source: 'gem-runtime', count: bound });
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Bind every "quantity-input" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
40
|
+
export function mount(root, options) {
|
|
41
|
+
return mountPlugin(key, install, root, options);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default mount;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/quick-view: types of the ES module next to this file. Generated; do not edit here.
|
|
2
|
+
import type { MountOptions, Unmount } from "../runtime-core.js";
|
|
3
|
+
|
|
4
|
+
export type { MountOptions, Unmount };
|
|
5
|
+
export declare const key: "quick-view";
|
|
6
|
+
export declare const readyEvent: "gem:quick-view:ready";
|
|
7
|
+
/** Bind every "quick-view" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
8
|
+
export declare function mount(root?: ParentNode | null, options?: MountOptions): Unmount;
|
|
9
|
+
export default mount;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/quick-view: the gem-runtime plugin "quick-view" as an ES module. Generated from
|
|
2
|
+
// packages/gem-framework/src/runtime.ts, the same lines gem-runtime.js carries; do not edit here.
|
|
3
|
+
import { mountPlugin } from "../runtime-core.js";
|
|
4
|
+
|
|
5
|
+
export const key = "quick-view";
|
|
6
|
+
export const readyEvent = "gem:quick-view:ready";
|
|
7
|
+
|
|
8
|
+
function install({ document, window, target, mark, canListen, listen: __gemListen, registerPlugin, registerNoopPlugin }) {
|
|
9
|
+
registerPlugin('quick-view', ({ api }) => {
|
|
10
|
+
const triggers = document.querySelectorAll('[data-gem-quick-view], [data-gem-action="quick-view"]');
|
|
11
|
+
const surface = document.querySelector('[data-gem-quick-view-surface], .gem-quick-view, .gem-modal[data-gem-component="quick-view"]');
|
|
12
|
+
let bound = 0;
|
|
13
|
+
const close = (reason = 'close-control') => {
|
|
14
|
+
if (surface) {
|
|
15
|
+
surface.dataset.gemQuickViewState = 'closed';
|
|
16
|
+
surface.removeAttribute && surface.removeAttribute('open');
|
|
17
|
+
surface.setAttribute && surface.setAttribute('aria-hidden', 'true');
|
|
18
|
+
}
|
|
19
|
+
api.emit('gem:quick-view:close', { source: 'gem-runtime', reason });
|
|
20
|
+
};
|
|
21
|
+
const openSurface = (detail) => {
|
|
22
|
+
if (surface) {
|
|
23
|
+
surface.dataset.gemQuickViewState = 'open';
|
|
24
|
+
surface.dataset.gemProductHandle = detail.productHandle || '';
|
|
25
|
+
surface.dataset.gemProductUrl = detail.productUrl || '';
|
|
26
|
+
surface.setAttribute && surface.setAttribute('open', '');
|
|
27
|
+
surface.setAttribute && surface.setAttribute('aria-hidden', 'false');
|
|
28
|
+
surface.setAttribute && surface.setAttribute('role', 'dialog');
|
|
29
|
+
surface.setAttribute && surface.setAttribute('aria-modal', 'true');
|
|
30
|
+
const content = surface.querySelector && surface.querySelector('[data-gem-quick-view-content]');
|
|
31
|
+
if (content && content.textContent !== undefined) content.textContent = 'Quick view for ' + (detail.productHandle || 'product');
|
|
32
|
+
else if (surface.textContent !== undefined) surface.textContent = 'Quick view for ' + (detail.productHandle || 'product');
|
|
33
|
+
const closeButton = surface.querySelector && surface.querySelector('[data-gem-quick-view-close]');
|
|
34
|
+
if (closeButton && closeButton.focus) closeButton.focus();
|
|
35
|
+
}
|
|
36
|
+
api.emit('gem:quick-view:open', { source: 'gem-runtime', ...detail, hasSurface: Boolean(surface) });
|
|
37
|
+
};
|
|
38
|
+
triggers.forEach((trigger) => {
|
|
39
|
+
if (!mark(trigger, 'quick-view')) return;
|
|
40
|
+
if (!canListen(trigger)) return;
|
|
41
|
+
__gemListen(trigger, 'click', (event) => {
|
|
42
|
+
const productUrl = trigger.getAttribute && (trigger.getAttribute('data-gem-product-url') || trigger.getAttribute('href'));
|
|
43
|
+
const productHandle = trigger.getAttribute && trigger.getAttribute('data-gem-product-handle');
|
|
44
|
+
if (!productUrl && !productHandle) return;
|
|
45
|
+
event.preventDefault && event.preventDefault();
|
|
46
|
+
openSurface({ productUrl, productHandle });
|
|
47
|
+
});
|
|
48
|
+
bound += 1;
|
|
49
|
+
});
|
|
50
|
+
if (surface && mark(surface, 'quick-view-surface')) {
|
|
51
|
+
surface.dataset.gemQuickViewState = 'closed';
|
|
52
|
+
surface.setAttribute && surface.setAttribute('aria-hidden', 'true');
|
|
53
|
+
if (canListen(surface)) __gemListen(surface, 'click', (event) => {
|
|
54
|
+
const target = event.target;
|
|
55
|
+
if (target && target.closest && target.closest('[data-gem-quick-view-close]')) close('close-control');
|
|
56
|
+
});
|
|
57
|
+
if (canListen(surface)) __gemListen(surface, 'keydown', (event) => { if (event.key === 'Escape') close('escape'); });
|
|
58
|
+
}
|
|
59
|
+
api.quickView = { open: (detail = {}) => openSurface({ ...detail }), close };
|
|
60
|
+
api.emit('gem:quick-view:ready', { source: 'gem-runtime', count: bound, hasSurface: Boolean(surface) });
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Bind every "quick-view" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
65
|
+
export function mount(root, options) {
|
|
66
|
+
return mountPlugin(key, install, root, options);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default mount;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/recently-viewed: types of the ES module next to this file. Generated; do not edit here.
|
|
2
|
+
import type { MountOptions, Unmount } from "../runtime-core.js";
|
|
3
|
+
|
|
4
|
+
export type { MountOptions, Unmount };
|
|
5
|
+
export declare const key: "recently-viewed";
|
|
6
|
+
export declare const readyEvent: "gem:recently-viewed:ready";
|
|
7
|
+
/** Bind every "recently-viewed" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
8
|
+
export declare function mount(root?: ParentNode | null, options?: MountOptions): Unmount;
|
|
9
|
+
export default mount;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/recently-viewed: the gem-runtime plugin "recently-viewed" as an ES module. Generated from
|
|
2
|
+
// packages/gem-framework/src/runtime.ts, the same lines gem-runtime.js carries; do not edit here.
|
|
3
|
+
import { mountPlugin } from "../runtime-core.js";
|
|
4
|
+
|
|
5
|
+
export const key = "recently-viewed";
|
|
6
|
+
export const readyEvent = "gem:recently-viewed:ready";
|
|
7
|
+
|
|
8
|
+
function install({ document, window, target, mark, canListen, listen: __gemListen, registerPlugin, registerNoopPlugin }) {
|
|
9
|
+
registerPlugin('recently-viewed', ({ api }) => {
|
|
10
|
+
const storageKey = 'gem:recently-viewed-products';
|
|
11
|
+
const readItems = () => {
|
|
12
|
+
try { return JSON.parse(target.localStorage && target.localStorage.getItem(storageKey) || '[]'); }
|
|
13
|
+
catch { return []; }
|
|
14
|
+
};
|
|
15
|
+
const writeItems = (items) => {
|
|
16
|
+
try { if (target.localStorage) target.localStorage.setItem(storageKey, JSON.stringify(items.slice(0, 12))); }
|
|
17
|
+
catch { /* storage can be unavailable in private contexts */ }
|
|
18
|
+
};
|
|
19
|
+
const escapeHtml = (value) => String(value || '').replace(/[&<>'"]/g, (char) => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[char]));
|
|
20
|
+
const renderItem = (item) => {
|
|
21
|
+
const url = item && item.url ? String(item.url) : '#';
|
|
22
|
+
const label = item && (item.title || item.handle || item.url) ? String(item.title || item.handle || item.url) : 'Recently viewed product';
|
|
23
|
+
return '<a class="gem-card gem-recently-viewed__item" href="' + escapeHtml(url) + '"><strong>' + escapeHtml(label) + '</strong></a>';
|
|
24
|
+
};
|
|
25
|
+
const current = document.querySelector('[data-gem-current-product], [data-gem-product-handle]');
|
|
26
|
+
if (current) {
|
|
27
|
+
const handle = current.getAttribute && current.getAttribute('data-gem-product-handle');
|
|
28
|
+
const url = current.getAttribute && (current.getAttribute('data-gem-product-url') || current.getAttribute('href'));
|
|
29
|
+
const title = current.getAttribute && current.getAttribute('data-gem-product-title');
|
|
30
|
+
if (handle || url) {
|
|
31
|
+
const nextItem = { handle, url, title, recordedAt: new Date().toISOString() };
|
|
32
|
+
const items = [nextItem, ...readItems().filter((item) => item && item.handle !== handle && item.url !== url)];
|
|
33
|
+
writeItems(items);
|
|
34
|
+
api.emit('gem:recently-viewed:record', { source: 'gem-runtime', handle, url });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const rails = document.querySelectorAll('[data-gem-recently-viewed], .gem-recently-viewed');
|
|
38
|
+
let bound = 0;
|
|
39
|
+
rails.forEach((rail) => {
|
|
40
|
+
if (!mark(rail, 'recently-viewed')) return;
|
|
41
|
+
const limit = Number(rail.getAttribute && (rail.getAttribute('data-gem-recently-viewed-limit') || '4'));
|
|
42
|
+
const items = readItems().slice(0, Number.isFinite(limit) ? limit : 4);
|
|
43
|
+
const list = rail.querySelector && (rail.querySelector('[data-gem-recently-viewed-list]') || rail);
|
|
44
|
+
const empty = rail.querySelector && rail.querySelector('[data-gem-recently-viewed-empty]');
|
|
45
|
+
rail.dataset.gemRecentlyViewedCount = String(items.length);
|
|
46
|
+
if (items.length > 0) {
|
|
47
|
+
rail.dataset.gemRecentlyViewedState = 'ready';
|
|
48
|
+
if (list) {
|
|
49
|
+
list.hidden = false;
|
|
50
|
+
if (list.dataset) list.dataset.gemRecentlyViewedCount = String(items.length);
|
|
51
|
+
if ('innerHTML' in list) list.innerHTML = items.map(renderItem).join('');
|
|
52
|
+
else if ('textContent' in list) list.textContent = items.map((item) => item && (item.title || item.handle || item.url) || 'Recently viewed product').join(', ');
|
|
53
|
+
}
|
|
54
|
+
if (empty) empty.hidden = true;
|
|
55
|
+
} else {
|
|
56
|
+
rail.dataset.gemRecentlyViewedState = 'empty';
|
|
57
|
+
if (empty) empty.hidden = false;
|
|
58
|
+
}
|
|
59
|
+
bound += 1;
|
|
60
|
+
});
|
|
61
|
+
api.recentlyViewed = { list: readItems };
|
|
62
|
+
api.emit('gem:recently-viewed:ready', { source: 'gem-runtime', count: bound });
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Bind every "recently-viewed" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
67
|
+
export function mount(root, options) {
|
|
68
|
+
return mountPlugin(key, install, root, options);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default mount;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/reveal-on-scroll: types of the ES module next to this file. Generated; do not edit here.
|
|
2
|
+
import type { MountOptions, Unmount } from "../runtime-core.js";
|
|
3
|
+
|
|
4
|
+
export type { MountOptions, Unmount };
|
|
5
|
+
export declare const key: "reveal-on-scroll";
|
|
6
|
+
export declare const readyEvent: "gem:reveal:ready";
|
|
7
|
+
/** Bind every "reveal-on-scroll" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
8
|
+
export declare function mount(root?: ParentNode | null, options?: MountOptions): Unmount;
|
|
9
|
+
export default mount;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/reveal-on-scroll: the gem-runtime plugin "reveal-on-scroll" as an ES module. Generated from
|
|
2
|
+
// packages/gem-framework/src/runtime.ts, the same lines gem-runtime.js carries; do not edit here.
|
|
3
|
+
import { mountPlugin } from "../runtime-core.js";
|
|
4
|
+
|
|
5
|
+
export const key = "reveal-on-scroll";
|
|
6
|
+
export const readyEvent = "gem:reveal:ready";
|
|
7
|
+
|
|
8
|
+
function install({ document, window, target, mark, canListen, listen: __gemListen, registerPlugin, registerNoopPlugin }) {
|
|
9
|
+
registerPlugin('reveal-on-scroll', ({ api }) => {
|
|
10
|
+
const nodes = document.querySelectorAll('[data-gem-reveal], .gem-reveal');
|
|
11
|
+
let bound = 0;
|
|
12
|
+
const reveal = (node) => { node.dataset.gemRevealState = 'in'; };
|
|
13
|
+
if (!target.IntersectionObserver) { nodes.forEach(reveal); api.emit('gem:reveal:ready', { source: 'gem-runtime', count: 0 }); return; }
|
|
14
|
+
const observer = new target.IntersectionObserver((entries, obs) => {
|
|
15
|
+
entries.forEach((entry) => {
|
|
16
|
+
if (!entry.isIntersecting) return;
|
|
17
|
+
reveal(entry.target);
|
|
18
|
+
obs.unobserve(entry.target);
|
|
19
|
+
api.emit('gem:reveal:in', { source: 'gem-runtime', id: entry.target.id || undefined });
|
|
20
|
+
});
|
|
21
|
+
}, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });
|
|
22
|
+
nodes.forEach((node) => {
|
|
23
|
+
if (!mark(node, 'reveal-on-scroll')) return;
|
|
24
|
+
if (!node.dataset.gemRevealState) node.dataset.gemRevealState = 'ready';
|
|
25
|
+
observer.observe(node);
|
|
26
|
+
bound += 1;
|
|
27
|
+
});
|
|
28
|
+
api.emit('gem:reveal:ready', { source: 'gem-runtime', count: bound });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Bind every "reveal-on-scroll" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
33
|
+
export function mount(root, options) {
|
|
34
|
+
return mountPlugin(key, install, root, options);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default mount;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @gemboss/gem-framework/plugins/runtime-slot-loader: types of the ES module next to this file. Generated; do not edit here.
|
|
2
|
+
import type { MountOptions, Unmount } from "../runtime-core.js";
|
|
3
|
+
|
|
4
|
+
export type { MountOptions, Unmount };
|
|
5
|
+
export declare const key: "runtime-slot-loader";
|
|
6
|
+
export declare const readyEvent: "gem:slot:ready";
|
|
7
|
+
/** Bind every "runtime-slot-loader" hook inside root (default: the whole document). Returns unmount(). Mounting the same root twice binds once. */
|
|
8
|
+
export declare function mount(root?: ParentNode | null, options?: MountOptions): Unmount;
|
|
9
|
+
export default mount;
|