@featherk/composables 0.4.11 → 0.5.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 +62 -122
- package/dist/date/index.d.ts +1 -0
- package/dist/date/useMaskedDateInput.d.ts +33 -0
- package/dist/featherk-composables.es.js +1140 -228
- package/dist/featherk-composables.umd.js +1 -1
- package/dist/grid/index.d.ts +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/range/index.d.ts +1 -0
- package/dist/range/useMaskedDateRangeInput.d.ts +48 -0
- package/dist/time/index.d.ts +1 -0
- package/dist/time/useMaskedTimeInput.d.ts +42 -0
- package/dist/trap/index.d.ts +1 -0
- package/dist/trap/usePopupTrap.d.ts +20 -0
- package/docs/date/useMaskedDateInput.md +173 -0
- package/docs/grid/useGridA11y.md +243 -0
- package/docs/range/useMaskedDateRangeInput.md +213 -0
- package/docs/time/useMaskedTimeInput.md +197 -0
- package/docs/trap/usePopupTrap.md +138 -0
- package/package.json +53 -3
- package/dist/useGridComposableEx.d.ts +0 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(b,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],c):(b=typeof globalThis<"u"?globalThis:b||self,c(b.FeatherKComposables={},b.Vue))})(this,(function(b,c){"use strict";const L=p=>{const s=c.ref(null);let g=!1,d=null,m=null;const k=[],h=".k-table-row[data-grid-row-index] [tabindex]",v=()=>p?.value?.columns,E=t=>{const o=t.key||t.code;[" ","Spacebar","Space","Enter"].includes(o)&&(t.preventDefault(),t.stopPropagation(),s.value=t.target,t.target.click())},y=t=>{if(!s.value)return;if(t.code==="Escape"){t.preventDefault(),t.stopPropagation(),s.value&&s.value.focus();return}const o=Array.from(document.querySelectorAll(".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item")),e=document.querySelector(".k-filter-menu-container");if(e){if(t.code==="Tab"){const r=[".k-filter-menu-container .k-dropdownlist[tabindex='0']",".k-filter-menu-container input.k-input-inner:not([tabindex='-1']):not([disabled])",".k-filter-menu-container button:not([tabindex='-1']):not([disabled])"],n=Array.from(e.querySelectorAll(r.join(",")));if(n.length===0)return;const a=n.findIndex(l=>l===document.activeElement);let i;a===-1?i=0:t.shiftKey?i=(a-1+n.length)%n.length:i=(a+1)%n.length,t.preventDefault(),t.stopPropagation(),n[i]?.focus();return}}else if(t.code==="ArrowUp"||t.code==="ArrowDown"){t.preventDefault(),t.stopPropagation();const r=o.findIndex(a=>a===document.activeElement);let n=r;t.code==="ArrowUp"?n=r>0?r-1:o.length-1:t.code==="ArrowDown"&&(n=r<o.length-1?r+1:0),o[n]?.focus();return}t.code==="Tab"&&(t.preventDefault(),t.stopPropagation(),t.shiftKey?(s.value?.previousElementSibling).focus():(s.value?.nextElementSibling).focus())},D=()=>{d=new MutationObserver(t=>{t.forEach(o=>{o.addedNodes.forEach(e=>{if(e.nodeType===Node.ELEMENT_NODE){const r=e;if(r.classList.contains("k-animation-container")){const a=s.value;a&&(a.dataset.featherKSortable==="true"||c.nextTick(()=>{r.querySelectorAll(".k-columnmenu-item-wrapper").forEach(f=>{f.textContent?.toLowerCase().includes("sort")&&f.remove()})})),r.addEventListener("keydown",y),c.nextTick(()=>{const i=()=>{const l=Array.from(r.querySelectorAll(".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item"));if(l.length===1)l[0].focus(),l[0].click(),i.attempts=0;else if(l.length>1){i.attempts=0;return}else i.attempts===void 0&&(i.attempts=0),i.attempts++<3&&setTimeout(i,200)};i()})}r.querySelectorAll(".k-animation-container").forEach(a=>{a.addEventListener("keydown",y)})}}),o.removedNodes.forEach(e=>{if(e.nodeType===Node.ELEMENT_NODE){const r=e;r.classList.contains("k-animation-container")&&r.removeEventListener("keydown",y),r.querySelectorAll(".k-animation-container").forEach(a=>{a.removeEventListener("keydown",y)})}})})}),d.observe(document.body,{childList:!0,subtree:!0})},q=t=>{if(!t.type||!t)return;const o=t.target;if(o){if(t.code==="Escape"){const e=document.activeElement?.closest(".k-table-row[data-grid-row-index]");if(e){t.preventDefault(),t.stopPropagation();try{Array.from(p?.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]")).forEach(n=>n.setAttribute("tabindex","-1"))}catch{}e.setAttribute("tabindex","0"),e.focus();return}}if(["ArrowDown","ArrowLeft","ArrowRight","ArrowUp","Enter","Space"].includes(t.code)){if(t.preventDefault(),o.classList.contains("k-grid-header-menu")&&o.classList.contains("k-grid-column-menu")){s.value=o;return}const e=o.closest(".k-table-row[data-grid-row-index]");if(e){if(["ArrowDown","ArrowUp"].includes(t.code)){const r=(a,i)=>{let l=i==="next"?a.nextElementSibling:a.previousElementSibling;for(;l;){const f=l;try{if(f.hasAttribute&&f.classList.contains("k-table-row"))return f}catch{}l=i==="next"?l.nextElementSibling:l.previousElementSibling}return null},n=t.code==="ArrowDown"?r(e,"next"):r(e,"previous");n&&(e.setAttribute("tabindex","-1"),n.setAttribute("tabindex","0"),n.focus());return}if(["ArrowLeft","ArrowRight"].includes(t.code)){t.preventDefault();const r=e.querySelectorAll(h);if(r.length===0)return;let n=Array.from(r).findIndex(a=>a===document.activeElement);if(n===-1&&document.activeElement===e){r[0].focus();return}t.code==="ArrowRight"?n=n===r.length-1?0:n+1:t.code==="ArrowLeft"&&(n=n===r.length-1?n-1:r.length-1),r[n].focus();return}}}}},C=()=>{c.nextTick(()=>{const t=p.value.$el.closest(".k-grid");t&&t.classList.add("fk-grid")})},I=()=>{if(!m)try{const t=()=>{try{const r=Array.from(p.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]"));if(!r||r.length===0||r.filter(i=>i.getAttribute("tabindex")==="0").length===1)return;const a=r.find(i=>i===document.activeElement||i.contains(document.activeElement));if(r.forEach(i=>i.setAttribute("tabindex","-1")),a){a.setAttribute("tabindex","0");return}r[0].setAttribute("tabindex","0")}catch(r){console.error("ensureSingleTabindex error:",r)}},o=Array.from(p.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]"));o.length>0&&o.forEach((r,n)=>{r.setAttribute("tabindex",n===0?"0":"-1")});const e=p.value.$el.querySelector(".k-table-tbody");e&&(m=new MutationObserver(()=>{t()}),m.observe(e,{childList:!0,subtree:!0}))}catch(t){console.error("Error setting up row navigation:",t)}},K=()=>{c.nextTick(()=>{const o=document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");o&&o.forEach(e=>{e.setAttribute("role","button"),e.addEventListener("keydown",E)}),D(),M()})},S=()=>{const o=document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");o&&o.forEach(e=>{e.removeEventListener("keydown",E)}),d&&(d.disconnect(),d=null),m&&(m.disconnect(),m=null),k.forEach(e=>e()),k.length=0},M=()=>{document.querySelectorAll(".k-grid-header .k-table-thead th").forEach((e,r)=>{const n=e.querySelector(".k-grid-header-menu.k-grid-column-menu");if(!n)return;const a=v();if(a&&a[r]){const u=a[r].field??"";e.setAttribute("data-feather-k-field",u),e.setAttribute("data-feather-k-filterable",a[r].filterable===!1?"false":"true"),e.setAttribute("data-feather-k-sortable",a[r].sortable===!1?"false":"true")}const i=e.dataset.featherKFilterable!=="false",l=e.dataset.featherKSortable!=="false";n.setAttribute("tabindex","-1"),i||l?e.style.cursor="pointer":e.style.cursor="default",i?(e.setAttribute("tabindex","0"),e.setAttribute("role","button"),e.setAttribute("aria-haspopup","menu")):(e.setAttribute("tabindex","0"),e.setAttribute("role","columnheader"),e.removeAttribute("aria-haspopup"));const f=u=>{u.target?.closest(".k-column-resizer")||(s.value=e,n.click())},A=u=>{if(i)s.value=e,u.preventDefault(),u.stopPropagation(),f(u);else if(l){s.value=e;const w=new KeyboardEvent("keydown",{key:"Enter",code:"Enter",keyCode:13,which:13,bubbles:!0,cancelable:!0});e.dispatchEvent(w)}};e.addEventListener("click",A),k.push(()=>{e.removeEventListener("click",A)});const x=u=>{if((u.code==="Enter"||u.code==="Space")&&(i||l)){if(s.value=e,s.value.focus(),i)u.preventDefault(),u.stopPropagation(),f(u);else if(l){const w=e.querySelector(".k-link");w&&w.click()}}};e.addEventListener("keydown",x,!0),k.push(()=>{e.removeEventListener("keydown",x,!0)})});const o=document.querySelector(".k-grid-header .k-table-thead");if(o){const e=r=>{const n=r.target.closest("th");n&&(r.code==="Enter"||r.code==="Space")&&n.dataset.featherKFilterable==="false"&&n.dataset.featherKSortable==="false"&&(r.preventDefault(),r.stopImmediatePropagation())};o.addEventListener("keydown",e,!0),k.push(()=>{o.removeEventListener("keydown",e,!0)})}},G=function(t,o){const e=t?.event.event.target,r=v();if(!e||!r)return;const n=e.classList.contains("k-link"),a=e.classList.contains("k-columnmenu-item"),i=r.find(l=>l.field===t.event.field)?.sortable&&!0;if(!n){if(a&&!i){(t.event.sort&&void 0)?.filter(f=>f.field!==t.event.field);return}typeof o=="function"&&c.nextTick(()=>{s.value&&s.value.focus(),o(t)})}},N=()=>{if(!g)try{C(),I(),K(),g=!0}catch(t){console.error("initA11y failed:",t),S()}};return c.onBeforeUnmount(()=>{S()}),{activeFilterButton:s,handleGridKeyDown:q,handleSortChange:G,initA11y:N}};function T(p={}){const{activeByDefault:s=!1,autoActivateDelay:g=0}=p,d=c.ref(s);function m(){d.value=!0}function k(){d.value=!1}function h(){d.value=!d.value}return c.onMounted(()=>{g>0&&!s&&setTimeout(()=>{m()},g)}),c.onUnmounted(()=>{}),{isGridActive:d,activateGrid:m,deactivateGrid:k,toggleGrid:h}}b.useGridA11y=L,b.useGridComposableEx=T,Object.defineProperty(b,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(z,t){typeof exports=="object"&&typeof module<"u"?t(exports,require("vue"),require("@vueuse/integrations/useFocusTrap")):typeof define=="function"&&define.amd?define(["exports","vue","@vueuse/integrations/useFocusTrap"],t):(z=typeof globalThis<"u"?globalThis:z||self,t(z.FeatherKComposables={},z.Vue,z.useFocusTrap))})(this,(function(z,t,ye){"use strict";const ge=e=>{const n=t.ref(null);let d=!1,P=null,F=null;const U=[],N=".k-table-row[data-grid-row-index] [tabindex]",p=()=>e?.value?.columns,g=l=>{const S=l.key||l.code;[" ","Spacebar","Space","Enter"].includes(S)&&(l.preventDefault(),l.stopPropagation(),n.value=l.target,l.target.click())},T=l=>{if(!n.value)return;if(l.code==="Escape"){l.preventDefault(),l.stopPropagation(),n.value&&n.value.focus();return}const S=Array.from(document.querySelectorAll(".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item")),i=document.querySelector(".k-filter-menu-container");if(i){if(l.code==="Tab"){const u=[".k-filter-menu-container .k-dropdownlist[tabindex='0']",".k-filter-menu-container input.k-input-inner:not([tabindex='-1']):not([disabled])",".k-filter-menu-container button:not([tabindex='-1']):not([disabled])"],f=Array.from(i.querySelectorAll(u.join(",")));if(f.length===0)return;const A=f.findIndex($=>$===document.activeElement);let w;A===-1?w=0:l.shiftKey?w=(A-1+f.length)%f.length:w=(A+1)%f.length,l.preventDefault(),l.stopPropagation(),f[w]?.focus();return}}else if(l.code==="ArrowUp"||l.code==="ArrowDown"){l.preventDefault(),l.stopPropagation();const u=S.findIndex(A=>A===document.activeElement);let f=u;l.code==="ArrowUp"?f=u>0?u-1:S.length-1:l.code==="ArrowDown"&&(f=u<S.length-1?u+1:0),S[f]?.focus();return}l.code==="Tab"&&(l.preventDefault(),l.stopPropagation(),l.shiftKey?(n.value?.previousElementSibling).focus():(n.value?.nextElementSibling).focus())},b=()=>{P=new MutationObserver(l=>{l.forEach(S=>{S.addedNodes.forEach(i=>{if(i.nodeType===Node.ELEMENT_NODE){const u=i;if(u.classList.contains("k-animation-container")){const A=n.value;A&&(A.dataset.featherKSortable==="true"||t.nextTick(()=>{u.querySelectorAll(".k-columnmenu-item-wrapper").forEach(Y=>{Y.textContent?.toLowerCase().includes("sort")&&Y.remove()})})),u.addEventListener("keydown",T),t.nextTick(()=>{const w=()=>{const $=Array.from(u.querySelectorAll(".k-animation-container .k-popup .k-column-menu .k-columnmenu-item-wrapper .k-columnmenu-item"));if($.length===1)$[0].focus(),$[0].click(),w.attempts=0;else if($.length>1){w.attempts=0;return}else w.attempts===void 0&&(w.attempts=0),w.attempts++<3&&setTimeout(w,200)};w()})}u.querySelectorAll(".k-animation-container").forEach(A=>{A.addEventListener("keydown",T)})}}),S.removedNodes.forEach(i=>{if(i.nodeType===Node.ELEMENT_NODE){const u=i;u.classList.contains("k-animation-container")&&u.removeEventListener("keydown",T),u.querySelectorAll(".k-animation-container").forEach(A=>{A.removeEventListener("keydown",T)})}})})}),P.observe(document.body,{childList:!0,subtree:!0})},L=l=>{if(!l.type||!l)return;const S=l.target;if(S){if(l.code==="Escape"){const i=document.activeElement?.closest(".k-table-row[data-grid-row-index]");if(i){l.preventDefault(),l.stopPropagation();try{Array.from(e?.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]")).forEach(f=>f.setAttribute("tabindex","-1"))}catch{}i.setAttribute("tabindex","0"),i.focus();return}}if(["ArrowDown","ArrowLeft","ArrowRight","ArrowUp","Enter","Space"].includes(l.code)){if(l.preventDefault(),S.classList.contains("k-grid-header-menu")&&S.classList.contains("k-grid-column-menu")){n.value=S;return}const i=S.closest(".k-table-row[data-grid-row-index]");if(i){if(["ArrowDown","ArrowUp"].includes(l.code)){const u=(A,w)=>{let $=w==="next"?A.nextElementSibling:A.previousElementSibling;for(;$;){const Y=$;try{if(Y.hasAttribute&&Y.classList.contains("k-table-row"))return Y}catch{}$=w==="next"?$.nextElementSibling:$.previousElementSibling}return null},f=l.code==="ArrowDown"?u(i,"next"):u(i,"previous");f&&(i.setAttribute("tabindex","-1"),f.setAttribute("tabindex","0"),f.focus());return}if(["ArrowLeft","ArrowRight"].includes(l.code)){l.preventDefault();const u=i.querySelectorAll(N);if(u.length===0)return;let f=Array.from(u).findIndex(A=>A===document.activeElement);if(f===-1&&document.activeElement===i){u[0].focus();return}l.code==="ArrowRight"?f=f===u.length-1?0:f+1:l.code==="ArrowLeft"&&(f=f===u.length-1?f-1:u.length-1),u[f].focus();return}}}}},E=()=>{t.nextTick(()=>{const l=e.value.$el.closest(".k-grid");l&&l.classList.add("fk-grid")})},x=()=>{if(!F)try{const l=()=>{try{const u=Array.from(e.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]"));if(!u||u.length===0||u.filter(w=>w.getAttribute("tabindex")==="0").length===1)return;const A=u.find(w=>w===document.activeElement||w.contains(document.activeElement));if(u.forEach(w=>w.setAttribute("tabindex","-1")),A){A.setAttribute("tabindex","0");return}u[0].setAttribute("tabindex","0")}catch(u){console.error("ensureSingleTabindex error:",u)}},S=Array.from(e.value.$el.querySelectorAll(".k-table-row[data-grid-row-index]"));S.length>0&&S.forEach((u,f)=>{u.setAttribute("tabindex",f===0?"0":"-1")});const i=e.value.$el.querySelector(".k-table-tbody");i&&(F=new MutationObserver(()=>{l()}),F.observe(i,{childList:!0,subtree:!0}))}catch(l){console.error("Error setting up row navigation:",l)}},I=()=>{t.nextTick(()=>{const S=document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");S&&S.forEach(i=>{i.setAttribute("role","button"),i.addEventListener("keydown",g)}),b(),h()})},O=()=>{const S=document.querySelectorAll(".k-grid-header .k-grid-header-menu.k-grid-column-menu");S&&S.forEach(i=>{i.removeEventListener("keydown",g)}),P&&(P.disconnect(),P=null),F&&(F.disconnect(),F=null),U.forEach(i=>i()),U.length=0},h=()=>{document.querySelectorAll(".k-grid-header .k-table-thead th").forEach((i,u)=>{const f=i.querySelector(".k-grid-header-menu.k-grid-column-menu");if(!f)return;const A=p();if(A&&A[u]){const o=A[u].field??"";i.setAttribute("data-feather-k-field",o),i.setAttribute("data-feather-k-filterable",A[u].filterable===!1?"false":"true"),i.setAttribute("data-feather-k-sortable",A[u].sortable===!1?"false":"true")}const w=i.dataset.featherKFilterable!=="false",$=i.dataset.featherKSortable!=="false";f.setAttribute("tabindex","-1"),w||$?i.style.cursor="pointer":i.style.cursor="default",w?(i.setAttribute("tabindex","0"),i.setAttribute("role","button"),i.setAttribute("aria-haspopup","menu")):(i.setAttribute("tabindex","0"),i.setAttribute("role","columnheader"),i.removeAttribute("aria-haspopup"));const Y=o=>{o.target?.closest(".k-column-resizer")||(n.value=i,f.click())},a=o=>{if(w)n.value=i,o.preventDefault(),o.stopPropagation(),Y(o);else if($){n.value=i;const c=new KeyboardEvent("keydown",{key:"Enter",code:"Enter",keyCode:13,which:13,bubbles:!0,cancelable:!0});i.dispatchEvent(c)}};i.addEventListener("click",a),U.push(()=>{i.removeEventListener("click",a)});const v=o=>{if((o.code==="Enter"||o.code==="Space")&&(w||$)){if(n.value=i,n.value.focus(),w)o.preventDefault(),o.stopPropagation(),Y(o);else if($){const c=i.querySelector(".k-link");c&&c.click()}}};i.addEventListener("keydown",v,!0),U.push(()=>{i.removeEventListener("keydown",v,!0)})});const S=document.querySelector(".k-grid-header .k-table-thead");if(S){const i=u=>{const f=u.target.closest("th");f&&(u.code==="Enter"||u.code==="Space")&&f.dataset.featherKFilterable==="false"&&f.dataset.featherKSortable==="false"&&(u.preventDefault(),u.stopImmediatePropagation())};S.addEventListener("keydown",i,!0),U.push(()=>{S.removeEventListener("keydown",i,!0)})}},D=function(l,S){const i=l?.event.event.target,u=p();if(!i||!u)return;const f=i.classList.contains("k-link"),A=i.classList.contains("k-columnmenu-item"),w=u.find($=>$.field===l.event.field)?.sortable&&!0;if(!f){if(A&&!w){(l.event.sort&&void 0)?.filter(Y=>Y.field!==l.event.field);return}typeof S=="function"&&t.nextTick(()=>{n.value&&n.value.focus(),S(l)})}},q=()=>{if(!d)try{E(),x(),I(),d=!0}catch(l){console.error("initA11y failed:",l),O()}};return t.onBeforeUnmount(()=>{O()}),{activeFilterButton:n,handleGridKeyDown:L,handleSortChange:D,initA11y:q}};function he(e){const n=t.ref(""),d=t.ref(void 0),P=t.ref(!!e.debug),F=t.ref(e.dateFormat??"mm/dd/yyyy"),U=["ArrowUp","ArrowDown"],N=["ArrowLeft","ArrowRight"],p=t.computed(()=>(n.value??"").replace(/\D/g,"")),g=a=>a<=2?"mm":a<=5?"dd":p.value.length<=10?"yyyy":"mm",T=t.computed(()=>{const a=n.value&&n.value.substring(0,2).replace(/\D/g,"0")||"0";return parseInt(a).toString().padStart(2,"0")}),b=t.computed(()=>{const a=n.value&&n.value.substring(3,5).replace(/\D/g,"0")||"0";return parseInt(a).toString().padStart(2,"0")}),L=t.computed(()=>{const a=n.value&&n.value.substring(6,10).replace(/\D/g,"0")||"0";return parseInt(a).toString().padStart(4,"0")}),E=a=>{if(p.value.length===0){e.onChange({value:null,event:a});return}if(p.value.length<8){e.onChange({value:null,event:a});return}if((n.value??"").length===10&&p.value.length===8){const[v,o,c]=n.value.split("/"),m=parseInt(v),k=parseInt(o),M=parseInt(c);if(D(M,m,k)){const H=new Date(M,m-1,k);x(H)?e.onChange({value:H,event:a}):e.onChange({value:null,event:a})}else e.onChange({value:null,event:a})}},x=a=>{const v=e.minDate?.value??void 0,o=e.maxDate?.value??void 0;return!(v&&a<v||o&&a>o)},I=t.computed(()=>{if((n.value??"").length===10&&p.value.length===8){const[a,v,o]=(n.value??"").split("/"),c=parseInt(a),m=parseInt(v),k=parseInt(o);if(D(k,c,m))return new Date(k,c-1,m)}return null}),O=t.computed(()=>(n.value??"").length===10&&p.value.length===8),h=(a,v)=>new Date(a,v,0).getDate(),D=(a,v,o)=>v>=1&&v<=12&&a>=1900&&o>=1&&o<=h(a,v),q=a=>{n.value=a.value;const v=a.event?.target;d.value=v?.selectionStart??0,E(a)},l=a=>{d.value=a.target.selectionStart??0},S=(a,v,o)=>{const c=v?.selectionStart??d.value??0;d.value=c;let m=parseInt(T.value),k=parseInt(b.value),M=parseInt(L.value);if(!(p.value.length>=8)||!D(M,m,k)){const s=new Date;m=s.getMonth()+1,k=s.getDate(),M=s.getFullYear(),n.value=`${String(m).padStart(2,"0")}/${String(k).padStart(2,"0")}/${String(M)}`;const y=new Date(M,m-1,k);e.onChange({value:x(y)?y:null,event:o}),t.nextTick(()=>{requestAnimationFrame(()=>{const C=document.getElementById(e.id),R=d.value??0;C&&(C.focus(),C.setSelectionRange(R,R))})});return}const B=g(c);if(B==="mm")m=a==="ArrowUp"?m<12?m+1:1:m>1?m-1:12;else if(B==="dd"){const s=new Date(M,m,0).getDate();k=a==="ArrowUp"?k<s?k+1:1:k>1?k-1:s}else B==="yyyy"&&(M=a==="ArrowUp"?M+1:Math.max(1,M-1));n.value=`${String(m).padStart(2,"0")}/${String(k).padStart(2,"0")}/${String(M)}`;const[V,Q,J]=n.value.split("/"),r=new Date(parseInt(J),parseInt(V)-1,parseInt(Q));r.toString()!=="Invalid Date"&&parseInt(J)>=1e3?e.onChange({value:x(r)?r:null,event:o}):e.onChange({value:null,event:o}),t.nextTick(()=>{requestAnimationFrame(()=>{const s=document.getElementById(e.id),y=d.value??0;s&&(s.focus(),s.setSelectionRange(y,y))})})},i=a=>{if(a.code==="Space"||a.key===" "){e.onShowCalendar(a);return}if(U.includes(a.key)){a.preventDefault();const v=a.target;S(a.key,v,a)}},u=a=>{a.preventDefault();const v=a.deltaY<0?"ArrowUp":"ArrowDown";S(v,a.target,a)},f=a=>{const v=a.target;if(N.includes(a.key)){d.value=v?.selectionStart??0;return}U.includes(a.key)&&(a.preventDefault(),d.value=v?.selectionStart??0)};e.externalValue&&t.watch(e.externalValue,a=>{if(a){const v=new Date(a);if(v.toString()!=="Invalid Date"){const o=(v.getMonth()+1).toString().padStart(2,"0"),c=v.getDate().toString().padStart(2,"0"),m=v.getFullYear().toString();n.value=`${o}/${c}/${m}`;return}}},{immediate:!0});const A=t.computed(()=>[["Date",`${T.value} / ${b.value} / ${L.value}`],["Digits only",p.value],["min",e.minDate?.value.toLocaleDateString("en-US",{year:"numeric",month:"2-digit",day:"2-digit"})??null],["max",e.maxDate?.value.toLocaleDateString("en-US",{year:"numeric",month:"2-digit",day:"2-digit"})??null]]),w=t.computed(()=>{if(!O.value)return!0;const a=I.value;return a?x(a):!1}),$=t.computed(()=>{if(w.value)return"";if(O.value&&!I.value)return`Must be in ${F.value} format.`;const a=e.minDate?.value?e.minDate.value.toLocaleDateString("en-US",{year:"numeric",month:"2-digit",day:"2-digit"}):null,v=e.maxDate?.value?e.maxDate.value.toLocaleDateString("en-US",{year:"numeric",month:"2-digit",day:"2-digit"}):null;return a&&v?`Must be between ${a} and ${v}.`:a?`Must be on or after ${a}.`:v?`Must be on or before ${v}.`:""}),Y=()=>{t.nextTick(()=>{const a=document.getElementById(e.id);if(!a){console.warn(`ID (#${e.id}) not found for styling.`);return}const v=a.closest(".k-datepicker");if(v)v.classList.add("fk-datepicker");else{console.warn(`.k-datepicker parent not found for #${e.id} styling.`);return}})};return t.onMounted(()=>{Y()}),{raw:n,cursorPos:d,debugEnabled:P,debugLines:A,placeholder:F,isValid:w,validationMessage:$,digitsOnly:p,month:t.readonly(T),day:t.readonly(b),year:t.readonly(L),datePart:g,handleChange:q,handleKeyDown:i,handleWheel:u,handleKeyUp:f,handleClick:l}}function ve(e){const n=t.ref(""),d=t.ref(void 0),P=t.ref(!!e.debug),F=t.ref(!1),U=t.computed(()=>(n.value??"").replace(/\D/g,"")),N=(r,s)=>new Date(r,s,0).getDate(),p=(r,s,y)=>s>=1&&s<=12&&r>=1e3&&y>=1&&y<=N(r,s),g=r=>{if(!r)return null;const s=e.min??new Date(-864e13),y=e.max??new Date(864e13);return r<s||r>y?null:r},T=(r,s)=>e.allowReverse??!1?!0:r<=s,b=t.computed(()=>(n.value?.length??0)>=23&&U.value.length>=16),L=r=>{if((r??"").length<23||U.value.length<16)return{start:null,end:null};const s=r.substring(0,10),y=r.substring(13,23),[C,R,W]=s.split("/").map(X=>parseInt(X||"0",10)),[oe,ee,te]=y.split("/").map(X=>parseInt(X||"0",10)),ne=new Date(W,C-1,R),ae=new Date(te,oe-1,ee),ue=p(W,C,R)&&ne.toString()!=="Invalid Date",ce=p(te,oe,ee)&&ae.toString()!=="Invalid Date";return{start:ue?ne:null,end:ce?ae:null}},E=e.externalValid??t.ref(void 0),x=t.computed(()=>L(n.value)),I=t.computed(()=>{const r=g(x.value.start),s=g(x.value.end);return{start:r,end:s}}),O=t.computed(()=>{if(!b.value)return;const r=I.value.start,s=I.value.end;if(!r||!s)return;const y=R=>Date.UTC(R.getFullYear(),R.getMonth(),R.getDate()),C=Math.abs(y(s)-y(r));return Math.round(C/864e5)}),h=t.computed(()=>{const{start:r,end:s}=x.value;if(!b.value)return;if(!r||!s||!I.value.start||!I.value.end)return!1;const y=I.value.start,C=I.value.end;return!(!T(y,C)||typeof e.maxSpanDays=="number"&&O.value!==void 0&&O.value>e.maxSpanDays)}),D=t.computed(()=>{const{start:r,end:s}=x.value;if(!b.value)return"incomplete";if(!r||!s)return"invalid-date";if(!I.value.start||!I.value.end)return"out-of-bounds";const y=I.value.start,C=I.value.end;return T(y,C)?typeof e.maxSpanDays=="number"&&O.value!==void 0&&O.value>e.maxSpanDays?"span-exceeds-limit":"valid":"reversed-range"}),q=t.computed(()=>{switch(D.value){case"invalid-date":return"Enter valid start and end dates.";case"out-of-bounds":return"Must be within the allowed range.";case"reversed-range":return"Must be in sequential order.";case"span-exceeds-limit":return typeof e.maxSpanDays=="number"?`Exceeds maximum of ${e.maxSpanDays} days.`:"Exceeds maximum allowed span.";case"valid":default:return""}});t.watch(h,r=>{(e.manageValid??!0)&&(E.value=r)},{immediate:!0});const l=["ArrowUp","ArrowDown"],S=["ArrowLeft","ArrowRight"],i=(r,s)=>String(r).padStart(s,"0"),u=r=>`${i(r.getMonth()+1,2)}/${i(r.getDate(),2)}/${i(r.getFullYear(),4)}`,f=r=>r<=2?{side:"start",part:"mm"}:r<=5?{side:"start",part:(r<=3,"dd")}:r<=11?{side:"start",part:"yyyy"}:r<=15?{side:"end",part:"mm"}:r<=18?{side:"end",part:"dd"}:{side:"end",part:"yyyy"},A=(r,s,y)=>{const C=s?.selectionStart??d.value??0;d.value=C;const{start:R,end:W}=L(n.value);if(!R&&!W){const K=new Date,_=u(K);n.value=`${_} - ${_}`,e.onChange({value:{start:K,end:K},event:y}),t.nextTick(()=>{requestAnimationFrame(()=>{const j=document.getElementById(e.id),G=d.value??0;j&&(j.focus(),j.setSelectionRange(G,G))})});return}const oe=new Date,ee=f(C);let te=R?new Date(R):new Date(oe),ne=W?new Date(W):new Date(oe);const ae=(K,_)=>{if(_==="mm"){const j=K.getMonth()+1,G=r==="ArrowUp"?j<12?j+1:1:j>1?j-1:12;K.setMonth(G-1);const le=N(K.getFullYear(),G);K.getDate()>le&&K.setDate(le)}else if(_==="dd"){const j=N(K.getFullYear(),K.getMonth()+1),G=K.getDate(),le=r==="ArrowUp"?G<j?G+1:1:G>1?G-1:j;K.setDate(le)}else if(_==="yyyy"){const j=K.getFullYear();K.setFullYear(r==="ArrowUp"?j+1:Math.max(1,j-1));const G=N(K.getFullYear(),K.getMonth()+1);K.getDate()>G&&K.setDate(G)}};ee.side==="start"?ae(te,ee.part):ae(ne,ee.part);const ue=u(te),ce=u(ne);n.value=`${ue} - ${ce}`;const X=g(te),de=g(ne);X&&de&&T(X,de)?e.onChange({value:{start:X,end:de},event:y}):e.onChange({value:null,event:y}),t.nextTick(()=>{requestAnimationFrame(()=>{const K=document.getElementById(e.id),_=d.value??0;K&&(K.focus(),K.setSelectionRange(_,_))})})},w=r=>{const{start:s,end:y}=L(n.value);if(!s||!y){e.onChange({value:null,event:r});return}const C=g(s),R=g(y);if(!C||!R){e.onChange({value:null,event:r});return}if(!T(C,R)){e.onChange({value:null,event:r});return}e.onChange({value:{start:C,end:R},event:r})},$=r=>{n.value=r.value;const s=r.event?.target;d.value=s?.selectionStart??0,w(r)},Y=r=>{d.value=r.target.selectionStart??0},a=r=>{if(r.code==="Space"||r.key===" "){r.preventDefault(),e.onShowCalendar(r);return}if(l.includes(r.key)){r.preventDefault();const s=r.target;A(r.key,s,r)}},v=r=>{r.preventDefault();const s=r.deltaY<0?"ArrowUp":"ArrowDown";A(s,r.target,r)},o=r=>{if(S.includes(r.key)||l.includes(r.key)){r.preventDefault();const s=r.target;d.value=s?.selectionStart??0}};e.externalValue&&t.watch(e.externalValue,r=>{const s=r?.start??null,y=r?.end??null;if(s&&y){if(n.value=`${u(s)} - ${u(y)}`,e.isOpen?.value&&F.value&&e.onRequestClose){const C=typeof e.closeDelay=="number"?e.closeDelay:e.closeDelay?.value??0;setTimeout(()=>{e.onRequestClose?.(),F.value=!1},C)}return}},{immediate:!0});const c=t.computed(()=>n.value&&n.value.substring(0,2).replace(/\D/g,"0")||"0"),m=t.computed(()=>n.value&&n.value.substring(3,5).replace(/\D/g,"0")||"0"),k=t.computed(()=>n.value&&n.value.substring(6,10).replace(/\D/g,"0")||"0"),M=t.computed(()=>n.value&&n.value.substring(13,15).replace(/\D/g,"0")||"0"),H=t.computed(()=>n.value&&n.value.substring(16,18).replace(/\D/g,"0")||"0"),B=t.computed(()=>n.value&&n.value.substring(19,23).replace(/\D/g,"0")||"0"),V=t.computed(()=>{const{start:r,end:s}=L(n.value);return[{label:"Raw",value:n.value},{label:"Start",value:`${String(c.value).padStart(2,"0")} / ${String(m.value).padStart(2,"0")} / ${String(k.value).padStart(4,"0")}`},{label:"End",value:`${String(M.value).padStart(2,"0")} / ${String(H.value).padStart(2,"0")} / ${String(B.value).padStart(4,"0")}`},{label:"Digits only",value:U.value},{label:"Mask complete",value:`${b.value}`},{label:"Parsed",value:`${r?r.toDateString():"-"} | ${s?s.toDateString():"-"}`},{label:"Valid (managed)",value:`${E.value??"-"}`},{label:"Span (days)",value:`${O.value??"-"}`},{label:"Reason",value:`${D.value??"-"}`},{label:"Cursor in",value:`${d.value??"-"} (${(()=>{const y=d.value??0,C=f(y);return`${C.side}.${C.part}`})()})`}]}),Q=()=>{t.nextTick(()=>{const r=document.getElementById(e.id);if(!r){console.warn(`#${e.id} not found for styling.`);return}const s=r.closest("div");s?s.classList.add("fk-daterangepicker"):console.warn(`Parent div of #${e.id} not found for styling.`)})};t.onMounted(()=>{Q()});const J=()=>{t.nextTick(()=>{try{const r=Array.from(document.querySelectorAll(".k-animation-container"));if(!r.length)return;const y=[...r].reverse().find(W=>W.querySelector(".k-calendar"))?.querySelector(".k-calendar");if(!y)return;const C=y.querySelectorAll(".k-calendar-table");let R=!1;C.forEach(W=>{W.tabIndex===0&&(R?W.tabIndex=-1:R=!0)})}catch(r){console.warn(r)}})};return e.isOpen&&t.watch(e.isOpen,r=>{r&&setTimeout(()=>J(),0)},{immediate:!1}),{raw:n,cursorPos:d,debugEnabled:P,debugLines:V,digitsOnly:U,valid:E,validComputed:t.readonly(h),reason:t.readonly(D),validationMessage:t.readonly(q),spanDays:t.readonly(O),month1:t.readonly(c),day1:t.readonly(m),year1:t.readonly(k),month2:t.readonly(M),day2:t.readonly(H),year2:t.readonly(B),handleChange:$,handleKeyDown:a,handleWheel:v,handleKeyUp:o,handleClick:Y,onCalendarChange:()=>{F.value=!0}}}function Se(e){const n=t.ref(""),d=t.ref(void 0),P=t.ref(!!e.debug),F=t.ref(e.timeFormat??"hh:mm AM"),U=["ArrowUp","ArrowDown"],N=["ArrowLeft","ArrowRight"],p=t.computed(()=>(n.value??"").replace(/\D/g,"")),g={H:/[0-9]/,h:/[0-9]/,M:/[0-9]/,m:/[0-9]/,A:/[AaPp]/,a:/[Mm]/},T=o=>o<=2?"hh":o<=5?"mm":"ampm",b=t.computed(()=>{const o=n.value&&n.value.substring(0,2).replace(/\D/g,"0")||"0",c=parseInt(o);return Math.min(Math.max(c,0),23)}),L=t.computed(()=>{const o=n.value&&n.value.substring(3,5).replace(/\D/g,"0")||"0",c=parseInt(o);return Math.min(Math.max(c,0),59)}),E=t.computed(()=>(n.value?.substring(6,8)||"AM").toUpperCase().startsWith("P")?"PM":"AM"),x=t.computed(()=>/^(\d{2}):(\d{2})\s([AP]M)$/i.test(n.value??"")),I=(o,c,m)=>{let k=o%12;return k===0&&(k=12),`${String(k).padStart(2,"0")}:${String(c).padStart(2,"0")} ${m}`},O=o=>{const c=o.match(/^(\d{2}):(\d{2})\s([AP]M)$/i);if(!c)return null;const m=parseInt(c[1]),k=parseInt(c[2]),M=c[3].toUpperCase();if(m<1||m>12||k<0||k>59)return null;let H=m%12;M==="PM"&&(H+=12);const B=new Date;return B.setSeconds(0,0),B.setHours(H),B.setMinutes(k),B},h=o=>{if(p.value.length===0){e.onChange({value:null,event:o});return}if(!x.value){e.onChange({value:null,event:o});return}const c=O(n.value);c&&q(c)?e.onChange({value:c,event:o}):e.onChange({value:null,event:o})},D=o=>o.getHours()*60+o.getMinutes(),q=o=>{const c=e.minTime?.value??void 0,m=e.maxTime?.value??void 0,k=D(o);if(c){const M=D(c);if(k<M)return!1}if(m){const M=D(m);if(k>M)return!1}return!0},l=o=>{n.value=o.value;const c=o.event?.target;d.value=c?.selectionStart??0;try{const m=d.value??0;if(T(m)==="ampm"&&(n.value??"").length>=8){const k=n.value.charAt(6);/[Aa]/.test(k)?(n.value=`${n.value.slice(0,6)}AM${n.value.slice(8)}`,t.nextTick(()=>{requestAnimationFrame(()=>{const M=document.getElementById(e.id);M&&(M.focus(),M.setSelectionRange(8,8))})})):/[Pp]/.test(k)&&(n.value=`${n.value.slice(0,6)}PM${n.value.slice(8)}`,t.nextTick(()=>{requestAnimationFrame(()=>{const M=document.getElementById(e.id);M&&(M.focus(),M.setSelectionRange(8,8))})}))}}catch{}h(o)},S=o=>{d.value=o.target.selectionStart??0},i=(o,c,m)=>{const k=c?.selectionStart??d.value??0;d.value=k;const M=T(k),H=(n.value??"").match(/^(\d{2}):(\d{2})\s([AP]M)$/i);if(!H){if((n.value??"").length===0||p.value.length===0){const r=new Date,s=r.getHours(),y=s>=12?"PM":"AM";n.value=I(s,r.getMinutes(),y);const C=O(n.value);C&&q(C)?e.onChange({value:C,event:m}):e.onChange({value:null,event:m}),t.nextTick(()=>{requestAnimationFrame(()=>{const R=document.getElementById(e.id),W=d.value??0;R&&(R.focus(),R.setSelectionRange(W,W))})})}return}let B=parseInt(H[1]),V=parseInt(H[2]),Q=H[3].toUpperCase();if(M==="hh")o==="ArrowUp"?B=B<12?B+1:1:B=B>1?B-1:12;else if(M==="mm"){const r=e.minuteStepRef?.value??e.minuteStep??1;if(r===1)o==="ArrowUp"?V=V<59?V+1:0:V=V>0?V-1:59;else if(o==="ArrowUp"){const s=V%r;let y=s===0?V+r:V+(r-s);y>=60&&(y=0),V=y}else{const s=V%r;let y=s===0?V-r:V-s;y<0&&(y=60-r),V=y}}else M==="ampm"&&(Q=Q==="AM"?"PM":"AM");n.value=`${String(B).padStart(2,"0")}:${String(V).padStart(2,"0")} ${Q}`;const J=O(n.value);J&&q(J)?e.onChange({value:J,event:m}):e.onChange({value:null,event:m}),t.nextTick(()=>{requestAnimationFrame(()=>{const r=document.getElementById(e.id),s=d.value??0;r&&(r.focus(),r.setSelectionRange(s,s))})})},u=o=>{if(o.code==="Space"||o.key===" "){e.onShowPicker(o);return}if(U.includes(o.key)){o.preventDefault();const c=o.target;i(o.key,c,o)}},f=o=>{o.preventDefault();const c=o.deltaY<0?"ArrowUp":"ArrowDown";i(c,o.target,o)},A=o=>{const c=o.target;if(N.includes(o.key)){d.value=c?.selectionStart??0;return}U.includes(o.key)&&(o.preventDefault(),d.value=c?.selectionStart??0)};e.externalValue&&t.watch(e.externalValue,o=>{if(o){const c=new Date(o);if(c.toString()!=="Invalid Date"){const m=c.getHours(),k=m>=12?"PM":"AM";n.value=I(m,c.getMinutes(),k);return}}},{immediate:!0});const w=t.computed(()=>[["Time",n.value||"--:-- --"],["Hour (12h)",b.value],["Minute",L.value],["Period",E.value],["Digits only",p.value],["min",e.minTime?.value.toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit"})??null],["max",e.maxTime?.value.toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit"})??null]]),$=t.computed(()=>O(n.value)??null),Y=t.computed(()=>{if(!x.value)return!0;const o=$.value;return o?q(o):!1}),a=t.computed(()=>{if(Y.value)return"";if(x.value&&!$.value)return`Must be in ${F.value} format.`;const o={hour:"2-digit",minute:"2-digit"},c=e.minTime?.value?e.minTime.value.toLocaleTimeString("en-US",o):null,m=e.maxTime?.value?e.maxTime.value.toLocaleTimeString("en-US",o):null;return c&&m?`Must be between ${c} and ${m}.`:c?`Must be ${c} or later.`:m?`Must be ${m} or earlier.`:""}),v=()=>{t.nextTick(()=>{const o=document.getElementById(e.id);if(!o){console.warn(`#${e.id} not found for styling.`);return}const c=o.closest(".k-timepicker");c?c.classList.add("fk-timepicker"):console.warn(`.k-timepicker parent of #${e.id} not found for styling.`)})};return t.onMounted(()=>{v()}),{raw:n,rules:g,debugEnabled:P,debugLines:w,placeholder:F,isValid:Y,validationMessage:a,isComplete:x,inRangeTime:q,hour:t.readonly(b),minute:t.readonly(L),period:t.readonly(E),handleChange:l,handleKeyDown:u,handleKeyUp:A,handleClick:S,handleWheel:f}}function be(e){return t.getCurrentScope()?(t.onScopeDispose(e),!0):!1}const fe=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const we=Object.prototype.toString,ke=e=>we.call(e)==="[object Object]",Z=()=>{},De=Ae();function Ae(){var e,n;return fe&&((e=window?.navigator)==null?void 0:e.userAgent)&&(/iP(?:ad|hone|od)/.test(window.navigator.userAgent)||((n=window?.navigator)==null?void 0:n.maxTouchPoints)>2&&/iPad|Macintosh/.test(window?.navigator.userAgent))}function ie(e){return Array.isArray(e)?e:[e]}function Ee(e,n,d){return t.watch(e,n,{...d,immediate:!0})}const me=fe?window:void 0;function re(e){var n;const d=t.toValue(e);return(n=d?.$el)!=null?n:d}function se(...e){const n=[],d=()=>{n.forEach(p=>p()),n.length=0},P=(p,g,T,b)=>(p.addEventListener(g,T,b),()=>p.removeEventListener(g,T,b)),F=t.computed(()=>{const p=ie(t.toValue(e[0])).filter(g=>g!=null);return p.every(g=>typeof g!="string")?p:void 0}),U=Ee(()=>{var p,g;return[(g=(p=F.value)==null?void 0:p.map(T=>re(T)))!=null?g:[me].filter(T=>T!=null),ie(t.toValue(F.value?e[1]:e[0])),ie(t.unref(F.value?e[2]:e[1])),t.toValue(F.value?e[3]:e[2])]},([p,g,T,b])=>{if(d(),!p?.length||!g?.length||!T?.length)return;const L=ke(b)?{...b}:b;n.push(...p.flatMap(E=>g.flatMap(x=>T.map(I=>P(E,x,I,L)))))},{flush:"post"}),N=()=>{U(),d()};return be(d),N}let pe=!1;function xe(e,n,d={}){const{window:P=me,ignore:F=[],capture:U=!0,detectIframe:N=!1,controls:p=!1}=d;if(!P)return p?{stop:Z,cancel:Z,trigger:Z}:Z;if(De&&!pe){pe=!0;const h={passive:!0};Array.from(P.document.body.children).forEach(D=>D.addEventListener("click",Z,h)),P.document.documentElement.addEventListener("click",Z,h)}let g=!0;const T=h=>t.toValue(F).some(D=>{if(typeof D=="string")return Array.from(P.document.querySelectorAll(D)).some(q=>q===h.target||h.composedPath().includes(q));{const q=re(D);return q&&(h.target===q||h.composedPath().includes(q))}});function b(h){const D=t.toValue(h);return D&&D.$.subTree.shapeFlag===16}function L(h,D){const q=t.toValue(h),l=q.$.subTree&&q.$.subTree.children;return l==null||!Array.isArray(l)?!1:l.some(S=>S.el===D.target||D.composedPath().includes(S.el))}const E=h=>{const D=re(e);if(h.target!=null&&!(!(D instanceof Element)&&b(e)&&L(e,h))&&!(!D||D===h.target||h.composedPath().includes(D))){if("detail"in h&&h.detail===0&&(g=!T(h)),!g){g=!0;return}n(h)}};let x=!1;const I=[se(P,"click",h=>{x||(x=!0,setTimeout(()=>{x=!1},0),E(h))},{passive:!0,capture:U}),se(P,"pointerdown",h=>{const D=re(e);g=!T(h)&&!!(D&&!h.composedPath().includes(D))},{passive:!0}),N&&se(P,"blur",h=>{setTimeout(()=>{var D;const q=re(e);((D=P.document.activeElement)==null?void 0:D.tagName)==="IFRAME"&&!q?.contains(P.document.activeElement)&&n(h)},0)},{passive:!0})].filter(Boolean),O=()=>I.forEach(h=>h());return p?{stop:O,cancel:()=>{g=!1},trigger:h=>{g=!0,E(h),g=!1}}:O}function Me(e){const n=t.shallowRef(null),d=[".k-animation-container .k-popup",".k-popup",".k-timepicker-popup",".k-menu-popup"],P=Array.isArray(e.popupSelector)?e.popupSelector:[e.popupSelector??d].flat(),F=b=>b?typeof e.initialFocus=="string"?b.querySelector(e.initialFocus)??b:typeof e.initialFocus=="function"?e.initialFocus(b)??b:b.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')??b:null,{activate:U,deactivate:N}=ye.useFocusTrap(n,{escapeDeactivates:!1,clickOutsideDeactivates:!1,fallbackFocus:()=>n.value,initialFocus:()=>F(n.value),...e.focusTrapOptions??{}});let p=null,g=t.ref(null);const T=()=>{if(e.resolvePopupEl)return e.resolvePopupEl();const b=(E,x)=>{const I=Array.from(E.querySelectorAll(x));if(!I.length)return null;const O=I.filter(h=>h.offsetParent!==null||h.getClientRects().length>0);return O[O.length-1]??I[I.length-1]??null},L=e.triggerEl?.value?.closest?.(".k-animation-container")??document.body;for(const E of P){const x=b(L,E);if(x)return x}for(const E of P){const x=b(document,E);if(x)return x}return null};return xe(n,b=>{g.value="outside",e.isOpen.value&&e.onRequestClose?.("outside",b)}),t.watch(()=>e.isOpen.value,b=>{b?(g.value=null,t.nextTick(()=>{const L=T();L&&(n.value=L,setTimeout(()=>U(),0),p||(p=E=>{E.key==="Escape"&&(g.value="escape",e.isOpen.value&&e.onRequestClose?.("escape",E))},document.addEventListener("keydown",p,!0)))})):(N(),p&&(document.removeEventListener("keydown",p,!0),p=null),n.value=null,g.value==="outside"&&t.nextTick(()=>{const L=()=>{const E=e.triggerEl?.value,x=E?.querySelector("input"),I=document.activeElement;I&&(I===x||E&&E.contains(I))&&(x??E)?.blur?.()};requestAnimationFrame(()=>setTimeout(L,0))}),e.returnFocusToTrigger!==!1&&g.value==="escape"&&e.triggerEl?.value&&(console.log("Returning focus to trigger element:",e.triggerEl.value),t.nextTick(()=>{const L=()=>{const E=e.triggerEl?.value;if(!E)return;(E.querySelector("input")??E)?.focus?.()};requestAnimationFrame(()=>setTimeout(L,0))})))}),{popupRef:n,activate:U,deactivate:N,lastCloseReason:t.readonly(g),setPopupEl:b=>n.value=b}}z.useGridA11y=ge,z.useMaskedDateInput=he,z.useMaskedDateRangeInput=ve,z.useMaskedTimeInput=Se,z.usePopupTrap=Me,Object.defineProperty(z,Symbol.toStringTag,{value:"Module"})}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useGridA11y } from "../useGridA11y";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
export
|
|
2
|
-
export {
|
|
1
|
+
export * from "./grid";
|
|
2
|
+
export { useMaskedDateInput } from "./date";
|
|
3
|
+
export type { ChangePayload as DateChangePayload } from "./date";
|
|
4
|
+
export { useMaskedDateRangeInput } from "./range";
|
|
5
|
+
export type { RangeChangePayload } from "./range";
|
|
6
|
+
export { useMaskedTimeInput } from "./time";
|
|
7
|
+
export type { ChangePayload as TimeChangePayload } from "./time";
|
|
8
|
+
export { usePopupTrap } from "./trap";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useMaskedDateRangeInput";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Ref } from "vue";
|
|
2
|
+
import type { SelectionRange } from "@progress/kendo-vue-dateinputs";
|
|
3
|
+
export type RangeChangePayload = {
|
|
4
|
+
value: SelectionRange | null;
|
|
5
|
+
event: any;
|
|
6
|
+
};
|
|
7
|
+
export declare function useMaskedDateRangeInput(args: {
|
|
8
|
+
id: string;
|
|
9
|
+
onChange: (p: RangeChangePayload) => void;
|
|
10
|
+
onShowCalendar: (e: KeyboardEvent) => void;
|
|
11
|
+
externalValue?: Ref<SelectionRange | null | undefined>;
|
|
12
|
+
externalValid?: Ref<boolean | undefined>;
|
|
13
|
+
manageValid?: boolean;
|
|
14
|
+
maxSpanDays?: number;
|
|
15
|
+
min?: Date;
|
|
16
|
+
max?: Date;
|
|
17
|
+
allowReverse?: boolean;
|
|
18
|
+
isOpen?: Ref<boolean>;
|
|
19
|
+
onRequestClose?: () => void;
|
|
20
|
+
closeDelay?: Ref<number> | number;
|
|
21
|
+
debug?: boolean;
|
|
22
|
+
}): {
|
|
23
|
+
raw: Ref<string, string>;
|
|
24
|
+
cursorPos: Ref<number, number>;
|
|
25
|
+
debugEnabled: Ref<boolean, boolean>;
|
|
26
|
+
debugLines: import("vue").ComputedRef<{
|
|
27
|
+
label: string;
|
|
28
|
+
value: string;
|
|
29
|
+
}[]>;
|
|
30
|
+
digitsOnly: import("vue").ComputedRef<string>;
|
|
31
|
+
valid: Ref<boolean, boolean>;
|
|
32
|
+
validComputed: Readonly<Ref<boolean, boolean>>;
|
|
33
|
+
reason: Readonly<Ref<string, string>>;
|
|
34
|
+
validationMessage: Readonly<Ref<string, string>>;
|
|
35
|
+
spanDays: Readonly<Ref<number, number>>;
|
|
36
|
+
month1: Readonly<Ref<string, string>>;
|
|
37
|
+
day1: Readonly<Ref<string, string>>;
|
|
38
|
+
year1: Readonly<Ref<string, string>>;
|
|
39
|
+
month2: Readonly<Ref<string, string>>;
|
|
40
|
+
day2: Readonly<Ref<string, string>>;
|
|
41
|
+
year2: Readonly<Ref<string, string>>;
|
|
42
|
+
handleChange: (event: any) => void;
|
|
43
|
+
handleKeyDown: (event: KeyboardEvent) => void;
|
|
44
|
+
handleWheel: (event: WheelEvent) => void;
|
|
45
|
+
handleKeyUp: (event: KeyboardEvent) => void;
|
|
46
|
+
handleClick: (event: MouseEvent) => void;
|
|
47
|
+
onCalendarChange: () => void;
|
|
48
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useMaskedTimeInput";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type Ref } from "vue";
|
|
2
|
+
export type ChangePayload = {
|
|
3
|
+
value: Date | null;
|
|
4
|
+
event: any;
|
|
5
|
+
};
|
|
6
|
+
export declare function useMaskedTimeInput(args: {
|
|
7
|
+
id: string;
|
|
8
|
+
onChange: (p: ChangePayload) => void;
|
|
9
|
+
onShowPicker: (e: KeyboardEvent) => void;
|
|
10
|
+
externalValue?: Ref<string | Date | null | undefined>;
|
|
11
|
+
minuteStep?: 1 | 5 | 10 | 15 | 20 | 30;
|
|
12
|
+
minuteStepRef?: Ref<1 | 5 | 10 | 15 | 20 | 30 | undefined>;
|
|
13
|
+
timeFormat?: string;
|
|
14
|
+
minTime?: Ref<Date | null | undefined>;
|
|
15
|
+
maxTime?: Ref<Date | null | undefined>;
|
|
16
|
+
debug?: boolean;
|
|
17
|
+
}): {
|
|
18
|
+
raw: Ref<string, string>;
|
|
19
|
+
rules: {
|
|
20
|
+
readonly H: RegExp;
|
|
21
|
+
readonly h: RegExp;
|
|
22
|
+
readonly M: RegExp;
|
|
23
|
+
readonly m: RegExp;
|
|
24
|
+
readonly A: RegExp;
|
|
25
|
+
readonly a: RegExp;
|
|
26
|
+
};
|
|
27
|
+
debugEnabled: Ref<boolean, boolean>;
|
|
28
|
+
debugLines: import("vue").ComputedRef<(string | number)[][]>;
|
|
29
|
+
placeholder: Ref<string, string>;
|
|
30
|
+
isValid: import("vue").ComputedRef<boolean>;
|
|
31
|
+
validationMessage: import("vue").ComputedRef<string>;
|
|
32
|
+
isComplete: import("vue").ComputedRef<boolean>;
|
|
33
|
+
inRangeTime: (d: Date) => boolean;
|
|
34
|
+
hour: Readonly<Ref<number, number>>;
|
|
35
|
+
minute: Readonly<Ref<number, number>>;
|
|
36
|
+
period: Readonly<Ref<"AM" | "PM", "AM" | "PM">>;
|
|
37
|
+
handleChange: (event: any) => void;
|
|
38
|
+
handleKeyDown: (event: KeyboardEvent) => void;
|
|
39
|
+
handleKeyUp: (event: KeyboardEvent) => void;
|
|
40
|
+
handleClick: (event: MouseEvent) => void;
|
|
41
|
+
handleWheel: (event: WheelEvent) => void;
|
|
42
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./usePopupTrap";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Ref } from "vue";
|
|
2
|
+
import { useFocusTrap } from "@vueuse/integrations/useFocusTrap";
|
|
3
|
+
export type CloseReason = "escape" | "outside";
|
|
4
|
+
export type InitialFocus = string | ((root: HTMLElement) => HTMLElement | null | undefined);
|
|
5
|
+
export declare function usePopupTrap(opts: {
|
|
6
|
+
isOpen: Ref<boolean>;
|
|
7
|
+
onRequestClose?: (reason: CloseReason, ev?: Event) => void;
|
|
8
|
+
popupSelector?: string | string[];
|
|
9
|
+
triggerEl?: Ref<HTMLElement | null>;
|
|
10
|
+
resolvePopupEl?: () => HTMLElement | null;
|
|
11
|
+
initialFocus?: InitialFocus;
|
|
12
|
+
focusTrapOptions?: Parameters<typeof useFocusTrap>[1];
|
|
13
|
+
returnFocusToTrigger?: boolean;
|
|
14
|
+
}): {
|
|
15
|
+
popupRef: import("vue").ShallowRef<HTMLElement, HTMLElement>;
|
|
16
|
+
activate: (opts?: import("focus-trap").ActivateOptions) => void;
|
|
17
|
+
deactivate: (opts?: import("focus-trap").DeactivateOptions) => void;
|
|
18
|
+
lastCloseReason: Readonly<Ref<CloseReason, CloseReason>>;
|
|
19
|
+
setPopupEl: (el: HTMLElement | null) => HTMLElement;
|
|
20
|
+
};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# useMaskedDateInput
|
|
2
|
+
|
|
3
|
+
[← Back to Composables README](../../README.md)
|
|
4
|
+
|
|
5
|
+
Composable for a masked single-date input that pairs naturally with Kendo Vue `MaskedTextBox`, and optionally coordinates with a `DatePicker`. It handles typed input in the mask `MM/DD/YYYY`, keyboard steppers (ArrowUp/ArrowDown), wheel steppers, caret persistence, min/max range validation, and clean emission only when the input is complete and valid.
|
|
6
|
+
|
|
7
|
+
This composable is designed to preserve the user's raw input: it does not coerce or rewrite what the user has typed in `onChange`. It only emits a parsed `Date` when the input is complete and valid.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
- Vue 3 Composition API
|
|
12
|
+
- `@progress/kendo-vue-inputs` (for `MaskedTextBox`)
|
|
13
|
+
- Optional: `@progress/kendo-vue-dateinputs` (`DatePicker`)
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```vue
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import { ref } from "vue";
|
|
20
|
+
import { DatePicker } from "@progress/kendo-vue-dateinputs";
|
|
21
|
+
import { MaskedTextBox } from "@progress/kendo-vue-inputs";
|
|
22
|
+
import { Error } from "@progress/kendo-vue-labels";
|
|
23
|
+
import { useMaskedDateInput } from "@featherk/composables/date";
|
|
24
|
+
|
|
25
|
+
const selectedDate = ref<Date | null | undefined>();
|
|
26
|
+
const showPicker = ref(false);
|
|
27
|
+
|
|
28
|
+
const MIN_DATE = new Date(2024, 0, 1);
|
|
29
|
+
const MAX_DATE = new Date(2026, 11, 31);
|
|
30
|
+
|
|
31
|
+
const masked = useMaskedDateInput({
|
|
32
|
+
id: "startDate",
|
|
33
|
+
onChange: ({ value }) => { selectedDate.value = value ?? undefined; },
|
|
34
|
+
onShowCalendar: () => (showPicker.value = true),
|
|
35
|
+
externalValue: selectedDate as any,
|
|
36
|
+
minDate: ref(MIN_DATE),
|
|
37
|
+
maxDate: ref(MAX_DATE),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Destructure only values used as component props
|
|
41
|
+
// (avoids .value in templates while keeping handlers namespaced)
|
|
42
|
+
const { isValid, validationMessage, debugEnabled, debugLines } = masked;
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<DatePicker
|
|
47
|
+
v-model="selectedDate"
|
|
48
|
+
dateInput="masked"
|
|
49
|
+
:min="MIN_DATE"
|
|
50
|
+
:max="MAX_DATE"
|
|
51
|
+
:valid="isValid"
|
|
52
|
+
:show="showPicker"
|
|
53
|
+
:validationMessage="validationMessage"
|
|
54
|
+
@open="showPicker = true"
|
|
55
|
+
@close="showPicker = false"
|
|
56
|
+
>
|
|
57
|
+
<template #masked="{ props }">
|
|
58
|
+
<div data-ref-id="custom-date-input" class="custom-date-input">
|
|
59
|
+
<MaskedTextBox
|
|
60
|
+
id="startDate"
|
|
61
|
+
class="masked-date-input"
|
|
62
|
+
:mask="'00/00/0000'"
|
|
63
|
+
:value="masked.raw"
|
|
64
|
+
:placeholder="masked.placeholder"
|
|
65
|
+
:showClearButton="false"
|
|
66
|
+
@change="masked.handleChange"
|
|
67
|
+
@keydown="masked.handleKeyDown"
|
|
68
|
+
@keyup="masked.handleKeyUp"
|
|
69
|
+
@click="masked.handleClick"
|
|
70
|
+
@wheel="masked.handleWheel"
|
|
71
|
+
@blur="() => props.onBlur()"
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
<div v-if="debugEnabled" class="composable-debugging debug-info">
|
|
75
|
+
<div v-for="(row, idx) in debugLines" :key="idx">
|
|
76
|
+
<strong>{{ row[0] }}:</strong> {{ row[1] }}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
81
|
+
</DatePicker>
|
|
82
|
+
|
|
83
|
+
<Error for="startDate">{{ validationMessage }}</Error>
|
|
84
|
+
|
|
85
|
+
</template>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Note: Destructure only the values used as component props that would otherwise require `.value` in templates — e.g., `isValid`, `validationMessage`, `debugEnabled`, and `debugLines`. Keep event handlers (e.g., `masked.handleChange`) namespaced to clearly indicate their origin from the composable and to avoid unnecessary destructuring.
|
|
89
|
+
|
|
90
|
+
### Integrating with Kendo DatePicker (masked slot)
|
|
91
|
+
|
|
92
|
+
`useMaskedDateInput` can also power a custom masked input inside Kendo `DatePicker` via the `dateInput="masked"` slot. See the full reference in [src/components/custom-date-picker/CustomDatePicker.vue](../src/components/custom-date-picker/CustomDatePicker.vue).
|
|
93
|
+
|
|
94
|
+
## API
|
|
95
|
+
|
|
96
|
+
### `useMaskedDateInput(options)`
|
|
97
|
+
|
|
98
|
+
Creates a controller for a masked single-date input.
|
|
99
|
+
|
|
100
|
+
#### Options
|
|
101
|
+
|
|
102
|
+
- `id: string`: DOM id of the `MaskedTextBox` input element.
|
|
103
|
+
- `onChange: (p: ChangePayload) => void`: Called with `{ value, event }` when the composable has a new parsed value to emit.
|
|
104
|
+
- Emits `value = null` if input is empty, incomplete, invalid, or outside min/max.
|
|
105
|
+
- Does not mutate the user's raw string.
|
|
106
|
+
- `onShowCalendar: (e: KeyboardEvent) => void`: Called when Space is pressed on the input; use this to open a calendar/popup.
|
|
107
|
+
- `externalValue?: Ref<string | Date | null | undefined>`: Reactive external source for the selected date (e.g., calendar `v-model`). When it updates to a valid date, the composable mirrors it into `raw` without clobbering in‑progress typing.
|
|
108
|
+
- `minDate?: Ref<Date | null | undefined>` / `maxDate?: Ref<Date | null | undefined>`: Bounds for validation.
|
|
109
|
+
- `dateFormat?: string`: Placeholder/format hint (default: `mm/dd/yyyy`).
|
|
110
|
+
- `debug?: boolean`: Enables internal debug reporting (e.g., `debugLines`).
|
|
111
|
+
|
|
112
|
+
#### Returns
|
|
113
|
+
|
|
114
|
+
- `raw: Ref<string>`: The masked string `MM/DD/YYYY`. Use as `v-model` for `MaskedTextBox`.
|
|
115
|
+
- `cursorPos: Ref<number | undefined>`: Caret position for restoring after programmatic updates.
|
|
116
|
+
- `placeholder: Ref<string>`: Format placeholder (defaults to `mm/dd/yyyy`).
|
|
117
|
+
- `digitsOnly: Computed<string>`: Raw string stripped of non-digits.
|
|
118
|
+
- `month`, `day`, `year`: Readonly computed parts (strings, zero-padded) for diagnostics.
|
|
119
|
+
- `isValid: Computed<boolean>`: Validity considering completeness, parsability, and min/max bounds.
|
|
120
|
+
- `validationMessage: Computed<string>`: Human-readable message for invalid states (format or range messages).
|
|
121
|
+
- `datePart(pos: number): 'mm' | 'dd' | 'yyyy'`: Helper to determine focused part from caret position.
|
|
122
|
+
- Event handlers to wire to the input:
|
|
123
|
+
- `handleChange(event)`
|
|
124
|
+
- `handleKeyDown(event)` — Space opens calendar via `onShowCalendar`; ArrowUp/ArrowDown act as steppers.
|
|
125
|
+
- `handleWheel(event)` — Prevents page scroll; interprets wheel as steppers (up/down) for the focused part.
|
|
126
|
+
- `handleKeyUp(event)` — Maintains caret position after cursor keys/steppers.
|
|
127
|
+
- `handleClick(event)` — Captures caret position on click for restoring.
|
|
128
|
+
- Debug:
|
|
129
|
+
- `debugEnabled: Ref<boolean>`
|
|
130
|
+
- `debugLines: Computed<Array<[label: string, value: string]>>`
|
|
131
|
+
|
|
132
|
+
## Behavior Details
|
|
133
|
+
|
|
134
|
+
- **Mask and parsing**: Expects `00/00/0000`. Parsing requires the input to be complete (10 chars including slashes, 8 digits) and form a real calendar date (e.g., Feb 29 checks).
|
|
135
|
+
- **Emission policy**: Emits `Date` only when the raw is complete, valid, and within `minDate`/`maxDate`. Otherwise emits `null`. Empty input emits `null`.
|
|
136
|
+
- **Min/Max validation**: If provided, dates outside bounds are considered invalid (`isValid=false`) and produce a range `validationMessage`.
|
|
137
|
+
- **Steppers**: ArrowUp/ArrowDown and mouse wheel increment/decrement the focused part (`mm`, `dd`, `yyyy`) with wrapping (months and days). If raw is incomplete/invalid, steppers initialize to today, emit that date (if in range), and restore caret.
|
|
138
|
+
- **Caret persistence**: After programmatic updates (including steppers), the caret is restored to the prior position to preserve typing flow.
|
|
139
|
+
- **Space key**: The input consumes Space and calls `onShowCalendar` to open the DatePicker popup without inserting a space into `raw`.
|
|
140
|
+
- **External sync**: When `externalValue` is set to a valid date, `raw` mirrors it (e.g., from a calendar selection). If external becomes null/invalid, `raw` is preserved to avoid clobbering in-progress text.
|
|
141
|
+
- **Styling hook**: On mount, adds `fk-datepicker` class to the closest `.k-datepicker` parent for theming.
|
|
142
|
+
|
|
143
|
+
## Integration Patterns
|
|
144
|
+
|
|
145
|
+
- **MaskedTextBox only**: Use the Quick Start setup to accept typed dates with validation — no calendar required.
|
|
146
|
+
- **Kendo DatePicker (masked slot)**: Provide a custom `MaskedTextBox` in `DatePicker`'s `masked` slot. See [src/components/custom-date-picker/CustomDatePicker.vue](../src/components/custom-date-picker/CustomDatePicker.vue) for a complete example, including focus management and theming.
|
|
147
|
+
|
|
148
|
+
## Accessibility Notes
|
|
149
|
+
|
|
150
|
+
- Ensure labels (e.g., Kendo `Label` with `for`) correctly reference the input `id`.
|
|
151
|
+
- When used with `DatePicker`, Kendo manages its popup and focus behavior.
|
|
152
|
+
- Keep tab order logical; Space opens the calendar, Escape should close it.
|
|
153
|
+
|
|
154
|
+
## Limitations & Assumptions
|
|
155
|
+
|
|
156
|
+
- Date format is fixed to `MM/DD/YYYY`; internationalization of the mask/parser is not included yet.
|
|
157
|
+
- Built to pair with Kendo Vue inputs/dateinputs; other inputs may need minor adjustments.
|
|
158
|
+
|
|
159
|
+
## Tips
|
|
160
|
+
|
|
161
|
+
- Keep `minDate`/`maxDate` aligned with your calendar or DatePicker configuration for consistent validation.
|
|
162
|
+
- Use `debug: true` and display `debugLines` during integration to verify caret, parsed values, and masks.
|
|
163
|
+
- Avoid mutating `raw` externally except through `v-model`; let the composable manage its value and emit parsed dates.
|
|
164
|
+
|
|
165
|
+
## Types
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
export type ChangePayload = { value: Date | null; event: any };
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Reference Implementation
|
|
172
|
+
|
|
173
|
+
See the project’s full reference usage in [src/components/custom-date-picker/CustomDatePicker.vue](../src/components/custom-date-picker/CustomDatePicker.vue), which includes calendar integration, focus-trap, external store sync, and accessibility tweaks.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# useGridA11y
|
|
2
|
+
|
|
3
|
+
[← Back to Composables README](../../README.md)
|
|
4
|
+
|
|
5
|
+
Composable that augments a Kendo Vue Grid with accessible keyboard navigation, focus management, and custom behaviors for column header menus (filter/sort).
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- Vue 3 Composition API
|
|
10
|
+
- `@progress/kendo-vue-grid`
|
|
11
|
+
- `@featherk/composables`
|
|
12
|
+
|
|
13
|
+
Install (if needed):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @featherk/composables
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Compatibility
|
|
20
|
+
|
|
21
|
+
- Built and tested against Kendo UI for Vue Grid v6.4.1. Grid usage requires a paid Telerik license.
|
|
22
|
+
|
|
23
|
+
## Integration Quick Reference
|
|
24
|
+
|
|
25
|
+
### 1) Minimal import + setup
|
|
26
|
+
|
|
27
|
+
Place inside a `<script setup lang="ts">` block. Provide a Grid ref and call the composable.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { ref, onMounted, watch } from 'vue';
|
|
31
|
+
import { useGridA11y } from '@featherk/composables';
|
|
32
|
+
|
|
33
|
+
const gridRef = ref(null);
|
|
34
|
+
const dataResult = ref({ data: [] }); // example data container
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
activeFilterButton,
|
|
38
|
+
handleGridKeyDown,
|
|
39
|
+
handleSortChange,
|
|
40
|
+
initA11y // NEW: must be called after Grid + data are present in DOM
|
|
41
|
+
} = useGridA11y(gridRef);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2) Initialize accessibility after Grid mounts and data is available
|
|
45
|
+
|
|
46
|
+
Call `initA11y()` once the Grid element is in the DOM and the initial (non-empty) data set is ready. If data loads async, watch it. `initA11y()` performs initial attribute setup, internal bookkeeping, and prepares focus targets.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
onMounted(() => {
|
|
50
|
+
// Adjust source as needed for your data state
|
|
51
|
+
watch(
|
|
52
|
+
() => dataResult.value.data,
|
|
53
|
+
(rows) => {
|
|
54
|
+
if (rows && rows.length && gridRef.value) {
|
|
55
|
+
initA11y(); // safe to call again; will no-op after first successful init
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{ immediate: true }
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If data can change from empty to non-empty multiple times, the composable guards against redundant full initialization.
|
|
64
|
+
|
|
65
|
+
### 3) Wire keyboard handler on the Grid
|
|
66
|
+
|
|
67
|
+
Template snippet showing essential bindings (keep other Grid props as required by your app):
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<Grid
|
|
71
|
+
ref="gridRef"
|
|
72
|
+
:dataItems="dataResult.data"
|
|
73
|
+
:dataItemKey="'id'"
|
|
74
|
+
:rowRender="renderRow" // optional: aria-label for screen reader
|
|
75
|
+
@keydown="handleGridKeyDown" // keyboard navigation
|
|
76
|
+
@sortchange="handleSortChange" // composable-aware sort handling
|
|
77
|
+
navigatable="false" // turn off cell to cell navigation
|
|
78
|
+
/>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 4) Provide an accessible row renderer (aria-label)
|
|
82
|
+
|
|
83
|
+
Not part of `@featherk/composable`, but good practice
|
|
84
|
+
|
|
85
|
+
Kendo Grid `rowRender` allows you to add an `aria-label` so screen readers announce row contents.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
const renderRow = (h: any, trElement: any, defaultSlots: any, props: any) => {
|
|
89
|
+
const ariaLabel = `Name: ${props.dataItem.name}, Price: ${props.dataItem.price}`;
|
|
90
|
+
const merged = { ...trElement.props, 'aria-label': ariaLabel };
|
|
91
|
+
return h('tr', merged, defaultSlots);
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 5) Focus the active filter button after filter changes
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { nextTick } from 'vue';
|
|
99
|
+
|
|
100
|
+
function onFilterChange(event: any) {
|
|
101
|
+
// update filter state + reload data
|
|
102
|
+
nextTick(() => {
|
|
103
|
+
if (activeFilterButton.value) {
|
|
104
|
+
activeFilterButton.value.focus();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 6) Custom sort handling with composable helper
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const optionalCustomSort = (event: any) => {
|
|
114
|
+
loader.value = true;
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
loader.value = false;
|
|
117
|
+
// apply sort state and reload data
|
|
118
|
+
}, 200);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
function onSortChange(event: any) {
|
|
122
|
+
handleSortChange(event, optionalCustomSort);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 7) Summary checklist
|
|
127
|
+
|
|
128
|
+
- Import and call `useGridA11y(gridRef)`
|
|
129
|
+
- Wait for Grid mount + data, then call `initA11y()`
|
|
130
|
+
- Bind returned keyboard handler to Grid `@keydown`
|
|
131
|
+
- Bind returned sort handler to Grid `@sortchange` (optionally pass a custom callback)
|
|
132
|
+
- Use returned `activeFilterButton` to manage focus after filter updates
|
|
133
|
+
- Provide a `rowRender` that adds a descriptive `aria-label` for each row
|
|
134
|
+
- Set `navigatable="false"` on the Grid to prefer row-level navigation
|
|
135
|
+
|
|
136
|
+
## Quick Start
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
// <script setup lang="ts">
|
|
140
|
+
import { ref, onMounted, watch } from 'vue';
|
|
141
|
+
import { useGridA11y } from '@featherk/composables/grid';
|
|
142
|
+
|
|
143
|
+
const gridRef = ref<any>(null);
|
|
144
|
+
const dataResult = ref<{ data: any[] }>({ data: [] });
|
|
145
|
+
|
|
146
|
+
const {
|
|
147
|
+
activeFilterButton,
|
|
148
|
+
handleGridKeyDown,
|
|
149
|
+
handleSortChange,
|
|
150
|
+
initA11y,
|
|
151
|
+
} = useGridA11y(gridRef);
|
|
152
|
+
|
|
153
|
+
onMounted(() => {
|
|
154
|
+
watch(
|
|
155
|
+
() => dataResult.value.data,
|
|
156
|
+
(rows) => {
|
|
157
|
+
if (rows && rows.length && gridRef.value) {
|
|
158
|
+
initA11y(); // safe to call; no-ops after first successful init
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{ immediate: true }
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
// </script>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Template bindings (essentials shown):
|
|
168
|
+
|
|
169
|
+
```html
|
|
170
|
+
<Grid
|
|
171
|
+
ref="gridRef"
|
|
172
|
+
:dataItems="dataResult.data"
|
|
173
|
+
:dataItemKey="'id'"
|
|
174
|
+
@keydown="handleGridKeyDown"
|
|
175
|
+
@sortchange="handleSortChange"
|
|
176
|
+
navigatable="false" />
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Optional row renderer for screen readers:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
const renderRow = (h: any, trEl: any, slots: any, props: any) => {
|
|
183
|
+
const ariaLabel = `Name: ${props.dataItem.name}, Price: ${props.dataItem.price}`;
|
|
184
|
+
return h('tr', { ...trEl.props, 'aria-label': ariaLabel }, slots);
|
|
185
|
+
};
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Restore focus after filter changes:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { nextTick } from 'vue';
|
|
192
|
+
|
|
193
|
+
function onFilterChange(event: any) {
|
|
194
|
+
// update filter state + reload data
|
|
195
|
+
nextTick(() => {
|
|
196
|
+
activeFilterButton.value?.focus();
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## API
|
|
202
|
+
|
|
203
|
+
### `useGridA11y(gridRef: Ref<any>)`
|
|
204
|
+
|
|
205
|
+
Returns helpers to initialize accessibility, manage focus, and handle keyboard and sorting interactions.
|
|
206
|
+
|
|
207
|
+
- **`activeFilterButton: Ref<HTMLElement | null>`**: Last-activated header filter/menu trigger; used to restore focus after menu closes.
|
|
208
|
+
- **`initA11y(): void`**: Initializes attributes, observers, and focus targets once Grid + non-empty data are present.
|
|
209
|
+
- **`handleGridKeyDown(e: KeyboardEvent): void`**: Row-level keyboard navigation.
|
|
210
|
+
- ArrowUp/ArrowDown: move between rows (`.k-table-row[data-grid-row-index]`).
|
|
211
|
+
- ArrowLeft/ArrowRight: cycle focus within tabbable elements inside the focused row.
|
|
212
|
+
- Escape: collapse focus back to the row.
|
|
213
|
+
- Safeguards menu triggers to defer to column menu logic.
|
|
214
|
+
- **`handleSortChange(event: any, cb?: (event: any) => void): void`**: Composable-aware sort handling; optionally call a custom callback before applying sort.
|
|
215
|
+
|
|
216
|
+
Internal behaviors include:
|
|
217
|
+
|
|
218
|
+
- Column menu keyboard support (Space/Enter activation on triggers; ArrowUp/ArrowDown navigation; Tab trapping in filter forms; Escape returns focus).
|
|
219
|
+
- Mutation observers to attach/detach listeners on Kendo popup containers and prune sort options when a column is non-sortable.
|
|
220
|
+
|
|
221
|
+
## Styling Hook
|
|
222
|
+
|
|
223
|
+
- Adds `.fk-grid` to the Grid root for FeatherK theming. The composable does not ship CSS; include a FeatherK stylesheet in your app to see visual indicators (e.g., filtered status).
|
|
224
|
+
|
|
225
|
+
## Accessibility Notes
|
|
226
|
+
|
|
227
|
+
- Prefer `navigatable="false"` to use row-level navigation.
|
|
228
|
+
- Provide descriptive `aria-label`s via `rowRender`.
|
|
229
|
+
- Ensure header menu triggers are reachable and announce intent (Filter/Sort).
|
|
230
|
+
|
|
231
|
+
## Limitations & Assumptions
|
|
232
|
+
|
|
233
|
+
- Assumes Kendo’s Grid DOM structure and popup containers (`.k-animation-container`).
|
|
234
|
+
- Built for row-level navigation; cell-level `navigatable` conflicts with this model.
|
|
235
|
+
|
|
236
|
+
## Tips
|
|
237
|
+
|
|
238
|
+
- Call `initA11y()` when the Grid mounts and data becomes non-empty; subsequent calls are safe.
|
|
239
|
+
- Use `activeFilterButton` to manage focus after filter updates.
|
|
240
|
+
|
|
241
|
+
## Migration Notice
|
|
242
|
+
|
|
243
|
+
- The runtime file `useGridA11y.ts` currently lives at the package root for compatibility. It may move to `src/grid/useGridA11y.ts` in a future release. When that happens, root exports will be preserved to avoid breaking imports, but consider migrating to subpath imports (e.g., `@featherk/composables/grid`).
|