@rip-lang/ui 0.3.67 → 0.4.2

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 (83) hide show
  1. package/AGENTS.md +93 -0
  2. package/README.md +22 -625
  3. package/browser/AGENTS.md +213 -0
  4. package/browser/CONTRIBUTING.md +375 -0
  5. package/browser/README.md +11 -0
  6. package/browser/TESTING.md +59 -0
  7. package/browser/browser.rip +56 -0
  8. package/{components → browser/components}/accordion.rip +1 -1
  9. package/{components → browser/components}/alert-dialog.rip +6 -3
  10. package/{components → browser/components}/autocomplete.rip +27 -21
  11. package/{components → browser/components}/avatar.rip +3 -3
  12. package/{components → browser/components}/badge.rip +1 -1
  13. package/{components → browser/components}/breadcrumb.rip +2 -2
  14. package/{components → browser/components}/button-group.rip +3 -3
  15. package/{components → browser/components}/button.rip +2 -2
  16. package/{components → browser/components}/card.rip +1 -1
  17. package/{components → browser/components}/carousel.rip +5 -5
  18. package/{components → browser/components}/checkbox-group.rip +40 -11
  19. package/{components → browser/components}/checkbox.rip +4 -4
  20. package/{components → browser/components}/collapsible.rip +2 -2
  21. package/{components → browser/components}/combobox.rip +36 -23
  22. package/{components → browser/components}/context-menu.rip +1 -1
  23. package/{components → browser/components}/date-picker.rip +5 -5
  24. package/{components → browser/components}/dialog.rip +8 -4
  25. package/{components → browser/components}/drawer.rip +8 -4
  26. package/{components → browser/components}/editable-value.rip +7 -1
  27. package/{components → browser/components}/field.rip +5 -5
  28. package/{components → browser/components}/fieldset.rip +2 -2
  29. package/{components → browser/components}/form.rip +1 -1
  30. package/{components → browser/components}/grid.rip +8 -8
  31. package/{components → browser/components}/input-group.rip +1 -1
  32. package/{components → browser/components}/input.rip +6 -6
  33. package/{components → browser/components}/label.rip +2 -2
  34. package/{components → browser/components}/menu.rip +17 -10
  35. package/{components → browser/components}/menubar.rip +1 -1
  36. package/{components → browser/components}/meter.rip +7 -7
  37. package/{components → browser/components}/multi-select.rip +76 -33
  38. package/{components → browser/components}/native-select.rip +3 -3
  39. package/{components → browser/components}/nav-menu.rip +3 -3
  40. package/{components → browser/components}/number-field.rip +11 -11
  41. package/{components → browser/components}/otp-field.rip +4 -4
  42. package/{components → browser/components}/pagination.rip +4 -4
  43. package/{components → browser/components}/popover.rip +11 -24
  44. package/{components → browser/components}/preview-card.rip +7 -11
  45. package/{components → browser/components}/progress.rip +3 -3
  46. package/{components → browser/components}/radio-group.rip +4 -4
  47. package/{components → browser/components}/resizable.rip +3 -3
  48. package/{components → browser/components}/scroll-area.rip +1 -1
  49. package/{components → browser/components}/select.rip +55 -27
  50. package/{components → browser/components}/separator.rip +2 -2
  51. package/{components → browser/components}/skeleton.rip +4 -4
  52. package/{components → browser/components}/slider.rip +15 -10
  53. package/{components → browser/components}/spinner.rip +2 -2
  54. package/{components → browser/components}/table.rip +2 -2
  55. package/{components → browser/components}/tabs.rip +12 -7
  56. package/{components → browser/components}/textarea.rip +8 -8
  57. package/{components → browser/components}/toast.rip +3 -3
  58. package/{components → browser/components}/toggle-group.rip +42 -11
  59. package/{components → browser/components}/toggle.rip +2 -2
  60. package/{components → browser/components}/toolbar.rip +2 -2
  61. package/{components → browser/components}/tooltip.rip +19 -23
  62. package/browser/hljs-rip.js +209 -0
  63. package/browser/playwright.config.mjs +31 -0
  64. package/browser/tests/overlays.js +352 -0
  65. package/email/AGENTS.md +16 -0
  66. package/email/README.md +55 -0
  67. package/email/benchmarks/benchmark.rip +94 -0
  68. package/email/benchmarks/samples.rip +104 -0
  69. package/email/compat.rip +129 -0
  70. package/email/components.rip +371 -0
  71. package/email/dom.rip +330 -0
  72. package/email/email.rip +10 -0
  73. package/email/render.rip +82 -0
  74. package/package.json +29 -39
  75. package/shared/README.md +3 -0
  76. package/shared/styles.rip +17 -0
  77. package/tailwind/AGENTS.md +3 -0
  78. package/tailwind/README.md +27 -0
  79. package/tailwind/engine.js +107 -0
  80. package/tailwind/inline.js +215 -0
  81. package/tailwind/serve.js +6 -0
  82. package/tailwind/tailwind.rip +13 -0
  83. package/ui.rip +3 -0
