stimulus_plumbers 0.2.2 → 0.2.8

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/README.md +57 -41
  4. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.es.js +1628 -0
  5. data/app/assets/javascripts/stimulus-plumbers/stimulus-plumbers-controllers.umd.js +1 -0
  6. data/lib/stimulus_plumbers/components/combobox/autocomplete.rb +57 -0
  7. data/lib/stimulus_plumbers/components/combobox/date.rb +52 -0
  8. data/lib/stimulus_plumbers/components/combobox/dropdown.rb +41 -0
  9. data/lib/stimulus_plumbers/components/combobox/option.rb +27 -0
  10. data/lib/stimulus_plumbers/components/combobox/option_group.rb +52 -0
  11. data/lib/stimulus_plumbers/components/combobox/renderer.rb +78 -0
  12. data/lib/stimulus_plumbers/components/combobox/time.rb +103 -0
  13. data/lib/stimulus_plumbers/components/date_picker/navigation.rb +1 -1
  14. data/lib/stimulus_plumbers/components/plumber/html_options.rb +22 -3
  15. data/lib/stimulus_plumbers/components/time_picker/renderer.rb +38 -0
  16. data/lib/stimulus_plumbers/form/builder.rb +57 -12
  17. data/lib/stimulus_plumbers/form/field_component.rb +12 -10
  18. data/lib/stimulus_plumbers/form/fields/combobox.rb +41 -0
  19. data/lib/stimulus_plumbers/form/fields/password.rb +55 -0
  20. data/lib/stimulus_plumbers/form/fields/renderer.rb +1 -2
  21. data/lib/stimulus_plumbers/form/fields/search.rb +40 -0
  22. data/lib/stimulus_plumbers/form/fields/select.rb +8 -2
  23. data/lib/stimulus_plumbers/form/fields/text.rb +12 -4
  24. data/lib/stimulus_plumbers/helpers/combobox_helper.rb +74 -0
  25. data/lib/stimulus_plumbers/helpers.rb +2 -2
  26. data/lib/stimulus_plumbers/themes/{form.rb → base/form.rb} +6 -2
  27. data/lib/stimulus_plumbers/themes/base.rb +7 -7
  28. data/lib/stimulus_plumbers/themes/tailwind/form.rb +10 -6
  29. data/lib/stimulus_plumbers/version.rb +1 -1
  30. data/lib/stimulus_plumbers.rb +18 -1
  31. metadata +25 -15
  32. data/lib/stimulus_plumbers/components/date_picker/renderer.rb +0 -82
  33. data/lib/stimulus_plumbers/helpers/date_picker_helper.rb +0 -17
  34. /data/lib/stimulus_plumbers/themes/{action_list.rb → base/action_list.rb} +0 -0
  35. /data/lib/stimulus_plumbers/themes/{avatar.rb → base/avatar.rb} +0 -0
  36. /data/lib/stimulus_plumbers/themes/{button.rb → base/button.rb} +0 -0
  37. /data/lib/stimulus_plumbers/themes/{calendar.rb → base/calendar.rb} +0 -0
  38. /data/lib/stimulus_plumbers/themes/{card.rb → base/card.rb} +0 -0
  39. /data/lib/stimulus_plumbers/themes/{layout.rb → base/layout.rb} +0 -0
