@rip-lang/ui 0.3.48 → 0.3.49

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.
@@ -12,78 +12,32 @@
12
12
  # button @click: (=> showConfirm = false), "Cancel"
13
13
  # button @click: handleDelete, "Delete"
14
14
 
15
- alertDialogStack = []
16
-
17
15
  export AlertDialog = component
18
16
  @open := false
19
17
  @initialFocus := null
20
18
 
21
19
  _prevFocus = null
22
20
  _cleanupTrap = null
23
- _scrollY = 0
24
21
  _id =! "adlg-#{Math.random().toString(36).slice(2, 8)}"
25
22
 
26
- _wireAria: ->
27
- panel = @_panel
28
- return unless panel
29
- heading = panel.querySelector('h1,h2,h3,h4,h5,h6')
30
- if heading
31
- heading.id ?= "#{_id}-title"
32
- panel.setAttribute 'aria-labelledby', heading.id
33
- desc = panel.querySelector('p')
34
- if desc
35
- desc.id ?= "#{_id}-desc"
36
- panel.setAttribute 'aria-describedby', desc.id
37
-
38
23
  ~>
39
24
  if @open
40
25
  _prevFocus = document.activeElement
41
- _scrollY = window.scrollY
42
- alertDialogStack.push this
43
- document.body.style.position = 'fixed'
44
- document.body.style.top = "-#{_scrollY}px"
45
- document.body.style.width = '100%'
46
-
47
- setTimeout =>
26
+ ARIA.lockScroll(this)
27
+ requestAnimationFrame =>
48
28
  panel = @_panel
49
29
  if panel
50
- @_wireAria()
30
+ ARIA.wireAria panel, _id
51
31
  if @initialFocus
52
32
  target = if typeof @initialFocus is 'string' then panel.querySelector(@initialFocus) else @initialFocus
53
33
  target?.focus()
54
34
  else
55
- focusable = panel.querySelectorAll 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'
56
- focusable[0]?.focus()
57
- _cleanupTrap = (e) ->
58
- return unless e.key is 'Tab'
59
- list = Array.from(panel.querySelectorAll('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')).filter (f) -> f.offsetParent isnt null
60
- return unless list.length
61
- first = list[0]
62
- last = list[list.length - 1]
63
- if e.shiftKey
64
- if document.activeElement is first then (e.preventDefault(); last.focus())
65
- else
66
- if document.activeElement is last then (e.preventDefault(); first.focus())
67
- panel.addEventListener 'keydown', _cleanupTrap
68
- , 0
69
-
35
+ panel.querySelectorAll('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')?[0]?.focus()
36
+ _cleanupTrap = ARIA.trapFocus(panel)
70
37
  return ->
71
- idx = alertDialogStack.indexOf this
72
- alertDialogStack.splice(idx, 1) if idx >= 0
73
- document.body.style.position = '' unless alertDialogStack.length
74
- document.body.style.top = '' unless alertDialogStack.length
75
- document.body.style.width = '' unless alertDialogStack.length
76
- window.scrollTo 0, _scrollY unless alertDialogStack.length
38
+ _cleanupTrap?()
39
+ ARIA.unlockScroll(this)
77
40
  _prevFocus?.focus()
78
- else
79
- idx = alertDialogStack.indexOf this
80
- alertDialogStack.splice(idx, 1) if idx >= 0
81
- unless alertDialogStack.length
82
- document.body.style.position = ''
83
- document.body.style.top = ''
84
- document.body.style.width = ''
85
- window.scrollTo 0, _scrollY
86
- _prevFocus?.focus()
87
41
 
88
42
  close: ->
89
43
  @open = false
@@ -45,22 +45,13 @@ export Autocomplete = component
45
45
  openMenu: ->
46
46
  open = true
47
47
  @_hlIdx = -1
48
- setTimeout => @_position(), 0
48
+ requestAnimationFrame => @_position()
49
49
 
50
50
  close: ->
51
51
  open = false
52
52
  @_hlIdx = -1
53
53
 