@@ -21,14 +21,14 @@
21
21
 
22
22
  export Grid = component
23
23
 
24
- @data := []
25
- @columns := []
26
- @rowHeight := 32
27
- @headerHeight := 36
28
- @overscan := 5
29
- @striped := false
30
- @beforeEdit := null
31
- @afterEdit := null
24
+ @data:: any[] := []
25
+ @columns:: any[] := []
26
+ @rowHeight:: number := 32
27
+ @headerHeight:: number := 36
28
+ @overscan:: number := 5
29
+ @striped:: boolean := false
30
+ @beforeEdit:: any := null
31
+ @afterEdit:: any := null
32
32
 
33
33
  formatRegistry = []
34
34
 
@@ -13,7 +13,7 @@
13
13
  # button $suffix: true, @click: doSearch, "Go"
14
14
 
15
15
  export InputGroup = component
16
- @disabled := false
16
+ @disabled:: boolean := false
17
17
 
18
18
  focused := false
19
19
 
@@ -7,12 +7,12 @@
7
7
  # Input value <=> name, placeholder: "Enter name"
8
8
  # Input value <=> email, type: "email", required: true
9
9
 
10
- export Input = component
11
- @value := ''
12
- @placeholder := ''
13
- @type := 'text'
14
- @disabled := false
15
- @required := false
10
+ export Input = component extends input
11
+ @value:: string := ""
12
+ @placeholder:: string := ""
13
+ @type:: string := "text"
14
+ @disabled:: boolean := false
15
+ @required:: boolean := false
16
16
 
17
17
  focused := false
18
18
  touched := false
@@ -8,8 +8,8 @@
8
8
  # Label required: true, "Username"
9
9
 
10
10
  export Label = component
11
- @for := null
12
- @required := false
11
+ @for:: any := null
12
+ @required:: boolean := false
13
13
 
14
14
  render
15
15
  label for: @for?!, $required: @required?!
@@ -12,12 +12,13 @@
12
12
  # div $item: "archive", "Archive"
13
13
 
14
14
  export Menu = component
15
- @disabled := false
15
+ @disabled:: boolean := false
16
16
 
17
17
  open := false
18
18
  highlightedIndex := -1
19
19
  typeaheadBuffer := ''
20
20
  typeaheadTimer := null
21
+ _ready := false
21
22
  _id =! "menu-#{Math.random().toString(36).slice(2, 8)}"
22
23
 
23
24
  items ~=
@@ -73,14 +74,10 @@ export Menu = component
73
74
  @_list?.querySelectorAll('[role="menuitem"]')[idx]?.focus()
74
75
 
75
76
  _applyPlacement: ->
76
- return unless @_list
77
- @_list.style.position = 'fixed'
78
- @_list.style.inset = 'auto'
79
- @_list.style.margin = '0'
80
- @_list.style.positionArea = 'bottom start'
81
- @_list.style.positionTry = 'flip-block, flip-inline, flip-block flip-inline'
82
- @_list.style.positionVisibility = 'anchors-visible'
83
- @_list.style.marginTop = '4px'
77
+ ARIA.position @_trigger, @_list, placement: 'bottom start', offset: 4
78
+
79
+ mounted: ->
80
+ _ready = true
84
81
 
