@prairielearn/browser-utils 2.2.11 → 2.2.13

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.13
4
+
5
+ ### Patch Changes
6
+
7
+ - f571b40: Upgrade all JavaScript dependencies
8
+ - Updated dependencies [f571b40]
9
+ - @prairielearn/html@4.0.19
10
+
11
+ ## 2.2.12
12
+
13
+ ### Patch Changes
14
+
15
+ - b55261c: Upgrade to TypeScript 5.9
16
+ - Updated dependencies [b55261c]
17
+ - @prairielearn/html@4.0.18
18
+
3
19
  ## 2.2.11
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":"AAAA,2BAA2B;AAC3B,oHAAoH;AACpH,MAAM,kBAAkB,GAAG;IACzB,GAAG;IACH,QAAQ;IACR,OAAO;IACP,UAAU;IACV,QAAQ;IACR,SAAS;IACT,YAAY;IACZ,0BAA0B;CAC3B;KACE,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,GAAG,QAAQ,uCAAuC,CAAC;KACrE,IAAI,CAAC,GAAG,CAAC,CAAC;AAEb,SAAS,SAAS,CAAC,MAAe;IAChC,OAAO,OAAO,MAAM,EAAE,QAAQ,KAAK,WAAW,CAAC;AACjD,CAAC;AAED,SAAS,SAAS,CAAC,OAAgB;IACjC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC,gBAAgB,CAAC,YAAY,CAAC,KAAK,SAAS,CAAC;IAChG,sFAAsF;IACtF,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAE7D,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IAED,IAAI,aAAa,KAAK,OAAO,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,OAAO,IAAI,OAAO,CAAC,UAAU,KAAK,aAAa,EAAE,CAAC;YACpD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,SAAS,UAAU,CAAC,OAAgB;IAClC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,OAAQ,OAAe,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;QACrD,OAAQ,OAAe,CAAC,QAAQ,CAAC;IACnC,CAAC;IAED,OAAO,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,OAAO,CAAC;AAC1F,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAgB;IACzC,MAAM,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAc,kBAAkB,CAAC,CAAC;IACpF,OAAO,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;AACjG,CAAC;AAMD,MAAM,UAAU,SAAS,CAAC,OAAgB;IACxC,gEAAgE;IAChE,MAAM,qBAAqB,GAAG,QAAQ,CAAC,aAAa,IAAI,QAAQ,CAAC,IAAI,CAAC;IAEtE,SAAS,OAAO,CAAC,CAAgB;QAC/B,IAAI,CAAC,CAAC,GAAG,KAAK,KAAK;YAAE,OAAO;QAE5B,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC7C,MAAM,cAAc,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,aAAa,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAEtD,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;YACf,oBAAoB;YACpB,IAAI,QAAQ,CAAC,aAAa,KAAK,cAAc,EAAE,CAAC;gBAC7C,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAiB,CAAC,KAAK,EAAE,CAAC;gBACzD,CAAC,CAAC,cAAc,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,mBAAmB;YACnB,IAAI,QAAQ,CAAC,aAAa,KAAK,aAAa,EAAE,CAAC;gBAC5C,SAAS,CAAC,CAAC,CAAiB,CAAC,KAAK,EAAE,CAAC;gBACtC,CAAC,CAAC,cAAc,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAE9C,OAAO;QACL,UAAU;YACR,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YACjD,uEAAuE;YACvE,uCAAuC;YACvC,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;gBAC5C,qBAAqC,EAAE,KAAK,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;YACzE,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,EAAe;IACtD,2EAA2E;IAC3E,gFAAgF;IAChF,gCAAgC;IAChC,IAAI,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC;QAAE,OAAO;IAEhD,2EAA2E;IAC3E,+DAA+D;IAC/D,MAAM,gBAAgB,GAAG,EAAE,CAAC,aAAa,CAAc,aAAa,CAAC,CAAC;IACtE,IAAI,gBAAgB,EAAE,CAAC;QACrB,gBAAgB,CAAC,KAAK,EAAE,CAAC;QACzB,OAAO;IACT,CAAC;IAED,MAAM,wBAAwB,GAAG,iBAAiB,CAAC,EAAE,CAAC,CAAC;IACvD,IAAI,wBAAwB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxC,wBAAwB,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;QACpC,OAAO;IACT,CAAC;IAED,yEAAyE;IACzE,EAAE,CAAC,KAAK,EAAE,CAAC;AACb,CAAC","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"]}
@@ -12,7 +12,6 @@ type AttributeMap = Record<string, string>;
12
12
  * @param source The element to copy attributes from
13
13
  * @param target The element to copy attributes into
14
14
  * @param attributes A map of attributes to copy from `source` to `target`
15
- * @param param.debug If true, logs debug information to the console
16
15
  */
17
16
  export declare function templateFromAttributes(source: HTMLElement, target: HTMLElement, attributes: AttributeMap): void;
18
17
  export {};
@@ -11,7 +11,6 @@
11
11
  * @param source The element to copy attributes from
12
12
  * @param target The element to copy attributes into
13
13
  * @param attributes A map of attributes to copy from `source` to `target`
14
- * @param param.debug If true, logs debug information to the console
15
14
  */
16
15
  export function templateFromAttributes(source, target, attributes) {
17
16
  Object.entries(attributes).forEach(([sourceAttribute, targetSelector]) => {
@@ -40,7 +39,7 @@ export function templateFromAttributes(source, target, attributes) {
40
39
  }
41
40
  else if (targetElement instanceof HTMLSelectElement) {
42
41
  const i = Array.from(targetElement.options).findIndex((o) => o.value === attributeValue);
43
- if (i >= 0) {
42
+ if (i !== -1) {
44
43
  targetElement.selectedIndex = i;
45
44
  // Manually trigger a 'change' event. This does not trigger
46
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;;;;;;;;;;;;;;GAcG;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 * @param param.debug If true, logs debug information to the console\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.11",
3
+ "version": "2.2.13",
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.15",
17
- "js-base64": "^3.7.7"
16
+ "@prairielearn/html": "^4.0.19",
17
+ "js-base64": "^3.7.8"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@prairielearn/tsconfig": "^0.0.0",
21
- "typescript": "^5.8.3"
21
+ "typescript": "^5.9.2"
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
 
@@ -13,7 +13,6 @@ type AttributeMap = Record<string, string>;
13
13
  * @param source The element to copy attributes from
14
14
  * @param target The element to copy attributes into
15
15
  * @param attributes A map of attributes to copy from `source` to `target`
16
- * @param param.debug If true, logs debug information to the console
17
16
  */
18
17
  export function templateFromAttributes(
19
18
  source: HTMLElement,
@@ -46,7 +45,7 @@ export function templateFromAttributes(
46
45
  }
47
46
  } else if (targetElement instanceof HTMLSelectElement) {
48
47
  const i = Array.from(targetElement.options).findIndex((o) => o.value === attributeValue);
49
- if (i >= 0) {
48
+ if (i !== -1) {
50
49
  targetElement.selectedIndex = i;
51
50
  // Manually trigger a 'change' event. This does not trigger
52
51
  // automatically when we change properties like 'checked'.