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,300 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { portalContent, resolvePortalContainer, restoreContent } from "@poetry/controllers/helpers/portal"
3
+ import { enterPresence, exitPresence } from "@poetry/controllers/helpers/presence"
4
+ import { setState, stateOf } from "@poetry/controllers/helpers/state"
5
+
6
+ // The Popover controller (the popper-consumer trio's click-open member):
7
+ // the menu controller's #show/#hide + token-activated layer skeleton with
8
+ // ALL item machinery deleted - no typeahead, no roving, no subs, no
9
+ // collection. What remains is exactly the APG dialog-pattern-lite: the
10
+ // trigger toggles a role=dialog panel, focus MOVES INTO the content on open
11
+ // (focus-scope's mount default - deliberately NOT vetoed, the contrast with
12
+ // the menu family's data-open-reason contract) and RETURNS to the trigger on
13
+ // close; the trap is enforced only when modal (the default is
14
+ // modal: FALSE - the deliberate contrast with the menu family's true).
15
+ //
16
+ // STRUCTURAL RESOLUTION, no targets: the content is found via the trigger's
17
+ // aria-controls id (portal-safe - the menu-controller pattern verbatim);
18
+ // content-level listeners are wired programmatically in connect for the same
19
+ // reason.
20
+ //
21
+ // The layer stack is ACTIVATED on open: focus-scope + dismissable are
22
+ // appended to the content's data-controller (a statically-connected trap on
23
+ // hidden content would steal focus at page load; a static dismissable would
24
+ // swallow topmost-Esc), with trapped / disable-outside-pointer-events both
25
+ // set from modal. Close reverses: presence exit -> hidden -> tokens removed
26
+ // -> focus-scope's disconnect restores focus to the trigger (suppressed for
27
+ // outside-press when non-modal: focus follows the click).
28
+ const TRIGGER_SELECTOR = '[data-slot="popover-trigger"]'
29
+
30
+ const EVENT_PREFIX = "poetry:popover"
31
+
32
+ const CONTENT_LAYER_CONTROLLERS = ["poetry--core--focus-scope", "poetry--core--dismissable"]
33
+ const POPPER_STRATEGY = "data-poetry--core--popper-strategy-value"
34
+
35
+ export default class PopoverController extends Controller {
36
+ // The events this controller dispatches (manifest surface;
37
+ // events_declaration.test.js enforces the list stays honest).
38
+ static events = ["poetry:popover:closed", "poetry:popover:open"]
39
+
40
+ static values = {
41
+ open: { type: Boolean, default: false },
42
+ modal: { type: Boolean, default: false }
43
+ }
44
+
45
+ #connected = false
46
+ #wired = []
47
+ #suppressRestore = false
48
+ #cancelExit = null
49
+
50
+ /**
51
+ * Wires the content listeners (programmatic - portal-safe) and
52
+ * reconciles: server-open content adopts the layer stack and re-portals
53
+ * one frame late (the body comments hold the rules).
54
+ */
55
+ connect() {
56
+ const content = this.#content()
57
+
58
+ if (content) this.#wireContent(content)
59
+
60
+ this.#connected = true
61
+
62
+ // Reconcile-on-connect: the server may own the open state (Turbo Stream
63
+ // re-render). DOM attributes win; the layer stack catches up.
64
+ if (this.#isOpen()) {
65
+ if (content) {
66
+ this.#activateLayers(content)
67
+ this.#portalPinned(content)
68
+ }
69
+ this.openValue = true
70
+ } else if (this.openValue) {
71
+ this.#show()
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Restores portaled content (drop-never-strand) and unwires the
77
+ * content listeners.
78
+ */
79
+ disconnect() {
80
+ this.#connected = false
81
+
82
+ // Never leave content stranded at the container (drop-never-strand).
83
+ const content = this.#content()
84
+
85
+ if (content) restoreContent(content)
86
+
87
+ for (const [target, type, listener] of this.#wired) target.removeEventListener(type, listener)
88
+
89
+ this.#wired = []
90
+ this.#cancelExit?.()
91
+ this.#cancelExit = null
92
+ }
93
+
94
+ /**
95
+ * Stimulus value callback - controllable state: a host (outlet / Turbo
96
+ * Stream / URL param) may own the open value; flipping the attribute
97
+ * drives the same machine.
98
+ *
99
+ * @param {boolean} value
100
+ */
101
+ openValueChanged(value) {
102
+ if (!this.#connected) return
103
+
104
+ if (value && !this.#isOpen()) this.#show()
105
+ else if (!value && this.#isOpen()) this.#hide("none")
106
+ }
107
+
108
+ /**
109
+ * The trigger's click action (native button Enter/Space arrive as
110
+ * click - no custom keydown map, the deliberate contrast with the menu
111
+ * trigger).
112
+ */
113
+ toggle() {
114
+ if (this.#isOpen()) this.#hide("trigger-press")
115
+ else this.#show()
116
+ }
117
+
118
+ // --- programmatic API (the controllable-state surface) ---
119
+
120
+ /** Programmatically opens. */
121
+ open() {
122
+ this.#show()
123
+ }
124
+
125
+ /**
126
+ * Programmatically closes.
127
+ *
128
+ * @param {string | Event} [reason="none"] - a family reason string; an
129
+ * Event (data-action use) reads as "none"
130
+ */
131
+ close(reason = "none") {
132
+ this.#hide(reason instanceof Event ? "none" : reason)
133
+ }
134
+
135
+ // --- open / close ---
136
+
137
+ #show() {
138
+ const content = this.#content()
139
+
140
+ if (!content || this.#isOpen()) return
141
+
142
+ this.#cancelExit?.()
143
+ this.#cancelExit = null
144
+ this.#suppressRestore = false
145
+
146
+ const trigger = this.#trigger()
147
+
148
+ // Portal-on-open: move BEFORE the
149
+ // enter presence (reparenting mid-animation restarts it), re-anchor
150
+ // absolute - static under compositor scroll, transform-immune.
151
+ portalContent(content, { container: resolvePortalContainer(this.element) })
152
+ this.element.setAttribute(POPPER_STRATEGY, "absolute")
153
+
154
+ content.hidden = false
155
+ trigger?.setAttribute("aria-expanded", "true")
156
+ if (trigger) setState(trigger, "popup-open")
157
+ enterPresence(content)
158
+ this.#activateLayers(content)
159
+ this.openValue = true
160
+
161
+ // The layer controllers connect on the attribute-mutation microtask;
162
+ // focus-scope's mount-auto-focus (first tabbable, else the content) is
163
+ // NOT vetoed here - the dialog pattern. The open event rides one
164
+ // microtask behind so it fires after initial focus applied.
165
+ queueMicrotask(() => {
166
+ if (!this.#isOpen()) return
167
+
168
+ this.dispatch("open", { prefix: EVENT_PREFIX, detail: {} })
169
+ })
170
+ }
171
+
172
+ #hide(reason) {
173
+ const content = this.#content()
174
+
175
+ if (!content || !this.#isOpen()) return
176
+
177
+ // Focus return to the trigger is focus-scope's disconnect job - vetoed
178
+ // for outside interaction on a non-modal popover (non-modal
179
+ // semantics: focus follows the click).
180
+ this.#suppressRestore = reason === "outside-press" && !this.modalValue
181
+
182
+ const trigger = this.#trigger()
183
+
184
+ trigger?.setAttribute("aria-expanded", "false")
185
+ if (trigger) setState(trigger, "popup-closed")
186
+ this.openValue = false
187
+
188
+ this.#cancelExit = exitPresence(content, {
189
+ onRemove: () => {
190
+ this.#cancelExit = null
191
+ content.hidden = true
192
+ // Home AFTER the exit finished and hidden landed; the
193
+ // focus-scope teardown below restores focus by element ref,
194
+ // indifferent to where the node sits.
195
+ restoreContent(content)
196
+ this.element.setAttribute(POPPER_STRATEGY, "fixed")
197
+ this.#removeControllers(content, CONTENT_LAYER_CONTROLLERS)
198
+ this.dispatch("closed", { prefix: EVENT_PREFIX, detail: { reason } })
199
+ }
200
+ })
201
+ }
202
+
203
+ // The reconcile path portals ONE FRAME LATE: connect order within a boot
204
+ // is unordered, and portaling before the sibling popper's connect would
205
+ // rob it of its content target before it could cache the node.
206
+ #portalPinned(content) {
207
+ window.requestAnimationFrame(() => {
208
+ if (!this.#connected || !this.#isOpen()) return
209
+
210
+ portalContent(content, { container: resolvePortalContainer(this.element) })
211
+ this.element.setAttribute(POPPER_STRATEGY, "absolute")
212
+ })
213
+ }
214
+
215
+ // --- content wiring (programmatic: portal-safe, no data-action required) ---
216
+
217
+ #wireContent(content) {
218
+ this.#listen(content, "poetry--core--dismissable:dismiss", this.#onDismiss)
219
+ this.#listen(content, "poetry--core--dismissable:interact-outside", this.#onInteractOutside)
220
+ this.#listen(content, "poetry--core--focus-scope:unmount-auto-focus", this.#onUnmountAutoFocus)
221
+ }
222
+
223
+ #listen(target, type, listener) {
224
+ target.addEventListener(type, listener)
225
+ this.#wired.push([target, type, listener])
226
+ }
227
+
228
+ // A press on the popover's OWN trigger is the toggle's job, not an
229
+ // outside dismissal: without this veto the pointerdown closes and the
230
+ // trailing click re-opens, so the popover appears to never close on
231
+ // trigger press (the trigger-is-not-outside rule; iOS light-dismiss
232
+ // double-fires arrive through the same seam and are covered by it).
233
+ #onInteractOutside = (event) => {
234
+ if (event.target !== this.#content()) return
235
+
236
+ const origin = event.detail?.originalEvent?.target
237
+
238
+ if (origin instanceof Element && this.#trigger()?.contains(origin)) event.preventDefault()
239
+ }
240
+
241
+ // Esc / outside-press arrive as the dismissable layer's dismiss event
242
+ // (topmost-only via the class-level stack).
243
+ #onDismiss = (event) => {
244
+ if (event.target !== this.#content()) return
245
+
246
+ const escaped = event.detail?.originalEvent?.type === "keydown"
247
+
248
+ this.#hide(escaped ? "escape-key" : "outside-press")
249
+ }
250
+
251
+ #onUnmountAutoFocus = (event) => {
252
+ if (event.target === this.#content() && this.#suppressRestore) event.preventDefault()
253
+ }
254
+
255
+ // --- the layer stack ---
256
+
257
+ #activateLayers(content) {
258
+ content.setAttribute("data-poetry--core--focus-scope-trapped-value", String(this.modalValue))
259
+ content.setAttribute(
260
+ "data-poetry--core--dismissable-disable-outside-pointer-events-value", String(this.modalValue)
261
+ )
262
+ this.#addControllers(content, CONTENT_LAYER_CONTROLLERS)
263
+ }
264
+
265
+ #addControllers(element, identifiers) {
266
+ const tokens = (element.getAttribute("data-controller") ?? "").split(/\s+/).filter(Boolean)
267
+
268
+ for (const identifier of identifiers) {
269
+ if (!tokens.includes(identifier)) tokens.push(identifier)
270
+ }
271
+
272
+ element.setAttribute("data-controller", tokens.join(" "))
273
+ }
274
+
275
+ #removeControllers(element, identifiers) {
276
+ const tokens = (element.getAttribute("data-controller") ?? "")
277
+ .split(/\s+/)
278
+ .filter((token) => token && !identifiers.includes(token))
279
+
280
+ element.setAttribute("data-controller", tokens.join(" "))
281
+ }
282
+
283
+ // --- structural resolution (the DOM is the registry; ids are the seams) ---
284
+
285
+ #trigger() {
286
+ return this.element.querySelector(TRIGGER_SELECTOR)
287
+ }
288
+
289
+ #content() {
290
+ const id = this.#trigger()?.getAttribute("aria-controls")
291
+
292
+ return id ? document.getElementById(id) : null
293
+ }
294
+
295
+ #isOpen() {
296
+ const content = this.#content()
297
+
298
+ return Boolean(content) && stateOf(content) === "open"
299
+ }
300
+ }