85
82
  onTriggerKeydown: (e) ->
86
83
  return if @disabled
@@ -106,6 +103,7 @@ export Menu = component
106
103
  char: => @_typeahead(e.key)
107
104
 
108
105
  ~>
106
+ return unless _ready
109
107
  if @_list
110
108
  @_list.id = _id
111
109
  @_list.setAttribute 'popover', 'auto'
@@ -119,6 +117,7 @@ export Menu = component
119
117
  button ref: "_trigger"
120
118
  aria-haspopup: "menu"
121
119
  aria-expanded: !!open
120
+ aria-controls: _id
122
121
  $open: open?!
123
122
  $disabled: @disabled?!
124
123
  disabled: @disabled
@@ -129,7 +128,15 @@ export Menu = component
129
128
  . ref: "_slot", style: "display:none"
130
129
  slot
131
130
 
132
- div ref: "_list", role: "menu", $open: open?!, style: "position:fixed;margin:0;inset:auto", @keydown: @onMenuKeydown
131
+ div ref: "_list"
132
+ id: _id
133
+ role: "menu"
134
+ popover: "auto"
135
+ hidden: not open
136
+ aria-hidden: (open ? undefined : "true")
137
+ $open: open?!
138
+ style: "position:fixed;margin:0;inset:auto"
139
+ @keydown: @onMenuKeydown
133
140
  for item, idx in items
134
141
  div role: item.getAttribute('role') or 'menuitem'
135
142
  tabindex: "-1"
@@ -15,7 +15,7 @@
15
15
  # div $item: "redo", "Redo"
16
16
 
17
17
  export Menubar = component
18
- @disabled := false
18
+ @disabled:: boolean := false
19
19
 
20
20
  activeMenu := null
21
21
  highlightedIndex := -1
@@ -9,13 +9,13 @@
9
9
  # Meter value: 75, min: 0, max: 100
10
10
 
11
11
  export Meter = component
12
- @value := 0
13
- @min := 0
14
- @max := 1
15
- @low := null
16
- @high := null
17
- @optimum := null
18
- @label := null
12
+ @value:: number := 0
13
+ @min:: number := 0
14
+ @max:: number := 1
15
+ @low:: any := null
16
+ @high:: any := null
17
+ @optimum:: any := null
18
+ @label:: any := null
19
19
 
20
20
  percent ~= Math.min(100, Math.max(0, ((@value - @min) / (@max - @min)) * 100))
21
21
 
@@ -8,14 +8,17 @@
8
8
  # MultiSelect value <=> selectedColors, items: colors, placeholder: "Choose colors..."
9
9
 
10
10
  export MultiSelect = component
11
- @value := []
12
- @items := []
13
- @placeholder := 'Select...'
14
- @disabled := false
11
+ @value:: any[] := []
12
+ @items:: any[] := []
13
+ @placeholder:: string := "Select..."
14
+ @disabled:: boolean := false
15
15
 
16
16
  open := false
17
17
  query := ''
18
18
  highlightedIndex := -1
19
+ _ready := false
20
+ _ignoreInputClickOnce := false
21
+ _popupGuard =! ARIA.popupGuard()
19
22
  _listId =! "ms-#{Math.random().toString(36).slice(2, 8)}"
20
23
 
21
24
  filtered ~=
@@ -25,6 +28,14 @@ export MultiSelect = component
25
28
  label = if typeof item is 'string' then item else (item.label or item.name or String(item))
26
29
  label.toLowerCase().includes(q)
27
30
 
31
+ # Block reopen briefly so the same pointer gesture that closed the popup
32
+ # cannot immediately reopen it via focus/click side effects.
33
+ _blockOpenBriefly: ->
34
+ _popupGuard.block()
35
+
36
+ _canOpen: ->
37
+ _popupGuard.canOpen()
38
+
28
39
  _label: (item) ->
29
40
  if typeof item is 'string' then item else (item.label or item.name or String(item))
30
41
 
@@ -45,47 +56,73 @@ export MultiSelect = component
45
56
  arr.push v