54
- _position: ->
55
- return unless @_input and @_list
56
- tr = @_input.getBoundingClientRect()
57
- @_list.style.position = 'fixed'
58
- @_list.style.left = "#{tr.left}px"
59
- @_list.style.top = "#{tr.bottom + 2}px"
60
- @_list.style.minWidth = "#{tr.width}px"
61
- fl = @_list.getBoundingClientRect()
62
- if fl.bottom > window.innerHeight
63
- @_list.style.top = "#{tr.top - fl.height - 2}px"
54
+ _position: -> ARIA.positionBelow @_input, @_list, 2, false
64
55
 
65
56
  selectIndex: (idx) ->
66
57
  item = filteredItems[idx]
@@ -46,21 +46,13 @@ export Combobox = component
46
46
  openMenu: ->
47
47
  open = true
48
48
  highlightedIndex = -1
49
- setTimeout => @_position(), 0
49
+ requestAnimationFrame => @_position()
50
50
 
51
51
  close: ->
52
52
  open = false
53
53
  highlightedIndex = -1
54
54
 
55
- _position: ->
56
- return unless @_input and @_list
57
- tr = @_input.getBoundingClientRect()
58
- @_list.style.left = "#{tr.left}px"
59
- @_list.style.top = "#{tr.bottom + 2}px"
60
- @_list.style.minWidth = "#{tr.width}px"
61
- fl = @_list.getBoundingClientRect()
62
- if fl.bottom > window.innerHeight
63
- @_list.style.top = "#{tr.top - fl.height - 2}px"
55
+ _position: -> ARIA.positionBelow @_input, @_list, 2, false
64
56
 
65
57
  isDisabled: (item) -> item?.hasAttribute?('data-disabled')
66
58
 
@@ -133,22 +133,14 @@ export DatePicker = component
133
133
  else if @range and Array.isArray(@value) and @value[0]
134
134
  viewMonth = new Date(@value[0].getFullYear(), @value[0].getMonth(), 1)
135
135
  _inputText = _displayText
136
- setTimeout => @_position(), 0
136
+ requestAnimationFrame => @_position()
137
137
 
138
138
  close: ->
139
139
  open = false
140
140
  _rangeStart = null
141
141
  _hoveredDay = null
142
142
 
143
- _position: ->
144
- return unless @_trigger and @_cal
145
- tr = @_trigger.getBoundingClientRect()
146
- @_cal.style.position = 'fixed'
147
- @_cal.style.left = "#{tr.left}px"
148
- @_cal.style.top = "#{tr.bottom + 4}px"
149
- fl = @_cal.getBoundingClientRect()
150
- if fl.bottom > window.innerHeight
151
- @_cal.style.top = "#{tr.top - fl.height - 4}px"
143
+ _position: -> ARIA.positionBelow @_trigger, @_cal, 4, false
152
144
 
153
145
  _onKeydown: (e) ->
154
146
  switch e.key
@@ -12,8 +12,6 @@
12
12
  # p "Content"
13
13
  # button @click: (=> showDialog = false), "Close"
14
14
 
15
- dialogStack = []
16
-
17
15
  export Dialog = component
18
16
  @open := false
19
17
  @dismissable := true
@@ -21,70 +19,26 @@ export Dialog = component
21
19
 
22
20
  _prevFocus = null
23
21
  _cleanupTrap = null
24
- _scrollY = 0
25
22
  _id =! "dlg-#{Math.random().toString(36).slice(2, 8)}"
26
23
 
27
- _wireAria: ->
28
- panel = @_panel
29
- return unless panel
30
- heading = panel.querySelector('h1,h2,h3,h4,h5,h6')
31
- if heading
32
- heading.id ?= "#{_id}-title"
33
- panel.setAttribute 'aria-labelledby', heading.id
34
- desc = panel.querySelector('p')
35
- if desc
36
- desc.id ?= "#{_id}-desc"
37
- panel.setAttribute 'aria-describedby', desc.id
38
-
39
24
  ~>
40
25
  if @open
41
26
  _prevFocus = document.activeElement
42
- _scrollY = window.scrollY
43
- dialogStack.push this
44
- document.body.style.position = 'fixed'
45
- document.body.style.top = "-#{_scrollY}px"
46
- document.body.style.width = '100%'
47
-
48
- setTimeout =>
27
+ ARIA.lockScroll(this)
28
+ requestAnimationFrame =>
49
29
  panel = @_panel
50
30
  if panel
51
- @_wireAria()
31
+ ARIA.wireAria panel, _id
52
32
  if @initialFocus
