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.
Files changed (74) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/README.md +3 -3
  3. package/bin/rip +11 -1
  4. package/docs/AGENTS.md +43 -0
  5. package/docs/RIP-LANG.md +3 -3
  6. package/docs/RIP-TYPES.md +72 -91
  7. package/docs/charts.html +15 -15
  8. package/docs/dist/rip.js +142 -38
  9. package/docs/dist/rip.min.js +174 -174
  10. package/docs/dist/rip.min.js.br +0 -0
  11. package/docs/index.html +2 -2
  12. package/package.json +1 -1
  13. package/src/AGENTS.md +456 -0
  14. package/src/lexer.js +1 -2
  15. package/src/typecheck.js +188 -6
  16. package/src/types.js +63 -38
  17. package/src/ui.rip +65 -0
  18. package/docs/ui/accordion.rip +0 -113
  19. package/docs/ui/alert-dialog.rip +0 -96
  20. package/docs/ui/autocomplete.rip +0 -141
  21. package/docs/ui/avatar.rip +0 -37
  22. package/docs/ui/badge.rip +0 -15
  23. package/docs/ui/breadcrumb.rip +0 -46
  24. package/docs/ui/button-group.rip +0 -26
  25. package/docs/ui/button.rip +0 -23
  26. package/docs/ui/card.rip +0 -25
  27. package/docs/ui/carousel.rip +0 -110
  28. package/docs/ui/checkbox-group.rip +0 -65
  29. package/docs/ui/checkbox.rip +0 -33
  30. package/docs/ui/collapsible.rip +0 -50
  31. package/docs/ui/combobox.rip +0 -155
  32. package/docs/ui/context-menu.rip +0 -105
  33. package/docs/ui/date-picker.rip +0 -214
  34. package/docs/ui/dialog.rip +0 -107
  35. package/docs/ui/drawer.rip +0 -79
  36. package/docs/ui/editable-value.rip +0 -80
  37. package/docs/ui/field.rip +0 -53
  38. package/docs/ui/fieldset.rip +0 -22
  39. package/docs/ui/form.rip +0 -39
  40. package/docs/ui/grid.rip +0 -901
  41. package/docs/ui/hljs-rip.js +0 -209
  42. package/docs/ui/index.css +0 -1772
  43. package/docs/ui/index.html +0 -2433
  44. package/docs/ui/input-group.rip +0 -28
  45. package/docs/ui/input.rip +0 -36
  46. package/docs/ui/label.rip +0 -16
  47. package/docs/ui/menu.rip +0 -162
  48. package/docs/ui/menubar.rip +0 -155
  49. package/docs/ui/meter.rip +0 -36
  50. package/docs/ui/multi-select.rip +0 -158
  51. package/docs/ui/native-select.rip +0 -32
  52. package/docs/ui/nav-menu.rip +0 -129
  53. package/docs/ui/number-field.rip +0 -162
  54. package/docs/ui/otp-field.rip +0 -89
  55. package/docs/ui/pagination.rip +0 -123
  56. package/docs/ui/popover.rip +0 -143
  57. package/docs/ui/preview-card.rip +0 -73
  58. package/docs/ui/progress.rip +0 -25
  59. package/docs/ui/radio-group.rip +0 -67
  60. package/docs/ui/resizable.rip +0 -123
  61. package/docs/ui/scroll-area.rip +0 -145
  62. package/docs/ui/select.rip +0 -184
  63. package/docs/ui/separator.rip +0 -17
  64. package/docs/ui/skeleton.rip +0 -22
  65. package/docs/ui/slider.rip +0 -165
  66. package/docs/ui/spinner.rip +0 -17
  67. package/docs/ui/table.rip +0 -27
  68. package/docs/ui/tabs.rip +0 -124
  69. package/docs/ui/textarea.rip +0 -48
  70. package/docs/ui/toast.rip +0 -87
  71. package/docs/ui/toggle-group.rip +0 -78
  72. package/docs/ui/toggle.rip +0 -24
  73. package/docs/ui/toolbar.rip +0 -46
  74. package/docs/ui/tooltip.rip +0 -115
@@ -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))}"
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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