poetry-core 0.0.2

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 (166) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +3 -0
  3. data/DESIGN.md +145 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +30 -0
  6. data/THIRD_PARTY_NOTICES.md +507 -0
  7. data/app/components/poetry/core/box/component.rb +82 -0
  8. data/app/components/poetry/core/component.rb +390 -0
  9. data/app/components/poetry/core/concerns/agent_tools.rb +418 -0
  10. data/app/components/poetry/core/concerns/declared_attributes.rb +267 -0
  11. data/app/components/poetry/core/concerns/introspection.rb +533 -0
  12. data/app/components/poetry/core/concerns/options.rb +262 -0
  13. data/app/components/poetry/core/concerns/parts.rb +148 -0
  14. data/app/components/poetry/core/concerns/stimulus.rb +363 -0
  15. data/app/components/poetry/core/concerns/styles.rb +323 -0
  16. data/app/components/poetry/core/style.rb +150 -0
  17. data/app/components/poetry/core/wrapper/component.rb +90 -0
  18. data/app/javascript/poetry/core/accordion_controller.js +165 -0
  19. data/app/javascript/poetry/core/action_bar_controller.js +109 -0
  20. data/app/javascript/poetry/core/autocomplete_controller.js +220 -0
  21. data/app/javascript/poetry/core/calendar_controller.js +406 -0
  22. data/app/javascript/poetry/core/carousel_controller.js +167 -0
  23. data/app/javascript/poetry/core/checkbox_group_controller.js +97 -0
  24. data/app/javascript/poetry/core/checked_controller.js +208 -0
  25. data/app/javascript/poetry/core/clipboard_text_controller.js +111 -0
  26. data/app/javascript/poetry/core/combobox_controller.js +1095 -0
  27. data/app/javascript/poetry/core/command_controller.js +510 -0
  28. data/app/javascript/poetry/core/context_menu_controller.js +204 -0
  29. data/app/javascript/poetry/core/date_field_controller.js +584 -0
  30. data/app/javascript/poetry/core/date_picker_controller.js +111 -0
  31. data/app/javascript/poetry/core/deferred_controller.js +111 -0
  32. data/app/javascript/poetry/core/dialog_controller.js +206 -0
  33. data/app/javascript/poetry/core/dismissable_controller.js +182 -0
  34. data/app/javascript/poetry/core/drawer_controller.js +365 -0
  35. data/app/javascript/poetry/core/file_input_controller.js +191 -0
  36. data/app/javascript/poetry/core/focus_scope_controller.js +162 -0
  37. data/app/javascript/poetry/core/helpers/announce.js +228 -0
  38. data/app/javascript/poetry/core/helpers/breakpoint.js +28 -0
  39. data/app/javascript/poetry/core/helpers/collection.js +18 -0
  40. data/app/javascript/poetry/core/helpers/direction.js +15 -0
  41. data/app/javascript/poetry/core/helpers/escape.js +38 -0
  42. data/app/javascript/poetry/core/helpers/filter_rank.js +104 -0
  43. data/app/javascript/poetry/core/helpers/focus_guards.js +46 -0
  44. data/app/javascript/poetry/core/helpers/hotkey.js +43 -0
  45. data/app/javascript/poetry/core/helpers/id_integrity.js +65 -0
  46. data/app/javascript/poetry/core/helpers/incomplete_date.js +328 -0
  47. data/app/javascript/poetry/core/helpers/mask.js +283 -0
  48. data/app/javascript/poetry/core/helpers/portal.js +217 -0
  49. data/app/javascript/poetry/core/helpers/presence.js +210 -0
  50. data/app/javascript/poetry/core/helpers/registration_guard.js +76 -0
  51. data/app/javascript/poetry/core/helpers/scroll_lock.js +67 -0
  52. data/app/javascript/poetry/core/helpers/scroller_geometry.js +422 -0
  53. data/app/javascript/poetry/core/helpers/state.js +80 -0
  54. data/app/javascript/poetry/core/helpers/tabbable.js +56 -0
  55. data/app/javascript/poetry/core/helpers/turbo_cache.js +21 -0
  56. data/app/javascript/poetry/core/helpers/typeahead.js +85 -0
  57. data/app/javascript/poetry/core/hotkey_controller.js +55 -0
  58. data/app/javascript/poetry/core/hover_card_controller.js +431 -0
  59. data/app/javascript/poetry/core/index.js +218 -0
  60. data/app/javascript/poetry/core/mask_controller.js +500 -0
  61. data/app/javascript/poetry/core/menu_controller.js +1050 -0
  62. data/app/javascript/poetry/core/menubar_controller.js +329 -0
  63. data/app/javascript/poetry/core/message_scroller_controller.js +930 -0
  64. data/app/javascript/poetry/core/navigation_menu_controller.js +458 -0
  65. data/app/javascript/poetry/core/number_field_controller.js +404 -0
  66. data/app/javascript/poetry/core/optimistic_form_controller.js +56 -0
  67. data/app/javascript/poetry/core/otp_controller.js +219 -0
  68. data/app/javascript/poetry/core/popover_controller.js +300 -0
  69. data/app/javascript/poetry/core/popper_controller.js +439 -0
  70. data/app/javascript/poetry/core/pressed_controller.js +74 -0
  71. data/app/javascript/poetry/core/questionnaire_controller.js +468 -0
  72. data/app/javascript/poetry/core/radio_group_controller.js +197 -0
  73. data/app/javascript/poetry/core/resizable_controller.js +202 -0
  74. data/app/javascript/poetry/core/roving_focus_controller.js +215 -0
  75. data/app/javascript/poetry/core/scroll_spy_controller.js +84 -0
  76. data/app/javascript/poetry/core/search_field_controller.js +95 -0
  77. data/app/javascript/poetry/core/select_controller.js +924 -0
  78. data/app/javascript/poetry/core/sensitive_input_controller.js +195 -0
  79. data/app/javascript/poetry/core/sheet_controller.js +24 -0
  80. data/app/javascript/poetry/core/sidebar_controller.js +272 -0
  81. data/app/javascript/poetry/core/slider_controller.js +419 -0
  82. data/app/javascript/poetry/core/state_controller.js +82 -0
  83. data/app/javascript/poetry/core/table_selection_controller.js +153 -0
  84. data/app/javascript/poetry/core/tabs_controller.js +151 -0
  85. data/app/javascript/poetry/core/tag_group_controller.js +134 -0
  86. data/app/javascript/poetry/core/toast_controller.js +228 -0
  87. data/app/javascript/poetry/core/toast_trigger_controller.js +26 -0
  88. data/app/javascript/poetry/core/toaster_controller.js +197 -0
  89. data/app/javascript/poetry/core/toggle_group_controller.js +206 -0
  90. data/app/javascript/poetry/core/tooltip_controller.js +541 -0
  91. data/app/javascript/poetry/core/tree_controller.js +265 -0
  92. data/app/javascript/poetry/core/vendor/floating_ui_core.js +1043 -0
  93. data/app/javascript/poetry/core/vendor/floating_ui_dom.js +794 -0
  94. data/app/javascript/poetry/core/vendor/floating_ui_utils.js +141 -0
  95. data/app/javascript/poetry/core/vendor/floating_ui_utils_dom.js +170 -0
  96. data/app/views/poetry/core/preview.html.erb +13 -0
  97. data/config/component_registry.yml +20 -0
  98. data/config/controllers_manifest.json +1528 -0
  99. data/config/importmap.rb +11 -0
  100. data/config/state_vocabulary.json +31 -0
  101. data/lib/active_model/type/list.rb +33 -0
  102. data/lib/active_model/type/symbol.rb +36 -0
  103. data/lib/poetry/core/check/stable_identity.rb +104 -0
  104. data/lib/poetry/core/check.rb +1445 -0
  105. data/lib/poetry/core/config.rb +286 -0
  106. data/lib/poetry/core/contrib/wrapped_helper.rb +68 -0
  107. data/lib/poetry/core/css/bem_merger.rb +45 -0
  108. data/lib/poetry/core/css/bem_reference.rb +87 -0
  109. data/lib/poetry/core/css/override_scan.rb +110 -0
  110. data/lib/poetry/core/css/resolver.rb +161 -0
  111. data/lib/poetry/core/css/safelist.rb +41 -0
  112. data/lib/poetry/core/css/tailwind_merger.rb +102 -0
  113. data/lib/poetry/core/css/template_classes.rb +102 -0
  114. data/lib/poetry/core/css/theme_coverage.rb +72 -0
  115. data/lib/poetry/core/css/var_coverage.rb +75 -0
  116. data/lib/poetry/core/css/verifier.rb +106 -0
  117. data/lib/poetry/core/design_lint.rb +720 -0
  118. data/lib/poetry/core/design_md/import.rb +250 -0
  119. data/lib/poetry/core/design_md.rb +461 -0
  120. data/lib/poetry/core/engine.rb +71 -0
  121. data/lib/poetry/core/errors.rb +53 -0
  122. data/lib/poetry/core/html/attributes.rb +667 -0
  123. data/lib/poetry/core/icons.rb +157 -0
  124. data/lib/poetry/core/llms_text.rb +354 -0
  125. data/lib/poetry/core/page_architectures.rb +304 -0
  126. data/lib/poetry/core/part_contract.rb +285 -0
  127. data/lib/poetry/core/preview/abstract.rb +121 -0
  128. data/lib/poetry/core/preview/base.rb +227 -0
  129. data/lib/poetry/core/preview/sidecarable.rb +136 -0
  130. data/lib/poetry/core/preview/template.rb +186 -0
  131. data/lib/poetry/core/recipe_items.rb +82 -0
  132. data/lib/poetry/core/registry.rb +363 -0
  133. data/lib/poetry/core/registry_address.rb +115 -0
  134. data/lib/poetry/core/registry_client.rb +217 -0
  135. data/lib/poetry/core/registry_installer.rb +234 -0
  136. data/lib/poetry/core/registry_items.rb +184 -0
  137. data/lib/poetry/core/skill_text.rb +288 -0
  138. data/lib/poetry/core/stable_id.rb +110 -0
  139. data/lib/poetry/core/stimulus/builder.rb +395 -0
  140. data/lib/poetry/core/stimulus/declarations.rb +444 -0
  141. data/lib/poetry/core/stimulus/manifest.rb +68 -0
  142. data/lib/poetry/core/stimulus/merger.rb +131 -0
  143. data/lib/poetry/core/stimulus_contract.rb +263 -0
  144. data/lib/poetry/core/tag_helper.rb +10 -0
  145. data/lib/poetry/core/template_compile.rb +74 -0
  146. data/lib/poetry/core/token_import.rb +317 -0
  147. data/lib/poetry/core/tokens/color.rb +215 -0
  148. data/lib/poetry/core/tokens/contrast_gate.rb +120 -0
  149. data/lib/poetry/core/tokens/generator.rb +211 -0
  150. data/lib/poetry/core/tokens.rb +117 -0
  151. data/lib/poetry/core/version.rb +9 -0
  152. data/lib/poetry/core.rb +52 -0
  153. data/lib/poetry-core.rb +5 -0
  154. data/tokens/aliases.css +145 -0
  155. data/tokens/tailwind-theme.css +47 -0
  156. data/tokens/tokens.css +78 -0
  157. data/tokens/tokens.dtcg.json +779 -0
  158. data/vendor/floating-ui/LICENSE +20 -0
  159. data/vendor/floating-ui/VENDORED_VERSIONS +15 -0
  160. data/vendor/shadcn-tailwind/LICENSE +21 -0
  161. data/vendor/shadcn-tailwind/VENDORED_COMMIT +1 -0
  162. data/vendor/shadcn-tailwind/tailwind.css +629 -0
  163. data/vendor/tw-animate-css/LICENSE +21 -0
  164. data/vendor/tw-animate-css/VENDORED_VERSION +1 -0
  165. data/vendor/tw-animate-css/tw-animate.css +1 -0
  166. metadata +267 -0
