stimeo-ui 0.6.0 → 0.7.0

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +62 -0
  3. data/dist/controllers/alert_dialog_controller.js +75 -4
  4. data/dist/controllers/avatar_controller.js +47 -5
  5. data/dist/controllers/character_counter_controller.js +338 -63
  6. data/dist/controllers/command_palette_controller.js +75 -4
  7. data/dist/controllers/conditional_fields_controller.js +345 -51
  8. data/dist/controllers/confirm_controller.js +75 -4
  9. data/dist/controllers/dialog_controller.js +75 -4
  10. data/dist/controllers/direct_upload_controller.js +201 -45
  11. data/dist/controllers/dirty_form_controller.js +192 -29
  12. data/dist/controllers/dismissible_controller.js +83 -18
  13. data/dist/controllers/drawer_controller.js +75 -4
  14. data/dist/controllers/focus_controller.js +75 -4
  15. data/dist/controllers/form_field_controller.js +280 -62
  16. data/dist/controllers/form_validation_controller.js +208 -83
  17. data/dist/controllers/number_input_controller.js +47 -5
  18. data/dist/controllers/overflow_menu_controller.js +2 -1
  19. data/dist/controllers/pagination_controller.js +2 -1
  20. data/dist/controllers/persist_controller.js +432 -122
  21. data/dist/controllers/popover_controller.js +77 -4
  22. data/dist/controllers/rating_controller.js +9 -5
  23. data/dist/controllers/scroll_area_controller.js +462 -162
  24. data/dist/controllers/separator_controller.js +354 -38
  25. data/dist/controllers/sidebar_controller.js +83 -10
  26. data/dist/controllers/submit_once_controller.js +399 -121
  27. data/dist/controllers/theme_controller.js +8 -6
  28. data/dist/controllers/tree_view_controller.js +2 -1
  29. data/dist/index.js +2387 -861
  30. data/lib/stimeo/ui/version.rb +1 -1
  31. metadata +2 -2
@@ -92,6 +92,81 @@ var EscapeLayer = class _EscapeLayer {
92
92
  }
93
93
  };
94
94
 
95
+ // src/utils/focus_candidate.ts
96
+ function inheritsFieldsetDisabled(control) {
97
+ let fieldset = control.closest("fieldset[disabled]");
98
+ while (fieldset) {
99
+ const legend = Array.from(fieldset.children).find((child) => child.tagName === "LEGEND");
100
+ if (!legend?.contains(control)) return true;
101
+ fieldset = fieldset.parentElement?.closest("fieldset[disabled]") ?? null;
102
+ }
103
+ return false;
104
+ }
105
+ function canTakeFocus(element) {
106
+ if (element.closest("[hidden], [inert]")) return false;
107
+ if (element instanceof HTMLInputElement && element.type === "hidden") return false;
108
+ if (!("disabled" in element)) return true;
109
+ if (element.disabled) return false;
110
+ return !inheritsFieldsetDisabled(element);
111
+ }
112
+ var TAB_STOP_CANDIDATE_SELECTOR = [
113
+ "a[href]",
114
+ "area[href]",
115
+ "button",
116
+ "input",
117
+ "select",
118
+ "textarea",
119
+ "summary",
120
+ "iframe",
121
+ "audio[controls]",
122
+ "video[controls]",
123
+ "[tabindex]",
124
+ "[contenteditable]"
125
+ ].join(",");
126
+ function isRenderedForFocus(element) {
127
+ const check = element.checkVisibility;
128
+ return typeof check === "function" ? check.call(element, { visibilityProperty: true }) : true;
129
+ }
130
+ function authoredTabindex(element) {
131
+ const value = element.getAttribute("tabindex");
132
+ if (value === null || !/^[+-]?\d+$/.test(value.trim())) return null;
133
+ return Number(value);
134
+ }
135
+ function hasNativeTabStop(element) {
136
+ if (element instanceof HTMLAnchorElement || element instanceof HTMLAreaElement) {
137
+ return element.hasAttribute("href");
138
+ }
139
+ if (element instanceof HTMLButtonElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) {
140
+ return true;
141
+ }
142
+ if (element instanceof HTMLInputElement) return element.type !== "hidden";
143
+ if (element instanceof HTMLIFrameElement) return true;
144
+ if (element.tagName === "AUDIO" || element.tagName === "VIDEO") {
145
+ return element.hasAttribute("controls");
146
+ }
147
+ if (element instanceof HTMLElement && element.tagName === "SUMMARY") {
148
+ const details = element.parentElement;
149
+ return details instanceof HTMLDetailsElement && Array.from(details.children).find((child) => child.tagName === "SUMMARY") === element;
150
+ }
151
+ return false;
152
+ }
153
+ function hasEditableTabStop(element) {
154
+ const value = element.getAttribute("contenteditable")?.toLowerCase();
155
+ return value === "" || value === "true" || value === "plaintext-only";
156
+ }
157
+ function isTabStop(element) {
158
+ if (!canTakeFocus(element) || !isRenderedForFocus(element)) return false;
159
+ const tabindex = authoredTabindex(element);
160
+ if (tabindex !== null) return tabindex >= 0;
161
+ return hasNativeTabStop(element) || hasEditableTabStop(element);
162
+ }
163
+ function firstTabStop(root) {
164
+ for (const candidate of root.querySelectorAll(TAB_STOP_CANDIDATE_SELECTOR)) {
165
+ if (isTabStop(candidate)) return candidate;
166
+ }
167
+ return null;
168
+ }
169
+
95
170
  // src/utils/scroll_dismiss.ts