46
57
  @value = arr
47
58
  @emit 'change', @value
59
+ @_close(true, true)
48
60
 
49
61
  _removeChip: (v) ->
50
62
  return if @disabled
51
63
  @value = @value.filter (x) -> x isnt v
52
64
  @emit 'change', @value
53
65
 
54
- clearAll: ->
55
- return if @disabled
56
- @value = []
57
- query = ''
58
- @emit 'change', @value
66
+ _onRemoveMousedown: (e) ->
67
+ e.preventDefault()
68
+ e.stopPropagation()
69
+ @_blockOpenBriefly()
59
70
 
60
71
  _onInput: (e) ->
61
72
  query = e.target.value
62
73
  open = true
63
74
  highlightedIndex = if filtered.length > 0 then 0 else -1
64
75
 
65
- _close: ->
76
+ _openMenu: ->
77
+ return if @disabled
78
+ return unless @_canOpen()
79
+ open = true
80
+ highlightedIndex = if filtered.length > 0 then Math.max(highlightedIndex, 0) else -1
81
+
82
+ _close: (restoreFocus = false, blockOpen = false) ->
66
83
  open = false
67
84
  query = ''
68
85
  highlightedIndex = -1
86
+ @_blockOpenBriefly() if blockOpen
87
+ @_input?.focus() if restoreFocus
88
+
89
+ onFocusin: ->
90
+ return unless @_canOpen()
91
+ @_openMenu()
92
+
93
+ _onInputMousedown: (e) ->
94
+ return unless open and not query
95
+ e.preventDefault()
96
+ _ignoreInputClickOnce = true
97
+ @_close(false, true)
98
+
99
+ _onInputClick: ->
100
+ if _ignoreInputClickOnce
101
+ _ignoreInputClickOnce = false
102
+ return
103
+ return unless @_canOpen()
104
+ @_openMenu()
105
+
106
+ _onChipsClick: (e) ->
107
+ return unless e.target is e.currentTarget
108
+ if open and not query
109
+ @_close(false, true)
110
+ return
111
+ return unless @_canOpen()
69
112
  @_input?.focus()
70
-
71
- onFocusin: -> open = true
113
+ @_openMenu() if document.activeElement is @_input
72
114
 
73
115
  onFocusout: ->
74
116
  setTimeout =>
75
117
  return if @_content?.contains(document.activeElement)
76
- @_close()
118
+ @_close(false, true)
77
119
  , 0
78
120
 
121
+ mounted: ->
122
+ _ready = true
123
+
79
124
  _applyPlacement: ->
80
- return unless @_list
81
- @_list.style.position = 'fixed'
82
- @_list.style.inset = 'auto'
83
- @_list.style.margin = '0'
84
- @_list.style.positionArea = 'bottom start'
85
- @_list.style.positionTry = 'flip-block, flip-inline, flip-block flip-inline'
86
- @_list.style.positionVisibility = 'anchors-visible'
87
- @_list.style.marginTop = '2px'
88
- @_list.style.minWidth = 'anchor-size(width)'
125
+ ARIA.position @_content, @_list, placement: 'bottom start', offset: 2, matchWidth: true
89
126
 
90
127
  _onKeydown: (e) ->
91
128
  len = filtered.length
@@ -103,31 +140,31 @@ export MultiSelect = component
103
140
  @_toggleItem(filtered[highlightedIndex])
104
141
  when 'Escape'
105
142
  e.preventDefault()
106
- open = false
107
- query = ''
143
+ @_close()
108
144
  when 'Backspace'
109
145
  if not query and @value.length > 0
110
146
  @value = @value.slice(0, -1)
111
147
  @emit 'change', @value
112
148
  when 'Tab'
113
- open = false
114
- query = ''
149
+ @_close(false, true)
115
150
 
116
151
  ~>
152
+ return unless _ready
117
153
  if @_list
118
- @_list.setAttribute 'popover', 'auto'
154
+ @_list.setAttribute 'popover', 'manual'
119
155
  @_applyPlacement()