@@ -0,0 +1,265 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { directionOf } from "@poetry/controllers/helpers/direction"
3
+ import { createTypeahead, typeaheadLabel } from "@poetry/controllers/helpers/typeahead"
4
+
5
+ // The Tree engine (the flat-treegrid contract): the
6
+ // server renders a FLAT list of role=row siblings - hierarchy lives
7
+ // entirely in aria-level/posinset/setsize (static per render) - so this
8
+ // controller owns only what HTML cannot: roving focus over VISIBLE rows,
9
+ // the four-branch ArrowLeft/Right expansion logic (including
10
+ // focus-to-parent), Enter-toggles-expandable (the no-action default),
11
+ // typeahead, and subtree show/hide that preserves nested collapsed state
12
+ // (a row is visible iff every ancestor is expanded).
13
+ //
14
+ // Expansion state IS the DOM (aria-expanded + hidden); the host persists
15
+ // it by listening for poetry:tree:toggle. Selection modes are deferred
16
+ // (the TagGroup reasoning).
17
+ const EVENT_PREFIX = "poetry:tree"
18
+
19
+ export default class TreeController extends Controller {
20
+ // The events this controller dispatches (manifest surface;
21
+ // events_declaration.test.js enforces the list stays honest).
22
+ static events = ["poetry:tree:toggle"]
23
+
24
+ #typeahead = createTypeahead()
25
+
26
+ /**
27
+ * Applies ancestor-expansion visibility and settles the single tab
28
+ * stop.
29
+ */
30
+ connect() {
31
+ this.#applyVisibility()
32
+ this.#settleTabstop()
33
+ }
34
+
35
+ /**
36
+ * The treegrid container's keydown action (row-origin keys only):
37
+ * arrows rove the visible rows, the direction-aware expand/collapse
38
+ * pair runs the four-branch logic (collapse on a leaf focuses the
39
+ * parent), Home/End jump the edges, Enter toggles expandables, and
40
+ * printable keys run typeahead.
41
+ *
42
+ * @param {KeyboardEvent} event
43
+ */
44
+ keydown(event) {
45
+ const row = event.target.closest("[data-slot='tree-item']")
46
+
47
+ // Row-origin keys only (a control inside a row keeps its own keys).
48
+ if (!row || event.target !== row) return
49
+ if (event.isComposing) return
50
+
51
+ const [expandKey, collapseKey] =
52
+ directionOf(row) === "rtl" ? ["ArrowLeft", "ArrowRight"] : ["ArrowRight", "ArrowLeft"]
53
+
54
+ switch (event.key) {
55
+ case "ArrowDown": event.preventDefault(); this.#move(row, 1); break
56
+ case "ArrowUp": event.preventDefault(); this.#move(row, -1); break
57
+ case "Home": event.preventDefault(); this.#focusRow(this.#visibleRows()[0]); break
58
+ case "End": event.preventDefault(); this.#focusRow(this.#visibleRows().at(-1)); break
59
+ case expandKey: this.#expandKey(event, row); break
60
+ case collapseKey: this.#collapseKey(event, row); break
61
+ case "Enter": this.#enterKey(event, row); break
62
+ case " ": if (this.#typeahead.pending()) { event.preventDefault(); this.#search(" ", row) } break
63
+ default: this.#maybeTypeahead(event, row)
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Each row's click action: toggles when expandable (the default press
69
+ * behavior with no action/link/selection); clicks on inner controls
70
+ * (links, the chevron) stay their own.
71
+ *
72
+ * @param {MouseEvent} event
73
+ */
74
+ press(event) {
75
+ const row = event.target.closest("[data-slot='tree-item']")
76
+
77
+ if (!row || row.hasAttribute("data-disabled")) return
78
+ if (event.target.closest("a, button, input, select, textarea")) return
79
+ if (!this.#expandable(row)) return
80
+
81
+ this.#toggle(row)
82
+ }
83
+
84
+ /**
85
+ * The chevron's click action (the chevron rides tabindex -1; its
86
+ * pointerdown is preventDefault'd via pressStart so focus never leaves
87
+ * the row): toggles, then refocuses the row.
88
+ *
89
+ * @param {MouseEvent} event
90
+ */
91
+ toggle(event) {
92
+ event.preventDefault()
93
+ event.stopPropagation()
94
+ const row = event.target.closest("[data-slot='tree-item']")
95
+
96
+ if (!row || row.hasAttribute("data-disabled")) return
97
+
98
+ this.#toggle(row)
99
+ this.#focusRow(row)
100
+ }
101
+
102
+ /**
103
+ * The chevron's pointerdown action: never steal focus from the row.
104
+ *
105
+ * @param {PointerEvent} event
106
+ */
107
+ pressStart(event) {
108
+ event.preventDefault()
109
+ }
110
+
111
+ // --- expansion ---
112
+
113
+ #expandKey(event, row) {
114
+ if (!this.#expandable(row)) return
115
+
116
+ if (row.getAttribute("aria-expanded") === "false") {
117
+ event.preventDefault()
118
+ this.#toggle(row)
119
+ }
120
+ // Expanded parent / leaf: no-op v1 (focus-into-row-children deferred).
121
+ }
122
+
123
+ #collapseKey(event, row) {
124
+ if (this.#expandable(row) && row.getAttribute("aria-expanded") === "true") {
125
+ event.preventDefault()
126
+ this.#toggle(row)
127
+ return
128
+ }
129
+
130
+ // Leaf or collapsed: focus the PARENT row (the nearest preceding row
131
+ // with a smaller level).
132
+ const parent = this.#parentOf(row)
133
+
134
+ if (parent) {
135
+ event.preventDefault()
136
+ this.#focusRow(parent)
137
+ }
138
+ }
139
+
140
+ #enterKey(event, row) {
141
+ if (row.hasAttribute("data-disabled") || !this.#expandable(row)) return
142
+
143
+ event.preventDefault()
144
+ this.#toggle(row)
145
+ }
146
+
147
+ #toggle(row) {
148
+ const expanded = row.getAttribute("aria-expanded") !== "true"
149
+
150
+ row.setAttribute("aria-expanded", String(expanded))
151
+ row.toggleAttribute("data-expanded", expanded)
152
+
153
+ // The chevron's name flips with the state (Expand/Collapse; the
154
+ // strings arrive server-localized as data attributes).
155
+ const chevron = row.querySelector("[data-slot='tree-item-toggle']")
156
+ const label = chevron?.getAttribute(expanded ? "data-collapse-label" : "data-expand-label")
157
+
158
+ if (chevron && label) chevron.setAttribute("aria-label", label)
159
+
160
+ this.#applyVisibility()
161
+
162
+ this.dispatch("toggle", {
163
+ prefix: EVENT_PREFIX,
164
+ detail: { id: row.id, value: row.getAttribute("data-value"), expanded }
165
+ })
166
+ }
167
+
168
+ // A row is visible iff EVERY ancestor is expanded: walk the flat list
169
+ // tracking the shallowest collapsed level - rows deeper than it hide.
170
+ #applyVisibility() {
171
+ let hideBelow = Infinity
172
+
173
+ for (const row of this.#rows()) {
174
+ const level = this.#levelOf(row)
175
+
176
+ if (level > hideBelow) {
177
+ row.hidden = true
178
+ continue
179
+ }
180
+
181
+ row.hidden = false
182
+ hideBelow = this.#expandable(row) && row.getAttribute("aria-expanded") !== "true"
183
+ ? level
184
+ : Infinity
185
+ }
186
+
187
+ this.#settleTabstop()
188
+ }
189
+
190
+ // --- roving focus over visible rows ---
191
+
192
+ #move(row, direction) {
193
+ const rows = this.#visibleRows()
194
+ const next = rows[rows.indexOf(row) + direction]
195
+
196
+ this.#focusRow(next)
197
+ }
198
+
199
+ #focusRow(row) {
200
+ if (!row) return
201
+
202
+ for (const candidate of this.#rows()) {
203
+ candidate.setAttribute("tabindex", candidate === row ? "0" : "-1")
204
+ }
205
+
206
+ row.focus()
207
+ }
208
+
209
+ // Exactly one visible row keeps tabindex 0 (the focused one if it is
210
+ // still visible, else the first visible row).
211
+ #settleTabstop() {
212
+ const rows = this.#visibleRows()
213
+
214
+ if (rows.length === 0) return
215
+
216
+ const current = rows.find((row) => row.getAttribute("tabindex") === "0") ?? rows[0]
217
+
218
+ for (const row of this.#rows()) {
219
+ row.setAttribute("tabindex", row === current ? "0" : "-1")
220
+ }
221
+ }
222
+
223
+ #maybeTypeahead(event, row) {
224
+ if (event.key.length !== 1 || event.metaKey || event.ctrlKey || event.altKey) return
225
+
226
+ event.preventDefault()
227
+ this.#search(event.key, row)
228
+ }
229
+
230
+ #search(key, row) {
231
+ const rows = this.#visibleRows().filter((candidate) => !candidate.hasAttribute("data-disabled"))
232
+ const match = this.#typeahead.search(key, rows, { active: row, labelOf: typeaheadLabel })
233
+
234
+ if (match) this.#focusRow(match)
235
+ }
236
+
237
+ // --- the flat-row math ---
238
+
239
+ #rows() {
240
+ return Array.from(this.element.querySelectorAll("[data-slot='tree-item']"))
241
+ }
242
+
243
+ #visibleRows() {
244
+ return this.#rows().filter((row) => !row.hidden)
245
+ }
246
+
247
+ #levelOf(row) {
248
+ return Number(row.getAttribute("aria-level")) || 1
249
+ }
250
+
251
+ #expandable(row) {
252
+ return row.hasAttribute("aria-expanded")
253
+ }
254
+
255
+ #parentOf(row) {
256
+ const rows = this.#rows()
257
+ const level = this.#levelOf(row)
258
+
259
+ for (let index = rows.indexOf(row) - 1; index >= 0; index -= 1) {
260
+ if (this.#levelOf(rows[index]) < level) return rows[index]
261
+ }
262
+
263
+ return null
264
+ }
265
+ }