@featherds/composables
Advanced tools
Comparing version 0.9.6 to 0.10.0
import { watch, onBeforeUnmount, ref, onMounted } from "vue"; | ||
const useOutsideClick = (elementRef, listener) => { | ||
const active = ref(false); | ||
const windowBlurChecker = (e) => { | ||
if (e.target === window) { | ||
listener(e); | ||
} | ||
}; | ||
const outSideClick = (e) => { | ||
if (!elementRef.value.contains(e.target)) { | ||
listener(e); | ||
} | ||
}; | ||
const removeEvents = () => { | ||
if (document && window) { | ||
document.removeEventListener("click", outSideClick); | ||
document.removeEventListener("focus", outSideClick, true); | ||
window.removeEventListener("blur", windowBlurChecker); | ||
} | ||
}; | ||
onMounted(() => { | ||
const unwatch = watch( | ||
[elementRef, active], | ||
([el, enabled]) => { | ||
if (el && document && window && enabled) { | ||
document.addEventListener("click", outSideClick); | ||
document.addEventListener("focus", outSideClick, true); | ||
window.addEventListener("blur", windowBlurChecker); | ||
} else { | ||
removeEvents(); | ||
const active = ref(false); | ||
const windowBlurChecker = (e) => { | ||
if (e.target === window) { | ||
listener(e); | ||
} | ||
}, | ||
{ | ||
immediate: true, | ||
} | ||
); | ||
onBeforeUnmount(() => { | ||
unwatch(); | ||
removeEvents(); | ||
}; | ||
const outSideClick = (e) => { | ||
let elementArr = []; | ||
if (Array.isArray(elementRef.value)) { | ||
elementArr = elementRef.value; | ||
} | ||
else { | ||
elementArr = [elementRef.value]; | ||
} | ||
const contained = elementArr.some((el) => el && el.contains(e.target)); | ||
if (!contained) { | ||
listener(e); | ||
} | ||
}; | ||
const removeEvents = () => { | ||
if (document && window) { | ||
document.removeEventListener("click", outSideClick, true); | ||
document.removeEventListener("focus", outSideClick, true); | ||
window.removeEventListener("blur", windowBlurChecker); | ||
} | ||
}; | ||
onMounted(() => { | ||
const unwatch = watch(active, (enabled) => { | ||
if (document && window && enabled) { | ||
document.addEventListener("click", outSideClick, true); | ||
document.addEventListener("focus", outSideClick, true); | ||
window.addEventListener("blur", windowBlurChecker); | ||
} | ||
else { | ||
removeEvents(); | ||
} | ||
}, { | ||
immediate: true, | ||
}); | ||
onBeforeUnmount(() => { | ||
unwatch(); | ||
removeEvents(); | ||
}); | ||
}); | ||
}); | ||
return active; | ||
return active; | ||
}; | ||
export { useOutsideClick }; |
import { watch, onBeforeUnmount, ref, onMounted } from "vue"; | ||
const useResize = (listener) => { | ||
const active = ref(false); | ||
let ticking = false; | ||
const resizeHandler = () => { | ||
listener(); | ||
ticking = false; | ||
}; | ||
function requestTick() { | ||
if (!ticking) { | ||
requestAnimationFrame(resizeHandler); | ||
ticking = true; | ||
const active = ref(false); | ||
let ticking = false; | ||
const resizeHandler = () => { | ||
listener(); | ||
ticking = false; | ||
}; | ||
function requestTick() { | ||
if (!ticking) { | ||
requestAnimationFrame(resizeHandler); | ||
ticking = true; | ||
} | ||
} | ||
} | ||
const removeEvents = () => { | ||
if (window) { | ||
window.removeEventListener("resize", requestTick); | ||
} | ||
}; | ||
onMounted(() => { | ||
const unwatch = watch( | ||
active, | ||
(enabled) => { | ||
if (window && enabled) { | ||
window.addEventListener("resize", requestTick); | ||
} else { | ||
removeEvents(); | ||
const removeEvents = () => { | ||
if (window) { | ||
window.removeEventListener("resize", requestTick); | ||
} | ||
}, | ||
{ | ||
immediate: true, | ||
} | ||
); | ||
onBeforeUnmount(() => { | ||
unwatch(); | ||
removeEvents(); | ||
}; | ||
onMounted(() => { | ||
const unwatch = watch(active, (enabled) => { | ||
if (window && enabled) { | ||
window.addEventListener("resize", requestTick); | ||
} | ||
else { | ||
removeEvents(); | ||
} | ||
}, { | ||
immediate: true, | ||
}); | ||
onBeforeUnmount(() => { | ||
unwatch(); | ||
removeEvents(); | ||
}); | ||
}); | ||
}); | ||
return active; | ||
return active; | ||
}; | ||
export { useResize }; |
import { watch, onBeforeUnmount, ref } from "vue"; | ||
const useScroll = (elementRef, listener) => { | ||
const active = ref(false); | ||
let ticking = false; | ||
const scrollHandler = () => { | ||
listener(); | ||
ticking = false; | ||
}; | ||
function requestTick() { | ||
if (!ticking) { | ||
requestAnimationFrame(scrollHandler); | ||
ticking = true; | ||
const active = ref(false); | ||
let ticking = false; | ||
const scrollHandler = (e) => { | ||
listener(e); | ||
ticking = false; | ||
}; | ||
function requestTick(e) { | ||
if (!ticking) { | ||
requestAnimationFrame(() => scrollHandler(e)); | ||
ticking = true; | ||
} | ||
} | ||
} | ||
const removeEvents = () => { | ||
if (elementRef.value) { | ||
elementRef.value.removeEventListener("scroll", requestTick); | ||
} | ||
}; | ||
const unwatch = watch( | ||
[elementRef, active], | ||
([el, enabled], previous) => { | ||
if (previous && previous.length && previous[0]) { | ||
previous[0].removeEventListener("scroll", requestTick); | ||
} | ||
if (enabled && el) { | ||
el.addEventListener("scroll", requestTick, { passive: true }); | ||
} else { | ||
const removeEvents = () => { | ||
if (elementRef.value) { | ||
elementRef.value.removeEventListener("scroll", requestTick, true); | ||
} | ||
}; | ||
const unwatch = watch([elementRef, active], ([el, enabled], previous) => { | ||
if (previous && previous.length && previous[0]) { | ||
previous[0].removeEventListener("scroll", requestTick, true); | ||
} | ||
if (enabled && el) { | ||
el.addEventListener("scroll", requestTick, true); | ||
} | ||
else { | ||
removeEvents(); | ||
} | ||
}, { | ||
immediate: true, | ||
}); | ||
onBeforeUnmount(() => { | ||
unwatch(); | ||
removeEvents(); | ||
} | ||
}, | ||
{ | ||
immediate: true, | ||
} | ||
); | ||
onBeforeUnmount(() => { | ||
unwatch(); | ||
removeEvents(); | ||
}); | ||
return active; | ||
}); | ||
return active; | ||
}; | ||
export { useScroll }; |
@@ -1,8 +0,6 @@ | ||
import { Ref, ComputedRef } from "vue"; | ||
declare module "@featherds/composables/events/Scroll" { | ||
import { Ref } from "vue"; | ||
const useScroll: ( | ||
ref: Ref<HTMLElement>, | ||
callback: () => void, | ||
threshold: number | ||
ref: Ref<HTMLElement | Document>, | ||
callback: () => void | ||
) => Ref<boolean>; | ||
@@ -13,6 +11,4 @@ export { useScroll }; | ||
declare module "@featherds/composables/events/Resize" { | ||
const useResize: ( | ||
callback: () => void, | ||
threshold: number | ||
) => Ref<boolean>; | ||
import { Ref } from "vue"; | ||
const useResize: (callback: () => void) => Ref<boolean>; | ||
export { useResize }; | ||
@@ -22,2 +18,3 @@ } | ||
declare module "@featherds/composables/events/OutsideClick" { | ||
import { Ref } from "vue"; | ||
const useOutsideClick: ( | ||
@@ -32,7 +29,11 @@ ref: Ref<HTMLElement>, | ||
declare module "@featherds/composables/LabelProperty" { | ||
const useLabelProperty: ( | ||
label: Ref<Record<string, string>>, | ||
defaults: Record<string, string> | ||
) => ComputedRef<string>[]; | ||
import { Ref, ComputedRef } from "vue"; | ||
type Labels<Type> = { | ||
[Property in keyof Type as `${string & Property}Label`]: ComputedRef< | ||
Type[Property] | ||
>; | ||
}; | ||
const useLabelProperty: <T>(label: Ref<Partial<T>>, defaults: T) => Labels<T>; | ||
export { useLabelProperty }; | ||
@@ -42,2 +43,3 @@ } | ||
declare module "@featherds/composables/modal/CloseOnEsc" { | ||
import { Ref } from "vue"; | ||
const useCloseOnEsc: (ref: Ref<boolean>) => Ref<boolean>; | ||
@@ -48,2 +50,3 @@ export { useCloseOnEsc }; | ||
declare module "@featherds/composables/modal/HideOverflow" { | ||
import { Ref } from "vue"; | ||
const useHideBodyOverflow: (ref: Ref<boolean>) => void; | ||
@@ -58,4 +61,5 @@ const useHideRelativeOverflow: ( | ||
declare module "@featherds/composables/modal/RestoreFocus" { | ||
import { Ref } from "vue"; | ||
const useRestoreFocus: (ref: Ref<boolean>) => void; | ||
export { useRestoreFocus }; | ||
} |
import { computed } from "vue"; | ||
const useLabelProperty = (labelRef, defaultLabels) => { | ||
const result = {}; | ||
Object.keys(defaultLabels).forEach((key) => { | ||
result[`${key}Label`] = computed(() => { | ||
return labelRef.value[key] ? labelRef.value[key] : defaultLabels[key]; | ||
const result = {}; | ||
Object.keys(defaultLabels).forEach((key) => { | ||
result[`${key}Label`] = computed(() => { | ||
return labelRef.value[key] ? labelRef.value[key] : defaultLabels[key]; | ||
}); | ||
}); | ||
}); | ||
return result; | ||
return result; | ||
}; | ||
export { useLabelProperty }; |
import { watch, ref, onBeforeUnmount } from "vue"; | ||
import { KEYCODES } from "@featherds/utils/keys"; | ||
const useCloseOnEsc = (visibleRef) => { | ||
const result = ref(false); | ||
const handleEsc = (e) => { | ||
if (e.keyCode === KEYCODES.ESCAPE) { | ||
e.preventDefault(); | ||
result.value = !result.value; | ||
} | ||
}; | ||
watch( | ||
visibleRef, | ||
(v) => { | ||
if (v) { | ||
document.addEventListener("keydown", handleEsc); | ||
} else if (typeof document !== "undefined") { | ||
const result = ref(false); | ||
const handleEsc = (e) => { | ||
if (e.keyCode === KEYCODES.ESCAPE) { | ||
e.preventDefault(); | ||
result.value = !result.value; | ||
} | ||
}; | ||
watch(visibleRef, (v) => { | ||
if (v) { | ||
document.addEventListener("keydown", handleEsc); | ||
} | ||
else if (typeof document !== "undefined") { | ||
document.removeEventListener("keydown", handleEsc); | ||
} | ||
}, { immediate: true }); | ||
onBeforeUnmount(() => { | ||
document.removeEventListener("keydown", handleEsc); | ||
} | ||
}, | ||
{ immediate: true } | ||
); | ||
onBeforeUnmount(() => { | ||
document.removeEventListener("keydown", handleEsc); | ||
}); | ||
return result; | ||
}); | ||
return result; | ||
}; | ||
export { useCloseOnEsc }; |
import { watch, onBeforeUnmount, nextTick, onMounted } from "vue"; | ||
const hideBodyOverflow = (e) => { | ||
const originalOverflow = e.style.overflow; | ||
e.style.overflow = "hidden"; | ||
return originalOverflow; | ||
if (e === false) { | ||
return "hidden"; | ||
} | ||
const originalOverflow = e.style.overflow; | ||
e.style.overflow = "hidden"; | ||
return originalOverflow; | ||
}; | ||
const resetBodyOverflow = (originalOverflow, element) => { | ||
if (originalOverflow !== undefined && element) { | ||
element.style.overflow = originalOverflow; | ||
} | ||
return undefined; | ||
if (originalOverflow !== undefined && element !== false) { | ||
element.style.overflow = originalOverflow; | ||
} | ||
return undefined; | ||
}; | ||
const useHideBodyOverflow = (visibleRef) => { | ||
let originalOverflow; | ||
const element = | ||
typeof document !== "undefined" | ||
? document.body | ||
: { style: { overflow: "hidden" } }; | ||
onBeforeUnmount(() => resetBodyOverflow(originalOverflow, element)); | ||
onMounted(() => | ||
watch( | ||
visibleRef, | ||
(v) => { | ||
let originalOverflow; | ||
const element = typeof document !== "undefined" ? document.body : false; | ||
onBeforeUnmount(() => resetBodyOverflow(originalOverflow, element)); | ||
onMounted(() => watch(visibleRef, (v) => { | ||
if (v) { | ||
nextTick(() => { | ||
originalOverflow = hideBodyOverflow(element); | ||
}); | ||
} else { | ||
//hidden | ||
resetBodyOverflow(originalOverflow, element); | ||
nextTick(() => { | ||
originalOverflow = hideBodyOverflow(element); | ||
}); | ||
} | ||
}, | ||
{ immediate: true } | ||
) | ||
); | ||
else { | ||
//hidden | ||
resetBodyOverflow(originalOverflow, element); | ||
} | ||
}, { immediate: true })); | ||
}; | ||
const useHideRelativeOverflow = (visibleRef, elementRef) => { | ||
let originalOverflow; | ||
onBeforeUnmount(() => | ||
resetBodyOverflow( | ||
originalOverflow, | ||
elementRef.value ? elementRef.value.offsetParent : undefined | ||
) | ||
); | ||
watch( | ||
[visibleRef, elementRef], | ||
([v, e]) => { | ||
if (v && e) { | ||
nextTick(() => { | ||
originalOverflow = hideBodyOverflow(e.offsetParent); | ||
}); | ||
} else if (e) { | ||
//hidden | ||
resetBodyOverflow(originalOverflow, e.offsetParent); | ||
} | ||
}, | ||
{ | ||
immediate: true, | ||
} | ||
); | ||
let originalOverflow; | ||
onBeforeUnmount(() => resetBodyOverflow(originalOverflow, elementRef.value ? elementRef.value.offsetParent : false)); | ||
watch([visibleRef, elementRef], ([v, e]) => { | ||
if (v && e) { | ||
nextTick(() => { | ||
originalOverflow = hideBodyOverflow(e.offsetParent); | ||
}); | ||
} | ||
else if (e) { | ||
//hidden | ||
resetBodyOverflow(originalOverflow, e.offsetParent); | ||
} | ||
}, { | ||
immediate: true, | ||
}); | ||
}; | ||
export { useHideBodyOverflow, useHideRelativeOverflow }; |
import { watch } from "vue"; | ||
/** | ||
* When visible is set to false it will try focus the triggering element. | ||
* @param {Ref<Boolean>} visibleRef | ||
*/ | ||
const useRestoreFocus = (visibleRef) => { | ||
let focusAfterClosed; | ||
watch(visibleRef, (v) => { | ||
if (v) { | ||
focusAfterClosed = document.activeElement; | ||
} else { | ||
//hidden | ||
setTimeout(() => { | ||
if (focusAfterClosed && focusAfterClosed.focus) { | ||
focusAfterClosed.focus(); | ||
let focusAfterClosed; | ||
watch(visibleRef, (v) => { | ||
if (v) { | ||
focusAfterClosed = document.activeElement; | ||
} | ||
focusAfterClosed = undefined; | ||
}, 0); | ||
} | ||
}); | ||
else { | ||
//hidden | ||
setTimeout(() => { | ||
if (focusAfterClosed && focusAfterClosed.focus) { | ||
focusAfterClosed.focus(); | ||
} | ||
focusAfterClosed = undefined; | ||
}, 0); | ||
} | ||
}); | ||
}; | ||
export { useRestoreFocus }; |
{ | ||
"name": "@featherds/composables", | ||
"version": "0.9.6", | ||
"version": "0.10.0", | ||
"publishConfig": { | ||
@@ -8,5 +8,7 @@ "access": "public" | ||
"author": "NantHealth", | ||
"scripts": {}, | ||
"scripts": { | ||
"compile": "tsc --declaration" | ||
}, | ||
"dependencies": { | ||
"@featherds/utils": "^0.9.6", | ||
"@featherds/utils": "^0.10.0", | ||
"vue": "^3.1.0-0" | ||
@@ -18,4 +20,3 @@ }, | ||
], | ||
"types": "./index.d.ts", | ||
"gitHead": "04a74207e8bfa8d39acc470365e30dd6c90b2e8e" | ||
"gitHead": "9871b17eaedcfc90174b78b21acb0cc06afd594c" | ||
} |
@@ -1,126 +0,113 @@ | ||
import { ref, watch, watchEffect, computed, provide } from "vue"; | ||
import { ref, watch, watchEffect, computed, provide, onBeforeUnmount, } from "vue"; | ||
import { useSelection } from "./Selection"; | ||
import { useValidation } from "@featherds/input-helper"; | ||
import { useSelection } from "./Selection"; | ||
import { getSafeId } from "@featherds/utils/id"; | ||
const useRadioGroup = (modelValue, emit, label, schema, errorFromInput) => { | ||
const radios = ref([]); | ||
const currentSelected = ref(); | ||
const firstElement = ref(); | ||
const firstElementId = ref(); | ||
watchEffect(() => { | ||
if (!radios.value.length) { | ||
return; | ||
} | ||
const values = radios.value.map((x) => x.value); | ||
//select the radio that matches the current value | ||
if (modelValue.value !== undefined && modelValue.value !== null) { | ||
currentSelected.value = radios.value[values.indexOf(modelValue.value)]; | ||
} | ||
//if nothing is selected then set the first property on first radio that isnt disabled | ||
if (!currentSelected.value && radios.value.length) { | ||
let enabled = radios.value.filter((x) => !x.disabled); | ||
enabled = enabled.length ? enabled : radios.value; //if there is none enabled just use radios. | ||
firstElement.value = enabled[0]; | ||
firstElement.value.first = true; | ||
} | ||
}); | ||
watch(currentSelected, (nv, ov) => { | ||
if (ov) { | ||
ov.checked = false; | ||
} | ||
if (nv) { | ||
nv.checked = true; | ||
} | ||
}); | ||
const select = (radio) => { | ||
if (radio && radio.disabled) { | ||
return; | ||
} | ||
//reset first as we are now selecting and element | ||
if (firstElement.value) { | ||
firstElement.value.first = false; | ||
} | ||
if (currentSelected.value === radio) { | ||
return; | ||
} | ||
emit("update:modelValue", radio.value); | ||
currentSelected.value = radio; | ||
radio.focus(); | ||
}; | ||
const currentHighlight = computed(() => { | ||
return currentSelected.value || firstElement.value; | ||
}); | ||
const selection = useSelection(currentHighlight, radios, select); | ||
const groupId = computed(() => { | ||
return getSafeId("feather-radio-group"); | ||
}); | ||
firstElementId.value = groupId.value; | ||
let validate = useValidation( | ||
firstElementId, | ||
modelValue, | ||
label, | ||
schema, | ||
errorFromInput | ||
); | ||
const register = (radio) => { | ||
radios.value = [...radios.value, radio]; | ||
//lets try and instance validation | ||
if (firstElementId.value === groupId.value) { | ||
firstElementId.value = radio.id; | ||
} | ||
}; | ||
provide("register", register); | ||
provide("select", select); | ||
provide("blur", (e) => { | ||
emit("blur", e); | ||
}); | ||
const focus = () => { | ||
if (currentSelected.value) { | ||
currentSelected.value.focus(); | ||
} | ||
}; | ||
const keydown = (e) => { | ||
switch (e.keyCode) { | ||
case 13: | ||
case 32: | ||
const radios = ref([]); | ||
const currentSelected = ref(); | ||
const firstElement = ref(); | ||
const firstElementId = ref(); | ||
watchEffect(() => { | ||
if (!radios.value.length) { | ||
return; | ||
} | ||
const values = radios.value.map((x) => x.value); | ||
//select the radio that matches the current value | ||
if (modelValue.value !== undefined && modelValue.value !== null) { | ||
currentSelected.value = radios.value[values.indexOf(modelValue.value)]; | ||
} | ||
//if nothing is selected then set the first property on first radio that isnt disabled | ||
if (!currentSelected.value && radios.value.length) { | ||
let enabled = radios.value.filter((x) => !x.disabled); | ||
enabled = enabled.length ? enabled : radios.value; //if there is none enabled just use radios. | ||
firstElement.value = enabled[0]; | ||
firstElement.value.first = true; | ||
} | ||
}); | ||
watch(currentSelected, (nv, ov) => { | ||
if (ov) { | ||
ov.checked = false; | ||
} | ||
if (nv) { | ||
nv.checked = true; | ||
} | ||
}); | ||
const select = (radio) => { | ||
if (radio && radio.disabled) { | ||
return; | ||
} | ||
//reset first as we are now selecting and element | ||
if (firstElement.value) { | ||
firstElement.value.first = false; | ||
} | ||
if (currentSelected.value === radio) { | ||
return; | ||
} | ||
emit("update:modelValue", radio.value); | ||
currentSelected.value = radio; | ||
radio.focus(); | ||
}; | ||
const currentHighlight = computed(() => { | ||
return currentSelected.value || firstElement.value; | ||
}); | ||
const selection = useSelection(currentHighlight, radios, select); | ||
const groupId = computed(() => { | ||
return getSafeId("feather-radio-group"); | ||
}); | ||
firstElementId.value = groupId.value; | ||
onBeforeUnmount(() => { | ||
removeValidation(); | ||
}); | ||
const { validate, removeValidation } = useValidation(firstElementId, modelValue, label, schema, errorFromInput); | ||
const register = (radio) => { | ||
radios.value = [...radios.value, radio]; | ||
//lets try and instance validation | ||
if (firstElementId.value === groupId.value) { | ||
firstElementId.value = radio.id; | ||
} | ||
}; | ||
provide("register", register); | ||
provide("select", select); | ||
provide("blur", (e) => { | ||
emit("blur", e); | ||
}); | ||
const focus = () => { | ||
if (currentSelected.value) { | ||
select(currentSelected.value); | ||
} else if (firstElement.value) { | ||
select(firstElement.value); | ||
currentSelected.value.focus(); | ||
} | ||
break; | ||
//next | ||
case 40: | ||
case 39: | ||
selection.selectNext(); | ||
break; | ||
//previous | ||
case 37: | ||
case 38: | ||
selection.selectPrevious(); | ||
break; | ||
default: | ||
break; | ||
} | ||
}; | ||
return { | ||
keydown, | ||
...selection, | ||
focus, | ||
validate, | ||
firstElementId, | ||
groupId, | ||
}; | ||
}; | ||
const keydown = (e) => { | ||
switch (e.keyCode) { | ||
case 13: | ||
case 32: | ||
if (currentSelected.value) { | ||
select(currentSelected.value); | ||
} | ||
else if (firstElement.value) { | ||
select(firstElement.value); | ||
} | ||
break; | ||
//next | ||
case 40: | ||
case 39: | ||
selection.selectNext(); | ||
break; | ||
//previous | ||
case 37: | ||
case 38: | ||
selection.selectPrevious(); | ||
break; | ||
default: | ||
break; | ||
} | ||
}; | ||
return { | ||
keydown, | ||
...selection, | ||
focus, | ||
validate, | ||
firstElementId, | ||
groupId, | ||
}; | ||
}; | ||
export { useRadioGroup }; |
import { computed } from "vue"; | ||
const useSelection = (current, radios, select) => { | ||
const notDisabled = computed(() => { | ||
return radios.value.filter((x) => !x.disabled); | ||
}); | ||
const index = computed(() => notDisabled.value.indexOf(current.value)); | ||
return { | ||
selectPrevious() { | ||
if (current.value && current.value.disabled) { | ||
return; | ||
} | ||
if (index.value === 0) { | ||
select(notDisabled.value[notDisabled.value.length - 1]); | ||
} else { | ||
select(notDisabled.value[index.value - 1]); | ||
} | ||
}, | ||
selectNext() { | ||
if (current.value && current.value.disabled) { | ||
return; | ||
} | ||
if (index.value === notDisabled.value.length - 1) { | ||
select(notDisabled.value[0]); | ||
} else { | ||
select(notDisabled.value[index.value + 1]); | ||
} | ||
}, | ||
}; | ||
const notDisabled = computed(() => { | ||
return radios.value.filter((x) => !x.disabled); | ||
}); | ||
const index = computed(() => current.value ? notDisabled.value.indexOf(current.value) : -1); | ||
return { | ||
selectPrevious() { | ||
if (current.value && current.value.disabled) { | ||
return; | ||
} | ||
if (index.value === 0) { | ||
select(notDisabled.value[notDisabled.value.length - 1]); | ||
} | ||
else { | ||
select(notDisabled.value[index.value - 1]); | ||
} | ||
}, | ||
selectNext() { | ||
if (current.value && current.value.disabled) { | ||
return; | ||
} | ||
if (index.value === notDisabled.value.length - 1) { | ||
select(notDisabled.value[0]); | ||
} | ||
else { | ||
select(notDisabled.value[index.value + 1]); | ||
} | ||
}, | ||
}; | ||
}; | ||
export { useSelection }; |
102
tabs/Tab.js
import { ref, inject, computed, onMounted } from "vue"; | ||
const stockProps = { | ||
id: { | ||
type: String, | ||
}, | ||
controls: { | ||
type: String, | ||
}, | ||
disabled: { | ||
type: Boolean, | ||
default: false, | ||
}, | ||
id: { | ||
type: String, | ||
}, | ||
controls: { | ||
type: String, | ||
}, | ||
disabled: { | ||
type: Boolean, | ||
default: false, | ||
}, | ||
}; | ||
const useTab = (props) => { | ||
const selected = ref(false); | ||
const tab = ref(); | ||
const _controls = ref(props.controls); | ||
const _id = ref(props.id); | ||
const focus = () => { | ||
tab.value.focus(); | ||
}; | ||
const register = inject("registerTab"); | ||
let thisEl, parent, childNodes, index; | ||
onMounted(() => { | ||
thisEl = tab.value.parentElement; | ||
parent = thisEl && thisEl.parentNode ? thisEl.parentNode : []; | ||
childNodes = [].filter.call(parent.children, function (el) { | ||
return el.querySelectorAll("[role=tab]").length; | ||
const selected = ref(false); | ||
const tab = ref(); | ||
const _controls = ref(props.controls); | ||
const _id = ref(props.id); | ||
const focus = () => { | ||
if (tab.value) { | ||
tab.value.focus(); | ||
} | ||
}; | ||
const register = inject("registerTab"); | ||
onMounted(() => { | ||
if (tab.value && register) { | ||
const thisEl = tab.value.parentElement; | ||
const parent = thisEl && thisEl.parentElement ? thisEl.parentElement : undefined; | ||
const childNodes = Array.from(parent ? parent.children : []).filter((el) => el.querySelectorAll("[role=tab]").length); | ||
const index = thisEl ? childNodes.indexOf(thisEl) : -1; | ||
register({ | ||
el: tab.value, | ||
focus, | ||
disabled: props.disabled, | ||
selected, | ||
id: _id, | ||
controls: _controls, | ||
index: index, | ||
}); | ||
} | ||
}); | ||
index = thisEl ? [].indexOf.call(childNodes, thisEl) : null; | ||
register({ | ||
el: tab.value, | ||
focus, | ||
disabled: props.disabled, | ||
selected, | ||
id: _id, | ||
controls: _controls, | ||
index: index, | ||
const attrs = computed(() => { | ||
return { | ||
role: "tab", | ||
ref: "tab", | ||
tabindex: selected.value ? 0 : -1, | ||
id: _id.value, | ||
"aria-selected": selected.value ? "true" : "false", | ||
"aria-controls": _controls.value, | ||
"aria-disabled": props.disabled ? "true" : "false", | ||
"data-ref-id": "feather-tab", | ||
}; | ||
}); | ||
}); | ||
const attrs = computed(() => { | ||
return { | ||
role: "tab", | ||
ref: "tab", | ||
tabindex: selected.value ? 0 : -1, | ||
id: _id.value, | ||
"aria-selected": selected.value ? "true" : "false", | ||
"aria-controls": _controls.value, | ||
"aria-disabled": props.disabled ? "true" : "false", | ||
"data-ref-id": "feather-tab", | ||
selected, | ||
attrs, | ||
tab, | ||
}; | ||
}); | ||
return { | ||
selected, | ||
attrs, | ||
tab, | ||
}; | ||
}; | ||
export { useTab, stockProps }; |
@@ -1,170 +0,158 @@ | ||
import { ref, toRef, watch, provide } from "vue"; | ||
import { ref, toRef, watch, provide, } from "vue"; | ||
import { getSafeId } from "@featherds/utils/id"; | ||
import { KEYCODES } from "@featherds/utils/keys"; | ||
const model = { | ||
prop: "modelValue", | ||
event: "update:modelValue", | ||
prop: "modelValue", | ||
event: "update:modelValue", | ||
}; | ||
const emits = ["update:modelValue"]; | ||
const stockProps = { | ||
modelValue: { | ||
type: Number, | ||
default: 0, | ||
}, | ||
vertical: { | ||
type: Boolean, | ||
default: true, | ||
}, | ||
modelValue: { | ||
type: Number, | ||
default: 0, | ||
}, | ||
vertical: { | ||
type: Boolean, | ||
default: true, | ||
}, | ||
}; | ||
const useTabContainer = (props, context) => { | ||
const value = toRef(props, "modelValue"); | ||
const localSelected = ref(props.modelValue); | ||
const pairs = ref([]); | ||
watch(value, (v) => { | ||
activateIndex(v); | ||
}); | ||
const handleClick = (evt) => { | ||
evt.preventDefault(); | ||
pairs.value.some((pair, i) => { | ||
if (pair.tab.el.contains(evt.target)) { | ||
selectIndex(i); | ||
activateIndex(i); | ||
return true; | ||
} | ||
return false; | ||
const value = toRef(props, "modelValue"); | ||
const localSelected = ref(props.modelValue); | ||
const pairs = ref([]); | ||
watch(value, (v) => { | ||
activateIndex(v); | ||
}); | ||
}; | ||
const handleKey = (evt) => { | ||
const isModifiedKeyPress = (e) => { | ||
return e.shiftKey || e.ctrlKey || e.metaKey || e.altKey; | ||
const handleClick = (evt) => { | ||
evt.preventDefault(); | ||
pairs.value.some((pair, i) => { | ||
if (pair.tab && pair.tab.el.contains(evt.target)) { | ||
selectIndex(i); | ||
activateIndex(i); | ||
return true; | ||
} | ||
return false; | ||
}); | ||
}; | ||
if (isModifiedKeyPress(evt)) { | ||
return; | ||
} | ||
const key = evt.keyCode; | ||
const stop = (e) => { | ||
e.stopPropagation(); | ||
e.preventDefault(); | ||
const handleKey = (evt) => { | ||
const isModifiedKeyPress = (e) => { | ||
return e.shiftKey || e.ctrlKey || e.metaKey || e.altKey; | ||
}; | ||
if (isModifiedKeyPress(evt)) { | ||
return; | ||
} | ||
const key = evt.keyCode; | ||
const stop = (e) => { | ||
e.stopPropagation(); | ||
e.preventDefault(); | ||
}; | ||
const notDisabledPairs = pairs.value.filter((pair) => pair.tab && !pair.tab.disabled); | ||
const focusedIndex = pairs.value.findIndex((pair) => pair.tab && pair.tab.el.contains(document.activeElement)); | ||
let index = focusedIndex !== -1 ? focusedIndex : localSelected.value; | ||
const nextKeys = [KEYCODES.RIGHT]; | ||
const prevKeys = [KEYCODES.LEFT]; | ||
const selectKeys = [KEYCODES.ENTER, KEYCODES.SPACE]; | ||
if (props.vertical) { | ||
nextKeys.push(KEYCODES.DOWN); | ||
prevKeys.push(KEYCODES.UP); | ||
} | ||
if (nextKeys.indexOf(key) > -1) { | ||
index++; | ||
if (index >= notDisabledPairs.length) { | ||
index = 0; | ||
} | ||
stop(evt); | ||
selectIndex(pairs.value.indexOf(notDisabledPairs[index])); | ||
} | ||
else if (prevKeys.indexOf(key) > -1) { | ||
index--; | ||
if (index < 0) { | ||
index = notDisabledPairs.length - 1; | ||
} | ||
stop(evt); | ||
selectIndex(pairs.value.indexOf(notDisabledPairs[index])); | ||
} | ||
if (selectKeys.indexOf(key) > -1) { | ||
activateIndex(index); | ||
} | ||
}; | ||
const notDisabledPairs = pairs.value.filter((pair) => !pair.tab.disabled); | ||
const focusedIndex = pairs.value.findIndex((pair) => | ||
pair.tab.el.contains(document.activeElement) | ||
); | ||
let index = focusedIndex !== -1 ? focusedIndex : localSelected.value; | ||
const nextKeys = [KEYCODES.RIGHT]; | ||
const prevKeys = [KEYCODES.LEFT]; | ||
const selectKeys = [KEYCODES.ENTER, KEYCODES.SPACE]; | ||
if (props.vertical) { | ||
nextKeys.push(KEYCODES.DOWN); | ||
prevKeys.push(KEYCODES.UP); | ||
} | ||
if (nextKeys.indexOf(key) > -1) { | ||
index++; | ||
if (index >= notDisabledPairs.length) { | ||
index = 0; | ||
} | ||
stop(evt); | ||
selectIndex(pairs.value.indexOf(notDisabledPairs[index])); | ||
} else if (prevKeys.indexOf(key) > -1) { | ||
index--; | ||
if (index < 0) { | ||
index = notDisabledPairs.length - 1; | ||
} | ||
stop(evt); | ||
selectIndex(pairs.value.indexOf(notDisabledPairs[index])); | ||
} | ||
if (selectKeys.indexOf(key) > -1) { | ||
activateIndex(index); | ||
} | ||
}; | ||
const selectIndex = (index) => { | ||
pairs.value.forEach(function (pair, i) { | ||
if (index === i) { | ||
pair.tab.focus(); | ||
} | ||
}); | ||
}; | ||
const activateIndex = (index) => { | ||
const selected = pairs.value[index]; | ||
//couldnt find selected or tab is disabled | ||
if (!selected || selected.tab.disabled) { | ||
return; | ||
} | ||
pairs.value.forEach((pair, i) => { | ||
pair.tab.selected = index === i; | ||
if (pair.panel) { | ||
pair.panel.selected = index === i; | ||
} | ||
}); | ||
localSelected.value = index; | ||
context.emit("update:modelValue", index); | ||
}; | ||
const registerTab = (tabVM) => { | ||
const index = tabVM.index; | ||
if (index > -1) { | ||
pairs.value[index] = { ...pairs.value[index], tab: tabVM }; | ||
pairs.value = [...pairs.value]; | ||
linkIds(); | ||
} | ||
}; | ||
provide("registerTab", registerTab); | ||
const registerPanel = (tabPanelVM) => { | ||
const index = tabPanelVM.index; | ||
if (index > -1) { | ||
pairs.value[index] = { ...pairs.value[index], panel: tabPanelVM }; | ||
pairs.value = [...pairs.value]; | ||
linkIds(); | ||
} | ||
}; | ||
provide("registerPanel", registerPanel); | ||
const linkIds = () => { | ||
pairs.value.forEach(({ tab, panel }, index) => { | ||
if (panel && tab) { | ||
const tabId = tab.id || getSafeId("tab"); | ||
const panelId = tab.controls || getSafeId("panel"); | ||
tab.controls = panelId; | ||
tab.id = tabId; | ||
panel.tab = tabId; | ||
panel.id = panelId; | ||
} | ||
if (index === localSelected.value) { | ||
if (panel) { | ||
panel.selected = true; | ||
const selectIndex = (index) => { | ||
pairs.value.forEach(function (pair, i) { | ||
if (index === i && pair.tab) { | ||
pair.tab.focus(); | ||
} | ||
}); | ||
}; | ||
const activateIndex = (index) => { | ||
const selected = pairs.value[index]; | ||
//couldnt find selected or tab is disabled | ||
if (!selected || (selected.tab && selected.tab.disabled)) { | ||
return; | ||
} | ||
if (tab) { | ||
tab.selected = true; | ||
pairs.value.forEach((pair, i) => { | ||
if (pair.tab) { | ||
pair.tab.selected = index === i; | ||
} | ||
if (pair.panel) { | ||
pair.panel.selected = index === i; | ||
} | ||
}); | ||
localSelected.value = index; | ||
context.emit("update:modelValue", index); | ||
}; | ||
const registerTab = (tabVM) => { | ||
const index = tabVM.index; | ||
if (index > -1) { | ||
pairs.value[index] = { ...pairs.value[index], tab: tabVM }; | ||
pairs.value = [...pairs.value]; | ||
linkIds(); | ||
} | ||
} | ||
}); | ||
}; | ||
const listeners = { | ||
click: handleClick, | ||
keydown: handleKey, | ||
}; | ||
const attrs = { | ||
role: "tablist", | ||
}; | ||
return { | ||
listeners, | ||
attrs, | ||
selected: localSelected, | ||
pairs, //feather tab container watches this | ||
}; | ||
}; | ||
provide("registerTab", registerTab); | ||
const registerPanel = (tabPanelVM) => { | ||
const index = tabPanelVM.index; | ||
if (index > -1) { | ||
pairs.value[index] = { | ||
...pairs.value[index], | ||
panel: tabPanelVM, | ||
}; | ||
pairs.value = [...pairs.value]; | ||
linkIds(); | ||
} | ||
}; | ||
provide("registerPanel", registerPanel); | ||
const linkIds = () => { | ||
pairs.value.forEach(({ tab, panel }, index) => { | ||
if (panel && tab) { | ||
const tabId = tab.id || getSafeId("tab"); | ||
const panelId = tab.controls || getSafeId("panel"); | ||
tab.controls = panelId; | ||
tab.id = tabId; | ||
panel.tab = tabId; | ||
panel.id = panelId; | ||
} | ||
if (index === localSelected.value) { | ||
if (panel) { | ||
panel.selected = true; | ||
} | ||
if (tab) { | ||
tab.selected = true; | ||
} | ||
} | ||
}); | ||
}; | ||
const listeners = { | ||
click: handleClick, | ||
keydown: handleKey, | ||
}; | ||
const attrs = { | ||
role: "tablist", | ||
}; | ||
return { | ||
listeners, | ||
attrs, | ||
selected: localSelected, | ||
pairs, //feather tab container watches this | ||
}; | ||
}; | ||
export { useTabContainer, stockProps, model, emits }; |
import { ref, inject, computed, onMounted } from "vue"; | ||
const stockProps = { | ||
id: { | ||
type: String, | ||
}, | ||
tab: { | ||
type: String, | ||
}, | ||
id: { | ||
type: String, | ||
}, | ||
tab: { | ||
type: String, | ||
}, | ||
}; | ||
const useTabPanel = (props) => { | ||
const selected = ref(false); | ||
const panel = ref(); | ||
const _tab = ref(props.tab); | ||
const _id = ref(props.id); | ||
const register = inject("registerPanel"); | ||
let thisEl, parent, index; | ||
onMounted(() => { | ||
thisEl = panel.value; | ||
parent = thisEl && thisEl.parentNode ? thisEl.parentNode : []; | ||
index = thisEl | ||
? Array.prototype.indexOf.call(parent.children, thisEl) | ||
: null; | ||
register({ | ||
selected, | ||
id: _id, | ||
tab: _tab, | ||
el: panel.value, | ||
index: index, | ||
const selected = ref(false); | ||
const panel = ref(); | ||
const _tab = ref(props.tab); | ||
const _id = ref(props.id); | ||
const register = inject("registerPanel"); | ||
onMounted(() => { | ||
if (register) { | ||
const thisEl = panel.value; | ||
const parent = thisEl && thisEl.parentElement ? thisEl.parentElement : undefined; | ||
const index = thisEl | ||
? Array.from(parent ? parent.children : []).indexOf(thisEl) | ||
: -1; | ||
register({ | ||
selected, | ||
id: _id, | ||
tab: _tab, | ||
el: panel.value, | ||
index: index, | ||
}); | ||
} | ||
}); | ||
}); | ||
const attrs = computed(() => { | ||
const attrs = computed(() => { | ||
return { | ||
role: "tabpanel", | ||
id: _id.value, | ||
ref: "panel", | ||
tabindex: "0", | ||
"aria-expanded": selected.value ? "true" : "false", | ||
"aria-labelledby": _tab.value, | ||
"data-ref-id": "feather-tab-panel", | ||
}; | ||
}); | ||
return { | ||
role: "tabpanel", | ||
id: _id.value, | ||
ref: "panel", | ||
tabindex: "0", | ||
"aria-expanded": selected.value ? "true" : "false", | ||
"aria-labelledby": _tab.value, | ||
"data-ref-id": "feather-tab-panel", | ||
selected, | ||
attrs, | ||
panel, | ||
}; | ||
}); | ||
return { | ||
selected, | ||
attrs, | ||
panel, | ||
}; | ||
}; | ||
export { useTabPanel, stockProps }; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
71342
47
2062
+ Added@featherds/utils@0.10.17(transitive)
- Removed@featherds/utils@0.9.6(transitive)
Updated@featherds/utils@^0.10.0