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.
Files changed (112) hide show
  1. package/README.md +6 -4
  2. package/bin/rip +167 -12
  3. package/docs/AGENTS.md +1 -1
  4. package/docs/RIP-APP.md +808 -0
  5. package/docs/RIP-DUCKDB.md +477 -0
  6. package/docs/RIP-INTRO.md +396 -0
  7. package/docs/RIP-LANG.md +59 -5
  8. package/docs/RIP-SCHEMA.md +191 -8
  9. package/docs/RIP-TYPES.md +74 -103
  10. package/docs/demo/README.md +4 -3
  11. package/docs/dist/rip.js +3627 -1470
  12. package/docs/dist/rip.min.js +671 -244
  13. package/docs/dist/rip.min.js.br +0 -0
  14. package/docs/example/index.json +7 -7
  15. package/docs/example/index.json.br +0 -0
  16. package/docs/extensions/duckdb/manifest.json +1 -1
  17. package/docs/extensions/duckdb/v1.5.2/linux_amd64/ripdb.duckdb_extension.gz +0 -0
  18. package/docs/extensions/duckdb/v1.5.2/osx_arm64/ripdb.duckdb_extension.gz +0 -0
  19. package/docs/extensions/vscode/print/index.html +2 -1
  20. package/docs/extensions/vscode/print/print-1.0.13.vsix +0 -0
  21. package/docs/extensions/vscode/print/print-1.0.14.vsix +0 -0
  22. package/docs/extensions/vscode/print/print-latest.vsix +0 -0
  23. package/docs/extensions/vscode/rip/rip-0.5.15.vsix +0 -0
  24. package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
  25. package/docs/ui/bundle.json +61 -0
  26. package/docs/ui/bundle.json.br +0 -0
  27. package/docs/ui/hljs-rip.js +0 -7
  28. package/docs/ui/index.css +66 -23
  29. package/docs/ui/index.html +6 -6
  30. package/package.json +9 -3
  31. package/rip-loader.js +64 -2
  32. package/src/AGENTS.md +63 -36
  33. package/src/browser.js +96 -14
  34. package/src/compiler.js +960 -143
  35. package/src/components.js +794 -88
  36. package/src/{types-emit.js → dts.js} +181 -71
  37. package/src/grammar/README.md +1 -1
  38. package/src/grammar/grammar.rip +111 -97
  39. package/src/lexer.js +132 -18
  40. package/src/parser.js +203 -205
  41. package/src/repl.js +74 -6
  42. package/src/schema/runtime-orm.js +168 -4
  43. package/src/schema/runtime-validate.js +146 -2
  44. package/src/schema/runtime.generated.js +314 -6
  45. package/src/schema/schema.js +5 -5
  46. package/src/sourcemaps.js +277 -1
  47. package/src/stdlib.js +253 -0
  48. package/src/typecheck.js +2023 -106
  49. package/src/types.js +127 -7
  50. package/docs/ui/accordion.rip +0 -103
  51. package/docs/ui/alert-dialog.rip +0 -53
  52. package/docs/ui/autocomplete.rip +0 -115
  53. package/docs/ui/avatar.rip +0 -37
  54. package/docs/ui/badge.rip +0 -15
  55. package/docs/ui/breadcrumb.rip +0 -47
  56. package/docs/ui/button-group.rip +0 -26
  57. package/docs/ui/button.rip +0 -23
  58. package/docs/ui/card.rip +0 -25
  59. package/docs/ui/carousel.rip +0 -110
  60. package/docs/ui/checkbox-group.rip +0 -61
  61. package/docs/ui/checkbox.rip +0 -33
  62. package/docs/ui/collapsible.rip +0 -50
  63. package/docs/ui/combobox.rip +0 -130
  64. package/docs/ui/context-menu.rip +0 -88
  65. package/docs/ui/date-picker.rip +0 -206
  66. package/docs/ui/dialog.rip +0 -60
  67. package/docs/ui/drawer.rip +0 -58
  68. package/docs/ui/editable-value.rip +0 -82
  69. package/docs/ui/field.rip +0 -53
  70. package/docs/ui/fieldset.rip +0 -22
  71. package/docs/ui/form.rip +0 -39
  72. package/docs/ui/grid.rip +0 -901
  73. package/docs/ui/input-group.rip +0 -28
  74. package/docs/ui/input.rip +0 -36
  75. package/docs/ui/label.rip +0 -16
  76. package/docs/ui/menu.rip +0 -134
  77. package/docs/ui/menubar.rip +0 -151
  78. package/docs/ui/meter.rip +0 -36
  79. package/docs/ui/multi-select.rip +0 -203
  80. package/docs/ui/native-select.rip +0 -33
  81. package/docs/ui/nav-menu.rip +0 -126
  82. package/docs/ui/number-field.rip +0 -162
  83. package/docs/ui/otp-field.rip +0 -89
  84. package/docs/ui/pagination.rip +0 -123
  85. package/docs/ui/popover.rip +0 -93
  86. package/docs/ui/preview-card.rip +0 -75
  87. package/docs/ui/progress.rip +0 -25
  88. package/docs/ui/radio-group.rip +0 -57
  89. package/docs/ui/resizable.rip +0 -123
  90. package/docs/ui/scroll-area.rip +0 -145
  91. package/docs/ui/select.rip +0 -151
  92. package/docs/ui/separator.rip +0 -17
  93. package/docs/ui/skeleton.rip +0 -22
  94. package/docs/ui/slider.rip +0 -165
  95. package/docs/ui/spinner.rip +0 -17
  96. package/docs/ui/table.rip +0 -27
  97. package/docs/ui/tabs.rip +0 -113
  98. package/docs/ui/textarea.rip +0 -48
  99. package/docs/ui/toast.rip +0 -87
  100. package/docs/ui/toggle-group.rip +0 -71
  101. package/docs/ui/toggle.rip +0 -24
  102. package/docs/ui/toolbar.rip +0 -38
  103. package/docs/ui/tooltip.rip +0 -85
  104. package/src/app.rip +0 -1571
  105. package/src/sourcemap-merge.js +0 -287
  106. /package/docs/demo/{components → routes}/_layout.rip +0 -0
  107. /package/docs/demo/{components → routes}/about.rip +0 -0
  108. /package/docs/demo/{components → routes}/card.rip +0 -0
  109. /package/docs/demo/{components → routes}/counter.rip +0 -0
  110. /package/docs/demo/{components → routes}/index.rip +0 -0
  111. /package/docs/demo/{components → routes}/todos.rip +0 -0
  112. /package/src/schema/{dts-emit.js → dts.js} +0 -0