53
33
  target = if typeof @initialFocus is 'string' then panel.querySelector(@initialFocus) else @initialFocus
54
34
  target?.focus()
55
35
  else
56
- focusable = panel.querySelectorAll 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'
57
- focusable[0]?.focus()
58
- _cleanupTrap = (e) ->
59
- return unless e.key is 'Tab'
60
- list = Array.from(panel.querySelectorAll('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')).filter (f) -> f.offsetParent isnt null
61
- return unless list.length
62
- first = list[0]
63
- last = list[list.length - 1]
64
- if e.shiftKey
65
- if document.activeElement is first then (e.preventDefault(); last.focus())
66
- else
67
- if document.activeElement is last then (e.preventDefault(); first.focus())
68
- panel.addEventListener 'keydown', _cleanupTrap
69
- , 0
70
-
36
+ panel.querySelectorAll('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')?[0]?.focus()
37
+ _cleanupTrap = ARIA.trapFocus(panel)
71
38
  return ->
72
- idx = dialogStack.indexOf this
73
- dialogStack.splice(idx, 1) if idx >= 0
74
- document.body.style.position = '' unless dialogStack.length
75
- document.body.style.top = '' unless dialogStack.length
76
- document.body.style.width = '' unless dialogStack.length
77
- window.scrollTo 0, _scrollY unless dialogStack.length
39
+ _cleanupTrap?()
40
+ ARIA.unlockScroll(this)
78
41
  _prevFocus?.focus()
79
- else
80
- idx = dialogStack.indexOf this
81
- dialogStack.splice(idx, 1) if idx >= 0
82
- unless dialogStack.length
83
- document.body.style.position = ''
84
- document.body.style.top = ''
85
- document.body.style.width = ''
86
- window.scrollTo 0, _scrollY
87
- _prevFocus?.focus()
88
42
 
89
43
  close: ->
90
44
  @open = false
@@ -18,44 +18,20 @@ export Drawer = component
18
18
  _scrollY = 0
19
19
  _id =! "drw-#{Math.random().toString(36).slice(2, 8)}"
20
20
 
21
- _wireAria: ->
22
- panel = @_panel
23
- return unless panel
24
- heading = panel.querySelector('h1,h2,h3,h4,h5,h6')
25
- if heading
26
- heading.id ?= "#{_id}-title"
27
- panel.setAttribute 'aria-labelledby', heading.id
28
- desc = panel.querySelector('p')
29
- if desc
30
- desc.id ?= "#{_id}-desc"
31
- panel.setAttribute 'aria-describedby', desc.id
32
-
33
21
  ~>
34
22
  if @open
35
23
  _prevFocus = document.activeElement
36
24
  document.body.style.overflow = 'hidden'
37
-
38
- setTimeout =>
25
+ requestAnimationFrame =>
39
26
  panel = @_panel
40
27
  if panel
41
- @_wireAria()
42
- focusable = panel.querySelectorAll 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'
43
- focusable[0]?.focus()
44
- @_trapHandler = (e) ->
45
- return unless e.key is 'Tab'
46
- list = Array.from(panel.querySelectorAll('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')).filter (f) -> f.offsetParent isnt null
47
- return unless list.length
48
- first = list[0]
49
- last = list[list.length - 1]
50
- if e.shiftKey
51
- if document.activeElement is first then (e.preventDefault(); last.focus())
52
- else
53
- if document.activeElement is last then (e.preventDefault(); first.focus())
54
- panel.addEventListener 'keydown', @_trapHandler
55
- , 0
56
- else
57
- document.body.style.overflow = ''
58
- _prevFocus?.focus()
28
+ ARIA.wireAria panel, _id
29
+ panel.querySelectorAll('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')?[0]?.focus()
30
+ @_cleanupTrap = ARIA.trapFocus(panel)
31
+ return ->
32
+ @_cleanupTrap?()
33
+ document.body.style.overflow = ''
34
+ _prevFocus?.focus()
59
35
 
60
36
  close: ->
61
37
  @open = false
@@ -73,19 +73,7 @@ export Menu = component
73
73
  highlightedIndex = idx
74
74
  @_list?.querySelectorAll('[role="menuitem"]')[idx]?.focus()
75
75
 