120
156
  ARIA.bindPopover open, (=> @_list), ((isOpen) => open = isOpen), (=> @_input)
157
+ ARIA.popupDismiss open, (=> @_list), (=> @_close(false, true)), [=> @_input, => @_content]
121
158
 
122
159
  render
123
160
  . ref: "_content", $open: open?!, $disabled: @disabled?!
124
161
 
125
162
  # Chip area + input
126
- . $chips: true, @click: (=> @_input?.focus())
163
+ . $chips: true, @click: @_onChipsClick
127
164
  for chip in @value
128
165
  span $chip: true
129
166
  "#{chip}"
130
- button $remove: true, aria-label: "Remove #{chip}", @click: (=> @_removeChip(chip))
167
+ button $remove: true, aria-label: "Remove #{chip}", @mousedown: @_onRemoveMousedown, @click: (=> @_removeChip(chip))
131
168
  "✕"
132
169
  input ref: "_input", type: "text", autocomplete: "off"
133
170
  role: "combobox"
@@ -138,14 +175,20 @@ export MultiSelect = component
138
175
  disabled: @disabled
139
176
  placeholder: if @value.length is 0 then @placeholder else ''
140
177
  value: query
178
+ @mousedown: @_onInputMousedown
141
179
  @input: @_onInput
180
+ @click: @_onInputClick
142
181
  @keydown: @_onKeydown
143
- if @value.length > 0
144
- button $clear: true, aria-label: "Clear all", @click: @clearAll
145
- "✕"
146
182
 
147
183
  # Dropdown
148
- div ref: "_list", id: _listId, role: "listbox", $open: open?!, aria-multiselectable: "true"
184
+ div ref: "_list"
185
+ id: _listId
186
+ role: "listbox"
187
+ popover: "manual"
188
+ hidden: not open
189
+ aria-hidden: (open ? undefined : "true")
190
+ $open: open?!
191
+ aria-multiselectable: "true"
149
192
  style: "position:fixed;margin:0;inset:auto"
150
193
  for item, idx in filtered
151
194
  div role: "option", tabindex: "-1", id: "#{_listId}-#{idx}"
@@ -10,9 +10,9 @@
10
10
  # option value: "user", "User"
11
11
 
12
12
  export NativeSelect = component
13
- @value := ''
14
- @disabled := false
15
- @required := false
13
+ @value:: string := ""
14
+ @disabled:: boolean := false
15
+ @required:: boolean := false
16
16
 
17
17
  focused := false
18
18
 
@@ -13,9 +13,9 @@
13
13
  # a $link: true, href: "/about", "About"
14
14
 
15
15
  export NavigationMenu = component
16
- @orientation := 'horizontal'
17
- @hoverDelay := 200
18
- @hoverCloseDelay := 300
16
+ @orientation:: "horizontal" | "vertical" := "horizontal"
17
+ @hoverDelay:: number := 200
18
+ @hoverCloseDelay:: number := 300
19
19
 
20
20
  activePanel := null
21
21
  _ready := false
@@ -12,15 +12,15 @@ START_DELAY = 400
12
12
  TICK_DELAY = 60
13
13
 
14
14
  export NumberField = component
15
- @value := 0
16
- @min := null
17
- @max := null
18
- @step := 1
19
- @smallStep := 0.1
20
- @largeStep := 10
21
- @disabled := false
22
- @readOnly := false
23
- @name := null
15
+ @value:: number := 0
16
+ @min:: any := null
17
+ @max:: any := null
18
+ @step:: number := 1
19
+ @smallStep:: number := 0.1
20
+ @largeStep:: number := 10
21
+ @disabled:: boolean := false
22
+ @readOnly:: boolean := false
23
+ @name:: any := null
24
24
 
25
25
  _timer = null
26
26
  _interval = null
@@ -87,7 +87,7 @@ export NumberField = component
87
87
  document.removeEventListener 'pointerup', onUp
88
88
  document.addEventListener 'pointerup', onUp
89
89
 
90
- onKeydown: (e) ->
90
+ _onKeydown: (e) ->
91
91
  return if @disabled or @readOnly
92
92
  amount = @_stepAmount(e)