package/docs/ui/table.rip DELETED
@@ -1,27 +0,0 @@
1
- # Table — accessible headless semantic table wrapper
2
- #
3
- # Lightweight wrapper for HTML tables with optional caption and
4
- # striped rows. For data-heavy tables with virtual scrolling, use Grid.
5
- # Ships zero CSS.
6
- #
7
- # Usage:
8
- # Table caption: "Team members", striped: true
9
- # thead
10
- # tr
11
- # th "Name"
12
- # th "Role"
13
- # tbody
14
- # tr
15
- # td "Alice"
16
- # td "Engineer"
17
-
18
- export Table = component
19
- @caption:: string := ""
20
- @striped:: boolean := false
21
-
22
- render
23
- div $striped: @striped?!
24
- table
25
- if @caption
26
- caption @caption
27
- slot
package/docs/ui/tabs.rip DELETED
@@ -1,113 +0,0 @@
1
- # Tabs — accessible headless tab widget
2
- #
3
- # Keyboard: ArrowLeft/Right (horizontal) or ArrowUp/Down (vertical) to navigate,
4
- # Home/End for first/last. Manages focus via roving tabindex.
5
- # Exposes $active on tabs and panels. Ships zero CSS.
6
- #
7
- # Props:
8
- # active — currently active tab id (two-way bindable)
9
- # orientation — 'horizontal' (default) or 'vertical'
10
- # activation — 'automatic' (default, selects on focus) or 'manual' (Enter/Space to select)
11
- #
12
- # Usage:
13
- # Tabs active <=> currentTab
14
- # div $tab: "one", "Tab One"
15
- # div $tab: "two", "Tab Two"
16
- # div $panel: "one"
17
- # p "Content for tab one"
18
- # div $panel: "two"
19
- # p "Content for tab two"
20
-
21
- export Tabs = component
22
- @active:: any := null
23
- @orientation:: "horizontal" | "vertical" := "horizontal"
24
- @activation:: "automatic" | "manual" := "automatic"
25
- _ready := false
26
- _id =! "tabs-#{Math.random().toString(36).slice(2, 8)}"
27
- activationDirection := 'none'
28
-
29
- tabs ~=
30
- return [] unless _ready
31
- Array.from(@_content?.querySelectorAll('[data-tab]') or [])
32
-
33
- panels ~=
34
- return [] unless _ready
35
- Array.from(@_content?.querySelectorAll('[data-panel]') or [])
36
-
37
- mounted: ->
38
- _ready = true
39
- unless @active
40
- @active = tabs[0]?.dataset.tab
41
-
42
- ~>
43
- return unless _ready
44
- tabs.forEach (el) -> el.hidden = true
45
- panels.forEach (el) =>
46
- id = el.dataset.panel
47
- isActive = id is @active
48
- el.id = "#{_id}-panel-#{id}"
49
- el.setAttribute 'role', 'tabpanel'
50
- el.setAttribute 'aria-labelledby', "#{_id}-tab-#{id}"
51
- el.toggleAttribute 'hidden', not isActive
52
- el.toggleAttribute 'data-active', isActive
53
-
54
- _isDisabled: (el) -> el?.hasAttribute('data-disabled')
55
-
56
- select: (id) ->
57
- prev = @active
58
- horiz = @orientation is 'horizontal'
59
- if prev and id isnt prev
60
- oldTab = tabs.find (t) -> t.dataset.tab is prev
61
- newTab = tabs.find (t) -> t.dataset.tab is id
62
- if oldTab and newTab
63
- oldRect = oldTab.getBoundingClientRect()
64
- newRect = newTab.getBoundingClientRect()
65
- activationDirection = if horiz
66
- if newRect.left > oldRect.left then 'right' else 'left'
67
- else
68
- if newRect.top > oldRect.top then 'down' else 'up'
69
- @active = id
70
- @emit 'change', id
71
-
72
- _nextEnabled: (ids, from, dir) ->
73
- len = ids.length
74
- i = from
75
- loop len
76
- i = (i + dir) %% len
77
- tab = tabs.find (t) -> t.dataset.tab is ids[i]
78
- return ids[i] unless @_isDisabled(tab)
79
- ids[from]
80
-
81
- onKeydown: (e) ->
82
- ids = tabs.map (t) -> t.dataset.tab
83
- idx = ids.indexOf @active
84
- return if idx is -1
85
- move = (nextId) =>
86
- return unless nextId
87
- tabs.find((t) -> t.dataset.tab is nextId)?.focus()
88
- @select(nextId) if @activation is 'automatic'
89
- ARIA.rovingNav e, {
90
- next: => move(@_nextEnabled(ids, idx, 1))
91
- prev: => move(@_nextEnabled(ids, idx, -1))
92
- first: => move(@_nextEnabled(ids, ids.length - 1, 1))
93
- last: => move(@_nextEnabled(ids, 0, -1))
94
- select: => @select(ids[idx]) if @activation is 'manual'
95
- }, @orientation
96
-
97
- render
98
- .
99
- div role: "tablist", aria-orientation: @orientation, data-activation-direction: activationDirection, @keydown: @onKeydown
100
- for tab in tabs
101
- button role: "tab"
102
- id: "#{_id}-tab-#{tab.dataset.tab}"
103
- aria-selected: tab.dataset.tab is @active
104
- aria-controls: "#{_id}-panel-#{tab.dataset.tab}"
105
- aria-disabled: @_isDisabled(tab)?!
106
- tabindex: if @_isDisabled(tab) then '-1' else (tab.dataset.tab is @active ? '0' : '-1')
107
- $active: (tab.dataset.tab is @active)?!
108
- $disabled: @_isDisabled(tab)?!
109
- @click: (=> @select(tab.dataset.tab) unless @_isDisabled(tab))
110
- = tab.textContent
111
-
112
- . ref: "_content"
113
- slot
@@ -1,48 +0,0 @@
1
- # Textarea — accessible headless auto-resizing text area
2
- #
3
- # Tracks focus, validation, and disabled state via data attributes.
4
- # Optional auto-resize adjusts height to fit content. Ships zero CSS.
5
- #
6
- # Usage:
7
- # Textarea value <=> bio, placeholder: "Tell us about yourself"
8
- # Textarea value <=> notes, autoResize: true, rows: 3
9
-
10
- export Textarea = component
11
- @value:: string := ""
12
- @placeholder:: string := ""
13
- @disabled:: boolean := false
14
- @required:: boolean := false
15
- @rows:: number := 3
16
- @autoResize:: boolean := false
17
-
18
- focused := false
19
- touched := false
20
-
21
- onInput: (e) ->
22
- @value = e.target.value
23
- @_resize(e.target) if @autoResize
24
-
25
- onFocus: -> focused = true
26
- onBlur: ->
27
- focused = false
28
- touched = true
29
-
30
- _resize: (el) ->
31
- el.style.height = 'auto'
32
- el.style.height = "#{el.scrollHeight}px"
33
-
34
- mounted: ->
35
- @_resize(@_root) if @autoResize and @value
36
-
37
- render
38
- textarea ref: "_root", value: @value, placeholder: @placeholder, rows: @rows
39
- disabled: @disabled
40
- required: @required
41
- aria-disabled: @disabled?!
42
- aria-required: @required?!
43
- $disabled: @disabled?!
44
- $focused: focused?!
45
- $touched: touched?!
46
- @input: @onInput
47
- @focusin: @onFocus
48
- @focusout: @onBlur
package/docs/ui/toast.rip DELETED
@@ -1,87 +0,0 @@
1
- # Toast — accessible headless toast notification system
2
- #
3
- # Managed toast system with stacking, timer pause on hover, and promise support.
4
- # Uses ARIA live region for screen reader announcements. Ships zero CSS.
5
- #
6
- # Usage:
7
- # toasts := []
8
- #
9
- # # Add a toast — reactive assignment is the API
10
- # toasts = [...toasts, { message: "Saved!", type: "success" }]
11
- #
12
- # # Dismiss — filter it out
13
- # toasts = toasts.filter (t) -> t isnt target
14
- #
15
- # # Clear all
16
- # toasts = []
17
- #
18
- # # In render block
19
- # ToastViewport toasts <=> toasts
20
-
21
- export ToastViewport = component
22
- @toasts:: any[] := []
23
- @placement:: "top-left" | "top-right" | "bottom-left" | "bottom-right" := "bottom-right"
24
-
25
- _onDismiss: (toast) ->
26
- @toasts = @toasts.filter (t) -> t isnt toast
27
-
28
- render
29
- div role: "region", aria-label: "Notifications", $placement: @placement
30
- for toast in @toasts
31
- Toast toast: toast, @dismiss: (e) => @_onDismiss(e.detail)
32
-
33
- export Toast = component
34
- @toast:: Record<string, any> := {}
35
-
36
- leaving := false
37
- _timer := null
38
- _remaining = 0
39
- _started = 0
40
-
41
- _startTimer: ->
42
- dur = @toast.duration ?? 4000
43
- return unless dur > 0
44
- _remaining = dur
45
- _started = Date.now()
46
- _timer = setTimeout => @dismiss(), dur
47
-
48
- _pauseTimer: ->
49
- return unless _timer
50
- clearTimeout _timer
51
- _remaining -= Date.now() - _started
52
- _timer = null
53
-
54
- _resumeTimer: ->
55
- return if _timer or _remaining <= 0
56
- _started = Date.now()
57
- _timer = setTimeout => @dismiss(), _remaining
58
-
59
- mounted: -> @_startTimer()
60
-
61
- beforeUnmount: ->
62
- clearTimeout _timer if _timer
63
-
64
- dismiss: ->
65
- leaving = true
66
- setTimeout =>
67
- @emit 'dismiss', @toast
68
- , 200
69
-
70
- render
71
- div role: (if @toast.type is 'error' then 'alert' else 'status'),
72
- aria-live: (if @toast.type is 'error' then 'assertive' else 'polite'),
73
- $type: @toast.type ?? 'info',
74
- $leaving: leaving?!,
75
- @mouseenter: @_pauseTimer,
76
- @mouseleave: @_resumeTimer,
77
- @focusin: @_pauseTimer,
78
- @focusout: @_resumeTimer
79
- .
80
- if @toast.title
81
- strong @toast.title
82
- span @toast.message
83
- if @toast.action
84
- button @click: @toast.action.onClick
85
- @toast.action.label or 'Action'
86
- button aria-label: "Dismiss", @click: @dismiss
87
- "✕"
@@ -1,71 +0,0 @@
1
- # ToggleGroup — accessible headless toggle group
2
- #
3
- # A set of two-state buttons where one or more can be pressed.
4
- # Set @multiple to false for single-select (radio-like) behavior.
5
- # Ships zero CSS.
6
- #
7
- # Usage:
8
- # ToggleGroup value <=> alignment
9
- # div $value: "left", "Left"
10
- # div $value: "center", "Center"
11
- # div $value: "right", "Right"
12
-
13
- export ToggleGroup = component
14
- @value:: any := null
15
- @disabled:: boolean := false
16
- @multiple:: boolean := false
17
- @orientation:: "horizontal" | "vertical" := "horizontal"
18
-
19
- _items ~=
20
- return [] unless @_slot
21
- Array.from(@_slot.querySelectorAll('[data-value]') or [])
22
-
23
- _isPressed: (item) ->
24
- val = item.dataset.value
25
- if @multiple
26
- Array.isArray(@value) and val in @value
27
- else
28
- val is @value
29
-
30
- _toggle: (val) ->
31
- return if @disabled
32
- if @multiple
33
- arr = if Array.isArray(@value) then [...@value] else []
34
- if val in arr
35
- arr = arr.filter (v) -> v isnt val
36
- else
37
- arr.push val
38
- @value = arr
39
- else
40
- @value = if val is @value then null else val
41
- @emit 'change', @value
42
-
43
- onKeydown: (e) ->
44
- opts = @_root?.querySelectorAll('[data-value]')
45
- return unless opts?.length
46
- focused = Array.from(opts).indexOf(document.activeElement)
47
- return if focused < 0
48
- len = opts.length
49
- ARIA.rovingNav e, {
50
- next: => opts[(focused + 1) %% len]?.focus()
51
- prev: => opts[(focused - 1) %% len]?.focus()
52
- first: => opts[0]?.focus()
53
- last: => opts[len - 1]?.focus()
54
- }, 'both'
55
-
56
- render
57
- div ref: "_root", role: "group", aria-orientation: @orientation
58
- $orientation: @orientation
59
- $disabled: @disabled?!
60
-
61
- . ref: "_slot", style: "display:none"
62
- slot
63
-
64
- for item, idx in _items
65
- button tabindex: (if idx is 0 then "0" else "-1")
66
- aria-pressed: !!@_isPressed(item)
67
- $pressed: @_isPressed(item)?!
68
- $disabled: @disabled?!
69
- $value: item.dataset.value
70
- @click: (=> @_toggle(item.dataset.value))
71
- = item.textContent
@@ -1,24 +0,0 @@
1
- # Toggle — accessible headless toggle button
2
- #
3
- # Stateful button that toggles pressed state on click.
4
- # Ships zero CSS.
5
- #
6
- # Usage:
7
- # Toggle pressed <=> isBold
8
- # "Bold"
9
-
10
- export Toggle = component
11
- @pressed:: boolean := false
12
- @disabled:: boolean := false
13
-
14
- onClick: ->
15
- return if @disabled
16
- @pressed = not @pressed
17
- @emit 'change', @pressed
18
-
19
- render
20
- button aria-pressed: !!@pressed
21
- aria-disabled: @disabled?!
22
- $pressed: @pressed?!
23
- $disabled: @disabled?!
24
- slot
@@ -1,38 +0,0 @@
1
- # Toolbar — accessible headless toolbar
2
- #
3
- # Groups interactive controls with roving tabindex keyboard navigation.
4
- # Arrow keys move focus between focusable children. Ships zero CSS.
5
- #
6
- # Usage:
7
- # Toolbar
8
- # Button @click: save, "Save"
9
- # Button @click: undo, "Undo"
10
- # Separator orientation: "vertical"
11
- # Toggle pressed <=> isBold, "Bold"
12
-
13
- export Toolbar = component
14
- @orientation:: "horizontal" | "vertical" := "horizontal"
15
- @label:: string := ""
16
-
17
- _getFocusable: ->
18
- return [] unless @_root
19
- Array.from(@_root.querySelectorAll('button, [tabindex], input, select, textarea')).filter (el) ->
20
- not el.disabled and el.offsetParent isnt null
21
-
22
- onKeydown: (e) ->
23
- els = @_getFocusable()
24
- return unless els.length
25
- focused = els.indexOf(document.activeElement)
26
- return if focused < 0
27
- len = els.length
28
- ARIA.rovingNav e, {
29
- next: => els[(focused + 1) %% len]?.focus()
30
- prev: => els[(focused - 1) %% len]?.focus()
31
- first: => els[0]?.focus()
32
- last: => els[len - 1]?.focus()
33
- }, @orientation
34
-
35
- render
36
- div role: "toolbar", aria-label: @label or undefined, aria-orientation: @orientation
37
- $orientation: @orientation
38
- slot
@@ -1,85 +0,0 @@
1
- # Tooltip — accessible headless tooltip with delay and positioning
2
- #
3
- # Shows on hover/focus with configurable delay. Uses aria-describedby and
4
- # native `popover="hint"` for top-layer behavior.
5
- # Exposes $open, $entering, $exiting. Ships zero CSS.
6
- #
7
- # Usage:
8
- # Tooltip text: "Helpful info", placement: "top"
9
- # button "Hover me"
10
-
11
- lastCloseTime = 0
12
- GROUP_TIMEOUT = 400
13
-
14
- export Tooltip = component
15
- @text:: string := ""
16
- @placement:: "top" | "top-start" | "top-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "right" := "top"
17
- @delay:: number := 300
18
- @offset:: number := 6
19
- @hoverable:: boolean := false
20
-
21
- open := false
22
- entering := false
23
- exiting := false
24
- _showTimer := null
25
- _hideTimer := null
26
- _id =! "tip-#{Math.random().toString(36).slice(2, 8)}"
27
-
28
- _applyPlacement: ->
29
- [side, align] = @placement.split('-')
30
- align ??= 'center'
31
- ARIA.position @_trigger, @_tip, placement: "#{side} #{align}", offset: @offset
32
-
33
- show: ->
34
- clearTimeout _hideTimer if _hideTimer
35
- delay = if (Date.now() - lastCloseTime) < GROUP_TIMEOUT then 0 else @delay
36
- _showTimer = setTimeout =>
37
- open = true
38
- entering = true
39
- setTimeout =>
40
- entering = false
41
- , 0
42
- , delay
43
-
44
- hide: ->
45
- clearTimeout _showTimer if _showTimer
46
- exiting = true
47
- _hideTimer = setTimeout =>
48
- open = false
49
- exiting = false
50
- lastCloseTime = Date.now()
51
- , 150
52
-
53
- _cancelHide: ->
54
- clearTimeout _hideTimer if _hideTimer
55
- exiting = false
56
-
57
- beforeUnmount: ->
58
- clearTimeout _showTimer if _showTimer
59
- clearTimeout _hideTimer if _hideTimer
60
-
61
- ~>
62
- if @_tip
63
- @_tip.setAttribute 'popover', 'hint'
64
- @_applyPlacement()
65
- if open then @_tip.setAttribute('data-open', '') else @_tip.removeAttribute('data-open')
66
- ARIA.bindPopover open, (=> @_tip), ((isOpen) => open = isOpen), (=> @_trigger)
67
-
68
- render
69
- .
70
- div ref: "_trigger"
71
- aria-describedby: open ? _id : undefined
72
- @mouseenter: @show
73
- @mouseleave: @hide
74
- @focusin: @show
75
- @focusout: @hide
76
- slot
77
-
78
- div ref: "_tip", id: _id, role: "tooltip", style: "position:fixed;margin:0;inset:auto"
79
- $open: open?!
80
- $entering: entering?!
81
- $exiting: exiting?!
82
- $placement: @placement
83
- @mouseenter: (=> @_cancelHide() if @hoverable)
84
- @mouseleave: (=> @hide() if @hoverable)
85
- @text