76
- _position: ->
77
- return unless @_trigger and @_list
78
- tr = @_trigger.getBoundingClientRect()
79
- @_list.style.position = 'fixed'
80
- @_list.style.left = "#{tr.left}px"
81
- @_list.style.top = "#{tr.bottom + 4}px"
82
- @_list.style.minWidth = "#{tr.width}px"
83
- fl = @_list.getBoundingClientRect()
84
- if fl.bottom > window.innerHeight
85
- @_list.style.top = "#{tr.top - fl.height - 4}px"
86
- if fl.right > window.innerWidth
87
- @_list.style.left = "#{window.innerWidth - fl.width - 4}px"
88
- @_list.style.visibility = 'visible'
76
+ _position: -> ARIA.positionBelow @_trigger, @_list
89
77
 
90
78
  onTriggerKeydown: (e) ->
91
79
  return if @disabled
@@ -41,14 +41,8 @@ export Menubar = component
41
41
  highlightedIndex = -1
42
42
 
43
43
  _position: (menuId) ->
44
- trigger = @_root?.querySelector("[data-menu-trigger=\"#{menuId}\"]")
45
- list = @_root?.querySelector("[data-menu-list=\"#{menuId}\"]")
46
- return unless trigger and list
47
- tr = trigger.getBoundingClientRect()
48
- list.style.position = 'fixed'
49
- list.style.left = "#{tr.left}px"
50
- list.style.top = "#{tr.bottom + 2}px"
51
- list.style.minWidth = "#{tr.width}px"
44
+ ARIA.positionBelow @_root?.querySelector("[data-menu-trigger=\"#{menuId}\"]"),
45
+ @_root?.querySelector("[data-menu-list=\"#{menuId}\"]"), 2, false
52
46
 
53
47
  selectItem: (menuId, itemId) ->
54
48
  @emit 'select', { menu: menuId, item: itemId }
@@ -62,25 +62,20 @@ export MultiSelect = component
62
62
  open = true
63
63
  highlightedIndex = if filtered.length > 0 then 0 else -1
64
64
 
65
+ _close: ->
66
+ open = false
67
+ query = ''
68
+ highlightedIndex = -1
69
+
65
70
  onFocusin: -> open = true
66
71
 
67
72
  onFocusout: ->
68
73
  setTimeout =>
69
74
  return if @_content?.contains(document.activeElement)
70
- open = false
71
- query = ''
72
- highlightedIndex = -1
75
+ @_close()
73
76
  , 0
74
77
 
75
- _position: ->
76
- return unless @_input and @_list
77
- tr = @_input.getBoundingClientRect()
78
- @_list.style.left = "#{tr.left}px"
79
- @_list.style.top = "#{tr.bottom + 2}px"
80
- @_list.style.minWidth = "#{tr.width}px"
81
- fl = @_list.getBoundingClientRect()
82
- if fl.bottom > window.innerHeight
83
- @_list.style.top = "#{tr.top - fl.height - 2}px"
78
+ _position: -> ARIA.positionBelow @_input, @_list, 2, false
84
79
 
85
80
  _onKeydown: (e) ->
86
81
  len = filtered.length
@@ -89,7 +84,6 @@ export MultiSelect = component
89
84
  e.preventDefault()
90
85
  open = true
91
86
  highlightedIndex = (highlightedIndex + 1) %% len if len
92
- setTimeout (=> @_position()), 0
93
87
  when 'ArrowUp'
94
88
  e.preventDefault()
95
89
  highlightedIndex = (highlightedIndex - 1) %% len if len
@@ -110,15 +104,8 @@ export MultiSelect = component
110
104
  query = ''
111
105
 
112
106
  ~>
113
- if open
114
- setTimeout (=> @_position()), 0
115
- onDown = (e) =>
116
- unless @_content?.contains(e.target)
117
- open = false
118
- query = ''
119
- highlightedIndex = -1
120
- document.addEventListener 'mousedown', onDown
121
- return -> document.removeEventListener 'mousedown', onDown
107
+ if open then requestAnimationFrame => @_position()
108
+ ~> ARIA.popupDismiss open, (=> @_list), (=> @_close()), [=> @_content], (=> @_position())
122
109
 
123
110
  render
124
111
  . ref: "_content", $open: open?!, $disabled: @disabled?!
