@prairielearn/browser-utils 2.2.12 → 2.2.14

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @prairielearn/browser-utils
2
2
 
3
+ ## 2.2.14
4
+
5
+ ### Patch Changes
6
+
7
+ - c72a4b8: Upgrade dependencies
8
+ - Updated dependencies [c72a4b8]
9
+ - @prairielearn/html@4.0.20
10
+
11
+ ## 2.2.13
12
+
13
+ ### Patch Changes
14
+
15
+ - f571b40: Upgrade all JavaScript dependencies
16
+ - Updated dependencies [f571b40]
17
+ - @prairielearn/html@4.0.19
18
+
3
19
  ## 2.2.12
4
20
 
5
21
  ### Patch Changes
package/dist/focus.js CHANGED
@@ -13,7 +13,7 @@ const FOCUSABLE_SELECTOR = [
13
13
  .map((selector) => `${selector}:not([tabindex^="-"]):not(.btn-close)`)
14
14
  .join(',');
15
15
  function isElement(object) {
16
- return typeof object?.nodeType !== 'undefined';
16
+ return object?.nodeType !== undefined;
17
17
  }
18
18
  function isVisible(element) {
19
19
  if (!isElement(element) || element.getClientRects().length === 0) {
@@ -40,7 +40,7 @@ function isDisabled(element) {
40
40
  if (!element || element.nodeType !== Node.ELEMENT_NODE) {
41
41
  return true;
42
42
  }
43
- if (typeof element.disabled !== 'undefined') {
43
+ if (element.disabled !== undefined) {
44
44
  return element.disabled;
45
45
  }
46
46
  return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';
package/dist/focus.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"focus.js","sourceRoot":"","sources":["../src/focus.ts"],"names":[],"mappings":"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","sourcesContent":["// Borrowed from Bootstrap:\n// https://github.com/twbs/bootstrap/blob/5f75413735d8779aeefe0097af9dc5a416208ae5/js/src/dom/selector-engine.js#L67\nconst FOCUSABLE_SELECTOR = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]',\n]\n .map((selector) => `${selector}:not([tabindex^=\"-\"]):not(.btn-close)`)\n .join(',');\n\nfunction isElement(object: Element) {\n return typeof object?.nodeType !== 'undefined';\n}\n\nfunction isVisible(element: Element) {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false;\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible';\n // Handle `details` element as its content may falsly appear visible when it is closed\n const closedDetails = element.closest('details:not([open])');\n\n if (!closedDetails) {\n return elementIsVisible;\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary');\n if (summary && summary.parentNode !== closedDetails) {\n return false;\n }\n\n if (summary === null) {\n return false;\n }\n }\n\n return elementIsVisible;\n}\n\nfunction isDisabled(element: Element) {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true;\n }\n\n if (typeof (element as any).disabled !== 'undefined') {\n return (element as any).disabled;\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';\n}\n\nfunction focusableChildren(element: Element): HTMLElement[] {\n const focusableChildren = element.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR);\n return Array.from(focusableChildren).filter((child) => !isDisabled(child) && isVisible(child));\n}\n\nexport interface FocusTrap {\n deactivate(): void;\n}\n\nexport function trapFocus(element: Element): FocusTrap {\n // Store the previous active element so we can restore it later.\n const previousActiveElement = document.activeElement ?? document.body;\n\n function keyDown(e: KeyboardEvent) {\n if (e.key !== 'Tab') return;\n\n const focusable = focusableChildren(element);\n const firstFocusable = focusable[0];\n const lastFocusable = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n // Tabbing backwards\n if (document.activeElement === firstFocusable) {\n (focusable[focusable.length - 1] as HTMLElement).focus();\n e.preventDefault();\n }\n } else {\n // Tabbing forwards\n if (document.activeElement === lastFocusable) {\n (focusable[0] as HTMLElement).focus();\n e.preventDefault();\n }\n }\n }\n\n document.addEventListener('keydown', keyDown);\n\n return {\n deactivate() {\n document.removeEventListener('keydown', keyDown);\n // Restore focus to the previously active element, but only if focus is\n // currently inside the trap container.\n if (element.contains(document.activeElement)) {\n (previousActiveElement as HTMLElement)?.focus({ preventScroll: true });\n }\n },\n };\n}\n\nexport function focusFirstFocusableChild(el: HTMLElement) {\n // In case the user (or more frequently, Cypress) is too fast and focuses a\n // specific element inside the container before this script runs, don't transfer\n // focus to a different element.\n if (el.contains(document.activeElement)) return;\n\n // Escape hatch: if the first element isn't the one that should be focused,\n // add the `autofocus` attribute to the element that should be.\n const autofocusElement = el.querySelector<HTMLElement>('[autofocus]');\n if (autofocusElement) {\n autofocusElement.focus();\n return;\n }\n\n const focusablePopoverChildren = focusableChildren(el);\n if (focusablePopoverChildren.length > 0) {\n focusablePopoverChildren[0].focus();\n return;\n }\n\n // If we still couldn't find a child element, focus the container itself.\n el.focus();\n}\n"]}
1
+ {"version":3,"file":"focus.js","sourceRoot":"","sources":["../src/focus.ts"],"names":[],"mappings":"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","sourcesContent":["// Borrowed from Bootstrap:\n// https://github.com/twbs/bootstrap/blob/5f75413735d8779aeefe0097af9dc5a416208ae5/js/src/dom/selector-engine.js#L67\nconst FOCUSABLE_SELECTOR = [\n 'a',\n 'button',\n 'input',\n 'textarea',\n 'select',\n 'details',\n '[tabindex]',\n '[contenteditable=\"true\"]',\n]\n .map((selector) => `${selector}:not([tabindex^=\"-\"]):not(.btn-close)`)\n .join(',');\n\nfunction isElement(object: Element) {\n return object?.nodeType !== undefined;\n}\n\nfunction isVisible(element: Element) {\n if (!isElement(element) || element.getClientRects().length === 0) {\n return false;\n }\n\n const elementIsVisible = getComputedStyle(element).getPropertyValue('visibility') === 'visible';\n // Handle `details` element as its content may falsly appear visible when it is closed\n const closedDetails = element.closest('details:not([open])');\n\n if (!closedDetails) {\n return elementIsVisible;\n }\n\n if (closedDetails !== element) {\n const summary = element.closest('summary');\n if (summary && summary.parentNode !== closedDetails) {\n return false;\n }\n\n if (summary === null) {\n return false;\n }\n }\n\n return elementIsVisible;\n}\n\nfunction isDisabled(element: Element) {\n if (!element || element.nodeType !== Node.ELEMENT_NODE) {\n return true;\n }\n\n if ((element as any).disabled !== undefined) {\n return (element as any).disabled;\n }\n\n return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false';\n}\n\nfunction focusableChildren(element: Element): HTMLElement[] {\n const focusableChildren = element.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR);\n return Array.from(focusableChildren).filter((child) => !isDisabled(child) && isVisible(child));\n}\n\nexport interface FocusTrap {\n deactivate(): void;\n}\n\nexport function trapFocus(element: Element): FocusTrap {\n // Store the previous active element so we can restore it later.\n const previousActiveElement = document.activeElement ?? document.body;\n\n function keyDown(e: KeyboardEvent) {\n if (e.key !== 'Tab') return;\n\n const focusable = focusableChildren(element);\n const firstFocusable = focusable[0];\n const lastFocusable = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n // Tabbing backwards\n if (document.activeElement === firstFocusable) {\n (focusable[focusable.length - 1] as HTMLElement).focus();\n e.preventDefault();\n }\n } else {\n // Tabbing forwards\n if (document.activeElement === lastFocusable) {\n (focusable[0] as HTMLElement).focus();\n e.preventDefault();\n }\n }\n }\n\n document.addEventListener('keydown', keyDown);\n\n return {\n deactivate() {\n document.removeEventListener('keydown', keyDown);\n // Restore focus to the previously active element, but only if focus is\n // currently inside the trap container.\n if (element.contains(document.activeElement)) {\n (previousActiveElement as HTMLElement)?.focus({ preventScroll: true });\n }\n },\n };\n}\n\nexport function focusFirstFocusableChild(el: HTMLElement) {\n // In case the user (or more frequently, Cypress) is too fast and focuses a\n // specific element inside the container before this script runs, don't transfer\n // focus to a different element.\n if (el.contains(document.activeElement)) return;\n\n // Escape hatch: if the first element isn't the one that should be focused,\n // add the `autofocus` attribute to the element that should be.\n const autofocusElement = el.querySelector<HTMLElement>('[autofocus]');\n if (autofocusElement) {\n autofocusElement.focus();\n return;\n }\n\n const focusablePopoverChildren = focusableChildren(el);\n if (focusablePopoverChildren.length > 0) {\n focusablePopoverChildren[0].focus();\n return;\n }\n\n // If we still couldn't find a child element, focus the container itself.\n el.focus();\n}\n"]}
@@ -39,7 +39,7 @@ export function templateFromAttributes(source, target, attributes) {
39
39
  }
40
40
  else if (targetElement instanceof HTMLSelectElement) {
41
41
  const i = Array.from(targetElement.options).findIndex((o) => o.value === attributeValue);
42
- if (i >= 0) {
42
+ if (i !== -1) {
43
43
  targetElement.selectedIndex = i;
44
44
  // Manually trigger a 'change' event. This does not trigger
45
45
  // automatically when we change properties like 'checked'.
@@ -1 +1 @@
1
- {"version":3,"file":"template-from-attributes.js","sourceRoot":"","sources":["../src/template-from-attributes.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAmB,EACnB,MAAmB,EACnB,UAAwB;IAExB,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,cAAc,CAAC,EAAE,EAAE;QACvE,MAAM,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;QAC5D,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,cAAc,eAAe,+BAA+B,CAAC,CAAC;YAC5E,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;QACxD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,wCAAwC,cAAc,GAAG,CAAC,CAAC;YACzE,OAAO;QACT,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,EAAE;YAChC,IAAI,aAAa,YAAY,gBAAgB,EAAE,CAAC;gBAC9C,IAAI,aAAa,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;oBACtC,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;oBACnD,aAAa,CAAC,OAAO,GAAG,CAAC,CAAC,eAAe,CAAC;oBAC1C,2DAA2D;oBAC3D,0DAA0D;oBAC1D,aAAa,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;gBACtE,CAAC;qBAAM,CAAC;oBACN,aAAa,CAAC,KAAK,GAAG,cAAc,CAAC;gBACvC,CAAC;YACH,CAAC;iBAAM,IAAI,aAAa,YAAY,iBAAiB,EAAE,CAAC;gBACtD,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,cAAc,CAAC,CAAC;gBACzF,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACX,aAAa,CAAC,aAAa,GAAG,CAAC,CAAC;oBAChC,2DAA2D;oBAC3D,0DAA0D;oBAC1D,aAAa,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;gBACtE,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,KAAK,CAAC,qCAAqC,cAAc,GAAG,CAAC,CAAC;gBACxE,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,aAAa,CAAC,WAAW,GAAG,cAAc,CAAC;YAC7C,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["type AttributeMap = Record<string, string>;\n\n/**\n * For each key in `attributes`, copies that attribute's value from `source`\n * into all elements within `target` that match the corresponding value in\n * `attributes`.\n *\n * For `<input type=\"checkbox\">` elements it interprets the attribute as JSON\n * and uses the truthiness of it to set `checked`. For other `<input>` elements,\n * it sets the `value` attribute. For all others, it sets the `textContent`\n * attribute.\n *\n * @param source The element to copy attributes from\n * @param target The element to copy attributes into\n * @param attributes A map of attributes to copy from `source` to `target`\n */\nexport function templateFromAttributes(\n source: HTMLElement,\n target: HTMLElement,\n attributes: AttributeMap,\n) {\n Object.entries(attributes).forEach(([sourceAttribute, targetSelector]) => {\n const attributeValue = source.getAttribute(sourceAttribute);\n if (attributeValue == null) {\n console.error(`Attribute \"${sourceAttribute}\" not found on source element`);\n return;\n }\n\n const targets = target.querySelectorAll(targetSelector);\n if (targets.length === 0) {\n console.error(`No elements found matching selector \"${targetSelector}\"`);\n return;\n }\n\n targets.forEach((targetElement) => {\n if (targetElement instanceof HTMLInputElement) {\n if (targetElement.type === 'checkbox') {\n const attributeParsed = JSON.parse(attributeValue);\n targetElement.checked = !!attributeParsed;\n // Manually trigger a 'change' event. This does not trigger\n // automatically when we change properties like 'checked'.\n targetElement.dispatchEvent(new Event('change', { bubbles: true }));\n } else {\n targetElement.value = attributeValue;\n }\n } else if (targetElement instanceof HTMLSelectElement) {\n const i = Array.from(targetElement.options).findIndex((o) => o.value === attributeValue);\n if (i >= 0) {\n targetElement.selectedIndex = i;\n // Manually trigger a 'change' event. This does not trigger\n // automatically when we change properties like 'checked'.\n targetElement.dispatchEvent(new Event('change', { bubbles: true }));\n } else {\n console.error(`Could not find option with value \"${attributeValue}\"`);\n }\n } else {\n targetElement.textContent = attributeValue;\n }\n });\n });\n}\n"]}
1
+ {"version":3,"file":"template-from-attributes.js","sourceRoot":"","sources":["../src/template-from-attributes.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAmB,EACnB,MAAmB,EACnB,UAAwB;IAExB,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,cAAc,CAAC,EAAE,EAAE;QACvE,MAAM,cAAc,GAAG,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;QAC5D,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,cAAc,eAAe,+BAA+B,CAAC,CAAC;YAC5E,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;QACxD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,wCAAwC,cAAc,GAAG,CAAC,CAAC;YACzE,OAAO;QACT,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,EAAE;YAChC,IAAI,aAAa,YAAY,gBAAgB,EAAE,CAAC;gBAC9C,IAAI,aAAa,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;oBACtC,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;oBACnD,aAAa,CAAC,OAAO,GAAG,CAAC,CAAC,eAAe,CAAC;oBAC1C,2DAA2D;oBAC3D,0DAA0D;oBAC1D,aAAa,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;gBACtE,CAAC;qBAAM,CAAC;oBACN,aAAa,CAAC,KAAK,GAAG,cAAc,CAAC;gBACvC,CAAC;YACH,CAAC;iBAAM,IAAI,aAAa,YAAY,iBAAiB,EAAE,CAAC;gBACtD,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,cAAc,CAAC,CAAC;gBACzF,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;oBACb,aAAa,CAAC,aAAa,GAAG,CAAC,CAAC;oBAChC,2DAA2D;oBAC3D,0DAA0D;oBAC1D,aAAa,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;gBACtE,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,KAAK,CAAC,qCAAqC,cAAc,GAAG,CAAC,CAAC;gBACxE,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,aAAa,CAAC,WAAW,GAAG,cAAc,CAAC;YAC7C,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["type AttributeMap = Record<string, string>;\n\n/**\n * For each key in `attributes`, copies that attribute's value from `source`\n * into all elements within `target` that match the corresponding value in\n * `attributes`.\n *\n * For `<input type=\"checkbox\">` elements it interprets the attribute as JSON\n * and uses the truthiness of it to set `checked`. For other `<input>` elements,\n * it sets the `value` attribute. For all others, it sets the `textContent`\n * attribute.\n *\n * @param source The element to copy attributes from\n * @param target The element to copy attributes into\n * @param attributes A map of attributes to copy from `source` to `target`\n */\nexport function templateFromAttributes(\n source: HTMLElement,\n target: HTMLElement,\n attributes: AttributeMap,\n) {\n Object.entries(attributes).forEach(([sourceAttribute, targetSelector]) => {\n const attributeValue = source.getAttribute(sourceAttribute);\n if (attributeValue == null) {\n console.error(`Attribute \"${sourceAttribute}\" not found on source element`);\n return;\n }\n\n const targets = target.querySelectorAll(targetSelector);\n if (targets.length === 0) {\n console.error(`No elements found matching selector \"${targetSelector}\"`);\n return;\n }\n\n targets.forEach((targetElement) => {\n if (targetElement instanceof HTMLInputElement) {\n if (targetElement.type === 'checkbox') {\n const attributeParsed = JSON.parse(attributeValue);\n targetElement.checked = !!attributeParsed;\n // Manually trigger a 'change' event. This does not trigger\n // automatically when we change properties like 'checked'.\n targetElement.dispatchEvent(new Event('change', { bubbles: true }));\n } else {\n targetElement.value = attributeValue;\n }\n } else if (targetElement instanceof HTMLSelectElement) {\n const i = Array.from(targetElement.options).findIndex((o) => o.value === attributeValue);\n if (i !== -1) {\n targetElement.selectedIndex = i;\n // Manually trigger a 'change' event. This does not trigger\n // automatically when we change properties like 'checked'.\n targetElement.dispatchEvent(new Event('change', { bubbles: true }));\n } else {\n console.error(`Could not find option with value \"${attributeValue}\"`);\n }\n } else {\n targetElement.textContent = attributeValue;\n }\n });\n });\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/browser-utils",
3
- "version": "2.2.12",
3
+ "version": "2.2.14",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,12 +13,12 @@
13
13
  "dev": "tsc --watch --preserveWatchOutput"
14
14
  },
15
15
  "dependencies": {
16
- "@prairielearn/html": "^4.0.18",
17
- "js-base64": "^3.7.7"
16
+ "@prairielearn/html": "^4.0.20",
17
+ "js-base64": "^3.7.8"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@prairielearn/tsconfig": "^0.0.0",
21
- "typescript": "^5.9.2"
21
+ "typescript": "^5.9.3"
22
22
  },
23
23
  "sideEffects": false
24
24
  }
package/src/focus.ts CHANGED
@@ -14,7 +14,7 @@ const FOCUSABLE_SELECTOR = [
14
14
  .join(',');
15
15
 
16
16
  function isElement(object: Element) {
17
- return typeof object?.nodeType !== 'undefined';
17
+ return object?.nodeType !== undefined;
18
18
  }
19
19
 
20
20
  function isVisible(element: Element) {
@@ -49,7 +49,7 @@ function isDisabled(element: Element) {
49
49
  return true;
50
50
  }
51
51
 
52
- if (typeof (element as any).disabled !== 'undefined') {
52
+ if ((element as any).disabled !== undefined) {
53
53
  return (element as any).disabled;
54
54
  }
55
55
 
@@ -45,7 +45,7 @@ export function templateFromAttributes(
45
45
  }
46
46
  } else if (targetElement instanceof HTMLSelectElement) {
47
47
  const i = Array.from(targetElement.options).findIndex((o) => o.value === attributeValue);
48
- if (i >= 0) {
48
+ if (i !== -1) {
49
49
  targetElement.selectedIndex = i;
50
50
  // Manually trigger a 'change' event. This does not trigger
51
51
  // automatically when we change properties like 'checked'.