From 807c411c6b0e3af35909880cfa535726327fa7c8 Mon Sep 17 00:00:00 2001 From: Hardin Gray Date: Wed, 11 Jun 2025 12:34:37 -0400 Subject: [PATCH 1/7] chore(add-styles): demo update add-styles behavior (#1184) Update demo `add-styles` behavior to reduce re-renders. [Asana](https://app.asana.com/1/47184964732898/project/1205761271270033/task/1210514672776456?focus=true) --- demo/src/Behaviors/AddStyles/AddStyles.ts | 74 +++++++++++++++++++++-- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/demo/src/Behaviors/AddStyles/AddStyles.ts b/demo/src/Behaviors/AddStyles/AddStyles.ts index 84f0e85b0..161b85553 100644 --- a/demo/src/Behaviors/AddStyles/AddStyles.ts +++ b/demo/src/Behaviors/AddStyles/AddStyles.ts @@ -10,6 +10,44 @@ import { shallowCloneToRoot, } from 'hyperview/src/services'; +/** + * Checks if a style element has changed by comparing its attributes and children + */ +const hasStyleChanged = (oldStyle: Element, newStyle: Element): boolean => { + // Compare attributes + const oldAttrs = oldStyle.attributes ? Array.from(oldStyle.attributes) : []; + const newAttrs = newStyle.attributes ? Array.from(newStyle.attributes) : []; + + if (oldAttrs.length !== newAttrs.length) { + return true; + } + + // Check if any attribute values differ + const hasAttrChanged = newAttrs.some( + newAttr => oldStyle.getAttribute(newAttr.name) !== newAttr.value, + ); + if (hasAttrChanged) { + return true; + } + + // Compare children recursively + const oldChildren = oldStyle.childNodes + ? Array.from(oldStyle.childNodes) + : []; + const newChildren = newStyle.childNodes + ? Array.from(newStyle.childNodes) + : []; + + if (oldChildren.length !== newChildren.length) { + return true; + } + + // Check if any child elements differ + return newChildren.some((newChild, index) => + hasStyleChanged(oldChildren[index] as Element, newChild as Element), + ); +}; + /** * This behavior allows injecting styles into the screen's styles element. * It is useful when loading partial Hyperview documents that need their own styles. @@ -33,17 +71,41 @@ export const AddStyles: HvBehavior = { if (newStyles) { const screen = getAncestorByTagName(element, 'screen'); if (screen) { - const styles = Dom.getFirstTag(screen, 'styles'); - const styleElements = newStyles.getElementsByTagName('style'); + const styles: Element | null | undefined = Dom.getFirstTag( + screen, + 'styles', + ); + const styleElements = Array.from( + newStyles.getElementsByTagName('style'), + ).filter(e => !!e.getAttribute('id')); if (styles && styleElements) { - Array.from(styleElements).forEach(style => { - styles.appendChild(style); + const existingStyles = Array.from( + styles.getElementsByTagName('style') || [], + ); + let hasChanges = false; + styleElements.forEach(style => { + const styleId = style.getAttribute('id'); + const existingStyle = existingStyles.find( + e => e.getAttribute('id') === styleId, + ); + if (!existingStyle) { + styles.appendChild(style); + hasChanges = true; + } else if (style && hasStyleChanged(existingStyle, style)) { + styles.replaceChild(style, existingStyle); + hasChanges = true; + } }); - const newRoot = shallowCloneToRoot(styles); - updateRoot(newRoot, true); + + if (hasChanges) { + // Only perform cloning if styles have changed + const newRoot: Document = shallowCloneToRoot(styles); + updateRoot(newRoot, true); + } } } } + const ranOnce = element.getAttribute('ran-once'); const once = element.getAttribute('once'); if (once === 'true') { From ee04e36d9c524ff8d24969fed0896cbaab7cfba6 Mon Sep 17 00:00:00 2001 From: Hardin Gray Date: Thu, 12 Jun 2025 15:27:51 -0400 Subject: [PATCH 2/7] chore(update): improve (#1186) Performing an update action (append, prepend, replace, replace-inner) with a delay requires that the behavior element has an id. The `basic-forms` demo was missing an id so was non functional. - Added an error to warn developers of this condition - Fixed the demo by adding an id to the element | Before | Error | After | | -- | -- | -- | | ![before](https://github.com/user-attachments/assets/dfa1fded-0d8d-47a7-846e-def4b7e573fe) | ![error](https://github.com/user-attachments/assets/66e2d97e-64cf-4bae-8c03-02ba9b2700bf) | ![after](https://github.com/user-attachments/assets/c349dacd-ac16-410b-8682-3d1b3e4c02a6) | [Asana](https://app.asana.com/1/47184964732898/project/1204008699308084/task/1210535376481469?focus=true) --- .../advanced/case-studies/basic-forms/index.xml.njk | 1 + src/core/components/hv-root/index.tsx | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/demo/backend/advanced/case-studies/basic-forms/index.xml.njk b/demo/backend/advanced/case-studies/basic-forms/index.xml.njk index d9ddcd2c8..6eccf4c6d 100644 --- a/demo/backend/advanced/case-studies/basic-forms/index.xml.njk +++ b/demo/backend/advanced/case-studies/basic-forms/index.xml.njk @@ -22,6 +22,7 @@ hv_button_behavior: "back" /> { onUpdateCallbacks.getDoc(), behaviorElement, ); + if (targetElement) { Services.setTimeoutId(targetElement, timeoutId.toString()); + } else { + // Warn developers if the behavior element is not found + Logging.error( + `Cannot find a behavior element to perform "${action}". It may be missing an id.`, + Logging.deferredToString(() => { + return new XMLSerializer().serializeToString( + behaviorElement as Element, + ); + }), + ); } } else { // If there's no delay, fetch immediately and update the doc when done. From cd1f17df1632c429d6d1e0b3d17869708bf158a0 Mon Sep 17 00:00:00 2001 From: Hardin Gray Date: Thu, 12 Jun 2025 16:28:46 -0400 Subject: [PATCH 3/7] fix(scroll): use scroll-to-input-offset value if available (#1185) The previous functionality was returning zero if the `scroll-to-input-offset` attribute was missing, otherwise it was returning the default value (120). There was no path which returned the value of `scroll-to-input-offset` if available. - Created a new demo "Scroll to Input Offset" - Corrected the value return - Fixed a few typos in the demo and documentation | Before | After | | -- | -- | | ![before](https://github.com/user-attachments/assets/1bcf806c-4b20-4bcf-b95e-4d904dc78dac) | ![after](https://github.com/user-attachments/assets/a49278dc-138e-4da2-8b28-a150b7baa999) | [Asana](https://app.asana.com/1/47184964732898/project/1204008699308084/task/1210525849054931?focus=true) --- .../scroll-to-input/_styles.xml.njk | 54 +++++++++++++++++++ .../scroll-to-input/index.xml.njk | 44 +++++++++++++++ .../case-studies/scroll-to-input/submit.xml | 18 +++++++ .../forms/text-field/index.xml.njk | 2 +- docs/reference_view.md | 6 +-- schema/core.xsd | 1 + src/components/hv-view/index.tsx | 2 +- 7 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 demo/backend/advanced/case-studies/scroll-to-input/_styles.xml.njk create mode 100644 demo/backend/advanced/case-studies/scroll-to-input/index.xml.njk create mode 100644 demo/backend/advanced/case-studies/scroll-to-input/submit.xml diff --git a/demo/backend/advanced/case-studies/scroll-to-input/_styles.xml.njk b/demo/backend/advanced/case-studies/scroll-to-input/_styles.xml.njk new file mode 100644 index 000000000..b30f6e787 --- /dev/null +++ b/demo/backend/advanced/case-studies/scroll-to-input/_styles.xml.njk @@ -0,0 +1,54 @@ + + +