dialog.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. export default function (Alpine) {
  2. Alpine.element('dialog.panel', () => ({
  3. '@click.outside'() { this.$data.__close() },
  4. }))
  5. Alpine.element('dialog', el => ({
  6. 'x-data'() {
  7. return {
  8. init() {
  9. // If the user chose to use :open and @close instead of x-model.
  10. (Alpine.bound(el, 'open') !== undefined) && Alpine.effect(() => {
  11. this.__isOpenState = Alpine.bound(el, 'open')
  12. })
  13. if (Alpine.bound(el, 'initial-focus') !== undefined) this.$watch('__isOpenState', () => {
  14. if (! this.__isOpenState) return
  15. setTimeout(() => {
  16. Alpine.bound(el, 'initial-focus').focus()
  17. }, 0);
  18. })
  19. },
  20. __isOpenState: false,
  21. __close() {
  22. if (Alpine.bound(el, 'open')) this.$dispatch('close')
  23. else this.__isOpenState = false
  24. },
  25. get __isOpen() {
  26. return Alpine.bound(el, 'static', this.__isOpenState)
  27. },
  28. }
  29. },
  30. 'x-modelable': '__isOpenState',
  31. 'x-id'() { return ['alpine-dialog-title', 'alpine-dialog-description'] },
  32. 'x-show'() { return this.__isOpen },
  33. 'x-trap.inert.noscroll'() { return this.__isOpen },
  34. '@keydown.escape'() { this.__close() },
  35. ':aria-labelledby'() { return this.$id('alpine-dialog-title') },
  36. ':aria-describedby'() { return this.$id('alpine-dialog-description') },
  37. 'role': 'dialog',
  38. 'aria-modal': 'true',
  39. }))
  40. Alpine.directive('dialog', (el, directive) => {
  41. if (directive.value === 'overlay') handleOverlay(el, Alpine)
  42. else if (directive.value === 'panel') handlePanel(el, Alpine)
  43. else if (directive.value === 'title') handleTitle(el, Alpine)
  44. else if (directive.value === 'description') handleDescription(el, Alpine)
  45. else handleRoot(el, Alpine)
  46. })
  47. Alpine.magic('dialog', el => {
  48. let $data = Alpine.$data(el)
  49. return {
  50. get open() {
  51. return $data.__isOpen
  52. }
  53. }
  54. })
  55. }
  56. function handleRoot(el, Alpine) {
  57. Alpine.bind(el, {
  58. })
  59. }
  60. function handleOverlay(el, Alpine) {
  61. Alpine.bind(el, {
  62. 'x-init'() { if (this.$data.__isOpen === undefined) console.warn('"x-dialog:overlay" is missing a parent element with "x-dialog".') },
  63. 'x-show'() { return this.__isOpen },
  64. '@click.prevent.stop'() { this.$data.__close() },
  65. })
  66. }
  67. function handlePanel(el, Alpine) {
  68. Alpine.bind(el, {
  69. '@click.outside'() { this.$data.__close() },
  70. })
  71. }
  72. function handleTitle(el, Alpine) {
  73. Alpine.bind(el, {
  74. 'x-init'() { if (this.$data.__isOpen === undefined) console.warn('"x-dialog:title" is missing a parent element with "x-dialog".') },
  75. ':id'() { return this.$id('alpine-dialog-title') },
  76. })
  77. }
  78. function handleDescription(el, Alpine) {
  79. Alpine.bind(el, {
  80. ':id'() { return this.$id('alpine-dialog-description') },
  81. })
  82. }