rip-lang 3.13.93 → 3.13.95

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 (63) hide show
  1. package/README.md +1 -1
  2. package/docs/dist/rip.js +142 -38
  3. package/docs/dist/rip.min.js +174 -174
  4. package/docs/dist/rip.min.js.br +0 -0
  5. package/package.json +1 -1
  6. package/src/ui.rip +65 -0
  7. package/docs/ui/accordion.rip +0 -113
  8. package/docs/ui/alert-dialog.rip +0 -96
  9. package/docs/ui/autocomplete.rip +0 -141
  10. package/docs/ui/avatar.rip +0 -37
  11. package/docs/ui/badge.rip +0 -15
  12. package/docs/ui/breadcrumb.rip +0 -46
  13. package/docs/ui/button-group.rip +0 -26
  14. package/docs/ui/button.rip +0 -23
  15. package/docs/ui/card.rip +0 -25
  16. package/docs/ui/carousel.rip +0 -110
  17. package/docs/ui/checkbox-group.rip +0 -65
  18. package/docs/ui/checkbox.rip +0 -33
  19. package/docs/ui/collapsible.rip +0 -50
  20. package/docs/ui/combobox.rip +0 -155
  21. package/docs/ui/context-menu.rip +0 -105
  22. package/docs/ui/date-picker.rip +0 -214
  23. package/docs/ui/dialog.rip +0 -107
  24. package/docs/ui/drawer.rip +0 -79
  25. package/docs/ui/editable-value.rip +0 -80
  26. package/docs/ui/field.rip +0 -53
  27. package/docs/ui/fieldset.rip +0 -22
  28. package/docs/ui/form.rip +0 -39
  29. package/docs/ui/grid.rip +0 -901
  30. package/docs/ui/hljs-rip.js +0 -209
  31. package/docs/ui/index.css +0 -1772
  32. package/docs/ui/index.html +0 -2433
  33. package/docs/ui/input-group.rip +0 -28
  34. package/docs/ui/input.rip +0 -36
  35. package/docs/ui/label.rip +0 -16
  36. package/docs/ui/menu.rip +0 -162
  37. package/docs/ui/menubar.rip +0 -155
  38. package/docs/ui/meter.rip +0 -36
  39. package/docs/ui/multi-select.rip +0 -158
  40. package/docs/ui/native-select.rip +0 -32
  41. package/docs/ui/nav-menu.rip +0 -129
  42. package/docs/ui/number-field.rip +0 -162
  43. package/docs/ui/otp-field.rip +0 -89
  44. package/docs/ui/pagination.rip +0 -123
  45. package/docs/ui/popover.rip +0 -143
  46. package/docs/ui/preview-card.rip +0 -73
  47. package/docs/ui/progress.rip +0 -25
  48. package/docs/ui/radio-group.rip +0 -67
  49. package/docs/ui/resizable.rip +0 -123
  50. package/docs/ui/scroll-area.rip +0 -145
  51. package/docs/ui/select.rip +0 -184
  52. package/docs/ui/separator.rip +0 -17
  53. package/docs/ui/skeleton.rip +0 -22
  54. package/docs/ui/slider.rip +0 -165
  55. package/docs/ui/spinner.rip +0 -17
  56. package/docs/ui/table.rip +0 -27
  57. package/docs/ui/tabs.rip +0 -124
  58. package/docs/ui/textarea.rip +0 -48
  59. package/docs/ui/toast.rip +0 -87
  60. package/docs/ui/toggle-group.rip +0 -78
  61. package/docs/ui/toggle.rip +0 -24
  62. package/docs/ui/toolbar.rip +0 -46
  63. package/docs/ui/tooltip.rip +0 -115
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rip-lang",
3
- "version": "3.13.93",
3
+ "version": "3.13.95",
4
4
  "description": "A modern language that compiles to JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
package/src/ui.rip CHANGED
@@ -1013,3 +1013,68 @@ export launch = (appBase = '', opts = {}) ->
1013
1013
  version: '0.3.0'
1014
1014
 