@@ -0,0 +1 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`@hotwired/stimulus`)):typeof define==`function`&&define.amd?define([`exports`,`@hotwired/stimulus`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.StimulusPlumbersControllers={},e.Stimulus))})(this,function(e,t){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var n=[`a[href]`,`area[href]`,`button:not([disabled])`,`input:not([disabled])`,`select:not([disabled])`,`textarea:not([disabled])`,`[tabindex]:not([tabindex="-1"])`,`audio[controls]`,`video[controls]`,`[contenteditable]:not([contenteditable="false"])`].join(`,`);function r(e){return Array.from(e.querySelectorAll(n)).filter(e=>i(e))}function i(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)}function a(e){let t=r(e);return t.length>0?(t[0].focus(),!0):!1}var o=class{constructor(e,t={}){this.container=e,this.previouslyFocused=null,this.options=t,this.isActive=!1}activate(){this.isActive||(this.previouslyFocused=document.activeElement,this.isActive=!0,this.options.initialFocus?this.options.initialFocus.focus():a(this.container),this.container.addEventListener(`keydown`,this.handleKeyDown))}deactivate(){if(!this.isActive)return;this.isActive=!1,this.container.removeEventListener(`keydown`,this.handleKeyDown);let e=this.options.returnFocus||this.previouslyFocused;e&&i(e)&&e.focus()}handleKeyDown=e=>{if(e.key===`Escape`&&this.options.escapeDeactivates){e.preventDefault(),this.deactivate();return}if(e.key!==`Tab`)return;let t=r(this.container);if(t.length===0)return;let n=t[0],i=t[t.length-1];e.shiftKey&&document.activeElement===n?(e.preventDefault(),i.focus()):!e.shiftKey&&document.activeElement===i&&(e.preventDefault(),n.focus())}},s=class{constructor(){this.savedElement=null}save(){this.savedElement=document.activeElement}restore(){this.savedElement&&i(this.savedElement)&&(this.savedElement.focus(),this.savedElement=null)}};function c(e,t){return e.key===t}function l(e){return e.key===`Enter`||e.key===` `}function u(e){return[`ArrowUp`,`ArrowDown`,`ArrowLeft`,`ArrowRight`].includes(e.key)}function d(e){e.preventDefault(),e.stopPropagation()}var f=class{constructor(e,t=0){this.items=e,this.currentIndex=t,this.updateTabIndex()}handleKeyDown(e){let t;switch(e.key){case`ArrowDown`:case`ArrowRight`:e.preventDefault(),t=(this.currentIndex+1)%this.items.length;break;case`ArrowUp`:case`ArrowLeft`:e.preventDefault(),t=this.currentIndex===0?this.items.length-1:this.currentIndex-1;break;case`Home`:e.preventDefault(),t=0;break;case`End`:e.preventDefault(),t=this.items.length-1;break;default:return}this.setCurrentIndex(t)}setCurrentIndex(e){e>=0&&e<this.items.length&&(this.currentIndex=e,this.updateTabIndex(),this.items[e].focus())}updateTabIndex(){this.items.forEach((e,t)=>{e.tabIndex=t===this.currentIndex?0:-1})}updateItems(e){this.items=e,this.currentIndex=Math.min(this.currentIndex,e.length-1),this.updateTabIndex()}},p=(e,t,n)=>{let r=document.querySelector(`[data-live-region="${e}"]`);return r||(r=document.createElement(`div`),r.className=`sr-only`,r.dataset.liveRegion=e,r.setAttribute(`aria-live`,e),r.setAttribute(`aria-atomic`,t.toString()),r.setAttribute(`aria-relevant`,n),document.body.appendChild(r)),r};function m(e,t={}){let{politeness:n=`polite`,atomic:r=!0,relevant:i=`additions text`}=t,a=p(n,r,i);a.textContent=``,setTimeout(()=>{a.textContent=e},100)}var h=(e=`a11y`)=>`${e}-${Math.random().toString(36).substr(2,9)}`,g=(e,t=`element`)=>e.id||=h(t),_=(e,t,n)=>{e.setAttribute(t,n.toString())},v=(e,t)=>_(e,`aria-expanded`,t),y=(e,t)=>_(e,`aria-pressed`,t),ee=(e,t)=>_(e,`aria-checked`,t);function te(e,t){_(e,`aria-disabled`,t),t?e.setAttribute(`tabindex`,`-1`):e.removeAttribute(`tabindex`)}var b={menu:`menu`,listbox:`listbox`,tree:`tree`,grid:`grid`,dialog:`dialog`},x=(e,t,n)=>{Object.entries(t).forEach(([t,r])=>{e.setAttribute(t,r),n[t]=r})},S=(e,t,n)=>n||!e.hasAttribute(t);function C({trigger:e,target:t,role:n=null,override:r=!1}){let i={trigger:{},target:{}};if(!e||!t)return i;let a={},o={};if(n&&S(t,`role`,r)&&(o.role=n),t.id&&(S(e,`aria-controls`,r)&&(a[`aria-controls`]=t.id),n===`tooltip`&&S(e,`aria-describedby`,r)&&(a[`aria-describedby`]=t.id)),n&&S(e,`aria-haspopup`,r)){let e=b[n]||`true`;e&&(a[`aria-haspopup`]=e)}return x(t,o,i.target),x(e,a,i.trigger),i}var w=(e,t)=>{t.forEach(t=>{e.hasAttribute(t)&&e.removeAttribute(t)})};function ne({trigger:e,target:t,attributes:n=null}){!e||!t||(w(e,n||[`aria-controls`,`aria-haspopup`,`aria-describedby`]),(!n||n.includes(`role`))&&w(t,[`role`]))}var T={get visibleOnly(){return!0},hiddenClass:null},E={get top(){return`bottom`},get bottom(){return`top`},get left(){return`right`},get right(){return`left`}};function D({x:e,y:t,width:n,height:r}){return{x:e,y:t,width:n,height:r,left:e,right:e+n,top:t,bottom:t+r}}function O(){return D({x:0,y:0,width:window.innerWidth||document.documentElement.clientWidth,height:window.innerHeight||document.documentElement.clientHeight})}function re(e){if(!(e instanceof HTMLElement))return!1;let t=O(),n=e.getBoundingClientRect(),r=n.top<=t.height&&n.top+n.height>0,i=n.left<=t.width&&n.left+n.width>0;return r&&i}function k(e){return e instanceof Date&&!isNaN(e)}function A(...e){if(e.length===0)throw`Missing values to parse as date`;if(e.length===1){let t=new Date(e[0]);if(e[0]&&k(t))return t}else{let t=new Date(...e);if(k(t))return t}}var ie={element:null,visible:null,dispatch:!0,prefix:``},j=class{constructor(e,t={}){this.controller=e;let{element:n,visible:r,dispatch:i,prefix:a}=Object.assign({},ie,t);this.element=n||e.element,this.visibleOnly=typeof r==`boolean`?r:T.visibleOnly,this.visibleCallback=typeof r==`string`?r:null,this.notify=!!i,this.prefix=typeof a==`string`&&a?a:e.identifier}get visible(){return this.element instanceof HTMLElement?this.visibleOnly?re(this.element)&&this.isVisible(this.element):!0:!1}isVisible(e){if(this.visibleCallback){let t=this.findCallback(this.visibleCallback);if(typeof t==`function`)return t(e)}return e instanceof HTMLElement?!e.hasAttribute(`hidden`):!1}dispatch(e,{target:t=null,prefix:n=null,detail:r=null}={}){if(this.notify)return this.controller.dispatch(e,{target:t||this.element,prefix:n||this.prefix,detail:r})}findCallback(e){if(typeof e!=`string`)return;let t=this,n=e.split(`.`).reduce((e,t)=>e&&e[t],t.controller);if(typeof n==`function`)return n.bind(t.controller);let r=e.split(`.`).reduce((e,t)=>e&&e[t],t);if(typeof r==`function`)return r.bind(t)}async awaitCallback(e,...t){if(typeof e==`string`&&(e=this.findCallback(e)),typeof e==`function`){let n=e(...t);return n instanceof Promise?await n:n}}},M=7,N={locales:[`default`],today:``,day:null,month:null,year:null,since:null,till:null,disabledDates:[],disabledWeekdays:[],disabledDays:[],disabledMonths:[],disabledYears:[],firstDayOfWeek:0,onNavigated:`navigated`},ae=class extends j{constructor(e,t={}){super(e,t);let n=Object.assign({},N,t),{onNavigated:r,since:i,till:a,firstDayOfWeek:o}=n;this.onNavigated=r,this.since=A(i),this.till=A(a),this.firstDayOfWeek=0<=o&&o<7?o:N.firstDayOfWeek;let{disabledDates:s,disabledWeekdays:c,disabledDays:l,disabledMonths:u,disabledYears:d}=n;this.disabledDates=Array.isArray(s)?s:[],this.disabledWeekdays=Array.isArray(c)?c:[],this.disabledDays=Array.isArray(l)?l:[],this.disabledMonths=Array.isArray(u)?u:[],this.disabledYears=Array.isArray(d)?d:[];let{today:f,day:p,month:m,year:h}=n;this.now=A(f)||new Date,typeof h==`number`&&typeof m==`number`&&typeof p==`number`?this.current=A(h,m,p):this.current=this.now,this.build(),this.enhance()}build(){this.daysOfWeek=this.buildDaysOfWeek(),this.daysOfMonth=this.buildDaysOfMonth(),this.monthsOfYear=this.buildMonthsOfYear()}buildDaysOfWeek(){let e=new Intl.DateTimeFormat(this.localesValue,{weekday:`long`}),t=new Intl.DateTimeFormat(this.localesValue,{weekday:`short`}),n=new Date(`2024-10-06`),r=[];for(let i=this.firstDayOfWeek,a=i+7;i<a;i++){let a=new Date(n);a.setDate(n.getDate()+i),r.push({date:a,value:a.getDay(),long:e.format(a),short:t.format(a)})}return r}buildDaysOfMonth(){let e=this.month,t=this.year,n=[],r=e=>({current:this.month===e.getMonth()&&this.year===e.getFullYear(),date:e,value:e.getDate(),month:e.getMonth(),year:e.getFullYear(),iso:e.toISOString()}),i=new Date(t,e).getDay(),a=this.firstDayOfWeek-i;for(let i=a>0?a-7:a;i<0;i++){let a=new Date(t,e,i+1);n.push(r(a))}let o=new Date(t,e+1,0).getDate();for(let i=1;i<=o;i++){let a=new Date(t,e,i);n.push(r(a))}let s=n.length%M,c=s===0?0:M-s;for(let i=1;i<=c;i++){let a=new Date(t,e+1,i);n.push(r(a))}return n}buildMonthsOfYear(){let e=new Intl.DateTimeFormat(this.localesValue,{month:`long`}),t=new Intl.DateTimeFormat(this.localesValue,{month:`short`}),n=new Intl.DateTimeFormat(this.localesValue,{month:`numeric`}),r=[];for(let i=0;i<12;i++){let a=new Date(this.year,i);r.push({date:a,value:a.getMonth(),long:e.format(a),short:t.format(a),numeric:n.format(a)})}return r}get today(){return this.now}set today(e){if(!k(e))return;let t=this.month?this.month:e.getMonth(),n=this.year?this.year:e.getFullYear(),r=t==e.getMonth()&&n==e.getFullYear(),i=this.hasDayValue?this.day:r?e.getDate():1;this.now=new Date(n,t,i).toISOString()}get current(){return typeof this.year==`number`&&typeof this.month==`number`&&typeof this.day==`number`?A(this.year,this.month,this.day):null}set current(e){k(e)&&(this.day=e.getDate(),this.month=e.getMonth(),this.year=e.getFullYear())}navigate=async e=>{if(!k(e))return;let t=this.current,n=e.toISOString(),r=t.toISOString();this.dispatch(`navigate`,{detail:{from:r,to:n}}),this.current=e,this.build(),await this.awaitCallback(this.onNavigated,{from:r,to:n}),this.dispatch(`navigated`,{detail:{from:r,to:n}})};step=async(e,t)=>{if(t===0)return;let n=this.current;switch(e){case`year`:n.setFullYear(n.getFullYear()+t);break;case`month`:n.setMonth(n.getMonth()+t);break;case`day`:n.setDate(n.getDate()+t);break;default:return}await this.navigate(n)};isDisabled=e=>{if(!k(e))return!1;if(this.disabledDates.length){let t=e.getTime();for(let e of this.disabledDates)if(t===new Date(e).getTime())return!0}if(this.disabledWeekdays.length){let t=e.getDay(),n=this.daysOfWeek,r=n.findIndex(e=>e.value===t);if(r>=0){let e=n[r];for(let t of this.disabledWeekdays)if(e.value==t||e.short===t||e.long===t)return!0}}if(this.disabledDays.length){let t=e.getDate();for(let e of this.disabledDays)if(t==e)return!0}if(this.disabledMonths.length){let t=e.getMonth(),n=this.monthsOfYear,r=n.findIndex(e=>e.value===t);if(r>=0){let e=n[r];for(let t of this.disabledMonths)if(e.value==t||e.short===t||e.long===t)return!0}}if(this.disabledYears.length){let t=e.getFullYear();for(let e of this.disabledYears)if(t==e)return!0}return!1};isWithinRange=e=>{if(!k(e))return!1;let t=!0;return this.since&&(t&&=e>=this.since),this.till&&(t&&=e<=this.till),t};enhance(){let e=this;Object.assign(this.controller,{get calendar(){return{get today(){return e.today},get current(){return e.current},get day(){return e.day},get month(){return e.month},get year(){return e.year},get since(){return e.since},get till(){return e.till},get firstDayOfWeek(){return e.firstDayOfWeek},get disabledDates(){return e.disabledDates},get disabledWeekdays(){return e.disabledWeekdays},get disabledDays(){return e.disabledDays},get disabledMonths(){return e.disabledMonths},get disabledYears(){return e.disabledYears},get daysOfWeek(){return e.daysOfWeek},get daysOfMonth(){return e.daysOfMonth},get monthsOfYear(){return e.monthsOfYear},navigate:async t=>await e.navigate(t),step:async(t,n)=>await e.step(t,n),isDisabled:t=>e.isDisabled(t),isWithinRange:t=>e.isWithinRange(t)}}})}},oe=(e,t)=>new ae(e,t),se=class extends j{constructor(e,t={}){super(e,t),this.debounceTimer=null,this.abortController=null}fuzzyFilter(e,t){let n=t.toLowerCase(),r=0;return e.querySelectorAll(`[role="option"]`).forEach(e=>{let t=this.fuzzyMatch(n,e.textContent.trim().toLowerCase());e.hidden=!t,t&&r++}),r}fuzzyMatch(e,t){let n=0;for(let r=0;r<t.length&&n<e.length;r++)t[r]===e[n]&&n++;return n===e.length}scheduleFetch(e,t,n){clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>this.fetch(e,n),t)}async fetch(e,{url:t,field:n,onLoading:r,onLoaded:i,onError:a}){this.abortController?.abort(),this.abortController=new AbortController,r?.(!0);let o=new URL(t,window.location.href);o.searchParams.set(n,e);try{let e=await fetch(o,{signal:this.abortController.signal,headers:{Accept:`text/html`,"X-Requested-With":`XMLHttpRequest`}});if(!e.ok)throw Error(`${e.status}`);i?.(await e.text())}catch(e){e.name!==`AbortError`&&a?.(e)}finally{r?.(!1)}}cancel(){clearTimeout(this.debounceTimer),this.abortController?.abort()}},ce=(e,t)=>new se(e,t),P={content:null,url:``,reload:`never`,stale:3600,onLoad:`canLoad`,onLoading:`contentLoading`,onLoaded:`contentLoaded`},le=class extends j{constructor(e,t={}){super(e,t);let n=Object.assign({},P,t),{content:r,url:i,reload:a,stale:o}=n;this.content=r,this.url=i,this.reload=typeof a==`string`?a:P.reload,this.stale=typeof o==`number`?o:P.stale;let{onLoad:s,onLoading:c,onLoaded:l}=n;this.onLoad=s,this.onLoading=c,this.onLoaded=l,this.enhance()}get reloadable(){switch(this.reload){case`never`:return!1;case`always`:return!0;default:{let e=A(this.loadedAt);return e&&new Date-e>this.stale*1e3}}}contentLoadable=({url:e})=>!!e;contentLoading=async({url:e})=>e?await this.remoteContentLoader(e):await this.contentLoader();contentLoader=async()=>``;remoteContentLoader=async e=>(await fetch(e)).text();load=async()=>{if(this.loadedAt&&!this.reloadable)return;let e=this.findCallback(this.onLoad),t=await this.awaitCallback(e||this.contentLoadable,{url:this.url});if(this.dispatch(`load`,{detail:{url:this.url}}),!t)return;let n=this.url?await this.remoteContentLoader(this.url):await this.contentLoader();this.dispatch(`loading`,{detail:{url:this.url}}),n&&(await this.awaitCallback(this.onLoaded,{url:this.url,content:n}),this.loadedAt=new Date().getTime(),this.dispatch(`loaded`,{detail:{url:this.url,content:n}}))};enhance(){let e=this;Object.assign(this.controller,{load:e.load.bind(e)})}},ue=(e,t)=>new le(e,t),de={trigger:null,events:[`click`],onDismissed:`dismissed`},fe=class extends j{constructor(e,t={}){super(e,t);let{trigger:n,events:r,onDismissed:i}=Object.assign({},de,t);this.onDismissed=i,this.trigger=n||this.element,this.events=r,this.enhance(),this.observe()}dismiss=async e=>{let{target:t}=e;t instanceof HTMLElement&&(this.element.contains(t)||this.visible&&(this.dispatch(`dismiss`),await this.awaitCallback(this.onDismissed,{target:this.trigger}),this.dispatch(`dismissed`)))};observe(){this.events.forEach(e=>{window.addEventListener(e,this.dismiss,!0)})}unobserve(){this.events.forEach(e=>{window.removeEventListener(e,this.dismiss,!0)})}enhance(){let e=this,t=e.controller.disconnect.bind(e.controller);Object.assign(this.controller,{disconnect:()=>{e.unobserve(),t()}})}},F=(e,t)=>new fe(e,t),I={anchor:null,events:[`click`],placement:`bottom`,alignment:`start`,onFlipped:`flipped`,ariaRole:null,respectMotion:!0},L=class extends j{constructor(e,t={}){super(e,t);let{anchor:n,events:r,placement:i,alignment:a,onFlipped:o,ariaRole:s,respectMotion:c}=Object.assign({},I,t);this.anchor=n,this.events=r,this.placement=i,this.alignment=a,this.onFlipped=o,this.ariaRole=s,this.respectMotion=c,this.prefersReducedMotion=window.matchMedia(`(prefers-reduced-motion: reduce)`).matches,this.anchor&&this.element&&C({trigger:this.anchor,target:this.element,role:this.ariaRole}),this.enhance(),this.observe()}flip=async()=>{if(!this.visible)return;this.dispatch(`flip`),window.getComputedStyle(this.element).position!=`absolute`&&(this.element.style.position=`absolute`);let e=this.flippedRect(this.anchor.getBoundingClientRect(),this.element.getBoundingClientRect());this.element.style.transition=this.respectMotion&&this.prefersReducedMotion?`none`:``;for(let[t,n]of Object.entries(e))this.element.style[t]=n;await this.awaitCallback(this.onFlipped,{target:this.element,placement:e}),this.dispatch(`flipped`,{detail:{placement:e}})};flippedRect(e,t){let n=this.quadrumRect(e,O()),r=[this.placement,E[this.placement]],i={};for(;!Object.keys(i).length&&r.length>0;){let a=r.shift();if(!this.biggerRectThan(n[a],t))continue;let o=this.quadrumPlacement(e,a,t),s=this.quadrumAlignment(e,a,o);i.top=`${s.top+window.scrollY}px`,i.left=`${s.left+window.scrollX}px`}return Object.keys(i).length||(i.top=``,i.left=``),i}quadrumRect(e,t){return{left:D({x:t.x,y:t.y,width:e.x-t.x,height:t.height}),right:D({x:e.x+e.width,y:t.y,width:t.width-(e.x+e.width),height:t.height}),top:D({x:t.x,y:t.y,width:t.width,height:e.y-t.y}),bottom:D({x:t.x,y:e.y+e.height,width:t.width,height:t.height-(e.y+e.height)})}}quadrumPlacement(e,t,n){switch(t){case`top`:return D({x:n.x,y:e.y-n.height,width:n.width,height:n.height});case`bottom`:return D({x:n.x,y:e.y+e.height,width:n.width,height:n.height});case`left`:return D({x:e.x-n.width,y:n.y,width:n.width,height:n.height});case`right`:return D({x:e.x+e.width,y:n.y,width:n.width,height:n.height});default:throw`Unable place at the quadrum, ${t}`}}quadrumAlignment(e,t,n){switch(t){case`top`:case`bottom`:{let t=e.x;return this.alignment===`center`?t=e.x+e.width/2-n.width/2:this.alignment===`end`&&(t=e.x+e.width-n.width),D({x:t,y:n.y,width:n.width,height:n.height})}case`left`:case`right`:{let t=e.y;return this.alignment===`center`?t=e.y+e.height/2-n.height/2:this.alignment===`end`&&(t=e.y+e.height-n.height),D({x:n.x,y:t,width:n.width,height:n.height})}default:throw`Unable align at the quadrum, ${t}`}}biggerRectThan(e,t){return e.height>=t.height&&e.width>=t.width}observe(){this.events.forEach(e=>{window.addEventListener(e,this.flip,!0)})}unobserve(){this.events.forEach(e=>{window.removeEventListener(e,this.flip,!0)})}enhance(){let e=this,t=e.controller.disconnect.bind(e.controller);Object.assign(this.controller,{disconnect:()=>{e.unobserve(),t()},flip:e.flip.bind(e)})}},R=(e,t)=>new L(e,t),z={normalize(e){return typeof e==`string`?e:``},validate(){return!0}};function B(e){let t=0,n=!1;for(let r=e.length-1;r>=0;r--){let i=parseInt(e[r],10);n&&(i*=2,i>9&&(i-=9)),t+=i,n=!n}return t%10==0}var V=/\D/g,pe=/^\d{13,19}$/,me=/(.{4})(?=.)/g,he={normalize(e){return typeof e==`string`?e.replace(V,``):``},validate(e){return typeof e!=`string`||!pe.test(e)?!1:B(e)},format(e){return typeof e==`string`?e.replace(me,`$1 `):``}},H={1:10},U=/\D/g,ge=/^\+\d{7,15}$/,_e={normalize(e){if(typeof e!=`string`)return``;let t=e.trimStart().startsWith(`+`),n=e.replace(U,``);return t?`+${n}`:n},validate(e){if(typeof e!=`string`)return!1;if(ge.test(e))return!0;let t=e.replace(U,``);return Object.values(H).includes(t.length)},format(e){if(typeof e!=`string`)return``;let t=e.replace(U,``);for(let[e,n]of Object.entries(H)){if(t.length===n)return`(${t.slice(0,3)}) ${t.slice(3,6)}-${t.slice(6)}`;let r=n+e.length;if(t.length===r&&t.startsWith(e)){let n=t.slice(e.length);return`+${e} (${n.slice(0,3)}) ${n.slice(3,6)}-${n.slice(6)}`}}return e}},ve=/[^\d.,-]/g,ye=/^-?\d+(\.\d+)?$/,be={normalize(e){if(typeof e!=`string`)return``;let t=e.replace(ve,``);if(!t)return``;let n=t.lastIndexOf(`,`),r=t.lastIndexOf(`.`);return n>-1&&r>-1?n>r?t.replace(/\./g,``).replace(`,`,`.`):t.replace(/,/g,``):n>-1?t.slice(n+1).length<=2?t.replace(`,`,`.`):t.replace(/,/g,``):t},validate(e){return typeof e==`string`?ye.test(e):!1},format(e,t={}){if(typeof e!=`string`)return``;let n=parseFloat(e);if(isNaN(n))return e;let r=t.locale||`en-US`,i=t.currency||`USD`,a=t.fractionDigits===void 0?{}:{minimumFractionDigits:t.fractionDigits,maximumFractionDigits:t.fractionDigits};try{return new Intl.NumberFormat(r,{style:`currency`,currency:i,...a}).format(n)}catch{return e}}},W=/^\d{4}-\d{2}-\d{2}$/,xe=/^(\d{1,4})[/\-.](\d{1,2})[/\-.](\d{1,4})$/,Se=/\D/g,G={normalize(e){if(typeof e!=`string`)return``;let t=e.trim();if(W.test(t))return t;let n=t.match(xe);if(n){let[,e,t,r]=n;if(e.length===4)return`${e}-${t.padStart(2,`0`)}-${r.padStart(2,`0`)}`;if(r.length===4)return`${r}-${e.padStart(2,`0`)}-${t.padStart(2,`0`)}`}let r=t.replace(Se,``);if(r.length===8){let e=parseInt(r.slice(0,4),10);return e>=1e3&&e<=9999?`${r.slice(0,4)}-${r.slice(4,6)}-${r.slice(6,8)}`:`${r.slice(4,8)}-${r.slice(0,2)}-${r.slice(2,4)}`}return t},validate(e){if(typeof e!=`string`)return!1;let t=G.normalize(e);if(!W.test(t))return!1;let n=new Date(`${t}T00:00:00Z`);return!isNaN(n.getTime())&&n.toISOString().startsWith(t)},format(e,t={}){if(typeof e!=`string`)return``;let n=new Date(`${e}T00:00:00Z`);if(isNaN(n.getTime()))return e;let r=t.locale||`en-US`;try{return new Intl.DateTimeFormat(r,{year:t.year||`numeric`,month:t.month||`2-digit`,day:t.day||`2-digit`,timeZone:t.timeZone||`UTC`}).format(n)}catch{return e}}},Ce=/^([01]?\d|2[0-3]):([0-5]\d)$/,K={normalize(e){if(typeof e!=`string`)return``;let t=e.trim();if(Ce.test(t)){let[e,n]=t.split(`:`);return`${String(parseInt(e,10)).padStart(2,`0`)}:${n}`}let n=t.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)$/i);if(n){let e=parseInt(n[1],10),t=n[2];return e=n[3].toUpperCase()===`AM`?e===12?0:e:e===12?12:e+12,e>23||parseInt(t,10)>59?``:`${String(e).padStart(2,`0`)}:${t}`}return``},validate(e){return K.normalize(e)!==``},format(e,t={}){if(typeof e!=`string`)return``;let n=e.match(/^(\d{2}):(\d{2})$/);if(!n)return e;let r=parseInt(n[1],10),i=n[2];if(t.format===`h24`)return`${String(r).padStart(2,`0`)}:${i}`;let a=r<12?`AM`:`PM`;return`${r%12||12}:${i} ${a}`}},q={PLAIN:`plain`,CREDIT_CARD:`creditCard`,PHONE:`phone`,CURRENCY:`currency`,DATE:`date`,TIME:`time`},J=new Map([[q.PLAIN,z],[q.CREDIT_CARD,he],[q.PHONE,_e],[q.CURRENCY,be],[q.DATE,G],[q.TIME,K]]),Y={type:q.PLAIN,options:{}},we=class extends j{static register(e,t){J.set(e,t)}constructor(e,t={}){super(e,t),this.type=t.type??Y.type,this.options=t.options??Y.options,this.enhance()}enhance(){let e=this,t=J.get(e.type)??J.get(q.PLAIN),n={normalize:n=>t.normalize?.(n,e.options)??(typeof n==`string`?n:``),validate:n=>t.validate?.(n,e.options)??!0,format:n=>t.format?.(n,e.options)??(typeof n==`string`?n:``),mask:n=>t.mask?.(n,e.options)??null,maskable:()=>typeof t.mask==`function`};Object.defineProperty(this.controller,`inputFormat`,{get(){return n},configurable:!0})}},X=(e,t)=>new we(e,t),Te={events:[`resize`],boundaries:[`top`,`left`,`right`],onShifted:`shifted`,respectMotion:!0},Ee=class extends j{constructor(e,t={}){super(e,t);let{onShifted:n,events:r,boundaries:i,respectMotion:a}=Object.assign({},Te,t);this.onShifted=n,this.events=r,this.boundaries=i,this.respectMotion=a,this.prefersReducedMotion=window.matchMedia(`(prefers-reduced-motion: reduce)`).matches,this.enhance(),this.observe()}shift=async()=>{if(!this.visible)return;this.dispatch(`shift`);let e=this.overflowRect(this.element.getBoundingClientRect(),this.elementTranslations(this.element)),t=e.left||e.right||0,n=e.top||e.bottom||0;this.element.style.transition=this.respectMotion&&this.prefersReducedMotion?`none`:``,this.element.style.transform=`translate(${t}px, ${n}px)`,await this.awaitCallback(this.onShifted,e),this.dispatch(`shifted`,{detail:e})};overflowRect(e,t){let n={},r=O(),i=D({x:e.x-t.x,y:e.y-t.y,width:e.width,height:e.height});for(let e of this.boundaries){let t=this.directionDistance(i,e,r),a=E[e];t<0?i[a]+t>=r[a]&&!n[a]&&(n[e]=t):n[e]=``}return n}directionDistance(e,t,n){switch(t){case`top`:case`left`:return e[t]-n[t];case`bottom`:case`right`:return n[t]-e[t];default:throw`Invalid direction to calcuate distance, ${t}`}}elementTranslations(e){let t=window.getComputedStyle(e),n=t.transform||t.webkitTransform||t.mozTransform;if(n===`none`||n===void 0)return{x:0,y:0};let r=n.includes(`3d`)?`3d`:`2d`,i=n.match(/matrix.*\((.+)\)/)[1].split(`, `);return r===`2d`?{x:Number(i[4]),y:Number(i[5])}:{x:0,y:0}}observe(){this.events.forEach(e=>{window.addEventListener(e,this.shift,!0)})}unobserve(){this.events.forEach(e=>{window.removeEventListener(e,this.shift,!0)})}enhance(){let e=this,t=e.controller.disconnect.bind(e.controller);Object.assign(this.controller,{disconnect:()=>{e.unobserve(),t()},shift:e.shift.bind(e)})}},De=(e,t)=>new Ee(e,t),Z={visibility:`visibility`,onShown:`shown`,onHidden:`hidden`},Oe=class extends j{constructor(e,t={}){let{visibility:n,onShown:r,onHidden:i,activator:a}=Object.assign({},Z,t),o=typeof n==`string`?n:Z.namespace,s=typeof t.visible==`string`?t.visible:`isVisible`;(typeof t.visible!=`boolean`||t.visible)&&(t.visible=`${o}.${s}`),super(e,t),this.visibility=o,this.visibilityResolver=s,this.onShown=r,this.onHidden=i,this.activator=a instanceof HTMLElement?a:null,this.enhance(),this.element instanceof HTMLElement&&this.activate(this.isVisible(this.element))}isVisible(e){if(!(e instanceof HTMLElement))return!1;let t=T.hiddenClass;return t?!e.classList.contains(t):!e.hasAttribute(`hidden`)}toggle(e,t){if(!(e instanceof HTMLElement))return;let n=T.hiddenClass;n?t?e.classList.remove(n):e.classList.add(n):t?e.removeAttribute(`hidden`):e.setAttribute(`hidden`,!0)}activate(e){this.activator&&this.activator.setAttribute(`aria-expanded`,e?`true`:`false`)}async show(){!(this.element instanceof HTMLElement)||this.isVisible(this.element)||(this.dispatch(`show`),this.toggle(this.element,!0),this.activate(!0),await this.awaitCallback(this.onShown,{target:this.element}),this.dispatch(`shown`))}async hide(){!(this.element instanceof HTMLElement)||!this.isVisible(this.element)||(this.dispatch(`hide`),this.toggle(this.element,!1),this.activate(!1),await this.awaitCallback(this.onHidden,{target:this.element}),this.dispatch(`hidden`))}enhance(){let e=this,t={show:e.show.bind(e),hide:e.hide.bind(e)};Object.defineProperty(t,`visible`,{get(){return e.isVisible(e.element)}}),Object.defineProperty(t,this.visibilityResolver,{value:e.isVisible.bind(e)}),Object.defineProperty(this.controller,this.visibility,{get(){return t}})}},Q=(e,t)=>new Oe(e,t),ke=class extends t.Controller{static targets=[`daysOfWeek`,`daysOfMonth`];static classes=[`dayOfWeek`,`dayOfMonth`];static values={locales:{type:Array,default:[`default`]},weekdayFormat:{type:String,default:`short`},dayFormat:{type:String,default:`numeric`},daysOfOtherMonth:{type:Boolean,default:!1}};initialize(){oe(this)}connect(){this.draw()}navigated(){this.draw()}draw(){this.drawDaysOfWeek(),this.drawDaysOfMonth()}createDayElement(e,{selectable:t=!1,disabled:n=!1}={}){let r=document.createElement(t?`button`:`div`);return r.tabIndex=-1,e?r.textContent=e:r.setAttribute(`aria-hidden`,`true`),n&&(r instanceof HTMLButtonElement?r.disabled=!0:r.setAttribute(`aria-disabled`,`true`)),r}drawDaysOfWeek(){if(!this.hasDaysOfWeekTarget)return;let e=new Intl.DateTimeFormat(this.localesValue,{weekday:this.weekdayFormatValue}),t=[];for(let n of this.calendar.daysOfWeek){let r=this.createDayElement(e.format(n.date));r.setAttribute(`role`,`columnheader`),r.title=n.long,this.hasDayOfWeekClass&&r.classList.add(...this.dayOfWeekClasses),t.push(r)}let n=document.createElement(`div`);n.setAttribute(`role`,`row`),n.replaceChildren(...t),this.daysOfWeekTarget.replaceChildren(n)}drawDaysOfMonth(){if(!this.hasDaysOfMonthTarget)return;let e=this.calendar.today,t=new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime(),n=[];for(let e of this.calendar.daysOfMonth){let r=!e.current||this.calendar.isDisabled(e.date)||!this.calendar.isWithinRange(e.date),i=e.current||this.daysOfOtherMonthValue?e.value:``,a=this.createDayElement(i,{selectable:e.current,disabled:r});t===e.date.getTime()&&a.setAttribute(`aria-current`,`date`),this.hasDayOfMonthClass&&a.classList.add(...this.dayOfMonthClasses);let o=document.createElement(`time`);o.dateTime=e.iso,a.appendChild(o),n.push(a)}let r=[];for(let e=0;e<n.length;e+=7){let t=document.createElement(`div`);t.setAttribute(`role`,`row`);for(let r of n.slice(e,e+7))r.setAttribute(`role`,`gridcell`),t.appendChild(r);r.push(t)}this.daysOfMonthTarget.replaceChildren(...r)}},Ae=class extends t.Controller{onSelect(e){if(!(e.target instanceof HTMLElement))return;e.preventDefault();let t=e.target instanceof HTMLTimeElement?e.target.parentElement:e.target;if(t.disabled||t.getAttribute(`aria-disabled`)===`true`)return;this.dispatch(`selecting`,{target:t});let n=e.target instanceof HTMLTimeElement?e.target:e.target.querySelector(`time`);if(!n)return console.error(`unable to locate time element within ${t}`);let r=A(n.dateTime);if(!r)return console.error(`unable to parse ${n.dateTime} found within the time element`);this.select(r.toISOString())}select(e){let t=A(e);t&&this.dispatch(`selected`,{detail:{epoch:t.getTime(),iso:e}})}},$=class extends t.Controller{static targets=[`source`];static values={type:{type:String,default:`text/plain`}};onPaste(e){let t=e.clipboardData?.getData(this.typeValue)??``,n=Array.from(e.clipboardData?.types??[]);e.preventDefault(),this.dispatch(`pasted`,{detail:{text:t,types:n},bubbles:!0})}async copy(e){let t=e.params?.text??(this.hasSourceTarget?this.sourceTarget.value??this.sourceTarget.textContent??``:``);try{await navigator.clipboard.writeText(t),this.dispatch(`copied`,{detail:{text:t},bubbles:!0})}catch(e){this.dispatch(`copy-failed`,{detail:{error:e},bubbles:!0})}}},je=class extends t.Controller{static targets=[`previous`,`next`,`day`,`month`,`year`];static outlets=[`calendar-month`];static values={date:String,locales:{type:Array,default:[`default`]},dayFormat:{type:String,default:`numeric`},monthFormat:{type:String,default:`long`},yearFormat:{type:String,default:`numeric`}};initialize(){this.previous=this.previous.bind(this),this.next=this.next.bind(this)}async calendarMonthOutletConnected(){if(this.dateValue){let e=A(this.dateValue);e&&await this.calendarMonthOutlet.calendar.navigate(e)}this.draw()}onSelect(e){this.dateValue=e.detail.iso,this.draw(),this.dispatch(`selected`,{detail:{value:e.detail.iso},bubbles:!0})}previousTargetConnected(e){e.addEventListener(`click`,this.previous)}previousTargetDisconnected(e){e.removeEventListener(`click`,this.previous)}async previous(){await this.calendarMonthOutlet.calendar.step(`month`,-1),this.draw()}nextTargetConnected(e){e.addEventListener(`click`,this.next)}nextTargetDisconnected(e){e.removeEventListener(`click`,this.next)}async next(){await this.calendarMonthOutlet.calendar.step(`month`,1),this.draw()}draw(){this.drawDay(),this.drawMonth(),this.drawYear()}drawDay(){if(!this.hasDayTarget||!this.hasCalendarMonthOutlet)return;let{year:e,month:t,day:n}=this.calendarMonthOutlet.calendar;this.dayTarget.textContent=new Intl.DateTimeFormat(this.localesValue,{day:this.dayFormatValue}).format(new Date(e,t,n))}drawMonth(){if(!this.hasMonthTarget||!this.hasCalendarMonthOutlet)return;let{year:e,month:t}=this.calendarMonthOutlet.calendar;this.monthTarget.textContent=new Intl.DateTimeFormat(this.localesValue,{month:this.monthFormatValue}).format(new Date(e,t))}drawYear(){if(!this.hasYearTarget||!this.hasCalendarMonthOutlet)return;let{year:e}=this.calendarMonthOutlet.calendar;this.yearTarget.textContent=new Intl.DateTimeFormat(this.localesValue,{year:this.yearFormatValue}).format(new Date(e,0))}},Me=class extends t.Controller{static targets=[`listbox`,`loading`,`empty`];static values={url:{type:String,default:``},field:{type:String,default:`q`},delay:{type:Number,default:300}};initialize(){this.comboboxDropdown=ce(this)}onSelect(e){let t=e.target.closest(`[role="option"]`);!t||t.getAttribute(`aria-disabled`)===`true`||this.select(t.dataset.value??``)}select(e){let t=this.listboxTarget.querySelectorAll(`[role="option"]`);t.forEach(e=>e.setAttribute(`aria-selected`,`false`));let n=[...t].find(t=>t.dataset.value===e);n&&n.setAttribute(`aria-selected`,`true`),this.dispatch(`selected`,{detail:{value:e},bubbles:!0})}onNavigate(e){if([`ArrowUp`,`ArrowDown`,`Enter`,` `].includes(e.key)){if(e.preventDefault(),e.key===`Enter`||e.key===` `){this.listboxTarget.querySelector(`[aria-selected="true"]`)?.click();return}this.step(e.key===`ArrowDown`?1:-1)}}step(e){let t=[...this.listboxTarget.querySelectorAll(`[role="option"]:not([aria-disabled="true"]):not([hidden])`)];if(!t.length)return;let n=this.listboxTarget.querySelector(`[aria-selected="true"]`),r=t.indexOf(n),i=e>0?t[Math.min(r+1,t.length-1)]:t[Math.max(r-1,0)];!i||i===n||(t.forEach(e=>e.setAttribute(`aria-selected`,`false`)),i.setAttribute(`aria-selected`,`true`),i.scrollIntoView({block:`nearest`}))}filter(e){if(this.urlValue)this.comboboxDropdown.scheduleFetch(e,this.delayValue,{url:this.urlValue,field:this.fieldValue,onLoading:e=>this.setLoading(e),onLoaded:e=>{this.listboxTarget.innerHTML=e,this.setEmpty(this.listboxTarget.querySelectorAll(`[role="option"]`).length===0)},onError:e=>console.error(`[combobox-dropdown] fetch failed`,e)});else{let t=this.comboboxDropdown.fuzzyFilter(this.listboxTarget,e);this.setEmpty(t===0)}}showAll(){this.listboxTarget.querySelectorAll(`[role="option"]`).forEach(e=>e.hidden=!1),this.setEmpty(!1)}setLoading(e){this.hasLoadingTarget&&(this.loadingTarget.hidden=!e)}setEmpty(e){this.hasEmptyTarget&&(this.emptyTarget.hidden=!e)}disconnect(){this.comboboxDropdown.cancel()}},Ne=class extends t.Controller{static targets=[`hour`,`minute`,`period`];connect(){this.select(this.toH24())}onSelect(e){let t=e.target.closest(`[role="option"]`);t&&(t.closest(`[role="listbox"]`).querySelectorAll(`[role="option"]`).forEach(e=>e.setAttribute(`aria-selected`,`false`)),t.setAttribute(`aria-selected`,`true`),this.select(this.toH24()))}select(e){e&&this.dispatch(`selected`,{detail:{value:e},bubbles:!0})}onNavigate(e){[`ArrowUp`,`ArrowDown`].includes(e.key)&&(e.preventDefault(),this.step(e.currentTarget,e.key===`ArrowDown`?1:-1))}step(e,t){let n=[...e.querySelectorAll(`[role="option"]`)],r=e.querySelector(`[aria-selected="true"]`),i=n.indexOf(r),a=t>0?n[Math.min(i+1,n.length-1)]:n[Math.max(i-1,0)];!a||a===r||(n.forEach(e=>e.setAttribute(`aria-selected`,`false`)),a.setAttribute(`aria-selected`,`true`),a.scrollIntoView({block:`nearest`}),this.select(this.toH24()))}toH24(){let e=this.selectedValue(this.hourTarget),t=this.selectedValue(this.minuteTarget);if(!e||!t)return null;if(!this.hasPeriodTarget)return`${e}:${t}`;let n=this.selectedValue(this.periodTarget),r=parseInt(e,10);return r=n===`AM`?r===12?0:r:r===12?12:r+12,`${String(r).padStart(2,`0`)}:${t}`}selectedValue(e){return e?.querySelector(`[aria-selected="true"]`)?.dataset.value??null}},Pe=class extends t.Controller{static targets=[`trigger`];connect(){F(this,{trigger:this.hasTriggerTarget?this.triggerTarget:null})}},Fe=class extends t.Controller{static targets=[`anchor`,`reference`];static values={placement:{type:String,default:`bottom`},alignment:{type:String,default:`start`},role:{type:String,default:`tooltip`}};connect(){if(!this.hasReferenceTarget){console.error(`FlipperController requires a reference target. Add data-flipper-target="reference" to your element.`);return}if(!this.hasAnchorTarget){console.error(`FlipperController requires an anchor target. Add data-flipper-target="anchor" to your element.`);return}R(this,{element:this.referenceTarget,anchor:this.anchorTarget,placement:this.placementValue,alignment:this.alignmentValue,ariaRole:this.roleValue})}},Ie=class extends t.Controller{static targets=[`trigger`,`popover`,`value`];static values={value:String,minLength:{type:Number,default:1}};static outlets=[`combobox-dropdown`];open(){this.hasPopoverTarget&&(this.popoverTarget.hidden=!1,this.hasTriggerTarget&&v(this.triggerTarget,!0),this.focusFirstInPopover())}close(){this.hasPopoverTarget&&(this.popoverTarget.hidden=!0,this.hasTriggerTarget&&(v(this.triggerTarget,!1),this.triggerTarget.focus()))}toggle(){this.hasPopoverTarget&&this.popoverTarget.hidden?this.open():this.close()}onSelect(e){e.detail?.value!==void 0&&(this.valueValue=e.detail.value),this.close()}onInput(e){if(e.target!==this.triggerTarget)return;let t=e.target.value.trim();if(t.length<this.minLengthValue){this.hasComboboxDropdownOutlet&&this.comboboxDropdownOutlet.showAll();return}this.hasComboboxDropdownOutlet&&this.comboboxDropdownOutlet.filter(t)}valueValueChanged(e){this.hasValueTarget&&(this.valueTarget.value=e),this.dispatch(`changed`,{detail:{value:e}})}focusFirstInPopover(){this.hasPopoverTarget&&this.popoverTarget.querySelector(`button:not([disabled]), [href], input:not([type="hidden"]):not([disabled]), [tabindex]:not([tabindex="-1"])`)?.focus()}},Le=class extends t.Controller{static targets=[`input`,`toggle`];static values={type:{type:String,default:`plain`},options:{type:Object,default:{}},revealed:{type:Boolean,default:!1}};connect(){X(this,{type:this.typeValue,options:this.optionsValue}),this.format(this.readValue()),this.drawToggle()}typeValueChanged(){this.inputFormat&&(X(this,{type:this.typeValue,options:this.optionsValue}),this.format(this.readValue()),this.drawToggle())}optionsValueChanged(){this.inputFormat&&(X(this,{type:this.typeValue,options:this.optionsValue}),this.format(this.readValue()))}revealedValueChanged(){this.inputFormat&&(this.format(this.readValue()),this.drawToggle())}onChange(e){this.format(e?.detail?.value??``)}format(e){this.inputFormat&&this.onFormatting(e)}toggle(){!this.inputFormat.maskable()&&this.typeValue!==`password`||(this.revealedValue=!this.revealedValue)}onPaste(e){let t=e.detail?.text??``;if(!this.inputFormat||!t)return;let n=this.inputFormat.normalize(t);this.inputFormat.validate(n)&&this.format(n)}drawToggle(){if(!this.hasToggleTarget)return;let e=this.inputFormat?.maskable()||this.typeValue===`password`;this.toggleTarget.hidden=!e,e&&y(this.toggleTarget,this.revealedValue)}readValue(){return this.hasInputTarget?this.inputTarget instanceof HTMLInputElement?this.inputTarget.value:this.inputTarget.textContent:``}onFormatting(e){if(!this.inputFormat)return;if(this.typeValue===`password`){this.hasInputTarget&&(this.inputTarget.type=this.revealedValue?`text`:`password`);return}let t=this.inputFormat.normalize(e),n=this.revealedValue||!this.inputFormat.maskable()?this.inputFormat.format(t):this.inputFormat.mask(t);this.hasInputTarget&&(this.inputTarget instanceof HTMLInputElement?this.inputTarget.value=n:this.inputTarget.textContent=n),this.dispatch(`formatted`,{detail:{value:n}})}},Re=class extends t.Controller{static targets=[`modal`,`overlay`];connect(){if(!this.hasModalTarget){console.error(`ModalController requires a modal target. Add data-modal-target="modal" to your element.`);return}this.isNativeDialog=this.modalTarget instanceof HTMLDialogElement,this.isNativeDialog?(this.modalTarget.addEventListener(`cancel`,this.close),this.modalTarget.addEventListener(`click`,this.onBackdropClick)):(this.focusTrap=new o(this.modalTarget,{escapeDeactivates:!0}),F(this,{element:this.modalTarget}))}dismissed=()=>{this.close()};disconnect(){this.isNativeDialog&&(this.modalTarget.removeEventListener(`cancel`,this.close),this.modalTarget.removeEventListener(`click`,this.onBackdropClick))}open(e){if(e&&e.preventDefault(),this.hasModalTarget){if(this.isNativeDialog)this.previouslyFocused=document.activeElement,this.modalTarget.showModal();else{let e=this.hasOverlayTarget?this.overlayTarget:this.modalTarget;e.hidden=!1,document.body.style.overflow=`hidden`,this.focusTrap&&this.focusTrap.activate()}m(`Modal opened`)}}close(e){if(e&&e.preventDefault(),this.hasModalTarget){if(this.isNativeDialog)this.modalTarget.close(),this.previouslyFocused&&this.previouslyFocused.isConnected&&setTimeout(()=>{this.previouslyFocused.focus()},0);else{let e=this.hasOverlayTarget?this.overlayTarget:this.modalTarget;e.hidden=!0,document.body.style.overflow=``,this.focusTrap&&this.focusTrap.deactivate()}m(`Modal closed`)}}onBackdropClick=e=>{let t=this.modalTarget.getBoundingClientRect();(e.clientY<t.top||e.clientY>t.bottom||e.clientX<t.left||e.clientX>t.right)&&this.close()}},ze=class extends t.Controller{static targets=[`content`];connect(){De(this,{element:this.hasContentTarget?this.contentTarget:null})}},Be=class extends t.Controller{static targets=[`content`,`template`,`loader`,`activator`];static classes=[`hidden`];static values={url:String,loadedAt:String,reload:{type:String,default:`never`},staleAfter:{type:Number,default:3600}};connect(){ue(this,{element:this.hasContentTarget?this.contentTarget:null,url:this.hasUrlValue?this.urlValue:null}),this.hasContentTarget&&Q(this,{element:this.contentTarget,activator:this.hasActivatorTarget?this.activatorTarget:null}),this.hasLoaderTarget&&Q(this,{element:this.loaderTarget,visibility:`contentLoaderVisibility`})}async show(){await this.visibility.show()}async hide(){await this.visibility.hide()}async shown(){await this.load()}canLoad(){return this.hasContentTarget&&this.contentTarget.tagName.toLowerCase()===`turbo-frame`?(this.hasUrlValue&&this.contentTarget.setAttribute(`src`,this.urlValue),!1):!0}async contentLoading(){this.hasLoaderTarget&&await this.contentLoaderVisibility.show()}async contentLoaded({content:e}){this.hasContentTarget&&this.contentTarget.replaceChildren(this.getContentNode(e)),this.hasLoaderTarget&&await this.contentLoaderVisibility.hide()}getContentNode(e){if(typeof e==`string`){let t=document.createElement(`template`);return t.innerHTML=e,document.importNode(t.content,!0)}return document.importNode(e,!0)}contentLoader(){if(this.hasTemplateTarget)return this.templateTarget instanceof HTMLTemplateElement?this.templateTarget.content:this.templateTarget.innerHTML}};e.ARIA_HASPOPUP_VALUES=b,e.CalendarMonthController=ke,e.CalendarMonthObserverController=Ae,e.ClipboardController=$,e.ComboboxDateController=je,e.ComboboxDropdownController=Me,e.ComboboxTimeController=Ne,e.DismisserController=Pe,e.FOCUSABLE_SELECTOR=n,e.FlipperController=Fe,e.FocusRestoration=s,e.FocusTrap=o,e.InputComboboxController=Ie,e.InputFormatController=Le,e.ModalController=Re,e.PannerController=ze,e.PopoverController=Be,e.RovingTabIndex=f,e.announce=m,e.connectTriggerToTarget=C,e.disconnectTriggerFromTarget=ne,e.ensureId=g,e.focusFirst=a,e.generateId=h,e.getFocusableElements=r,e.isActivationKey=l,e.isArrowKey=u,e.isKey=c,e.isVisible=i,e.preventDefault=d,e.setAriaState=_,e.setChecked=ee,e.setDisabled=te,e.setExpanded=v,e.setPressed=y});
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Combobox
6
+ # Renders a listbox popover body for autocomplete.
7
+ # Re-uses combobox-dropdown for selection/filtering; input-combobox relays
8
+ # the trigger input event via the combobox-dropdown outlet.
9
+ class Autocomplete < Plumber::Base
10
+ include OptionGroup
11
+
12
+ DROPDOWN_CONTROLLER = "combobox-dropdown"
13
+ DROPDOWN_ACTION = [
14
+ "click->combobox-dropdown#select",
15
+ "keydown->combobox-dropdown#navigate",
16
+ "combobox-dropdown:selected->input-combobox#onSelected"
17
+ ].join(" ").freeze
18
+
19
+ def self.default_opts
20
+ {
21
+ popover: {
22
+ tag: :div,
23
+ haspopup: "listbox",
24
+ data: { controller: DROPDOWN_CONTROLLER, action: DROPDOWN_ACTION }
25
+ },
26
+ trigger: { aria_autocomplete: "list", readonly: false }
27
+ }
28
+ end
29
+
30
+ def render(options: [], value: nil, label: nil, **_kwargs)
31
+ listbox_attrs = { role: "listbox", data: { "#{DROPDOWN_CONTROLLER}_target": "listbox" } }
32
+ listbox_attrs[:aria] = { label: label } if label
33
+
34
+ listbox = template.content_tag(:ul, **listbox_attrs) do
35
+ render_items(options, value: value)
36
+ end
37
+
38
+ loading = template.content_tag(
39
+ :div,
40
+ hidden: "",
41
+ aria: { live: "polite" },
42
+ data: { "#{DROPDOWN_CONTROLLER}_target": "loading" }
43
+ ) { "" }
44
+
45
+ empty = template.content_tag(
46
+ :div,
47
+ hidden: "",
48
+ role: "status",
49
+ data: { "#{DROPDOWN_CONTROLLER}_target": "empty" }
50
+ ) { "No results" }
51
+
52
+ template.safe_join([listbox, loading, empty])
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Combobox
6
+ # Renders the date picker popover body: navigation + calendar grid.
7
+ # Wires ComboboxDateController; dispatches combobox-date:selected → InputComboboxController.
8
+ class Date < Plumber::Base
9
+ PICKER_CONTROLLER = "combobox-date"
10
+ CALENDAR_OUTLET_KEY = "#{PICKER_CONTROLLER.tr("-", "_")}_calendar_month_outlet".freeze
11
+
12
+ def self.default_opts
13
+ {
14
+ input: { data: { combobox_date_date_value: nil } },
15
+ popover: { label: "Picker", role: "dialog", tag: :div }
16
+ }
17
+ end
18
+
19
+ def render(value: nil, **_kwargs)
20
+ calendar_id = "combobox_date_#{SecureRandom.hex(8)}_calendar"
21
+
22
+ template.content_tag(
23
+ :div,
24
+ data: {
25
+ controller: PICKER_CONTROLLER,
26
+ CALENDAR_OUTLET_KEY => "##{calendar_id}",
27
+ action: [
28
+ "calendar-month-observer:selected->#{PICKER_CONTROLLER}#onSelected",
29
+ "#{PICKER_CONTROLLER}:selected->input-combobox#onSelected"
30
+ ].join(" ")
31
+ }.tap { |d| d["#{PICKER_CONTROLLER.tr("-", "_")}_date_value"] = value if value }
32
+ ) do
33
+ template.safe_join([navigation, calendar_month(id: calendar_id)])
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def navigation
40
+ DatePicker::Navigation.new(template).render(
41
+ step: "month",
42
+ stimulus_controller: PICKER_CONTROLLER
43
+ )
44
+ end
45
+
46
+ def calendar_month(**kwargs)
47
+ Calendar::Renderer.new(template).month(**kwargs)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Combobox
6
+ # Renders a static listbox popover body with combobox-dropdown controller.
7
+ # Supports flat options, descriptions, and option groups.
8
+ class Dropdown < Plumber::Base
9
+ include OptionGroup
10
+
11
+ DROPDOWN_CONTROLLER = "combobox-dropdown"
12
+ DROPDOWN_ACTION = [
13
+ "click->combobox-dropdown#select",
14
+ "keydown->combobox-dropdown#navigate",
15
+ "combobox-dropdown:selected->input-combobox#onSelected"
16
+ ].join(" ").freeze
17
+
18
+ def self.default_opts
19
+ {
20
+ popover: {
21
+ tag: :div,
22
+ haspopup: "listbox",
23
+ data: { controller: DROPDOWN_CONTROLLER, action: DROPDOWN_ACTION }
24
+ }
25
+ }
26
+ end
27
+
28
+ def render(options: [], value: nil, label: nil, **_kwargs)
29
+ listbox_attrs = { role: "listbox", data: { "#{DROPDOWN_CONTROLLER}_target": "listbox" } }
30
+ listbox_attrs[:aria] = { label: label } if label
31
+
32
+ listbox = template.content_tag(:ul, **listbox_attrs) do
33
+ render_items(options, value: value)
34
+ end
35
+
36
+ template.safe_join([listbox])
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Combobox
6
+ class Option < Plumber::Base
7
+ def render(label:, value:, description: nil, disabled: false, selected: false)
8
+ aria = { selected: selected ? "true" : "false" }
9
+ aria[:disabled] = "true" if disabled
10
+
11
+ template.content_tag(:li, role: "option", aria: aria, data: { value: value }) do
12
+ if description
13
+ template.safe_join(
14
+ [
15
+ template.content_tag(:span, label),
16
+ template.content_tag(:span, description)
17
+ ]
18
+ )
19
+ else
20
+ label
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Combobox
6
+ module OptionGroup
7
+ private
8
+
9
+ def render_items(items, value: nil)
10
+ @selected_value = value.to_s
11
+ template.safe_join(items.map { |item| render_item(item) })
12
+ end
13
+
14
+ def render_item(item)
15
+ case item
16
+ when Hash
17
+ item.key?(:options) ? render_group(item[:label], item[:options]) : render_option_hash(item)
18
+ else
19
+ render_option(item[0], item[1].to_s, item[2] || {})
20
+ end
21
+ end
22
+
23
+ def render_option_hash(item)
24
+ render_option(item[:label], item[:value].to_s, item.except(:label, :value))
25
+ end
26
+
27
+ def render_option(label, value, attrs = {})
28
+ Option.new(template).render(
29
+ label: label,
30
+ value: value,
31
+ selected: @selected_value == value,
32
+ disabled: attrs[:disabled] || false,
33
+ description: attrs[:description]
34
+ )
35
+ end
36
+
37
+ def render_group(label, options)
38
+ template.content_tag(:li, role: "group", aria: { label: label }) do
39
+ template.safe_join(
40
+ [
41
+ template.content_tag(:span, label, aria: { hidden: "true" }),
42
+ template.content_tag(:ul) do
43
+ template.safe_join(options.map { |opt| render_item(opt) })
44
+ end
45
+ ]
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Combobox
6
+ class Renderer < Plumber::Base
7
+ STIMULUS_CONTROLLER = "input-combobox"
8
+ FORMAT_CONTROLLER = "input-format"
9
+ FORMAT_ACTION = "input-combobox:changed->input-format#format"
10
+
11
+ def render(base_id:, options: {}, **kwargs)
12
+ popover_id = "#{base_id}_popover"
13
+ initial_value = options.dig(:input, :value)
14
+
15
+ base_data = {
16
+ controller: "#{STIMULUS_CONTROLLER} #{FORMAT_CONTROLLER}",
17
+ action: FORMAT_ACTION
18
+ }
19
+ base_data[:input_combobox_value_value] = initial_value if initial_value.present?
20
+
21
+ html_options = merge_html_options({ data: base_data }, kwargs)
22
+
23
+ template.content_tag(:div, **html_options) do
24
+ template.safe_join(
25
+ [
26
+ trigger_input(
27
+ popover_id,
28
+ options.dig(:popover, :haspopup) || options.dig(:popover, :role) || "dialog",
29
+ options.fetch(:trigger, {})
30
+ ),
31
+ hidden_input(options.fetch(:input, {})),
32
+ popover_element(popover_id, options.fetch(:popover, {}))
33
+ ]
34
+ )
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def trigger_input(popover_id, haspopup, opts)
41
+ base_data = {
42
+ "#{STIMULUS_CONTROLLER}_target": "trigger",
43
+ input_format_target: "input",
44
+ action: "focus->#{STIMULUS_CONTROLLER}#open keydown.esc->#{STIMULUS_CONTROLLER}#close"
45
+ }
46
+ data = merge_data_options(base_data, opts.fetch(:data, {}).symbolize_keys)
47
+
48
+ aria = { haspopup: haspopup, expanded: "false", controls: popover_id }
49
+ aria[:autocomplete] = opts[:aria_autocomplete] if opts[:aria_autocomplete]
50
+ aria[:label] = opts[:aria_label] if opts[:aria_label]
51
+
52
+ template.tag.input(
53
+ type: "text",
54
+ readonly: (opts.fetch(:readonly, true) ? true : nil),
55
+ role: "combobox",
56
+ aria: aria,
57
+ data: data
58
+ )
59
+ end
60
+
61
+ def hidden_input(opts)
62
+ data = { "#{STIMULUS_CONTROLLER}_target": "value" }.merge(opts.fetch(:data, {}))
63
+ template.tag.input(type: "hidden", name: opts[:name], value: opts[:value], data: data)
64
+ end
65
+
66
+ def popover_element(popover_id, opts)
67
+ base_data = { "#{STIMULUS_CONTROLLER}_target": "popover" }
68
+ data = merge_data_options(base_data, (opts[:data] || {}).symbolize_keys)
69
+
70
+ attrs = { id: popover_id, hidden: "", data: data }
71
+ attrs[:role] = opts[:role] if opts[:role]
72
+ attrs[:aria] = { label: opts[:label] } if opts[:label]
73
+ template.content_tag(opts.fetch(:tag, :div), **attrs) { opts[:content] }
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module Combobox
6
+ # Renders an iOS-style drum/wheel time picker as the popover body.
7
+ # Wires ComboboxTimeController; dispatches combobox-time:selected → InputComboboxController.
8
+ class Time < Plumber::Base
9
+ PICKER_CONTROLLER = "combobox-time"
10
+
11
+ def self.default_opts
12
+ {
13
+ popover: { label: "Picker", role: "dialog", tag: :div }
14
+ }
15
+ end
16
+
17
+ def render(format: :h12, step: 1, value: nil, **_kwargs)
18
+ @format = format
19
+ @step = [1, step.to_i].max
20
+ @time = parse_time(value)
21
+
22
+ template.content_tag(
23
+ :div,
24
+ data: {
25
+ controller: PICKER_CONTROLLER,
26
+ action: "#{PICKER_CONTROLLER}:selected->input-combobox#onSelected"
27
+ }
28
+ ) do
29
+ template.safe_join(drums)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def drums
36
+ cols = [hour_drum, minute_drum]
37
+ cols << period_drum if @format == :h12
38
+ cols
39
+ end
40
+
41
+ def hour_drum
42
+ drum.render(target: "hour", label: "Hour", items: hour_items, selected: current_hour)
43
+ end
44
+
45
+ def minute_drum
46
+ items = (0...60).step(@step).map do |m|
47
+ s = m.to_s.rjust(2, "0")
48
+ [s, s]
49
+ end
50
+ selected = @time ? snap_minute(@time.min).to_s.rjust(2, "0") : nil
51
+ drum.render(target: "minute", label: "Minute", items: items, selected: selected)
52
+ end
53
+
54
+ def period_drum
55
+ selected = if @time
56
+ @time.hour < 12 ? "AM" : "PM"
57
+ end
58
+ drum.render(target: "period", label: "Period", items: [%w[AM AM], %w[PM PM]], selected: selected)
59
+ end
60
+
61
+ def hour_items
62
+ if @format == :h12
63
+ (1..12).map { |h| [h.to_s, h.to_s] }
64
+ else
65
+ (0..23).map do |h|
66
+ s = h.to_s.rjust(2, "0")
67
+ [s, s]
68
+ end
69
+ end
70
+ end
71
+
72
+ def current_hour
73
+ return nil unless @time
74
+
75
+ if @format == :h12
76
+ h = @time.hour % 12
77
+ (h.zero? ? 12 : h).to_s
78
+ else
79
+ @time.hour.to_s.rjust(2, "0")
80
+ end
81
+ end
82
+
83
+ def snap_minute(minute)
84
+ return minute if @step == 1
85
+
86
+ ((minute.to_f / @step).round * @step) % 60
87
+ end
88
+
89
+ def drum
90
+ TimePicker::Renderer.new(template)
91
+ end
92
+
93
+ def parse_time(value)
94
+ return nil if value.nil? || value.to_s.strip.empty?
95
+
96
+ ::Time.parse(value.to_s)
97
+ rescue ArgumentError
98
+ nil
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -4,7 +4,7 @@ module StimulusPlumbers
4
4
  module Components
5
5
  module DatePicker
6
6
  class Navigation < Plumber::Base
7
- def render(stimulus_controller:, step:, **kwargs)
7
+ def render(step:, stimulus_controller:, **kwargs)
8
8
  html_options = merge_html_options(
9
9
  { classes: theme.resolve(:calendar_navigation).fetch(:classes, ""), aria: { label: "DatePicker Navigation" } },
10
10
  kwargs
@@ -9,10 +9,29 @@ module StimulusPlumbers
9
9
  extend ActiveSupport::Concern
10
10
 
11
11
  def merge_html_options(*hashes)
12
- classes = hashes.flat_map { |h| [h[:class], h[:classes]] }
13
- rest = hashes.map { |h| h.except(:class, :classes) }.reduce({}, :deep_merge)
12
+ classes = hashes.flat_map { |h| [h[:class], h[:classes]] }
13
+ data_hashes = hashes.map { |h| h[:data] || {} }
14
+ rest = hashes.map { |h| h.except(:class, :classes, :data) }.reduce({}, :deep_merge)
15
+
14
16
  class_value = merge_string_option(*classes).presence
15
- class_value ? rest.merge(class: class_value) : rest
17
+ merged_data = merge_data_options(*data_hashes)
18
+
19
+ result = class_value ? rest.merge(class: class_value) : rest
20
+ merged_data.present? ? result.merge(data: merged_data) : result
21
+ end
22
+
23
+ STIMULUS_SPACEJOIN_KEYS = %i[controller action].freeze
24
+
25
+ def merge_data_options(*hashes, spacejoin: STIMULUS_SPACEJOIN_KEYS)
26
+ hashes.reduce({}) do |acc, d|
27
+ acc.merge(d) do |key, old_val, new_val|
28
+ if spacejoin.include?(key.to_sym)
29
+ merge_string_option(old_val, new_val).presence || new_val
30
+ else
31
+ new_val
32
+ end
33
+ end
34
+ end
16
35
  end
17
36
 
18
37
  def merge_string_option(*parts, delimiter: " ")
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusPlumbers
4
+ module Components
5
+ module TimePicker
6
+ # Renders a single scrollable drum column (hour, minute, or period) as a listbox.
7
+ class Renderer < Plumber::Base
8
+ CONTROLLER = "combobox-time"
9
+
10
+ def render(items:, label:, target:, selected: nil)
11
+ template.content_tag(
12
+ :ul,
13
+ role: "listbox",
14
+ aria: { label: label },
15
+ data: {
16
+ "#{CONTROLLER}_target": target,
17
+ action: "click->#{CONTROLLER}#select keydown->#{CONTROLLER}#navigate"
18
+ }
19
+ ) do
20
+ template.safe_join(items.map { |text, value| render_item(text, value, selected) })
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def render_item(text, value, selected)
27
+ template.content_tag(
28
+ :li,
29
+ text,
30
+ role: "option",
31
+ aria: { selected: value.to_s == selected.to_s ? "true" : "false" },
32
+ data: { value: value }
33
+ )
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end