rip-lang 3.13.64 → 3.13.66
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 +1 -1
- package/docs/dist/rip.js +26 -6
- package/docs/dist/rip.min.js +15 -15
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/index.html +4 -0
- package/docs/ui/accordion.rip +113 -0
- package/docs/ui/autocomplete.rip +141 -0
- package/docs/ui/avatar.rip +37 -0
- package/docs/ui/button.rip +23 -0
- package/docs/ui/checkbox-group.rip +65 -0
- package/docs/ui/checkbox.rip +33 -0
- package/docs/ui/combobox.rip +155 -0
- package/docs/ui/context-menu.rip +105 -0
- package/docs/ui/date-picker.rip +214 -0
- package/docs/ui/dialog.rip +107 -0
- package/docs/ui/drawer.rip +79 -0
- package/docs/ui/editable-value.rip +80 -0
- package/docs/ui/field.rip +53 -0
- package/docs/ui/fieldset.rip +22 -0
- package/docs/ui/form.rip +39 -0
- package/docs/ui/grid.rip +901 -0
- package/docs/ui/index.css +1379 -0
- package/docs/ui/index.html +2097 -0
- package/docs/ui/input.rip +36 -0
- package/docs/ui/menu.rip +162 -0
- package/docs/ui/menubar.rip +155 -0
- package/docs/ui/meter.rip +36 -0
- package/docs/ui/multi-select.rip +158 -0
- package/docs/ui/nav-menu.rip +129 -0
- package/docs/ui/number-field.rip +162 -0
- package/docs/ui/otp-field.rip +89 -0
- package/docs/ui/popover.rip +143 -0
- package/docs/ui/preview-card.rip +73 -0
- package/docs/ui/progress.rip +25 -0
- package/docs/ui/radio-group.rip +67 -0
- package/docs/ui/scroll-area.rip +145 -0
- package/docs/ui/select.rip +184 -0
- package/docs/ui/separator.rip +17 -0
- package/docs/ui/slider.rip +165 -0
- package/docs/ui/tabs.rip +124 -0
- package/docs/ui/toast.rip +87 -0
- package/docs/ui/toggle-group.rip +78 -0
- package/docs/ui/toggle.rip +24 -0
- package/docs/ui/toolbar.rip +46 -0
- package/docs/ui/tooltip.rip +115 -0
- package/package.json +2 -1
- package/src/compiler.js +1 -1
- package/src/components.js +27 -3
|
@@ -0,0 +1,46 @@
|
|
|
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'
|
|
15
|
+
@label := ''
|
|
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
|
+
horiz = @orientation is 'horizontal'
|
|
29
|
+
switch e.key
|
|
30
|
+
when (if horiz then 'ArrowRight' else 'ArrowDown')
|
|
31
|
+
e.preventDefault()
|
|
32
|
+
els[(focused + 1) %% len]?.focus()
|
|
33
|
+
when (if horiz then 'ArrowLeft' else 'ArrowUp')
|
|
34
|
+
e.preventDefault()
|
|
35
|
+
els[(focused - 1) %% len]?.focus()
|
|
36
|
+
when 'Home'
|
|
37
|
+
e.preventDefault()
|
|
38
|
+
els[0]?.focus()
|
|
39
|
+
when 'End'
|
|
40
|
+
e.preventDefault()
|
|
41
|
+
els[len - 1]?.focus()
|
|
42
|
+
|
|
43
|
+
render
|
|
44
|
+
div role: "toolbar", aria-label: @label or undefined, aria-orientation: @orientation
|
|
45
|
+
$orientation: @orientation
|
|
46
|
+
slot
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Tooltip — accessible headless tooltip with delay and positioning
|
|
2
|
+
#
|
|
3
|
+
# Shows on hover/focus with configurable delay. Uses aria-describedby.
|
|
4
|
+
# Exposes $open, $entering, $exiting. Ships zero CSS.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# Tooltip text: "Helpful info", placement: "top"
|
|
8
|
+
# button "Hover me"
|
|
9
|
+
|
|
10
|
+
lastCloseTime = 0
|
|
11
|
+
GROUP_TIMEOUT = 400
|
|
12
|
+
|
|
13
|
+
export Tooltip = component
|
|
14
|
+
@text := ''
|
|
15
|
+
@placement := 'top'
|
|
16
|
+
@delay := 300
|
|
17
|
+
@offset := 6
|
|
18
|
+
@hoverable := false
|
|
19
|
+
|
|
20
|
+
open := false
|
|
21
|
+
entering := false
|
|
22
|
+
exiting := false
|
|
23
|
+
_showTimer := null
|
|
24
|
+
_hideTimer := null
|
|
25
|
+
_id =! "tip-#{Math.random().toString(36).slice(2, 8)}"
|
|
26
|
+
|
|
27
|
+
show: ->
|
|
28
|
+
clearTimeout _hideTimer if _hideTimer
|
|
29
|
+
delay = if (Date.now() - lastCloseTime) < GROUP_TIMEOUT then 0 else @delay
|
|
30
|
+
_showTimer = setTimeout =>
|
|
31
|
+
open = true
|
|
32
|
+
entering = true
|
|
33
|
+
setTimeout =>
|
|
34
|
+
entering = false
|
|
35
|
+
@_position()
|
|
36
|
+
, 0
|
|
37
|
+
, delay
|
|
38
|
+
|
|
39
|
+
hide: ->
|
|
40
|
+
clearTimeout _showTimer if _showTimer
|
|
41
|
+
exiting = true
|
|
42
|
+
_hideTimer = setTimeout =>
|
|
43
|
+
open = false
|
|
44
|
+
exiting = false
|
|
45
|
+
lastCloseTime = Date.now()
|
|
46
|
+
, 150
|
|
47
|
+
|
|
48
|
+
_cancelHide: ->
|
|
49
|
+
clearTimeout _hideTimer if _hideTimer
|
|
50
|
+
exiting = false
|
|
51
|
+
|
|
52
|
+
_position: ->
|
|
53
|
+
return unless @_trigger and @_tip
|
|
54
|
+
tr = @_trigger.getBoundingClientRect()
|
|
55
|
+
fl = @_tip.getBoundingClientRect()
|
|
56
|
+
[side, align] = @placement.split('-')
|
|
57
|
+
align ?= 'center'
|
|
58
|
+
gap = @offset
|
|
59
|
+
|
|
60
|
+
x = switch side
|
|
61
|
+
when 'bottom', 'top'
|
|
62
|
+
switch align
|
|
63
|
+
when 'start' then tr.left
|
|
64
|
+
when 'end' then tr.right - fl.width
|
|
65
|
+
else tr.left + (tr.width - fl.width) / 2
|
|
66
|
+
when 'right' then tr.right + gap
|
|
67
|
+
when 'left' then tr.left - fl.width - gap
|
|
68
|
+
|
|
69
|
+
y = switch side
|
|
70
|
+
when 'bottom' then tr.bottom + gap
|
|
71
|
+
when 'top' then tr.top - fl.height - gap
|
|
72
|
+
when 'left', 'right'
|
|
73
|
+
switch align
|
|
74
|
+
when 'start' then tr.top
|
|
75
|
+
when 'end' then tr.bottom - fl.height
|
|
76
|
+
else tr.top + (tr.height - fl.height) / 2
|
|
77
|
+
|
|
78
|
+
if side is 'bottom' and y + fl.height > window.innerHeight
|
|
79
|
+
y = tr.top - fl.height - gap
|
|
80
|
+
if side is 'top' and y < 0
|
|
81
|
+
y = tr.bottom + gap
|
|
82
|
+
if side is 'right' and x + fl.width > window.innerWidth
|
|
83
|
+
x = tr.left - fl.width - gap
|
|
84
|
+
if side is 'left' and x < 0
|
|
85
|
+
x = tr.right + gap
|
|
86
|
+
|
|
87
|
+
x = Math.max(4, Math.min(x, window.innerWidth - fl.width - 4))
|
|
88
|
+
|
|
89
|
+
@_tip.style.position = 'fixed'
|
|
90
|
+
@_tip.style.left = "#{x}px"
|
|
91
|
+
@_tip.style.top = "#{y}px"
|
|
92
|
+
|
|
93
|
+
beforeUnmount: ->
|
|
94
|
+
clearTimeout _showTimer if _showTimer
|
|
95
|
+
clearTimeout _hideTimer if _hideTimer
|
|
96
|
+
|
|
97
|
+
render
|
|
98
|
+
.
|
|
99
|
+
div ref: "_trigger"
|
|
100
|
+
aria-describedby: open ? _id : undefined
|
|
101
|
+
@mouseenter: @show
|
|
102
|
+
@mouseleave: @hide
|
|
103
|
+
@focusin: @show
|
|
104
|
+
@focusout: @hide
|
|
105
|
+
slot
|
|
106
|
+
|
|
107
|
+
if open
|
|
108
|
+
div ref: "_tip", id: _id, role: "tooltip", style: "position:fixed"
|
|
109
|
+
$open: true
|
|
110
|
+
$entering: entering?!
|
|
111
|
+
$exiting: exiting?!
|
|
112
|
+
$placement: @placement
|
|
113
|
+
@mouseenter: (=> @_cancelHide() if @hoverable)
|
|
114
|
+
@mouseleave: (=> @hide() if @hoverable)
|
|
115
|
+
@text
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rip-lang",
|
|
3
|
-
"version": "3.13.
|
|
3
|
+
"version": "3.13.66",
|
|
4
4
|
"description": "A modern language that compiles to JavaScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/compiler.js",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "bun scripts/build.js",
|
|
35
35
|
"bump": "bun scripts/bump.js",
|
|
36
|
+
"gallery": "bun scripts/gallery.js",
|
|
36
37
|
"parser": "bun src/grammar/solar.rip -o src/parser.js src/grammar/grammar.rip",
|
|
37
38
|
"serve": "bun scripts/serve.js",
|
|
38
39
|
"test": "bun test/runner.js",
|
package/src/compiler.js
CHANGED
|
@@ -1072,7 +1072,7 @@ export class CodeGenerator {
|
|
|
1072
1072
|
generateReturn(head, rest, context, sexpr) {
|
|
1073
1073
|
if (rest.length === 0) return 'return';
|
|
1074
1074
|
let [expr] = rest;
|
|
1075
|
-
if (this.sideEffectOnly) return 'return';
|
|
1075
|
+
if (this.sideEffectOnly && !(this.is(expr, '->') || this.is(expr, '=>'))) return 'return';
|
|
1076
1076
|
|
|
1077
1077
|
if (this.is(expr, 'if')) {
|
|
1078
1078
|
let [, condition, body, ...elseParts] = expr;
|
package/src/components.js
CHANGED
|
@@ -489,7 +489,19 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
489
489
|
}
|
|
490
490
|
|
|
491
491
|
if (isTemplateElement) {
|
|
492
|
-
let isClassOrIdTail =
|
|
492
|
+
let isClassOrIdTail = false;
|
|
493
|
+
if (tag === 'PROPERTY' && i > 0 && tokens[i - 1][0] === '.') {
|
|
494
|
+
// Trace backward through the .PROPERTY chain to find its root —
|
|
495
|
+
// only a CSS class tail if the chain starts from a line-starting template tag
|
|
496
|
+
let j = i;
|
|
497
|
+
while (j >= 2 && tokens[j - 1][0] === '.' && tokens[j - 2][0] === 'PROPERTY') j -= 2;
|
|
498
|
+
if (j >= 2 && tokens[j - 1][0] === '.' && tokens[j - 2][0] === 'IDENTIFIER' && isTemplateTag(tokens[j - 2][1])) {
|
|
499
|
+
let before = j >= 3 ? tokens[j - 3][0] : null;
|
|
500
|
+
if (!before || before === 'INDENT' || before === 'OUTDENT' || before === 'TERMINATOR' || before === 'RENDER') {
|
|
501
|
+
isClassOrIdTail = true;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
493
505
|
let isBareTag = isClsxCallEnd || (tag === 'IDENTIFIER' && isTemplateTag(token[1])) || isClassOrIdTail;
|
|
494
506
|
|
|
495
507
|
if (isBareTag) {
|
|
@@ -1349,7 +1361,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1349
1361
|
}
|
|
1350
1362
|
|
|
1351
1363
|
// Regular attribute
|
|
1352
|
-
if (typeof key === 'string') {
|
|
1364
|
+
if (typeof key === 'string' || key instanceof String) {
|
|
1353
1365
|
// Strip quotes from string keys (e.g., "data-slot" → data-slot)
|
|
1354
1366
|
if (key.startsWith('"') && key.endsWith('"')) {
|
|
1355
1367
|
key = key.slice(1, -1);
|
|
@@ -1442,7 +1454,11 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1442
1454
|
this._pushEffect(`${elVar}.setAttribute('${key}', ${valueCode});`);
|
|
1443
1455
|
}
|
|
1444
1456
|
} else {
|
|
1445
|
-
|
|
1457
|
+
if (Array.isArray(value) && value[0] === 'presence') {
|
|
1458
|
+
this._createLines.push(`{ const __v = ${valueCode}; if (__v != null) ${elVar}.setAttribute('${key}', __v); }`);
|
|
1459
|
+
} else {
|
|
1460
|
+
this._createLines.push(`${elVar}.setAttribute('${key}', ${valueCode});`);
|
|
1461
|
+
}
|
|
1446
1462
|
}
|
|
1447
1463
|
}
|
|
1448
1464
|
}
|
|
@@ -1889,6 +1905,14 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1889
1905
|
return true;
|
|
1890
1906
|
}
|
|
1891
1907
|
|
|
1908
|
+
// Method call on component: [['.', 'this', method], ...args]
|
|
1909
|
+
// Methods may read reactive state internally — treat as reactive so the
|
|
1910
|
+
// call gets wrapped in __effect and re-runs when dependencies change.
|
|
1911
|
+
if (Array.isArray(sexpr[0]) && sexpr[0][0] === '.' && sexpr[0][1] === 'this') {
|
|
1912
|
+
const name = _str(sexpr[0][2]);
|
|
1913
|
+
if (name && this.componentMembers?.has(name)) return true;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1892
1916
|
for (const child of sexpr) {
|
|
1893
1917
|
if (this.hasReactiveDeps(child)) return true;
|
|
1894
1918
|
}
|