93
93
  switch e.key
@@ -152,7 +152,7 @@ export NumberField = component
152
152
  aria-readonly: @readOnly?!
153
153
  disabled: @disabled
154
154
  readonly: @readOnly
155
- @keydown: @onKeydown
155
+ @keydown: @_onKeydown
156
156
  @blur: @_onBlur
157
157
 
158
158
  button aria-label: "Increase", tabindex: "-1"
@@ -7,10 +7,10 @@
7
7
  # OTPField length: 6, value <=> code, @complete: handleVerify
8
8
 
9
9
  export OTPField = component
10
- @length := 6
11
- @value := ''
12
- @disabled := false
13
- @mask := false
10
+ @length:: number := 6
11
+ @value:: string := ""
12
+ @disabled:: boolean := false
13
+ @mask:: boolean := false
14
14
 
15
15
  _id =! "otp-#{Math.random().toString(36).slice(2, 8)}"
16
16
 
@@ -8,10 +8,10 @@
8
8
  # Pagination page <=> currentPage, total: 500, perPage: 20, siblingCount: 2
9
9
 
10
10
  export Pagination = component
11
- @page := 1
12
- @total := 0
13
- @perPage := 10
14
- @siblingCount := 1
11
+ @page:: number := 1
12
+ @total:: number := 0
13
+ @perPage:: number := 10
14
+ @siblingCount:: number := 1
15
15
 
16
16
  totalPages ~= Math.max(1, Math.ceil(@total / @perPage))
17
17
  _ready := false
@@ -10,12 +10,12 @@
10
10
  # p "Popover content"
11
11
 
12
12
  export Popover = component
13
- @placement := 'bottom-start'
14
- @offset := 4
15
- @disabled := false
16
- @openOnHover := false
17
- @hoverDelay := 300
18
- @hoverCloseDelay := 200
13
+ @placement:: "top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "right" := "bottom-start"
14
+ @offset:: number := 4
15
+ @disabled:: boolean := false
16
+ @openOnHover:: boolean := false
17
+ @hoverDelay:: number := 300
18
+ @hoverCloseDelay:: number := 200
19
19
 
20
20
  open := false
21
21
  _ready := false
@@ -24,26 +24,11 @@ export Popover = component
24
24
  _id =! "pop-#{Math.random().toString(36).slice(2, 8)}"
25
25
 
26
26
  _applyPlacement: ->
27
+ trigger = @_content?.querySelector('[data-trigger]')
27
28
  floating = @_content?.querySelector('[data-content]')
28
- return unless floating
29
29
  [side, align] = @placement.split('-')
30
- align ?= 'center'
31
- area = "#{side} #{align}"
32
- floating.style.position = 'fixed'
33
- floating.style.inset = 'auto'
34
- floating.style.margin = '0'
35
- floating.style.positionArea = area
36
- floating.style.positionTry = 'flip-block, flip-inline, flip-block flip-inline'
37
- floating.style.positionVisibility = 'anchors-visible'
38
- floating.style.marginTop = ''
39
- floating.style.marginRight = ''
40
- floating.style.marginBottom = ''
41
- floating.style.marginLeft = ''
42
- switch side
43
- when 'bottom' then floating.style.marginTop = "#{@offset}px"
44
- when 'top' then floating.style.marginBottom = "#{@offset}px"
45
- when 'left' then floating.style.marginRight = "#{@offset}px"
46
- when 'right' then floating.style.marginLeft = "#{@offset}px"
30
+ align ??= 'center'
31
+ ARIA.position trigger, floating, placement: "#{side} #{align}", offset: @offset
47
32
 
48
33
  mounted: ->
49
34
  _ready = true
@@ -89,6 +74,8 @@ export Popover = component
89
74
  if trigger
90
75
  trigger.setAttribute 'aria-expanded', !!open
91
76
  if floating
77
+ floating.hidden = not open
78
+ if open then floating.removeAttribute 'aria-hidden' else floating.setAttribute 'aria-hidden', 'true'
92
79
  floating.setAttribute 'data-placement', @placement
