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.
Files changed (48) hide show
  1. package/README.md +1 -1
  2. package/docs/dist/rip.js +26 -6
  3. package/docs/dist/rip.min.js +15 -15
  4. package/docs/dist/rip.min.js.br +0 -0
  5. package/docs/index.html +4 -0
  6. package/docs/ui/accordion.rip +113 -0
  7. package/docs/ui/autocomplete.rip +141 -0
  8. package/docs/ui/avatar.rip +37 -0
  9. package/docs/ui/button.rip +23 -0
  10. package/docs/ui/checkbox-group.rip +65 -0
  11. package/docs/ui/checkbox.rip +33 -0
  12. package/docs/ui/combobox.rip +155 -0
  13. package/docs/ui/context-menu.rip +105 -0
  14. package/docs/ui/date-picker.rip +214 -0
  15. package/docs/ui/dialog.rip +107 -0
  16. package/docs/ui/drawer.rip +79 -0
  17. package/docs/ui/editable-value.rip +80 -0
  18. package/docs/ui/field.rip +53 -0
  19. package/docs/ui/fieldset.rip +22 -0
  20. package/docs/ui/form.rip +39 -0
  21. package/docs/ui/grid.rip +901 -0
  22. package/docs/ui/index.css +1379 -0
  23. package/docs/ui/index.html +2097 -0
  24. package/docs/ui/input.rip +36 -0
  25. package/docs/ui/menu.rip +162 -0
  26. package/docs/ui/menubar.rip +155 -0
  27. package/docs/ui/meter.rip +36 -0
  28. package/docs/ui/multi-select.rip +158 -0
  29. package/docs/ui/nav-menu.rip +129 -0
  30. package/docs/ui/number-field.rip +162 -0
  31. package/docs/ui/otp-field.rip +89 -0
  32. package/docs/ui/popover.rip +143 -0
  33. package/docs/ui/preview-card.rip +73 -0
  34. package/docs/ui/progress.rip +25 -0
  35. package/docs/ui/radio-group.rip +67 -0
  36. package/docs/ui/scroll-area.rip +145 -0
  37. package/docs/ui/select.rip +184 -0
  38. package/docs/ui/separator.rip +17 -0
  39. package/docs/ui/slider.rip +165 -0
  40. package/docs/ui/tabs.rip +124 -0
  41. package/docs/ui/toast.rip +87 -0
  42. package/docs/ui/toggle-group.rip +78 -0
  43. package/docs/ui/toggle.rip +24 -0
  44. package/docs/ui/toolbar.rip +46 -0
  45. package/docs/ui/tooltip.rip +115 -0
  46. package/package.json +2 -1
  47. package/src/compiler.js +1 -1
  48. 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.64",
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 = tag === 'PROPERTY' && i > 0 && (tokens[i - 1][0] === '.' || tokens[i - 1][0] === '#');
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
- this._createLines.push(`${elVar}.setAttribute('${key}', ${valueCode});`);
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
  }