1015
1015
  { app, components: appComponents, router, renderer }
1016
+
1017
+ # ==============================================================================
1018
+ # ARIA — keyboard navigation and popup lifecycle utilities for UI components
1019
+ #
1020
+ # Provides the WAI-ARIA keyboard interaction patterns used by headless widgets.
1021
+ # Registered on globalThis so any component can use them without explicit imports.
1022
+ #
1023
+ # ARIA.listNav(e, handlers) — popup lists (listbox, menu, combobox)
1024
+ # ARIA.rovingNav(e, handlers, orient) — inline composites (radiogroup, tabs, toolbar)
1025
+ # ARIA.popupDismiss(open, popup, close, els) — close on outside click or scroll
1026
+ #
1027
+ # Both nav handlers:
1028
+ # - Guard against IME composition events (e.which === 229, CJK input)
1029
+ # - Call e.preventDefault() + e.stopPropagation() for handled keys
1030
+ # - Only invoke a handler if it is provided (all keys are optional)
1031
+ # - Alias PageUp/PageDown to first/last (fn+Up/Down on macOS)
1032
+ #
1033
+ # ARIA: https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/
1034
+ # ==============================================================================
1035
+
1036
+ _ariaNAV = (e, fn) ->
1037
+ return unless fn
1038
+ e.preventDefault()
1039
+ e.stopPropagation()
1040
+ fn()
1041
+
1042
+ _ariaListNav = (e, h) ->
1043
+ return if e.which is 229 # IME guard: suppress CJK composition events
1044
+ switch e.key
1045
+ when 'ArrowDown' then _ariaNAV e, h.next
1046
+ when 'ArrowUp' then _ariaNAV e, h.prev
1047
+ when 'Home', 'PageUp' then _ariaNAV e, h.first
1048
+ when 'End', 'PageDown' then _ariaNAV e, h.last
1049
+ when 'Enter', ' ' then _ariaNAV e, h.select
1050
+ when 'Escape' then _ariaNAV e, h.dismiss
1051
+ when 'Tab' then h.tab?() # no preventDefault: allow natural focus movement
1052
+ else h.char?(e.key) if e.key.length is 1 # printable chars: typeahead
1053
+
1054
+ _ariaPopupDismiss = (open, popup, close, els = []) ->
1055
+ return unless open
1056
+ inside = [popup, ...els]
1057
+ onDown = (e) => close() unless inside.some (el) -> el?.contains(e.target)
1058
+ onScroll = (e) => close() unless popup?.contains(e.target)
1059
+ document.addEventListener 'mousedown', onDown
1060
+ window.addEventListener 'scroll', onScroll, true
1061
+ ->
1062
+ document.removeEventListener 'mousedown', onDown
1063
+ window.removeEventListener 'scroll', onScroll, true
1064
+
1065
+ _ariaRovingNav = (e, h, orientation = 'vertical') ->
1066
+ return if e.which is 229 # IME guard
1067
+ vert = orientation isnt 'horizontal'
1068
+ horz = orientation isnt 'vertical'
1069
+ switch e.key
1070
+ when 'ArrowDown' then _ariaNAV e, h.next if vert
1071
+ when 'ArrowUp' then _ariaNAV e, h.prev if vert
1072
+ when 'ArrowRight' then _ariaNAV e, h.next if horz
1073
+ when 'ArrowLeft' then _ariaNAV e, h.prev if horz
1074
+ when 'Home', 'PageUp' then _ariaNAV e, h.first
1075
+ when 'End', 'PageDown' then _ariaNAV e, h.last
1076
+ when 'Enter', ' ' then _ariaNAV e, h.select
1077
+ when 'Escape' then _ariaNAV e, h.dismiss
1078
+
1079
+ globalThis.__aria ??= { listNav: _ariaListNav, rovingNav: _ariaRovingNav, popupDismiss: _ariaPopupDismiss }
1080
+ globalThis.ARIA ??= globalThis.__aria
@@ -1,113 +0,0 @@
1
- # Accordion — accessible headless expand/collapse widget
2
- #
3
- # Supports single or multiple expanded sections. Keyboard: Enter/Space to
4
- # toggle, ArrowDown/Up to move between triggers. Exposes $open on items.
5
- # Ships zero CSS.
6
- #
7
- # Usage:
8
- # Accordion multiple: false
9
- # div $item: "a"
10
- # button $trigger: true, "Section A"
11
- # div $content: true
12
- # p "Content A"
13
- # div $item: "b"
14
- # button $trigger: true, "Section B"
15
- # div $content: true
16
- # p "Content B"
17
-
18
- export Accordion = component
19
- @multiple := false
20
-
21
- openItems := new Set()
22
- _ready := false
23
- _id =! "acc-#{Math.random().toString(36).slice(2, 8)}"
24
-
25
- mounted: ->
26
- _ready = true
27
- @_content?.querySelectorAll('[data-trigger]').forEach (trigger) =>
28
- item = trigger.closest('[data-item]')
29
- return unless item
30
- id = item.dataset.item
31
- trigger.addEventListener 'click', =>
32
- return if item.hasAttribute('data-disabled')
33
- @toggle(id)
34
- trigger.addEventListener 'keydown', (e) => @onTriggerKeydown(e, id)
35
-
36
- ~>
37
- return unless _ready
38
- @_content?.querySelectorAll('[data-item]').forEach (item) =>
39
- id = item.dataset.item
40
- isOpen = openItems.has(id)
41
- item.toggleAttribute 'data-open', isOpen
42
- trigger = item.querySelector('[data-trigger]')
43
- content = item.querySelector('[data-content]')
44
- triggerId = "#{_id}-trigger-#{id}"
45
- panelId = "#{_id}-panel-#{id}"
46
- if trigger
47
- isDisabled = item.hasAttribute('data-disabled')
48
- trigger.id = triggerId
49
- trigger.setAttribute 'aria-expanded', isOpen
50
- trigger.setAttribute 'aria-controls', panelId
51
- trigger.setAttribute 'aria-disabled', isDisabled if isDisabled
52
- trigger.tabIndex = if isDisabled then -1 else 0
53
- if content
54
- content.id = panelId
55
- content.hidden = if isOpen then false else 'until-found'
56
- content.setAttribute 'role', 'region'
57
- content.setAttribute 'aria-labelledby', triggerId
58
- if isOpen
59
- rect = content.getBoundingClientRect()
60
- content.style.setProperty '--accordion-panel-height', "#{rect.height}px"
61
- content.style.setProperty '--accordion-panel-width', "#{rect.width}px"
62
-
63
- toggle: (id) ->
64
- if openItems.has(id)
65
- openItems.delete(id)
66
- else
67
- openItems.clear() unless @multiple
68
- openItems.add(id)
69
- openItems = new Set(openItems)
70
- @emit 'change', Array.from(openItems)
71
-
72
- isOpen: (id) ->
73
- openItems.has(id)
74
-
75
- onTriggerKeydown: (e, id) ->
76
- item = e.currentTarget.closest('[data-item]')
77
- return if item?.hasAttribute('data-disabled') and e.key in ['Enter', ' ']
78
- switch e.key
79
- when 'Enter', ' '
80
- e.preventDefault()
81
- @toggle(id)
82
- when 'ArrowDown'
83
- e.preventDefault()
84
- @_focusNext(1)
85
- when 'ArrowUp'
86
- e.preventDefault()
87
- @_focusNext(-1)
88
- when 'Home'
89
- e.preventDefault()
90
- @_focusTrigger(0)
91
- when 'End'
92
- e.preventDefault()
93
- @_focusTrigger(-1)
94
-
95
- _triggers: ->
96
- return [] unless @_content
97
- Array.from(@_content.querySelectorAll('[data-trigger]'))
98
-
99
- _focusNext: (dir) ->
100
- triggers = @_triggers()
101
- idx = triggers.indexOf(document.activeElement)
102
- return if idx is -1
103
- next = (idx + dir) %% triggers.length
104
- triggers[next]?.focus()
105
-
106
- _focusTrigger: (idx) ->
107
- triggers = @_triggers()
108
- target = if idx < 0 then triggers[triggers.length - 1] else triggers[idx]
109
- target?.focus()
110
-
111
- render
112
- div ref: "_content"
113
- slot
@@ -1,96 +0,0 @@
1
- # AlertDialog — accessible headless non-dismissable modal
2
- #
3
- # A Dialog variant that requires explicit user action to close.
4
- # Cannot be dismissed by clicking outside or pressing Escape.
5
- # Use for destructive confirmations, unsaved changes, etc.
6
- # Ships zero CSS.
7
- #
8
- # Usage:
9
- # AlertDialog open <=> showConfirm
10
- # h2 "Delete account?"
11
- # p "This action cannot be undone."
12
- # button @click: (=> showConfirm = false), "Cancel"
13
- # button @click: handleDelete, "Delete"
14
-
15
- alertDialogStack = []
16
-
17
- export AlertDialog = component
18
- @open := false
19
- @initialFocus := null
20
-
21
- _prevFocus = null
22
- _cleanupTrap = null
23
- _scrollY = 0
24
- _id =! "adlg-#{Math.random().toString(36).slice(2, 8)}"
25
-
26
- _wireAria: ->
27
- panel = @_panel
28
- return unless panel
29
- heading = panel.querySelector('h1,h2,h3,h4,h5,h6')
30
- if heading
31
- heading.id ?= "#{_id}-title"
32
- panel.setAttribute 'aria-labelledby', heading.id
33
- desc = panel.querySelector('p')
34
- if desc
35
- desc.id ?= "#{_id}-desc"
36
- panel.setAttribute 'aria-describedby', desc.id
37
-
38
- ~>
39
- if @open
40
- _prevFocus = document.activeElement
41
- _scrollY = window.scrollY
42
- alertDialogStack.push this
43
- document.body.style.position = 'fixed'
44
- document.body.style.top = "-#{_scrollY}px"
45
- document.body.style.width = '100%'
46
-
47
- setTimeout =>
48
- panel = @_panel
49
- if panel
50
- @_wireAria()
51
- if @initialFocus
52
- target = if typeof @initialFocus is 'string' then panel.querySelector(@initialFocus) else @initialFocus
53
- target?.focus()
54
- else
55
- focusable = panel.querySelectorAll 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'
56
- focusable[0]?.focus()
57
- _cleanupTrap = (e) ->
58
- return unless e.key is 'Tab'
59
- list = Array.from(panel.querySelectorAll('a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')).filter (f) -> f.offsetParent isnt null
60
- return unless list.length
61
- first = list[0]
62
- last = list[list.length - 1]
63
- if e.shiftKey
64
- if document.activeElement is first then (e.preventDefault(); last.focus())
65
- else
66
- if document.activeElement is last then (e.preventDefault(); first.focus())
67
- panel.addEventListener 'keydown', _cleanupTrap
68
- , 0
69
-
70
- return ->
71
- idx = alertDialogStack.indexOf this
72
- alertDialogStack.splice(idx, 1) if idx >= 0
73
- document.body.style.position = '' unless alertDialogStack.length
74
- document.body.style.top = '' unless alertDialogStack.length
75
- document.body.style.width = '' unless alertDialogStack.length
76
- window.scrollTo 0, _scrollY unless alertDialogStack.length
77
- _prevFocus?.focus()
78
- else
79
- idx = alertDialogStack.indexOf this
80
- alertDialogStack.splice(idx, 1) if idx >= 0
81
- unless alertDialogStack.length
82
- document.body.style.position = ''
83
- document.body.style.top = ''
84
- document.body.style.width = ''
85
- window.scrollTo 0, _scrollY
86
- _prevFocus?.focus()
87
-
88
- close: ->
89
- @open = false
90
- @emit 'close'
91
-
92
- render
93
- if @open
94
- div ref: "_backdrop", $open: true
95
- div ref: "_panel", role: "alertdialog", aria-modal: "true", tabindex: "-1"
96
- slot
@@ -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