rip-lang 3.15.4 → 3.16.1
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/README.md +6 -4
- package/bin/rip +167 -12
- package/docs/AGENTS.md +1 -1
- package/docs/RIP-APP.md +808 -0
- package/docs/RIP-DUCKDB.md +477 -0
- package/docs/RIP-INTRO.md +396 -0
- package/docs/RIP-LANG.md +59 -5
- package/docs/RIP-SCHEMA.md +191 -8
- package/docs/RIP-TYPES.md +74 -103
- package/docs/demo/README.md +4 -3
- package/docs/dist/rip.js +3627 -1470
- package/docs/dist/rip.min.js +671 -244
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/example/index.json +7 -7
- package/docs/example/index.json.br +0 -0
- package/docs/extensions/duckdb/manifest.json +1 -1
- package/docs/extensions/duckdb/v1.5.2/linux_amd64/ripdb.duckdb_extension.gz +0 -0
- package/docs/extensions/duckdb/v1.5.2/osx_arm64/ripdb.duckdb_extension.gz +0 -0
- package/docs/extensions/vscode/print/index.html +2 -1
- package/docs/extensions/vscode/print/print-1.0.13.vsix +0 -0
- package/docs/extensions/vscode/print/print-1.0.14.vsix +0 -0
- package/docs/extensions/vscode/print/print-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/rip-0.5.15.vsix +0 -0
- package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
- package/docs/ui/bundle.json +61 -0
- package/docs/ui/bundle.json.br +0 -0
- package/docs/ui/hljs-rip.js +0 -7
- package/docs/ui/index.css +66 -23
- package/docs/ui/index.html +6 -6
- package/package.json +9 -3
- package/rip-loader.js +64 -2
- package/src/AGENTS.md +63 -36
- package/src/browser.js +96 -14
- package/src/compiler.js +960 -143
- package/src/components.js +794 -88
- package/src/{types-emit.js → dts.js} +181 -71
- package/src/grammar/README.md +1 -1
- package/src/grammar/grammar.rip +111 -97
- package/src/lexer.js +132 -18
- package/src/parser.js +203 -205
- package/src/repl.js +74 -6
- package/src/schema/runtime-orm.js +168 -4
- package/src/schema/runtime-validate.js +146 -2
- package/src/schema/runtime.generated.js +314 -6
- package/src/schema/schema.js +5 -5
- package/src/sourcemaps.js +277 -1
- package/src/stdlib.js +253 -0
- package/src/typecheck.js +2023 -106
- package/src/types.js +127 -7
- package/docs/ui/accordion.rip +0 -103
- package/docs/ui/alert-dialog.rip +0 -53
- package/docs/ui/autocomplete.rip +0 -115
- package/docs/ui/avatar.rip +0 -37
- package/docs/ui/badge.rip +0 -15
- package/docs/ui/breadcrumb.rip +0 -47
- 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 -61
- package/docs/ui/checkbox.rip +0 -33
- package/docs/ui/collapsible.rip +0 -50
- package/docs/ui/combobox.rip +0 -130
- package/docs/ui/context-menu.rip +0 -88
- package/docs/ui/date-picker.rip +0 -206
- package/docs/ui/dialog.rip +0 -60
- package/docs/ui/drawer.rip +0 -58
- package/docs/ui/editable-value.rip +0 -82
- 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/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 -134
- package/docs/ui/menubar.rip +0 -151
- package/docs/ui/meter.rip +0 -36
- package/docs/ui/multi-select.rip +0 -203
- package/docs/ui/native-select.rip +0 -33
- package/docs/ui/nav-menu.rip +0 -126
- 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 -93
- package/docs/ui/preview-card.rip +0 -75
- package/docs/ui/progress.rip +0 -25
- package/docs/ui/radio-group.rip +0 -57
- package/docs/ui/resizable.rip +0 -123
- package/docs/ui/scroll-area.rip +0 -145
- package/docs/ui/select.rip +0 -151
- 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 -113
- package/docs/ui/textarea.rip +0 -48
- package/docs/ui/toast.rip +0 -87
- package/docs/ui/toggle-group.rip +0 -71
- package/docs/ui/toggle.rip +0 -24
- package/docs/ui/toolbar.rip +0 -38
- package/docs/ui/tooltip.rip +0 -85
- package/src/app.rip +0 -1571
- package/src/sourcemap-merge.js +0 -287
- /package/docs/demo/{components → routes}/_layout.rip +0 -0
- /package/docs/demo/{components → routes}/about.rip +0 -0
- /package/docs/demo/{components → routes}/card.rip +0 -0
- /package/docs/demo/{components → routes}/counter.rip +0 -0
- /package/docs/demo/{components → routes}/index.rip +0 -0
- /package/docs/demo/{components → routes}/todos.rip +0 -0
- /package/src/schema/{dts-emit.js → dts.js} +0 -0
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" | "vertical" := "horizontal"
|
|
22
|
-
@loop:: boolean := false
|
|
23
|
-
@autoplay:: boolean := false
|
|
24
|
-
@interval:: number := 4000
|
|
25
|
-
@label:: string := "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,61 +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:: any[] := []
|
|
15
|
-
@disabled:: boolean := false
|
|
16
|
-
@orientation:: "horizontal" | "vertical" := "vertical"
|
|
17
|
-
@label:: string := ""
|
|
18
|
-
|
|
19
|
-
_options ~=
|
|
20
|
-
return [] unless @_slot
|
|
21
|
-
Array.from(@_slot.querySelectorAll('[data-value]') or [])
|
|
22
|
-
|
|
23
|
-
_toggle: (val) ->
|
|
24
|
-
return if @disabled
|
|
25
|
-
arr = if Array.isArray(@value) then [...@value] else []
|
|
26
|
-
if val in arr
|
|
27
|
-
arr = arr.filter (v) -> v isnt val
|
|
28
|
-
else
|
|
29
|
-
arr.push val
|
|
30
|
-
@value = arr
|
|
31
|
-
@emit 'change', @value
|
|
32
|
-
|
|
33
|
-
onKeydown: (e) ->
|
|
34
|
-
boxes = @_root?.querySelectorAll('[role="checkbox"]')
|
|
35
|
-
return unless boxes?.length
|
|
36
|
-
focused = Array.from(boxes).indexOf(document.activeElement)
|
|
37
|
-
return if focused < 0
|
|
38
|
-
len = boxes.length
|
|
39
|
-
ARIA.rovingNav e, {
|
|
40
|
-
next: => boxes[(focused + 1) %% len]?.focus()
|
|
41
|
-
prev: => boxes[(focused - 1) %% len]?.focus()
|
|
42
|
-
first: => boxes[0]?.focus()
|
|
43
|
-
last: => boxes[len - 1]?.focus()
|
|
44
|
-
}, 'both'
|
|
45
|
-
|
|
46
|
-
render
|
|
47
|
-
div ref: "_root", role: "group", aria-label: @label or undefined, aria-orientation: @orientation
|
|
48
|
-
$orientation: @orientation
|
|
49
|
-
$disabled: @disabled?!
|
|
50
|
-
|
|
51
|
-
. ref: "_slot", style: "display:none"
|
|
52
|
-
slot
|
|
53
|
-
|
|
54
|
-
for opt, idx in _options
|
|
55
|
-
button role: "checkbox", tabindex: (if idx is 0 then "0" else "-1")
|
|
56
|
-
aria-checked: opt.dataset.value in @value
|
|
57
|
-
$checked: (opt.dataset.value in @value)?!
|
|
58
|
-
$disabled: @disabled?!
|
|
59
|
-
$value: opt.dataset.value
|
|
60
|
-
@click: (=> @_toggle(opt.dataset.value))
|
|
61
|
-
= 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:: boolean := false
|
|
16
|
-
@disabled:: boolean := false
|
|
17
|
-
@indeterminate:: boolean := false
|
|
18
|
-
@switch:: boolean := 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:: boolean := false
|
|
15
|
-
@disabled:: boolean := 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
|
-
if @disabled then trigger.setAttribute 'aria-disabled', true else trigger.removeAttribute 'aria-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
|
package/docs/ui/combobox.rip
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
# Combobox — accessible headless combobox (input + filterable listbox)
|
|
2
|
-
#
|
|
3
|
-
# Keyboard: ArrowDown/Up to navigate, Enter to select, Escape to close,
|
|
4
|
-
# typing filters the list via the @filter callback.
|
|
5
|
-
#
|
|
6
|
-
# Exposes $open on the wrapper, $highlighted on options.
|
|
7
|
-
# Ships zero CSS — style entirely via attribute selectors in your stylesheet.
|
|
8
|
-
#
|
|
9
|
-
# Usage:
|
|
10
|
-
# Combobox query <=> searchText, items: fruits, @select: handleSelect, @filter: filterFn
|
|
11
|
-
|
|
12
|
-
export Combobox = component
|
|
13
|
-
@query:: string := ""
|
|
14
|
-
@items:: any[] := []
|
|
15
|
-
@placeholder:: string := "Search..."
|
|
16
|
-
@disabled:: boolean := false
|
|
17
|
-
@autoHighlight:: boolean := true
|
|
18
|
-
|
|
19
|
-
open := false
|
|
20
|
-
highlightedIndex := -1
|
|
21
|
-
_listId =! "cb-#{Math.random().toString(36).slice(2, 8)}"
|
|
22
|
-
|
|
23
|
-
getItems: ->
|
|
24
|
-
return [] unless @_list
|
|
25
|
-
Array.from(@_list.querySelectorAll('[role="option"]'))
|
|
26
|
-
|
|
27
|
-
_scrollToItem: ->
|
|
28
|
-
@getItems()[highlightedIndex]?.scrollIntoView({ block: 'nearest' })
|
|
29
|
-
|
|
30
|
-
clear: ->
|
|
31
|
-
@query = ''
|
|
32
|
-
highlightedIndex = -1
|
|
33
|
-
@emit 'filter', ''
|
|
34
|
-
|
|
35
|
-
onInput: (e) ->
|
|
36
|
-
@query = e.target.value
|
|
37
|
-
open = true
|
|
38
|
-
highlightedIndex = if @autoHighlight and @items.length > 0 then 0 else -1
|
|
39
|
-
@emit 'filter', @query
|
|
40
|
-
|
|
41
|
-
onFocusin: -> @openMenu()
|
|
42
|
-
|
|
43
|
-
onFocusout: ->
|
|
44
|
-
setTimeout => @close() unless @_content?.contains(document.activeElement), 0
|
|
45
|
-
|
|
46
|
-
openMenu: ->
|
|
47
|
-
open = true
|
|
48
|
-
highlightedIndex = -1
|
|
49
|
-
@_input?.focus()
|
|
50
|
-
|
|
51
|
-
close: ->
|
|
52
|
-
open = false
|
|
53
|
-
highlightedIndex = -1
|
|
54
|
-
@_input?.focus()
|
|
55
|
-
|
|
56
|
-
_applyPlacement: ->
|
|
57
|
-
ARIA.position @_content, @_list, placement: 'bottom start', offset: 2, matchWidth: true
|
|
58
|
-
|
|
59
|
-
isDisabled: (item) -> item?.hasAttribute?('data-disabled')
|
|
60
|
-
|
|
61
|
-
selectIndex: (idx) ->
|
|
62
|
-
item = @getItems()[idx]
|
|
63
|
-
return unless item
|
|
64
|
-
return if @isDisabled(item)
|
|
65
|
-
val = item.dataset.value ?? item.textContent.trim()
|
|
66
|
-
@query = val
|
|
67
|
-
@emit 'select', val
|
|
68
|
-
@close()
|
|
69
|
-
|
|
70
|
-
_nextEnabled: (from, dir) ->
|
|
71
|
-
opts = @getItems()
|
|
72
|
-
len = opts.length
|
|
73
|
-
i = from
|
|
74
|
-
loop len
|
|
75
|
-
i = (i + dir) %% len
|
|
76
|
-
return i unless @isDisabled(opts[i])
|
|
77
|
-
from
|
|
78
|
-
|
|
79
|
-
_onKeydown: (e) ->
|
|
80
|
-
len = @getItems().length
|
|
81
|
-
ARIA.listNav e,
|
|
82
|
-
next: => @openMenu() unless open; highlightedIndex = @_nextEnabled(highlightedIndex, 1); @_scrollToItem()
|
|
83
|
-
prev: => @openMenu() unless open; highlightedIndex = @_nextEnabled(highlightedIndex, -1); @_scrollToItem()
|
|
84
|
-
first: => highlightedIndex = 0; @_scrollToItem()
|
|
85
|
-
last: => highlightedIndex = len - 1; @_scrollToItem()
|
|
86
|
-
select: => if highlightedIndex >= 0 then @selectIndex(highlightedIndex) else if len is 1 then @selectIndex(0)
|
|
87
|
-
dismiss: => if open then @close() else @query = ''
|
|
88
|
-
tab: => @close()
|
|
89
|
-
|
|
90
|
-
~>
|
|
91
|
-
if @_list
|
|
92
|
-
@_list.setAttribute 'popover', 'auto'
|
|
93
|
-
@_applyPlacement()
|
|
94
|
-
ARIA.bindPopover open, (=> @_list), ((isOpen) => open = isOpen), (=> @_input)
|
|
95
|
-
|
|
96
|
-
render
|
|
97
|
-
. ref: "_content", $open: open?!
|
|
98
|
-
|
|
99
|
-
# Text input
|
|
100
|
-
. style: "position:relative;display:inline-flex;align-items:center"
|
|
101
|
-
input ref: "_input", role: "combobox"
|
|
102
|
-
type: "text"
|
|
103
|
-
autocomplete: "off"
|
|
104
|
-
aria-expanded: !!open
|
|
105
|
-
aria-haspopup: "listbox"
|
|
106
|
-
aria-autocomplete: "list"
|
|
107
|
-
aria-controls: if open then _listId else undefined
|
|
108
|
-
aria-activedescendant: if highlightedIndex >= 0 then "#{_listId}-#{highlightedIndex}" else undefined
|
|
109
|
-
$disabled: @disabled?!
|
|
110
|
-
disabled: @disabled
|
|
111
|
-
placeholder: @placeholder
|
|
112
|
-
value: @query
|
|
113
|
-
@keydown: @_onKeydown
|
|
114
|
-
if @query
|
|
115
|
-
button aria-label: "Clear", $clear: true, @click: @clear
|
|
116
|
-
"✕"
|
|
117
|
-
|
|
118
|
-
# Listbox
|
|
119
|
-
div ref: "_list", id: _listId, role: "listbox", style: "position:fixed;margin:0;inset:auto"
|
|
120
|
-
$open: open?!
|
|
121
|
-
for item, idx in @items
|
|
122
|
-
div role: "option", tabindex: "-1", id: "#{_listId}-#{idx}"
|
|
123
|
-
$value: item
|
|
124
|
-
$highlighted: (idx is highlightedIndex)?!
|
|
125
|
-
@click: (=> @selectIndex(idx))
|
|
126
|
-
@mouseenter: (=> highlightedIndex = idx)
|
|
127
|
-
"#{item}"
|
|
128
|
-
if @items.length is 0 and @query
|
|
129
|
-
div role: "status", aria-live: "polite", $empty: true
|
|
130
|
-
"No results"
|
package/docs/ui/context-menu.rip
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# ContextMenu — accessible headless right-click menu
|
|
2
|
-
#
|
|
3
|
-
# Opens on contextmenu event (right-click) over the trigger area.
|
|
4
|
-
# Keyboard navigation matches Menu. Ships zero CSS.
|
|
5
|
-
#
|
|
6
|
-
# Usage:
|
|
7
|
-
# ContextMenu @select: handleAction
|
|
8
|
-
# div $trigger: true
|
|
9
|
-
# p "Right-click this area"
|
|
10
|
-
# div $item: "cut", "Cut"
|
|
11
|
-
# div $item: "copy", "Copy"
|
|
12
|
-
# div $item: "paste", "Paste"
|
|
13
|
-
|
|
14
|
-
export ContextMenu = component
|
|
15
|
-
@disabled:: boolean := false
|
|
16
|
-
|
|
17
|
-
open := false
|
|
18
|
-
highlightedIndex := -1
|
|
19
|
-
posX := 0
|
|
20
|
-
posY := 0
|
|
21
|
-
|
|
22
|
-
_menuItems ~=
|
|
23
|
-
return [] unless @_slot
|
|
24
|
-
Array.from(@_slot.querySelectorAll('[data-item]') or [])
|
|
25
|
-
|
|
26
|
-
_triggerEl ~=
|
|
27
|
-
return null unless @_slot
|
|
28
|
-
@_slot.querySelector('[data-trigger]')
|
|
29
|
-
|
|
30
|
-
_onContextMenu: (e) ->
|
|
31
|
-
return if @disabled
|
|
32
|
-
e.preventDefault()
|
|
33
|
-
posX = e.clientX
|
|
34
|
-
posY = e.clientY
|
|
35
|
-
open = true
|
|
36
|
-
highlightedIndex = 0
|
|
37
|
-
requestAnimationFrame =>
|
|
38
|
-
@_list?.querySelectorAll('[role="menuitem"]')[0]?.focus()
|
|
39
|
-
|
|
40
|
-
close: ->
|
|
41
|
-
open = false
|
|
42
|
-
highlightedIndex = -1
|
|
43
|
-
|
|
44
|
-
selectIndex: (idx) ->
|
|
45
|
-
item = _menuItems[idx]
|
|
46
|
-
return unless item
|
|
47
|
-
return if item.dataset.disabled?
|
|
48
|
-
@emit 'select', item.dataset.item
|
|
49
|
-
@close()
|
|
50
|
-
|
|
51
|
-
_focusItem: (idx) ->
|
|
52
|
-
highlightedIndex = idx
|
|
53
|
-
@_list?.querySelectorAll('[role="menuitem"]')[idx]?.focus()
|
|
54
|
-
|
|
55
|
-
_onKeydown: (e) ->
|
|
56
|
-
return unless _menuItems.length
|
|
57
|
-
len = _menuItems.length
|
|
58
|
-
ARIA.listNav e,
|
|
59
|
-
next: => @_focusItem((highlightedIndex + 1) %% len)
|
|
60
|
-
prev: => @_focusItem((highlightedIndex - 1) %% len)
|
|
61
|
-
first: => @_focusItem(0)
|
|
62
|
-
last: => @_focusItem(len - 1)
|
|
63
|
-
select: => @selectIndex(highlightedIndex)
|
|
64
|
-
dismiss: => @close()
|
|
65
|
-
tab: => @close()
|
|
66
|
-
|
|
67
|
-
~> ARIA.popupDismiss open, (=> @_list), (=> @close())
|
|
68
|
-
|
|
69
|
-
render
|
|
70
|
-
. @contextmenu: @_onContextMenu
|
|
71
|
-
|
|
72
|
-
. ref: "_slot", style: "display:none"
|
|
73
|
-
slot
|
|
74
|
-
|
|
75
|
-
if _triggerEl
|
|
76
|
-
. innerHTML: _triggerEl.innerHTML
|
|
77
|
-
|
|
78
|
-
if open
|
|
79
|
-
div ref: "_list", role: "menu", $open: true, @keydown: @_onKeydown
|
|
80
|
-
style: "position:fixed;left:#{posX}px;top:#{posY}px;z-index:50"
|
|
81
|
-
for item, idx in _menuItems
|
|
82
|
-
div role: "menuitem", tabindex: "-1"
|
|
83
|
-
$highlighted: (idx is highlightedIndex)?!
|
|
84
|
-
$disabled: item.dataset.disabled?!
|
|
85
|
-
$value: item.dataset.item
|
|
86
|
-
@click: (=> @selectIndex(idx))
|
|
87
|
-
@mouseenter: (=> highlightedIndex = idx)
|
|
88
|
-
= item.textContent
|
package/docs/ui/date-picker.rip
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
# DatePicker — accessible headless date picker with calendar
|
|
2
|
-
#
|
|
3
|
-
# A popover calendar for selecting a single date or a date range.
|
|
4
|
-
# Set @range to true for range selection (value becomes [from, to]).
|
|
5
|
-
# Keyboard: Arrow keys navigate days, Enter selects, Escape closes.
|
|
6
|
-
# Ships zero CSS — style entirely via attribute selectors in your stylesheet.
|
|
7
|
-
#
|
|
8
|
-
# Usage:
|
|
9
|
-
# DatePicker value <=> selectedDate, placeholder: "Pick a date"
|
|
10
|
-
# DatePicker value <=> dateRange, range: true
|
|
11
|
-
|
|
12
|
-
dpFmt = (d) ->
|
|
13
|
-
return '' unless d
|
|
14
|
-
m = String(d.getMonth() + 1).padStart(2, '0')
|
|
15
|
-
day = String(d.getDate()).padStart(2, '0')
|
|
16
|
-
"#{m}/#{day}/#{d.getFullYear()}"
|
|
17
|
-
|
|
18
|
-
dpParse = (str) ->
|
|
19
|
-
return null unless str?.length is 10
|
|
20
|
-
parts = str.split('/')
|
|
21
|
-
return null unless parts.length is 3
|
|
22
|
-
[m, d, y] = parts.map Number
|
|
23
|
-
return null if isNaN(m) or isNaN(d) or isNaN(y)
|
|
24
|
-
dt = new Date(y, m - 1, d)
|
|
25
|
-
return null if dt.getMonth() isnt m - 1
|
|
26
|
-
dt
|
|
27
|
-
|
|
28
|
-
dpSameDay = (a, b) ->
|
|
29
|
-
return false unless a and b
|
|
30
|
-
a.getFullYear() is b.getFullYear() and a.getMonth() is b.getMonth() and a.getDate() is b.getDate()
|
|
31
|
-
|
|
32
|
-
dpInRange = (day, from, to) ->
|
|
33
|
-
return false unless day and from and to
|
|
34
|
-
t = day.getTime()
|
|
35
|
-
lo = Math.min(from.getTime(), to.getTime())
|
|
36
|
-
hi = Math.max(from.getTime(), to.getTime())
|
|
37
|
-
t >= lo and t <= hi
|
|
38
|
-
|
|
39
|
-
export DatePicker = component
|
|
40
|
-
@value:: any := null
|
|
41
|
-
@placeholder:: string := "mm/dd/yyyy"
|
|
42
|
-
@disabled:: boolean := false
|
|
43
|
-
@range:: boolean := false
|
|
44
|
-
@firstDayOfWeek:: number := 0
|
|
45
|
-
|
|
46
|
-
open := false
|
|
47
|
-
viewMonth := new Date()
|
|
48
|
-
_rangeStart := null
|
|
49
|
-
_hoveredDay := null
|
|
50
|
-
_inputText := ''
|
|
51
|
-
_id =! "dp-#{Math.random().toString(36).slice(2, 8)}"
|
|
52
|
-
|
|
53
|
-
_dayNames =! ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
|
|
54
|
-
|
|
55
|
-
_daysInView ~=
|
|
56
|
-
yr = viewMonth.getFullYear()
|
|
57
|
-
mo = viewMonth.getMonth()
|
|
58
|
-
firstOfMonth = new Date(yr, mo, 1)
|
|
59
|
-
startDay = firstOfMonth.getDay()
|
|
60
|
-
offset = (startDay - @firstDayOfWeek + 7) %% 7
|
|
61
|
-
daysInMonth = new Date(yr, mo + 1, 0).getDate()
|
|
62
|
-
prevMonthDays = new Date(yr, mo, 0).getDate()
|
|
63
|
-
dayList = []
|
|
64
|
-
for n in [0...offset]
|
|
65
|
-
dayList.push { date: new Date(yr, mo - 1, prevMonthDays - offset + n + 1), outside: true }
|
|
66
|
-
for n in [1..daysInMonth]
|
|
67
|
-
dayList.push { date: new Date(yr, mo, n), outside: false }
|
|
68
|
-
trailing = (7 - dayList.length %% 7) %% 7
|
|
69
|
-
for n in [1..trailing]
|
|
70
|
-
dayList.push { date: new Date(yr, mo + 1, n), outside: true }
|
|
71
|
-
dayList
|
|
72
|
-
|
|
73
|
-
_displayText ~=
|
|
74
|
-
if @range
|
|
75
|
-
if Array.isArray(@value) and @value[0]
|
|
76
|
-
from = dpFmt(@value[0])
|
|
77
|
-
to = if @value[1] then dpFmt(@value[1]) else '...'
|
|
78
|
-
"#{from} – #{to}"
|
|
79
|
-
else
|
|
80
|
-
@placeholder
|
|
81
|
-
else
|
|
82
|
-
if @value then dpFmt(@value) else @placeholder
|
|
83
|
-
|
|
84
|
-
_monthLabel ~=
|
|
85
|
-
viewMonth.toLocaleDateString(undefined, { month: 'long', year: 'numeric' })
|
|
86
|
-
|
|
87
|
-
_today =! new Date()
|
|
88
|
-
|
|
89
|
-
_prevMonth: ->
|
|
90
|
-
viewMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() - 1, 1)
|
|
91
|
-
|
|
92
|
-
_nextMonth: ->
|
|
93
|
-
viewMonth = new Date(viewMonth.getFullYear(), viewMonth.getMonth() + 1, 1)
|
|
94
|
-
|
|
95
|
-
_selectDay: (day) ->
|
|
96
|
-
return if @disabled
|
|
97
|
-
if @range
|
|
98
|
-
if _rangeStart and not dpSameDay(_rangeStart, day)
|
|
99
|
-
from = if _rangeStart < day then _rangeStart else day
|
|
100
|
-
to = if _rangeStart < day then day else _rangeStart
|
|
101
|
-
@value = [from, to]
|
|
102
|
-
_rangeStart = null
|
|
103
|
-
@emit 'change', @value
|
|
104
|
-
open = false
|
|
105
|
-
else
|
|
106
|
-
_rangeStart = day
|
|
107
|
-
@value = [day, null]
|
|
108
|
-
else
|
|
109
|
-
@value = day
|
|
110
|
-
@emit 'change', @value
|
|
111
|
-
open = false
|
|
112
|
-
_inputText = _displayText
|
|
113
|
-
|
|
114
|
-
_onInputChange: (e) ->
|
|
115
|
-
raw = e.target.value.replace(/[^\d\/]/g, '')
|
|
116
|
-
if raw.length <= 10
|
|
117
|
-
_inputText = raw
|
|
118
|
-
if raw.length is 10
|
|
119
|
-
dt = dpParse(raw)
|
|
120
|
-
if dt
|
|
121
|
-
@value = dt
|
|
122
|
-
viewMonth = new Date(dt.getFullYear(), dt.getMonth(), 1)
|
|
123
|
-
@emit 'change', @value
|
|
124
|
-
|
|
125
|
-
toggle: ->
|
|
126
|
-
return if @disabled
|
|
127
|
-
if open then @close() else @openPicker()
|
|
128
|
-
|
|
129
|
-
openPicker: ->
|
|
130
|
-
open = true
|
|
131
|
-
if @value and not @range
|
|
132
|
-
viewMonth = new Date(@value.getFullYear(), @value.getMonth(), 1)
|
|
133
|
-
else if @range and Array.isArray(@value) and @value[0]
|
|
134
|
-
viewMonth = new Date(@value[0].getFullYear(), @value[0].getMonth(), 1)
|
|
135
|
-
_inputText = _displayText
|
|
136
|
-
requestAnimationFrame => @_position()
|
|
137
|
-
|
|
138
|
-
close: ->
|
|
139
|
-
open = false
|
|
140
|
-
_rangeStart = null
|
|
141
|
-
_hoveredDay = null
|
|
142
|
-
|
|
143
|
-
_position: -> ARIA.positionBelow @_trigger, @_cal, 4, false
|
|
144
|
-
|
|
145
|
-
_onKeydown: (e) ->
|
|
146
|
-
switch e.key
|
|
147
|
-
when 'Escape'
|
|
148
|
-
e.preventDefault()
|
|
149
|
-
@close()
|
|
150
|
-
@_trigger?.focus()
|
|
151
|
-
when 'Enter', ' '
|
|
152
|
-
e.preventDefault()
|
|
153
|
-
@toggle() unless open
|
|
154
|
-
|
|
155
|
-
~>
|
|
156
|
-
if open
|
|
157
|
-
onDown = (e) =>
|
|
158
|
-
unless @_trigger?.contains(e.target) or @_cal?.contains(e.target)
|
|
159
|
-
@close()
|
|
160
|
-
document.addEventListener 'mousedown', onDown
|
|
161
|
-
return -> document.removeEventListener 'mousedown', onDown
|
|
162
|
-
|
|
163
|
-
render
|
|
164
|
-
. $open: open?!, $disabled: @disabled?!, $range: @range?!
|
|
165
|
-
|
|
166
|
-
# Trigger
|
|
167
|
-
button ref: "_trigger", $trigger: true
|
|
168
|
-
aria-haspopup: "dialog"
|
|
169
|
-
aria-expanded: !!open
|
|
170
|
-
disabled: @disabled
|
|
171
|
-
@click: @toggle
|
|
172
|
-
@keydown: @_onKeydown
|
|
173
|
-
_displayText
|
|
174
|
-
|
|
175
|
-
# Calendar dropdown
|
|
176
|
-
if open
|
|
177
|
-
div ref: "_cal", role: "dialog", aria-label: "Date picker", $calendar: true
|
|
178
|
-
style: "position:fixed;z-index:50"
|
|
179
|
-
|
|
180
|
-
# Month navigation
|
|
181
|
-
. $header: true
|
|
182
|
-
button $prev: true, aria-label: "Previous month", @click: @_prevMonth
|
|
183
|
-
"‹"
|
|
184
|
-
span $month-label: true
|
|
185
|
-
_monthLabel
|
|
186
|
-
button $next: true, aria-label: "Next month", @click: @_nextMonth
|
|
187
|
-
"›"
|
|
188
|
-
|
|
189
|
-
# Day-of-week headers
|
|
190
|
-
. $weekdays: true
|
|
191
|
-
for dayName in _dayNames
|
|
192
|
-
span $weekday: true
|
|
193
|
-
dayName
|
|
194
|
-
|
|
195
|
-
# Day grid
|
|
196
|
-
. $days: true, role: "grid"
|
|
197
|
-
for entry, dIdx in _daysInView
|
|
198
|
-
button role: "gridcell", tabindex: "-1"
|
|
199
|
-
$outside: entry.outside?!
|
|
200
|
-
$today: dpSameDay(entry.date, _today)?!
|
|
201
|
-
$selected: (if @range then (Array.isArray(@value) and (dpSameDay(entry.date, @value[0]) or dpSameDay(entry.date, @value[1]))) else dpSameDay(entry.date, @value))?!
|
|
202
|
-
$in-range: (if @range then (if _rangeStart then dpInRange(entry.date, _rangeStart, _hoveredDay or _rangeStart) else if Array.isArray(@value) and @value[0] and @value[1] then dpInRange(entry.date, @value[0], @value[1]) else false) else false)?!
|
|
203
|
-
$range-start: (if @range and _rangeStart then dpSameDay(entry.date, _rangeStart) else false)?!
|
|
204
|
-
@click: (=> @_selectDay(entry.date))
|
|
205
|
-
@mouseenter: (=> _hoveredDay = entry.date)
|
|
206
|
-
entry.date.getDate()
|