1// https://tailwindui.com/plus/v2-assets/components.js 2 3window.Components = {}; 4 5window.Components.listbox = function listbox(options) { 6 let modelName = options.modelName || "selected"; 7 let pointer = useTrackedPointer(); 8 9 return { 10 init() { 11 this.optionCount = this.$refs.listbox.children.length; 12 this.$watch("activeIndex", (value) => { 13 if (!this.open) return; 14 15 if (this.activeIndex === null) { 16 this.activeDescendant = ""; 17 return; 18 } 19 20 this.activeDescendant = 21 this.$refs.listbox.children[this.activeIndex].id; 22 }); 23 }, 24 activeDescendant: null, 25 optionCount: null, 26 open: false, 27 activeIndex: null, 28 selectedIndex: 0, 29 get active() { 30 return this.items[this.activeIndex]; 31 }, 32 get [modelName]() { 33 return this.items[this.selectedIndex]; 34 }, 35 choose(option) { 36 this.selectedIndex = option; 37 this.open = false; 38 }, 39 onButtonClick() { 40 if (this.open) return; 41 this.activeIndex = this.selectedIndex; 42 this.open = true; 43 this.$nextTick(() => { 44 this.$refs.listbox.focus(); 45 this.$refs.listbox.children[this.activeIndex].scrollIntoView({ 46 block: "nearest", 47 }); 48 }); 49 }, 50 onOptionSelect() { 51 if (this.activeIndex !== null) { 52 this.selectedIndex = this.activeIndex; 53 } 54 this.open = false; 55 this.$refs.button.focus(); 56 }, 57 onEscape() { 58 this.open = false; 59 this.$refs.button.focus(); 60 }, 61 onArrowUp() { 62 this.activeIndex = 63 this.activeIndex - 1 < 0 ? this.optionCount - 1 : this.activeIndex - 1; 64 this.$refs.listbox.children[this.activeIndex].scrollIntoView({ 65 block: "nearest", 66 }); 67 }, 68 onArrowDown() { 69 this.activeIndex = 70 this.activeIndex + 1 > this.optionCount - 1 ? 0 : this.activeIndex + 1; 71 this.$refs.listbox.children[this.activeIndex].scrollIntoView({ 72 block: "nearest", 73 }); 74 }, 75 onMouseEnter(evt) { 76 pointer.update(evt); 77 }, 78 onMouseMove(evt, newIndex) { 79 // Only highlight when the cursor has moved 80 // Pressing arrow keys can otherwise scroll the container and override the selected item 81 if (!pointer.wasMoved(evt)) return; 82 this.activeIndex = newIndex; 83 }, 84 onMouseLeave(evt) { 85 // Only unhighlight when the cursor has moved 86 // Pressing arrow keys can otherwise scroll the container and override the selected item 87 if (!pointer.wasMoved(evt)) return; 88 this.activeIndex = null; 89 }, 90 ...options, 91 }; 92}; 93 94window.Components.menu = function menu(options = { open: false }) { 95 let pointer = useTrackedPointer(); 96 97 return { 98 init() { 99 this.items = Array.from(this.$el.querySelectorAll('[role="menuitem"]')); 100 this.$watch("open", () => { 101 if (this.open) { 102 this.activeIndex = -1; 103 } 104 }); 105 }, 106 activeDescendant: null, 107 activeIndex: null, 108 items: null, 109 open: options.open, 110 focusButton() { 111 this.$refs.button.focus(); 112 }, 113 onButtonClick() { 114 this.open = !this.open; 115 if (this.open) { 116 this.$nextTick(() => { 117 this.$refs["menu-items"].focus(); 118 }); 119 } 120 }, 121 onButtonEnter() { 122 this.open = !this.open; 123 if (this.open) { 124 this.activeIndex = 0; 125 this.activeDescendant = this.items[this.activeIndex].id; 126 this.$nextTick(() => { 127 this.$refs["menu-items"].focus(); 128 }); 129 } 130 }, 131 onArrowUp() { 132 if (!this.open) { 133 this.open = true; 134 this.activeIndex = this.items.length - 1; 135 this.activeDescendant = this.items[this.activeIndex].id; 136 137 return; 138 } 139 140 if (this.activeIndex === 0) { 141 return; 142 } 143 144 this.activeIndex = 145 this.activeIndex === -1 ? this.items.length - 1 : this.activeIndex - 1; 146 this.activeDescendant = this.items[this.activeIndex].id; 147 }, 148 onArrowDown() { 149 if (!this.open) { 150 this.open = true; 151 this.activeIndex = 0; 152 this.activeDescendant = this.items[this.activeIndex].id; 153 154 return; 155 } 156 157 if (this.activeIndex === this.items.length - 1) { 158 return; 159 } 160 161 this.activeIndex = this.activeIndex + 1; 162 this.activeDescendant = this.items[this.activeIndex].id; 163 }, 164 onClickAway($event) { 165 if (this.open) { 166 const focusableSelector = [ 167 "[contentEditable=true]", 168 "[tabindex]", 169 "a[href]", 170 "area[href]", 171 "button:not([disabled])", 172 "iframe", 173 "input:not([disabled])", 174 "select:not([disabled])", 175 "textarea:not([disabled])", 176 ] 177 .map((selector) => `${selector}:not([tabindex='-1'])`) 178 .join(","); 179 180 this.open = false;
181 182 if (!$event.target.closest(focusableSelector)) { 183 this.focusButton(); 184 } 185 } 186 }, 187 188 onMouseEnter(evt) { 189 pointer.update(evt); 190 }, 191 onMouseMove(evt, newIndex) { 192 // Only highlight when the cursor has moved 193 // Pressing arrow keys can otherwise scroll the container and override the selected item 194 if (!pointer.wasMoved(evt)) return; 195 this.activeIndex = newIndex; 196 }, 197 onMouseLeave(evt) { 198 // Only unhighlight when the cursor has moved 199 // Pressing arrow keys can otherwise scroll the container and override the selected item 200 if (!pointer.wasMoved(evt)) return; 201 this.activeIndex = -1; 202 }, 203 }; 204}; 205 206window.Components.popoverGroup = function popoverGroup() { 207 return { 208 __type: "popoverGroup", 209 init() { 210 let handler = (e) => { 211 if (!document.body.contains(this.$el)) { 212 window.removeEventListener("focus", handler, true); 213 return; 214 } 215 if (e.target instanceof Element && !this.$el.contains(e.target)) { 216 window.dispatchEvent( 217 new CustomEvent("close-popover-group", { 218 detail: this.$el, 219 }), 220 ); 221 } 222 }; 223 window.addEventListener("focus", handler, true); 224 }, 225 }; 226}; 227 228window.Components.popover = function popover({ 229 open = false, 230 focus = false, 231} = {}) { 232 const focusableSelector = [ 233 "[contentEditable=true]", 234 "[tabindex]", 235 "a[href]", 236 "area[href]", 237 "button:not([disabled])", 238 "iframe", 239 "input:not([disabled])", 240 "select:not([disabled])", 241 "textarea:not([disabled])", 242 ] 243 .map((selector) => `${selector}:not([tabindex='-1'])`) 244 .join(","); 245 246 function focusFirst(container) { 247 const focusableElements = Array.from( 248 container.querySelectorAll(focusableSelector), 249 ); 250 251 function tryFocus(element) { 252 if (element === undefined) return; 253 254 element.focus({ preventScroll: true }); 255 256 if (document.activeElement !== element) { 257 tryFocus(focusableElements[focusableElements.indexOf(element) + 1]); 258 } 259 } 260 261 tryFocus(focusableElements[0]); 262 } 263 264 return { 265 __type: "popover", 266 open, 267 init() { 268 if (focus) { 269 this.$watch("open", (open) => { 270 if (open) { 271 this.$nextTick(() => { 272 focusFirst(this.$refs.panel); 273 }); 274 } 275 }); 276 } 277 278 let handler = (e) => { 279 if (!document.body.contains(this.$el)) { 280 window.removeEventListener("focus", handler, true); 281 return; 282 } 283 let ref = focus ? this.$refs.panel : this.$el; 284 if ( 285 this.open && 286 e.target instanceof Element && 287 !ref.contains(e.target) 288 ) { 289 let node = this.$el; 290 while (node.parentNode) { 291 node = node.parentNode; 292 if (node.__x instanceof this.constructor) { 293 if (node.__x.$data.__type === "popoverGroup") return; 294 if (node.__x.$data.__type === "popover") break; 295 } 296 } 297 this.open = false; 298 } 299 }; 300 301 window.addEventListener("focus", handler, true); 302 }, 303 onEscape() { 304 this.open = false; 305 if (this.restoreEl) { 306 this.restoreEl.focus(); 307 } 308 }, 309 onClosePopoverGroup(e) { 310 if (e.detail.contains(this.$el)) { 311 this.open = false; 312 } 313 }, 314 toggle(e) { 315 this.open = !this.open; 316 if (this.open) { 317 this.restoreEl = e.currentTarget; 318 } else if (this.restoreEl) { 319 this.restoreEl.focus(); 320 } 321 }, 322 }; 323}; 324 325window.Components.radioGroup = function radioGroup({ 326 initialCheckedIndex = 0, 327} = {}) { 328 return { 329 value: undefined, 330 active: undefined, 331 init() { 332 let options = Array.from(this.$el.querySelectorAll("input")); 333 334 this.value = options[initialCheckedIndex]?.value; 335 336 for (let option of options) { 337 option.addEventListener("change", () => { 338 this.active = option.value; 339 }); 340 option.addEventListener("focus", () => { 341 this.active = option.value; 342 }); 343 } 344 345 window.addEventListener( 346 "focus", 347 () => { 348 if (!options.includes(document.activeElement)) { 349 this.active = undefined; 350 } 351 }, 352 true, 353 ); 354 }, 355 }; 356}; 357 358window.Components.tabs = function tabs() { 359 return { 360 selectedIndex: 0, 361 onTabClick(event) { 362 if (!this.$el.contains(event.detail)) return; 363 364 let tabs = Array.from( 365 this.$el.querySelectorAll('[x-data^="Components.tab("]'), 366 ); 367 let panels = Array.from( 368 this.$el.querySelectorAll('[x-data^="Components.tabPanel("]'), 369 ); 370 371 let idx = tabs.indexOf(event.detail); 372 this.selectedIndex = idx; 373 374 window.dispatchEvent( 375 new CustomEvent("tab-select", { 376 detail: { 377 tab: event.detail, 378 panel: panels[idx], 379 }, 380 }), 381 ); 382 }, 383 onTabKeydown(event) { 384 if (!this.$el.contains(event.detail.tab)) return; 385 386 let tabs = Array.from( 387 this.$el.querySelectorAll('[x-data^="Components.tab("]'), 388 ); 389 let tabIndex = tabs.indexOf(event.detail.tab); 390
391 if (event.detail.key === "ArrowLeft") { 392 this.onTabClick({ 393 detail: tabs[(tabIndex - 1 + tabs.length) % tabs.length], 394 }); 395 } else if (event.detail.key === "ArrowRight") { 396 this.onTabClick({ detail: tabs[(tabIndex + 1) % tabs.length] }); 397 } else if (event.detail.key === "Home" || event.detail.key === "PageUp") { 398 this.onTabClick({ detail: tabs[0] }); 399 } else if ( 400 event.detail.key === "End" || 401 event.detail.key === "PageDown" 402 ) { 403 this.onTabClick({ detail: tabs[tabs.length - 1] }); 404 } 405 }, 406 }; 407}; 408 409window.Components.tab = function tab(defaultIndex = 0) { 410 return { 411 selected: false, 412 init() { 413 let tabs = Array.from( 414 this.$el 415 .closest('[x-data^="Components.tabs("]') 416 .querySelectorAll('[x-data^="Components.tab("]'), 417 ); 418 this.selected = tabs.indexOf(this.$el) === defaultIndex; 419 this.$watch("selected", (selected) => { 420 if (selected) { 421 this.$el.focus(); 422 } 423 }); 424 }, 425 onClick() { 426 window.dispatchEvent( 427 new CustomEvent("tab-click", { 428 detail: this.$el, 429 }), 430 ); 431 }, 432 onKeydown(event) { 433 if ( 434 [ 435 "ArrowLeft", 436 "ArrowRight", 437 "Home", 438 "PageUp", 439 "End", 440 "PageDown", 441 ].includes(event.key) 442 ) { 443 event.preventDefault(); 444 } 445 446 window.dispatchEvent( 447 new CustomEvent("tab-keydown", { 448 detail: { 449 tab: this.$el, 450 key: event.key, 451 }, 452 }), 453 ); 454 }, 455 onTabSelect(event) { 456 this.selected = event.detail.tab === this.$el; 457 }, 458 }; 459}; 460 461window.Components.tabPanel = function tabPanel(defaultIndex = 0) { 462 return { 463 selected: false, 464 init() { 465 let panels = Array.from( 466 this.$el 467 .closest('[x-data^="Components.tabs("]') 468 .querySelectorAll('[x-data^="Components.tabPanel("]'), 469 ); 470 this.selected = panels.indexOf(this.$el) === defaultIndex; 471 }, 472 onTabSelect(event) { 473 this.selected = event.detail.panel === this.$el; 474 }, 475 }; 476}; 477 478function useTrackedPointer() { 479 /** @type {[x: number, y: number]} */ 480 let lastPos = [-1, -1]; 481 482 return { 483 /** 484 * @param {PointerEvent} evt 485 */ 486 wasMoved(evt) { 487 let newPos = [evt.screenX, evt.screenY]; 488 489 if (lastPos[0] === newPos[0] && lastPos[1] === newPos[1]) { 490 return false; 491 } 492 493 lastPos = newPos; 494 return true; 495 }, 496 497 /** 498 * @param {PointerEvent} evt 499 */ 500 update(evt) {
501 lastPos = [evt.screenX, evt.screenY]; 502 }, 503 }; 504}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.