93
80
  if open then floating.setAttribute 'data-open', '' else floating.removeAttribute 'data-open'
94
81
  ARIA.wireAria floating, _id
@@ -11,8 +11,8 @@
11
11
  # p "User details here..."
12
12
 
13
13
  export PreviewCard = component
14
- @delay := 400
15
- @closeDelay := 200
14
+ @delay:: number := 400
15
+ @closeDelay:: number := 200
16
16
 
17
17
  open := false
18
18
  _ready := false
@@ -42,21 +42,15 @@ export PreviewCard = component
42
42
  @_applyPlacement()
43
43
  trigger.addEventListener 'focus', =>
44
44
  clearTimeout _closeTimer if _closeTimer
45
- _openTimer = setTimeout (=> open = true; @_position()), @delay
45
+ _openTimer = setTimeout (=> open = true; @_applyPlacement()), @delay
46
46
  trigger.addEventListener 'blur', =>
47
47
  clearTimeout _openTimer if _openTimer
48
48
  _closeTimer = setTimeout (=> open = false), @closeDelay
49
49
 
50
50
  _applyPlacement: ->
51
+ trigger = @_root?.querySelector('[data-trigger]')
51
52
  floating = @_root?.querySelector('[data-content]')
52
- return unless floating
53
- floating.style.position = 'fixed'
54
- floating.style.inset = 'auto'
55
- floating.style.margin = '0'
56
- floating.style.positionArea = 'bottom start'
57
- floating.style.positionTry = 'flip-block, flip-inline, flip-block flip-inline'
58
- floating.style.positionVisibility = 'anchors-visible'
59
- floating.style.marginTop = '4px'
53
+ ARIA.position trigger, floating, placement: 'bottom start', offset: 4
60
54
 
61
55
  ~>
62
56
  return unless _ready
@@ -64,6 +58,8 @@ export PreviewCard = component
64
58
  floating = @_root?.querySelector('[data-content]')
65
59
  return unless floating and trigger
66
60
  trigger.setAttribute 'aria-expanded', !!open
61
+ floating.hidden = not open
62
+ if open then floating.removeAttribute 'aria-hidden' else floating.setAttribute 'aria-hidden', 'true'
67
63
  @_applyPlacement()
68
64
  if open then floating.setAttribute('data-open', '') else floating.removeAttribute('data-open')
69
65
  ARIA.bindPopover open, (=> floating), ((isOpen) => open = isOpen), (=> trigger)
@@ -8,9 +8,9 @@
8
8
  # Progress value: 42, max: 100
9
9
 
10
10
  export Progress = component
11
- @value := 0
12
- @max := 1
13
- @label := null
11
+ @value:: number := 0
12
+ @max:: number := 1
13
+ @label:: any := null
14
14
 
15
15
  percent ~= Math.min(100, Math.max(0, (@value / @max) * 100))
16
16
 
@@ -10,10 +10,10 @@
10
10
  # div $value: "lg", "Large"
11
11
 
12
12
  export RadioGroup = component
13
- @value := null
14
- @disabled := false
15
- @orientation := 'vertical'
16
- @name := ''
13
+ @value:: any := null
14
+ @disabled:: boolean := false
15
+ @orientation:: "horizontal" | "vertical" := "vertical"
16
+ @name:: string := ""
17
17
 
18
18
  _options ~=
19
19
  return [] unless @_slot
@@ -19,9 +19,9 @@
19
19
  # div $panel: true, "Bottom"
20
20
 
21
21
  export Resizable = component
22
- @orientation := 'horizontal'
23
- @minSize := 10
24
- @maxSize := 90
22
+ @orientation:: "horizontal" | "vertical" := "horizontal"
23
+ @minSize:: number := 10
24
+ @maxSize:: number := 90
25
25
 
26
26
  _ready := false
27
27
  _dragging = null
@@ -11,7 +11,7 @@
11
11
  MIN_THUMB = 20
12
12
 
13
13
  export ScrollArea = component
14
- @orientation := 'vertical'
14
+ @orientation:: "horizontal" | "vertical" := "vertical"
15
15
 
16
16
  hovering := false
17
17
  scrolling := false