SmartCane/laravel_app/resources/js/history-tabs.js
Martin Folkerts eb1def36a1 wip
2023-12-22 16:55:40 +01:00

168 lines
6.2 KiB
JavaScript

export default function (Alpine) {
Alpine.directive('htabs', (el, directive) => {
if (! directive.value) handleRoot(el, Alpine)
else if (directive.value === 'list') handleList(el, Alpine)
else if (directive.value === 'tab') handleTab(el, Alpine)
else if (directive.value === 'panels') handlePanels(el, Alpine)
else if (directive.value === 'panel') handlePanel(el, Alpine)
}).before('bind')
Alpine.magic('htab', el => {
let $data = Alpine.$data(el)
return {
get isSelected() {
return $data.__selectedIndex === $data.__tabs.indexOf($data.__tabEl)
},
get isDisabled() {
return $data.__isDisabled
}
}
})
Alpine.magic('hpanel', el => {
let $data = Alpine.$data(el)
return {
get isSelected() {
return $data.__selectedIndex === $data.__panels.indexOf($data.__panelEl)
}
}
})
}
function handleRoot(el, Alpine) {
Alpine.bind(el, {
'x-modelable': '__selectedIndex',
'x-data'() {
return {
init() {
window.addEventListener('popstate', this.__handlePopState.bind(this));
queueMicrotask(() => {
let defaultIndex = this.__selectedIndex || Number(Alpine.bound(this.$el, 'default-index', 0))
let tabs = this.__activeTabs()
let clamp = (number, min, max) => Math.min(Math.max(number, min), max)
this.__selectedIndex = clamp(defaultIndex, 0, tabs.length -1)
Alpine.effect(() => {
this.__manualActivation = Alpine.bound(this.$el, 'manual', false)
})
this.__syncTabWithUrl();
})
},
__tabs: [],
__panels: [],
__selectedIndex: null,
__tabGroupEl: undefined,
__manualActivation: false,
__addTab(el) { this.__tabs.push(el) },
__addPanel(el) { this.__panels.push(el) },
__selectTab(el) {
if (this.__selectedIndex !== this.__tabs.indexOf(el)) {
this.__selectedIndex = this.__tabs.indexOf(el)
this.__updateUrl();
}
},
__updateUrl() {
const selectedTab = this.__tabs[this.__selectedIndex];
const tabName = selectedTab.getAttribute('data-tab-name');
const url = new URL(window.location);
url.searchParams.set('selected-tab', tabName);
window.history.pushState({ tabIndex: this.__selectedIndex }, '', url);
},
__handlePopState() {
this.__syncTabWithUrl();
},
__syncTabWithUrl() {
const urlParams = new URLSearchParams(window.location.search);
const tabName = urlParams.get('selected-tab');
const tabIndex = this.__tabs.findIndex(tab => tab.getAttribute('data-tab-name') === tabName);
this.__selectedIndex = 0;
if (tabIndex >= 0) {
this.__selectedIndex = tabIndex;
}
},
__activeTabs() {
return this.__tabs.filter(i => !i.__disabled)
},
}
}
})
}
function handleList(el, Alpine) {
Alpine.bind(el, {
'x-init'() { this.$data.__tabGroupEl = this.$el }
})
}
function handleTab(el, Alpine) {
Alpine.bind(el, {
'x-init'() { if (this.$el.tagName.toLowerCase() === 'button' && !this.$el.hasAttribute('type')) this.$el.type = 'button' },
'x-data'() { return {
init() {
this.__tabEl = this.$el
this.$data.__addTab(this.$el)
this.__tabEl.__disabled = Alpine.bound(this.$el, 'disabled', false)
this.__isDisabled = this.__tabEl.__disabled
},
__tabEl: undefined,
__isDisabled: false,
}},
'@click'() {
if (this.$el.__disabled) return
this.$data.__selectTab(this.$el)
this.$el.focus()
},
'@keydown.enter.prevent.stop'() { this.__selectTab(this.$el) },
'@keydown.space.prevent.stop'() { this.__selectTab(this.$el) },
'@keydown.home.prevent.stop'() { this.$focus.within(this.$data.__activeTabs()).first() },
'@keydown.page-up.prevent.stop'() { this.$focus.within(this.$data.__activeTabs()).first() },
'@keydown.end.prevent.stop'() { this.$focus.within(this.$data.__activeTabs()).last() },
'@keydown.page-down.prevent.stop'() { this.$focus.within(this.$data.__activeTabs()).last() },
'@keydown.down.prevent.stop'() { this.$focus.within(this.$data.__activeTabs()).withWrapAround().next() },
'@keydown.right.prevent.stop'() { this.$focus.within(this.$data.__activeTabs()).withWrapAround().next() },
'@keydown.up.prevent.stop'() { this.$focus.within(this.$data.__activeTabs()).withWrapAround().prev() },
'@keydown.left.prevent.stop'() { this.$focus.within(this.$data.__activeTabs()).withWrapAround().prev() },
':tabindex'() { return this.$tab.isSelected ? 0 : -1 },
'@focus'() {
if (this.$data.__manualActivation) {
this.$el.focus()
} else {
if (this.$el.__disabled) return
this.$data.__selectTab(this.$el)
this.$el.focus()
}
},
})
}
function handlePanels(el, Alpine) {
Alpine.bind(el, {
//
})
}
function handlePanel(el, Alpine) {
Alpine.bind(el, {
':tabindex'() { return this.$panel.isSelected ? 0 : -1 },
'x-data'() { return {
init() {
this.__panelEl = this.$el
this.$data.__addPanel(this.$el)
},
__panelEl: undefined,
}},
'x-show'() { return this.$panel.isSelected },
})
}