rip-lang 3.13.92 → 3.13.94
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.
- package/CHANGELOG.md +2 -2
- package/README.md +3 -3
- package/bin/rip +11 -1
- package/docs/AGENTS.md +43 -0
- package/docs/RIP-LANG.md +3 -3
- package/docs/RIP-TYPES.md +72 -91
- package/docs/charts.html +15 -15
- package/docs/dist/rip.js +142 -38
- package/docs/dist/rip.min.js +174 -174
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/index.html +2 -2
- package/package.json +1 -1
- package/src/AGENTS.md +456 -0
- package/src/lexer.js +1 -2
- package/src/typecheck.js +188 -6
- package/src/types.js +63 -38
- package/src/ui.rip +65 -0
- package/docs/ui/accordion.rip +0 -113
- package/docs/ui/alert-dialog.rip +0 -96
- package/docs/ui/autocomplete.rip +0 -141
- package/docs/ui/avatar.rip +0 -37
- package/docs/ui/badge.rip +0 -15
- package/docs/ui/breadcrumb.rip +0 -46
- package/docs/ui/button-group.rip +0 -26
- package/docs/ui/button.rip +0 -23
- package/docs/ui/card.rip +0 -25
- package/docs/ui/carousel.rip +0 -110
- package/docs/ui/checkbox-group.rip +0 -65
- package/docs/ui/checkbox.rip +0 -33
- package/docs/ui/collapsible.rip +0 -50
- package/docs/ui/combobox.rip +0 -155
- package/docs/ui/context-menu.rip +0 -105
- package/docs/ui/date-picker.rip +0 -214
- package/docs/ui/dialog.rip +0 -107
- package/docs/ui/drawer.rip +0 -79
- package/docs/ui/editable-value.rip +0 -80
- package/docs/ui/field.rip +0 -53
- package/docs/ui/fieldset.rip +0 -22
- package/docs/ui/form.rip +0 -39
- package/docs/ui/grid.rip +0 -901
- package/docs/ui/hljs-rip.js +0 -209
- package/docs/ui/index.css +0 -1772
- package/docs/ui/index.html +0 -2433
- package/docs/ui/input-group.rip +0 -28
- package/docs/ui/input.rip +0 -36
- package/docs/ui/label.rip +0 -16
- package/docs/ui/menu.rip +0 -162
- package/docs/ui/menubar.rip +0 -155
- package/docs/ui/meter.rip +0 -36
- package/docs/ui/multi-select.rip +0 -158
- package/docs/ui/native-select.rip +0 -32
- package/docs/ui/nav-menu.rip +0 -129
- package/docs/ui/number-field.rip +0 -162
- package/docs/ui/otp-field.rip +0 -89
- package/docs/ui/pagination.rip +0 -123
- package/docs/ui/popover.rip +0 -143
- package/docs/ui/preview-card.rip +0 -73
- package/docs/ui/progress.rip +0 -25
- package/docs/ui/radio-group.rip +0 -67
- package/docs/ui/resizable.rip +0 -123
- package/docs/ui/scroll-area.rip +0 -145
- package/docs/ui/select.rip +0 -184
- package/docs/ui/separator.rip +0 -17
- package/docs/ui/skeleton.rip +0 -22
- package/docs/ui/slider.rip +0 -165
- package/docs/ui/spinner.rip +0 -17
- package/docs/ui/table.rip +0 -27
- package/docs/ui/tabs.rip +0 -124
- package/docs/ui/textarea.rip +0 -48
- package/docs/ui/toast.rip +0 -87
- package/docs/ui/toggle-group.rip +0 -78
- package/docs/ui/toggle.rip +0 -24
- package/docs/ui/toolbar.rip +0 -46
- package/docs/ui/tooltip.rip +0 -115
package/docs/ui/autocomplete.rip
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# Autocomplete — accessible headless suggestion input
|
|
2
|
-
#
|
|
3
|
-
# Like Combobox but the input value IS the value (no selection from a list).
|
|
4
|
-
# Suggestions are shown as the user types; selecting a suggestion fills the input.
|
|
5
|
-
# Ships zero CSS.
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# Autocomplete value <=> city, items: cities, @filter: filterCities
|
|
9
|
-
|
|
10
|
-
acCollator = new Intl.Collator(undefined, { sensitivity: 'base' })
|
|
11
|
-
|
|
12
|
-
export Autocomplete = component
|
|
13
|
-
@value := ''
|
|
14
|
-
@items := []
|
|
15
|
-
@placeholder := 'Type to search...'
|
|
16
|
-
@disabled := false
|
|
17
|
-
|
|
18
|
-
open := false
|
|
19
|
-
|
|
20
|
-
filteredItems ~=
|
|
21
|
-
q = @value.trim()
|
|
22
|
-
return @items unless q
|
|
23
|
-
@items.filter (item) ->
|
|
24
|
-
label = if typeof item is 'string' then item else (item.label or item.name or String(item))
|
|
25
|
-
acCollator.compare(label.slice(0, q.length), q) is 0
|
|
26
|
-
|
|
27
|
-
_listId =! "ac-list-#{Math.random().toString(36).slice(2, 8)}"
|
|
28
|
-
|
|
29
|
-
_getItems: ->
|
|
30
|
-
return [] unless @_list
|
|
31
|
-
Array.from(@_list.querySelectorAll('[role="option"]'))
|
|
32
|
-
|
|
33
|
-
_updateHighlight: ->
|
|
34
|
-
idx = @_hlIdx
|
|
35
|
-
opts = @_getItems()
|
|
36
|
-
opts.forEach (el, ndx) ->
|
|
37
|
-
el.id = "#{@_listId}-opt-#{ndx}" unless el.id
|
|
38
|
-
el.toggleAttribute 'data-highlighted', ndx is idx
|
|
39
|
-
activeId = if idx >= 0 and opts[idx] then opts[idx].id else undefined
|
|
40
|
-
if @_input
|
|
41
|
-
if activeId then @_input.setAttribute 'aria-activedescendant', activeId
|
|
42
|
-
else @_input.removeAttribute 'aria-activedescendant'
|
|
43
|
-
opts[idx]?.scrollIntoView({ block: 'nearest' })
|
|
44
|
-
|
|
45
|
-
openMenu: ->
|
|
46
|
-
open = true
|
|
47
|
-
@_hlIdx = -1
|
|
48
|
-
setTimeout => @_position(), 0
|
|
49
|
-
|
|
50
|
-
close: ->
|
|
51
|
-
open = false
|
|
52
|
-
@_hlIdx = -1
|
|
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"
|
|
64
|
-
|
|
65
|
-
selectIndex: (idx) ->
|
|
66
|
-
item = filteredItems[idx]
|
|
67
|
-
return unless item
|
|
68
|
-
label = if typeof item is 'string' then item else (item.label or item.name or String(item))
|
|
69
|
-
@value = label
|
|
70
|
-
@_input?.value = label
|
|
71
|
-
@emit 'select', item
|
|
72
|
-
@close()
|
|
73
|
-
|
|
74
|
-
onInput: (e) ->
|
|
75
|
-
newVal = e.target.value
|
|
76
|
-
return if newVal is @value
|
|
77
|
-
@value = newVal
|
|
78
|
-
open = true
|
|
79
|
-
@_hlIdx = if filteredItems.length > 0 then 0 else -1
|
|
80
|
-
setTimeout =>
|
|
81
|
-
@_position()
|
|
82
|
-
@_updateHighlight()
|
|
83
|
-
, 0
|
|
84
|
-
|
|
85
|
-
onKeydown: (e) ->
|
|
86
|
-
len = filteredItems.length
|
|
87
|
-
switch e.key
|
|
88
|
-
when 'ArrowDown'
|
|
89
|
-
e.preventDefault()
|
|
90
|
-
@openMenu() unless open
|
|
91
|
-
if len
|
|
92
|
-
@_hlIdx = (@_hlIdx + 1) %% len
|
|
93
|
-
@_updateHighlight()
|
|
94
|
-
when 'ArrowUp'
|
|
95
|
-
e.preventDefault()
|
|
96
|
-
@openMenu() unless open
|
|
97
|
-
if len
|
|
98
|
-
@_hlIdx = if @_hlIdx <= 0 then len - 1 else @_hlIdx - 1
|
|
99
|
-
@_updateHighlight()
|
|
100
|
-
when 'Enter'
|
|
101
|
-
e.preventDefault()
|
|
102
|
-
@selectIndex(@_hlIdx) if @_hlIdx >= 0
|
|
103
|
-
when 'Escape'
|
|
104
|
-
e.preventDefault()
|
|
105
|
-
@close()
|
|
106
|
-
when 'Tab'
|
|
107
|
-
@close()
|
|
108
|
-
|
|
109
|
-
~>
|
|
110
|
-
if open
|
|
111
|
-
onDown = (e) =>
|
|
112
|
-
unless @_input?.contains(e.target) or @_list?.contains(e.target)
|
|
113
|
-
@close()
|
|
114
|
-
document.addEventListener 'mousedown', onDown
|
|
115
|
-
return -> document.removeEventListener 'mousedown', onDown
|
|
116
|
-
|
|
117
|
-
mounted: ->
|
|
118
|
-
@_hlIdx = -1
|
|
119
|
-
@_input.value = @value if @_input and @value
|
|
120
|
-
|
|
121
|
-
render
|
|
122
|
-
. $open: open?!
|
|
123
|
-
|
|
124
|
-
input ref: "_input", role: "combobox", type: "text"
|
|
125
|
-
autocomplete: "off"
|
|
126
|
-
aria-expanded: !!open
|
|
127
|
-
aria-haspopup: "listbox"
|
|
128
|
-
aria-autocomplete: "list"
|
|
129
|
-
aria-controls: open ? _listId : undefined
|
|
130
|
-
$disabled: @disabled?!
|
|
131
|
-
disabled: @disabled
|
|
132
|
-
placeholder: @placeholder
|
|
133
|
-
@input: @onInput
|
|
134
|
-
|
|
135
|
-
if open and filteredItems.length > 0
|
|
136
|
-
div ref: "_list", id: _listId, role: "listbox", $open: true, style: "position:fixed"
|
|
137
|
-
for item, idx in filteredItems
|
|
138
|
-
div role: "option", tabindex: "-1"
|
|
139
|
-
@click: (=> @selectIndex(idx))
|
|
140
|
-
@mouseenter: (=> @_hlIdx = idx; @_updateHighlight())
|
|
141
|
-
"#{if typeof item is 'string' then item else (item.label or item.name or String(item))}"
|
package/docs/ui/avatar.rip
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# Avatar — accessible headless avatar
|
|
2
|
-
#
|
|
3
|
-
# Shows an image, falls back to initials or a generic icon placeholder.
|
|
4
|
-
# Ships zero CSS.
|
|
5
|
-
#
|
|
6
|
-
# Usage:
|
|
7
|
-
# Avatar src: user.photoUrl, alt: user.name, fallback: "AC"
|
|
8
|
-
# Avatar fallback: "JD"
|
|
9
|
-
# Avatar
|
|
10
|
-
|
|
11
|
-
export Avatar = component
|
|
12
|
-
@src := ''
|
|
13
|
-
@alt := ''
|
|
14
|
-
@fallback := ''
|
|
15
|
-
|
|
16
|
-
imgError := false
|
|
17
|
-
|
|
18
|
-
_onError: -> imgError = true
|
|
19
|
-
|
|
20
|
-
_initials ~=
|
|
21
|
-
return @fallback if @fallback
|
|
22
|
-
return '' unless @alt
|
|
23
|
-
parts = @alt.trim().split(/\s+/)
|
|
24
|
-
chars = parts.map (p) -> p[0]?.toUpperCase() or ''
|
|
25
|
-
chars.slice(0, 2).join('')
|
|
26
|
-
|
|
27
|
-
render
|
|
28
|
-
span role: "img", aria-label: @alt or 'Avatar'
|
|
29
|
-
$status: if @src and not imgError then 'image' else if _initials then 'fallback' else 'placeholder'
|
|
30
|
-
if @src and not imgError
|
|
31
|
-
img src: @src, alt: @alt, @error: @_onError
|
|
32
|
-
else if _initials
|
|
33
|
-
span $initials: true
|
|
34
|
-
_initials
|
|
35
|
-
else
|
|
36
|
-
span $placeholder: true
|
|
37
|
-
"?"
|
package/docs/ui/badge.rip
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Badge — accessible headless inline label
|
|
2
|
-
#
|
|
3
|
-
# Decorative label for status, counts, or categories.
|
|
4
|
-
# Ships zero CSS.
|
|
5
|
-
#
|
|
6
|
-
# Usage:
|
|
7
|
-
# Badge "New"
|
|
8
|
-
# Badge variant: "outline", "Beta"
|
|
9
|
-
|
|
10
|
-
export Badge = component
|
|
11
|
-
@variant := 'solid'
|
|
12
|
-
|
|
13
|
-
render
|
|
14
|
-
span $variant: @variant
|
|
15
|
-
slot
|
package/docs/ui/breadcrumb.rip
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# Breadcrumb — accessible headless navigation breadcrumb
|
|
2
|
-
#
|
|
3
|
-
# Renders a navigation trail with separator between items.
|
|
4
|
-
# The last item is automatically marked as the current page.
|
|
5
|
-
# Ships zero CSS.
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# Breadcrumb
|
|
9
|
-
# a $item: true, href: "/", "Home"
|
|
10
|
-
# a $item: true, href: "/products", "Products"
|
|
11
|
-
# span $item: true, "Widget Pro"
|
|
12
|
-
#
|
|
13
|
-
# Breadcrumb separator: ">"
|
|
14
|
-
# a $item: true, href: "/", "Home"
|
|
15
|
-
# span $item: true, "Settings"
|
|
16
|
-
|
|
17
|
-
export Breadcrumb = component
|
|
18
|
-
@separator := '/'
|
|
19
|
-
@label := 'Breadcrumb'
|
|
20
|
-
|
|
21
|
-
_ready := false
|
|
22
|
-
|
|
23
|
-
mounted: -> _ready = true
|
|
24
|
-
|
|
25
|
-
_items ~=
|
|
26
|
-
return [] unless _ready
|
|
27
|
-
return [] unless @_content
|
|
28
|
-
Array.from(@_content.querySelectorAll('[data-item]') or [])
|
|
29
|
-
|
|
30
|
-
~>
|
|
31
|
-
return unless _ready
|
|
32
|
-
items = _items
|
|
33
|
-
return unless items.length
|
|
34
|
-
items.forEach (el, idx) =>
|
|
35
|
-
isLast = idx is items.length - 1
|
|
36
|
-
if isLast
|
|
37
|
-
el.setAttribute 'aria-current', 'page'
|
|
38
|
-
el.toggleAttribute 'data-current', true
|
|
39
|
-
else
|
|
40
|
-
el.removeAttribute 'aria-current'
|
|
41
|
-
el.removeAttribute 'data-current'
|
|
42
|
-
|
|
43
|
-
render
|
|
44
|
-
nav aria-label: @label
|
|
45
|
-
ol ref: "_content"
|
|
46
|
-
slot
|
package/docs/ui/button-group.rip
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# ButtonGroup — accessible headless button group
|
|
2
|
-
#
|
|
3
|
-
# Groups related buttons with proper ARIA semantics.
|
|
4
|
-
# Ships zero CSS.
|
|
5
|
-
#
|
|
6
|
-
# Usage:
|
|
7
|
-
# ButtonGroup
|
|
8
|
-
# Button "Cut"
|
|
9
|
-
# Button "Copy"
|
|
10
|
-
# Button "Paste"
|
|
11
|
-
# ButtonGroup orientation: "vertical", label: "Text formatting"
|
|
12
|
-
# Toggle pressed <=> isBold, "Bold"
|
|
13
|
-
# Toggle pressed <=> isItalic, "Italic"
|
|
14
|
-
|
|
15
|
-
export ButtonGroup = component
|
|
16
|
-
@orientation := 'horizontal'
|
|
17
|
-
@disabled := false
|
|
18
|
-
@label := ''
|
|
19
|
-
|
|
20
|
-
render
|
|
21
|
-
div role: "group"
|
|
22
|
-
aria-label: @label?!
|
|
23
|
-
aria-orientation: @orientation
|
|
24
|
-
$orientation: @orientation
|
|
25
|
-
$disabled: @disabled?!
|
|
26
|
-
slot
|
package/docs/ui/button.rip
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# Button — accessible headless button
|
|
2
|
-
#
|
|
3
|
-
# Handles disabled-but-focusable pattern and pressed state.
|
|
4
|
-
# Ships zero CSS.
|
|
5
|
-
#
|
|
6
|
-
# Usage:
|
|
7
|
-
# Button @click: handleClick
|
|
8
|
-
# "Save"
|
|
9
|
-
# Button disabled: true
|
|
10
|
-
# "Unavailable"
|
|
11
|
-
|
|
12
|
-
export Button = component
|
|
13
|
-
@disabled := false
|
|
14
|
-
|
|
15
|
-
onClick: ->
|
|
16
|
-
return if @disabled
|
|
17
|
-
@emit 'press'
|
|
18
|
-
|
|
19
|
-
render
|
|
20
|
-
button disabled: @disabled
|
|
21
|
-
aria-disabled: @disabled?!
|
|
22
|
-
$disabled: @disabled?!
|
|
23
|
-
slot
|
package/docs/ui/card.rip
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Card — accessible headless content container
|
|
2
|
-
#
|
|
3
|
-
# Structured container with optional header, content, and footer sections.
|
|
4
|
-
# Use $header, $content, $footer on children to mark sections.
|
|
5
|
-
# Ships zero CSS.
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# Card
|
|
9
|
-
# div $header: true
|
|
10
|
-
# h3 "Title"
|
|
11
|
-
# div $content: true
|
|
12
|
-
# p "Body text"
|
|
13
|
-
# div $footer: true
|
|
14
|
-
# Button "Action"
|
|
15
|
-
#
|
|
16
|
-
# Card interactive: true, @click: handleClick
|
|
17
|
-
# p "Clickable card"
|
|
18
|
-
|
|
19
|
-
export Card = component
|
|
20
|
-
@interactive := false
|
|
21
|
-
|
|
22
|
-
render
|
|
23
|
-
div tabindex: (if @interactive then "0" else undefined)
|
|
24
|
-
$interactive: @interactive?!
|
|
25
|
-
slot
|
package/docs/ui/carousel.rip
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
# Carousel — accessible headless slide carousel
|
|
2
|
-
#
|
|
3
|
-
# Displays one slide at a time with arrow key navigation, optional
|
|
4
|
-
# autoplay, and loop mode. Discovers slides from [data-slide] children.
|
|
5
|
-
# Ships zero CSS.
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# Carousel loop: true
|
|
9
|
-
# div $slide: true
|
|
10
|
-
# img src: "slide1.jpg"
|
|
11
|
-
# div $slide: true
|
|
12
|
-
# img src: "slide2.jpg"
|
|
13
|
-
# div $slide: true
|
|
14
|
-
# img src: "slide3.jpg"
|
|
15
|
-
#
|
|
16
|
-
# Carousel autoplay: true, interval: 5000, @change: handleSlide
|
|
17
|
-
# div $slide: true, "Slide A"
|
|
18
|
-
# div $slide: true, "Slide B"
|
|
19
|
-
|
|
20
|
-
export Carousel = component
|
|
21
|
-
@orientation := 'horizontal'
|
|
22
|
-
@loop := false
|
|
23
|
-
@autoplay := false
|
|
24
|
-
@interval := 4000
|
|
25
|
-
@label := 'Carousel'
|
|
26
|
-
|
|
27
|
-
activeIndex := 0
|
|
28
|
-
_ready := false
|
|
29
|
-
_timer = null
|
|
30
|
-
|
|
31
|
-
_slides ~=
|
|
32
|
-
return [] unless _ready
|
|
33
|
-
return [] unless @_content
|
|
34
|
-
Array.from(@_content.querySelectorAll('[data-slide]') or [])
|
|
35
|
-
|
|
36
|
-
totalSlides ~= _slides.length
|
|
37
|
-
|
|
38
|
-
mounted: ->
|
|
39
|
-
_ready = true
|
|
40
|
-
@_startAutoplay() if @autoplay
|
|
41
|
-
|
|
42
|
-
beforeUnmount: ->
|
|
43
|
-
@_stopAutoplay()
|
|
44
|
-
|
|
45
|
-
_startAutoplay: ->
|
|
46
|
-
@_stopAutoplay()
|
|
47
|
-
_timer = setInterval (=> @next()), @interval
|
|
48
|
-
|
|
49
|
-
_stopAutoplay: ->
|
|
50
|
-
clearInterval _timer if _timer
|
|
51
|
-
_timer = null
|
|
52
|
-
|
|
53
|
-
goto: (idx) ->
|
|
54
|
-
count = totalSlides
|
|
55
|
-
return unless count
|
|
56
|
-
if @loop
|
|
57
|
-
idx = idx %% count
|
|
58
|
-
else
|
|
59
|
-
idx = Math.max(0, Math.min(idx, count - 1))
|
|
60
|
-
activeIndex = idx
|
|
61
|
-
@emit 'change', activeIndex
|
|
62
|
-
|
|
63
|
-
next: -> @goto(activeIndex + 1)
|
|
64
|
-
prev: -> @goto(activeIndex - 1)
|
|
65
|
-
|
|
66
|
-
onKeydown: (e) ->
|
|
67
|
-
horiz = @orientation is 'horizontal'
|
|
68
|
-
switch e.key
|
|
69
|
-
when (if horiz then 'ArrowRight' else 'ArrowDown')
|
|
70
|
-
e.preventDefault()
|
|
71
|
-
@next()
|
|
72
|
-
when (if horiz then 'ArrowLeft' else 'ArrowUp')
|
|
73
|
-
e.preventDefault()
|
|
74
|
-
@prev()
|
|
75
|
-
when 'Home'
|
|
76
|
-
e.preventDefault()
|
|
77
|
-
@goto(0)
|
|
78
|
-
when 'End'
|
|
79
|
-
e.preventDefault()
|
|
80
|
-
@goto(totalSlides - 1)
|
|
81
|
-
|
|
82
|
-
~>
|
|
83
|
-
return unless _ready
|
|
84
|
-
_slides.forEach (el, idx) =>
|
|
85
|
-
isActive = idx is activeIndex
|
|
86
|
-
el.hidden = not isActive
|
|
87
|
-
el.toggleAttribute 'data-active', isActive
|
|
88
|
-
el.setAttribute 'role', 'tabpanel'
|
|
89
|
-
el.setAttribute 'aria-roledescription', 'slide'
|
|
90
|
-
el.setAttribute 'aria-label', "Slide #{idx + 1} of #{totalSlides}"
|
|
91
|
-
|
|
92
|
-
onMouseenter: -> @_stopAutoplay() if @autoplay
|
|
93
|
-
onMouseleave: -> @_startAutoplay() if @autoplay
|
|
94
|
-
|
|
95
|
-
render
|
|
96
|
-
div role: "region", aria-roledescription: "carousel", aria-label: @label, tabindex: "0"
|
|
97
|
-
$orientation: @orientation
|
|
98
|
-
@keydown: @onKeydown
|
|
99
|
-
@mouseenter: @onMouseenter
|
|
100
|
-
@mouseleave: @onMouseleave
|
|
101
|
-
button $prev: true, aria-label: "Previous slide"
|
|
102
|
-
disabled: not @loop and activeIndex <= 0
|
|
103
|
-
$disabled: (not @loop and activeIndex <= 0)?!
|
|
104
|
-
@click: (=> @prev())
|
|
105
|
-
div ref: "_content"
|
|
106
|
-
slot
|
|
107
|
-
button $next: true, aria-label: "Next slide"
|
|
108
|
-
disabled: not @loop and activeIndex >= totalSlides - 1
|
|
109
|
-
$disabled: (not @loop and activeIndex >= totalSlides - 1)?!
|
|
110
|
-
@click: (=> @next())
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# CheckboxGroup — accessible headless checkbox group
|
|
2
|
-
#
|
|
3
|
-
# Multiple options can be checked independently. Wraps individual checkboxes
|
|
4
|
-
# with group semantics. Value is an array of checked option values.
|
|
5
|
-
# Ships zero CSS.
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# CheckboxGroup value <=> selectedToppings
|
|
9
|
-
# div $value: "cheese", "Cheese"
|
|
10
|
-
# div $value: "bacon", "Bacon"
|
|
11
|
-
# div $value: "lettuce", "Lettuce"
|
|
12
|
-
|
|
13
|
-
export CheckboxGroup = component
|
|
14
|
-
@value := []
|
|
15
|
-
@disabled := false
|
|
16
|
-
@orientation := 'vertical'
|
|
17
|
-
@label := ''
|
|
18
|
-
|
|
19
|
-
_options ~=
|
|
20
|
-
return [] unless @_slot
|
|
21
|
-
Array.from(@_slot.querySelectorAll('[data-value]') or [])
|
|
22
|
-
|
|
23
|
-
_isChecked: (val) ->
|
|
24
|
-
Array.isArray(@value) and val in @value
|
|
25
|
-
|
|
26
|
-
_toggle: (val) ->
|
|
27
|
-
return if @disabled
|
|
28
|
-
arr = if Array.isArray(@value) then [...@value] else []
|
|
29
|
-
if val in arr
|
|
30
|
-
arr = arr.filter (v) -> v isnt val
|
|
31
|
-
else
|
|
32
|
-
arr.push val
|
|
33
|
-
@value = arr
|
|
34
|
-
@emit 'change', @value
|
|
35
|
-
|
|
36
|
-
onKeydown: (e) ->
|
|
37
|
-
boxes = @_root?.querySelectorAll('[role="checkbox"]')
|
|
38
|
-
return unless boxes?.length
|
|
39
|
-
focused = Array.from(boxes).indexOf(document.activeElement)
|
|
40
|
-
return if focused < 0
|
|
41
|
-
len = boxes.length
|
|
42
|
-
switch e.key
|
|
43
|
-
when 'ArrowDown', 'ArrowRight'
|
|
44
|
-
e.preventDefault()
|
|
45
|
-
boxes[(focused + 1) %% len]?.focus()
|
|
46
|
-
when 'ArrowUp', 'ArrowLeft'
|
|
47
|
-
e.preventDefault()
|
|
48
|
-
boxes[(focused - 1) %% len]?.focus()
|
|
49
|
-
|
|
50
|
-
render
|
|
51
|
-
div ref: "_root", role: "group", aria-label: @label or undefined, aria-orientation: @orientation
|
|
52
|
-
$orientation: @orientation
|
|
53
|
-
$disabled: @disabled?!
|
|
54
|
-
|
|
55
|
-
. ref: "_slot", style: "display:none"
|
|
56
|
-
slot
|
|
57
|
-
|
|
58
|
-
for opt, idx in _options
|
|
59
|
-
button role: "checkbox", tabindex: (if idx is 0 then "0" else "-1")
|
|
60
|
-
aria-checked: !!@_isChecked(opt.dataset.value)
|
|
61
|
-
$checked: @_isChecked(opt.dataset.value)?!
|
|
62
|
-
$disabled: @disabled?!
|
|
63
|
-
$value: opt.dataset.value
|
|
64
|
-
@click: (=> @_toggle(opt.dataset.value))
|
|
65
|
-
= opt.textContent
|
package/docs/ui/checkbox.rip
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Checkbox — accessible headless checkbox/switch widget
|
|
2
|
-
#
|
|
3
|
-
# Toggles on click, Enter, or Space. Supports indeterminate state.
|
|
4
|
-
# Exposes $checked, $indeterminate, $disabled. Ships zero CSS.
|
|
5
|
-
# Set @switch to true for switch semantics (role="switch").
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# Checkbox checked <=> isActive, @change: handleChange
|
|
9
|
-
# span "Enable notifications"
|
|
10
|
-
#
|
|
11
|
-
# Checkbox checked <=> isDark, switch: true
|
|
12
|
-
# span "Dark mode"
|
|
13
|
-
|
|
14
|
-
export Checkbox = component
|
|
15
|
-
@checked := false
|
|
16
|
-
@disabled := false
|
|
17
|
-
@indeterminate := false
|
|
18
|
-
@switch := false
|
|
19
|
-
|
|
20
|
-
onClick: ->
|
|
21
|
-
return if @disabled
|
|
22
|
-
@indeterminate = false
|
|
23
|
-
@checked = not @checked
|
|
24
|
-
@emit 'change', @checked
|
|
25
|
-
|
|
26
|
-
render
|
|
27
|
-
button role: @switch ? 'switch' : 'checkbox'
|
|
28
|
-
aria-checked: @indeterminate ? 'mixed' : !!@checked
|
|
29
|
-
aria-disabled: @disabled?!
|
|
30
|
-
$checked: @checked?!
|
|
31
|
-
$indeterminate: @indeterminate?!
|
|
32
|
-
$disabled: @disabled?!
|
|
33
|
-
slot
|
package/docs/ui/collapsible.rip
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Collapsible — accessible headless expand/collapse section
|
|
2
|
-
#
|
|
3
|
-
# Single open/close section. Simpler than Accordion (no item IDs,
|
|
4
|
-
# no single/multiple mode). Exposes content dimensions as CSS
|
|
5
|
-
# custom properties for animated expand/collapse. Ships zero CSS.
|
|
6
|
-
#
|
|
7
|
-
# Usage:
|
|
8
|
-
# Collapsible open <=> isOpen
|
|
9
|
-
# button $trigger: true, "Show details"
|
|
10
|
-
# div $content: true
|
|
11
|
-
# p "Hidden content here"
|
|
12
|
-
|
|
13
|
-
export Collapsible = component
|
|
14
|
-
@open := false
|
|
15
|
-
@disabled := false
|
|
16
|
-
|
|
17
|
-
_ready := false
|
|
18
|
-
|
|
19
|
-
mounted: ->
|
|
20
|
-
_ready = true
|
|
21
|
-
trigger = @_root?.querySelector('[data-trigger]')
|
|
22
|
-
return unless trigger
|
|
23
|
-
trigger.addEventListener 'click', => @toggle() unless @disabled
|
|
24
|
-
trigger.addEventListener 'keydown', (e) =>
|
|
25
|
-
if e.key in ['Enter', ' '] and not @disabled
|
|
26
|
-
e.preventDefault()
|
|
27
|
-
@toggle()
|
|
28
|
-
|
|
29
|
-
toggle: ->
|
|
30
|
-
@open = not @open
|
|
31
|
-
@emit 'change', @open
|
|
32
|
-
|
|
33
|
-
~>
|
|
34
|
-
return unless _ready
|
|
35
|
-
trigger = @_root?.querySelector('[data-trigger]')
|
|
36
|
-
content = @_root?.querySelector('[data-content]')
|
|
37
|
-
if trigger
|
|
38
|
-
trigger.setAttribute 'aria-expanded', !!@open
|
|
39
|
-
trigger.setAttribute 'aria-disabled', true if @disabled
|
|
40
|
-
trigger.tabIndex = if @disabled then -1 else 0
|
|
41
|
-
if content
|
|
42
|
-
content.hidden = not @open
|
|
43
|
-
if @open
|
|
44
|
-
rect = content.getBoundingClientRect()
|
|
45
|
-
content.style.setProperty '--collapsible-height', "#{rect.height}px"
|
|
46
|
-
content.style.setProperty '--collapsible-width', "#{rect.width}px"
|
|
47
|
-
|
|
48
|
-
render
|
|
49
|
-
div ref: "_root", $open: @open?!, $disabled: @disabled?!
|
|
50
|
-
slot
|