donobu 5.52.2 → 5.52.3

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.
@@ -933,24 +933,22 @@ class PageInspector {
933
933
  if (document.scrollingElement) {
934
934
  maybeAddScrollable(document.scrollingElement);
935
935
  }
936
- // 2) Iterate and assign numbers
937
- uniqueElements.forEach((element) => {
938
- if (element === document.scrollingElement) {
939
- // Special-case: always keep the root scrolling element
940
- element.setAttribute(interactableAttribute, offset.toString());
941
- offset++;
942
- return; // skip the usual checks
943
- }
944
- else if (element.hasAttribute(interactableAttribute)) {
945
- // Skip if this element already carries a value (e.g. assigned via <label>)
946
- return;
936
+ /**
937
+ * Run the visibility / enabled / top-most checks on a single element and,
938
+ * if they pass, assign it the next interactable number. Returns `true` if
939
+ * the element (or, via the <label htmlFor> mapping, its associated control)
940
+ * was attributed.
941
+ */
942
+ function tryAttributeElement(element) {
943
+ if (element.hasAttribute(interactableAttribute)) {
944
+ return false;
947
945
  }
948
946
  const rect = element.getBoundingClientRect();
949
947
  const style = window.getComputedStyle(element);
950
948
  const visible = isElementVisible(rect, style) && isElementMoreThanHalfInViewport(rect);
951
949
  const enabled = isElementEnabled(element, style);
952
950
  if (!visible || !enabled) {
953
- return;
951
+ return false;
954
952
  }
955
953
  // Check a few probe points to make sure the element is top-most
956
954
  for (const pt of getPointsToCheck(rect)) {
@@ -959,9 +957,9 @@ class PageInspector {
959
957
  if (elToCheck === element) {
960
958
  element.setAttribute(interactableAttribute, offset.toString());
961
959
  offset++;
962
- return; // this element done
960
+ return true; // this element done
963
961
  }
964
- // Handle <label> -> control mapping
962
+ // Handle <label> -> control mapping (explicit `for`/`htmlFor`)
965
963
  if (elToCheck.tagName.toLowerCase() === 'label' &&
966
964
  elToCheck.htmlFor) {
967
965
  const forId = elToCheck.htmlFor;
@@ -972,11 +970,41 @@ class PageInspector {
972
970
  control.setAttribute(interactableAttribute, offset.toString());
973
971
  offset++;
974
972
  }
975
- return;
973
+ return true;
976
974
  }
977
975
  elToCheck = elToCheck.parentElement;
978
976
  }
979
977
  }
978
+ return false;
979
+ }
980
+ // 2) Iterate and assign numbers
981
+ uniqueElements.forEach((element) => {
982
+ if (element === document.scrollingElement) {
983
+ // Special-case: always keep the root scrolling element
984
+ element.setAttribute(interactableAttribute, offset.toString());
985
+ offset++;
986
+ return; // skip the usual checks
987
+ }
988
+ else if (element.hasAttribute(interactableAttribute)) {
989
+ // Skip if this element already carries a value (e.g. assigned via <label>)
990
+ return;
991
+ }
992
+ if (tryAttributeElement(element)) {
993
+ return;
994
+ }
995
+ // Fallback: the element is a visually-hidden native control (e.g. a 0x0,
996
+ // opacity-0, pointer-events:none <input>) wrapped in a styled <label>.
997
+ // This is the standard pattern for Ant Design Segmented/Radio/Checkbox/
998
+ // Switch and many other component libraries: the native input is hidden
999
+ // and the surrounding <label> is the real clickable surface. Since the
1000
+ // hidden control fails the visibility/enabled checks above, attribute the
1001
+ // wrapping <label> instead so the toggle is still annotated.
1002
+ const wrappingLabel = element.closest('label');
1003
+ if (wrappingLabel &&
1004
+ wrappingLabel !== element &&
1005
+ !wrappingLabel.hasAttribute(interactableAttribute)) {
1006
+ tryAttributeElement(wrappingLabel);
1007
+ }
980
1008
  });
981
1009
  return offset;
982
1010
  }
@@ -933,24 +933,22 @@ class PageInspector {
933
933
  if (document.scrollingElement) {
934
934
  maybeAddScrollable(document.scrollingElement);
935
935
  }
936
- // 2) Iterate and assign numbers
937
- uniqueElements.forEach((element) => {
938
- if (element === document.scrollingElement) {
939
- // Special-case: always keep the root scrolling element
940
- element.setAttribute(interactableAttribute, offset.toString());
941
- offset++;
942
- return; // skip the usual checks
943
- }
944
- else if (element.hasAttribute(interactableAttribute)) {
945
- // Skip if this element already carries a value (e.g. assigned via <label>)
946
- return;
936
+ /**
937
+ * Run the visibility / enabled / top-most checks on a single element and,
938
+ * if they pass, assign it the next interactable number. Returns `true` if
939
+ * the element (or, via the <label htmlFor> mapping, its associated control)
940
+ * was attributed.
941
+ */
942
+ function tryAttributeElement(element) {
943
+ if (element.hasAttribute(interactableAttribute)) {
944
+ return false;
947
945
  }
948
946
  const rect = element.getBoundingClientRect();
949
947
  const style = window.getComputedStyle(element);
950
948
  const visible = isElementVisible(rect, style) && isElementMoreThanHalfInViewport(rect);
951
949
  const enabled = isElementEnabled(element, style);
952
950
  if (!visible || !enabled) {
953
- return;
951
+ return false;
954
952
  }
955
953
  // Check a few probe points to make sure the element is top-most
956
954
  for (const pt of getPointsToCheck(rect)) {
@@ -959,9 +957,9 @@ class PageInspector {
959
957
  if (elToCheck === element) {
960
958
  element.setAttribute(interactableAttribute, offset.toString());
961
959
  offset++;
962
- return; // this element done
960
+ return true; // this element done
963
961
  }
964
- // Handle <label> -> control mapping
962
+ // Handle <label> -> control mapping (explicit `for`/`htmlFor`)
965
963
  if (elToCheck.tagName.toLowerCase() === 'label' &&
966
964
  elToCheck.htmlFor) {
967
965
  const forId = elToCheck.htmlFor;
@@ -972,11 +970,41 @@ class PageInspector {
972
970
  control.setAttribute(interactableAttribute, offset.toString());
973
971
  offset++;
974
972
  }
975
- return;
973
+ return true;
976
974
  }
977
975
  elToCheck = elToCheck.parentElement;
978
976
  }
979
977
  }
978
+ return false;
979
+ }
980
+ // 2) Iterate and assign numbers
981
+ uniqueElements.forEach((element) => {
982
+ if (element === document.scrollingElement) {
983
+ // Special-case: always keep the root scrolling element
984
+ element.setAttribute(interactableAttribute, offset.toString());
985
+ offset++;
986
+ return; // skip the usual checks
987
+ }
988
+ else if (element.hasAttribute(interactableAttribute)) {
989
+ // Skip if this element already carries a value (e.g. assigned via <label>)
990
+ return;
991
+ }
992
+ if (tryAttributeElement(element)) {
993
+ return;
994
+ }
995
+ // Fallback: the element is a visually-hidden native control (e.g. a 0x0,
996
+ // opacity-0, pointer-events:none <input>) wrapped in a styled <label>.
997
+ // This is the standard pattern for Ant Design Segmented/Radio/Checkbox/
998
+ // Switch and many other component libraries: the native input is hidden
999
+ // and the surrounding <label> is the real clickable surface. Since the
1000
+ // hidden control fails the visibility/enabled checks above, attribute the
1001
+ // wrapping <label> instead so the toggle is still annotated.
1002
+ const wrappingLabel = element.closest('label');
1003
+ if (wrappingLabel &&
1004
+ wrappingLabel !== element &&
1005
+ !wrappingLabel.hasAttribute(interactableAttribute)) {
1006
+ tryAttributeElement(wrappingLabel);
1007
+ }
980
1008
  });
981
1009
  return offset;
982
1010
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "donobu",
3
- "version": "5.52.2",
3
+ "version": "5.52.3",
4
4
  "description": "Create browser automations with an LLM agent and replay them as Playwright scripts.",
5
5
  "main": "dist/main.js",
6
6
  "module": "dist/esm/main.js",