@@ -53,13 +53,8 @@ export NavigationMenu = component
53
53
  clearTimeout _closeTimer if _closeTimer
54
54
 
55
55
  _position: (id) ->
56
- trigger = @_root?.querySelector("[data-nav-trigger=\"#{id}\"]")
57
- panel = @_root?.querySelector("[data-nav-panel=\"#{id}\"]")
58
- return unless trigger and panel
59
- tr = trigger.getBoundingClientRect()
60
- panel.style.position = 'fixed'
61
- panel.style.left = "#{tr.left}px"
62
- panel.style.top = "#{tr.bottom + 2}px"
56
+ ARIA.positionBelow @_root?.querySelector("[data-nav-trigger=\"#{id}\"]"),
57
+ @_root?.querySelector("[data-nav-panel=\"#{id}\"]"), 2, false
63
58
 
64
59
  _onKeydown: (e) ->
65
60
  navBtns = @_root?.querySelectorAll('[data-nav-trigger], [data-nav-link]')
@@ -86,11 +81,13 @@ export NavigationMenu = component
86
81
  ~>
87
82
  return unless _ready
88
83
  if activePanel
89
- onDown = (e) =>
90
- unless @_root?.contains(e.target)
91
- @_closePanel()
84
+ onDown = (e) => @_closePanel() unless @_root?.contains(e.target)
85
+ onScroll = => @_position(activePanel)
92
86
  document.addEventListener 'mousedown', onDown
93
- return -> document.removeEventListener 'mousedown', onDown
87
+ window.addEventListener 'scroll', onScroll, true
88
+ return ->
89
+ document.removeEventListener 'mousedown', onDown
90
+ window.removeEventListener 'scroll', onScroll, true
94
91
 
95
92
  render
96
93
  nav ref: "_root", role: "navigation", aria-orientation: @orientation
@@ -48,7 +48,7 @@ export Popover = component
48
48
 
49
49
  openPopover: ->
50
50
  open = true
51
- setTimeout => @_position(), 0
51
+ requestAnimationFrame => @_position()
52
52
 
53
53
  close: ->
54
54
  open = false
@@ -116,14 +116,7 @@ export Popover = component
116
116
  floating.style.visibility = 'hidden'
117
117
  floating.setAttribute 'data-open', ''
118
118
  floating.setAttribute 'data-placement', @placement
119
- heading = floating.querySelector('h1,h2,h3,h4,h5,h6')
120
- if heading
121
- heading.id ?= "#{_id}-title"
122
- floating.setAttribute 'aria-labelledby', heading.id
123
- desc = floating.querySelector('p')
124
- if desc
125
- desc.id ?= "#{_id}-desc"
126
- floating.setAttribute 'aria-describedby', desc.id
119
+ ARIA.wireAria floating, _id
127
120
  else
128
121
  floating.hidden = true
129
122
  floating.removeAttribute 'data-open'
@@ -105,17 +105,7 @@ export Select = component
105
105
  el?.focus()
106
106
  el?.scrollIntoView { block: 'nearest' }
107
107
 
108
- _position: ->
109
- return unless @_trigger and @_list
110
- tr = @_trigger.getBoundingClientRect()
111
- @_list.style.position = 'fixed'
112
- @_list.style.left = "#{tr.left}px"
113
- @_list.style.top = "#{tr.bottom + 4}px"
114
- @_list.style.minWidth = "#{tr.width}px"
115
- fl = @_list.getBoundingClientRect()
116
- if fl.bottom > window.innerHeight
117
- @_list.style.top = "#{tr.top - fl.height - 4}px"
118
- @_list.style.visibility = 'visible'
108
+ _position: -> ARIA.positionBelow @_trigger, @_list
119
109
 
120
110
  ~> ARIA.popupDismiss open, (=> @_list), (=> @close()), [=> @_trigger], (=> @_position())
121
111
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/ui",
3
- "version": "0.3.48",
3
+ "version": "0.3.49",
4
4
  "author": "Steve Shreeve <steve.shreeve@gmail.com>",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,6 +35,6 @@
35
35
  "license": "MIT",
36
36
  "type": "module",
37
37
  "dependencies": {
38
- "rip-lang": ">=3.13.101"
38
+ "rip-lang": ">=3.13.102"
39
39
  }
40
40
  }