96
171
  function observeScrollDismiss(element, onScroll) {
97
172
  const targets = [...scrollParents(element), window];
@@ -118,7 +193,7 @@ function isScrollable(element) {
118
193
  }
119
194
 
120
195
  // src/controllers/popover_controller.ts
121
- var PopoverController = class _PopoverController extends Controller {
196
+ var PopoverController = class extends Controller {
122
197
  static targets = ["trigger", "panel"];
123
198
  static values = {
124
199
  closeOnScroll: { type: Boolean, default: false }
@@ -128,8 +203,6 @@ var PopoverController = class _PopoverController extends Controller {
128
203
  #stopScrollDismiss = null;
129
204
  /** Escape-stack membership while open; the shared resolver dismisses via it. */
130
205
  #escapeLayer = new EscapeLayer();
131
- /** Selector for natively focusable elements used to find the first one. */
132
- static #FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
133
206
  /** Starts closed and registers the standing dismissal listeners. */
134
207
  connect() {
135
208
  this.close();
@@ -181,7 +254,7 @@ var PopoverController = class _PopoverController extends Controller {
181
254
  }
182
255
  /** Moves focus to the first focusable element in the panel, or the panel itself. */
183
256
  #focusFirst() {
184
- const first = this.panelTarget.querySelector(_PopoverController.#FOCUSABLE);
257
+ const first = firstTabStop(this.panelTarget);
185
258
  if (first) {
186
259
  first.focus();
187
260
  return;
@@ -32,17 +32,21 @@ var AttributeLease = class {
32
32
  written: value
33
33
  });
34
34
  }
35
- if (value === null) element.removeAttribute(this.#attribute);
36
- else element.setAttribute(this.#attribute, value);
35
+ this.#reflect(element, value);
37
36
  }
38
37
  /** Returns one lease without overwriting a value subsequently authored by a consumer. */
39
38
  return(element) {
40
39
  const record = this.#records.get(element);
41
40
  if (!record) return;
42
41
  this.#records.delete(element);
43
- if (element.getAttribute(this.#attribute) !== record.written) return;
44
- if (record.original === null) element.removeAttribute(this.#attribute);
45
- else element.setAttribute(this.#attribute, record.original);
42
+ const stillOwned = element.getAttribute(this.#attribute) === record.written;
43
+ if (stillOwned) this.#reflect(element, record.original);
44
+ }
45
+ /** Reflects only a real value transition, avoiding self-triggered mutation work. */
46
+ #reflect(element, value) {
47
+ if (element.getAttribute(this.#attribute) === value) return;
48
+ if (value === null) element.removeAttribute(this.#attribute);
49
+ else element.setAttribute(this.#attribute, value);
46
50
  }
47
51
  /** Returns every outstanding lease using the same ownership check as {@link return}. */
48
52
  returnAll() {