From 257b8994c44d64be143e3c8d62b497a055b3aec1 Mon Sep 17 00:00:00 2001 From: Josh Howenstine Date: Sat, 12 Oct 2024 10:12:00 -0700 Subject: [PATCH 01/11] feat: create TextMagnifier component --- packages/@lightningjs/ui-components/index.js | 1 + .../src/components/Surface/Surface.js | 5 ++ .../components/TextMagnifier/TextMagnifier.js | 59 +++++++++++++++++++ .../TextMagnifier/TextMagnifier.stories.js | 43 ++++++++++++++ .../TextMagnifier/TextMagnifier.styles.js | 28 +++++++++ .../src/components/TextMagnifier/index.js | 19 ++++++ .../src/mixins/withThemeStyles/index.js | 8 +-- 7 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js create mode 100644 packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.stories.js create mode 100644 packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js create mode 100644 packages/@lightningjs/ui-components/src/components/TextMagnifier/index.js diff --git a/packages/@lightningjs/ui-components/index.js b/packages/@lightningjs/ui-components/index.js index 91e227859..040f140bf 100644 --- a/packages/@lightningjs/ui-components/index.js +++ b/packages/@lightningjs/ui-components/index.js @@ -90,6 +90,7 @@ export { default as TitleRow } from './src/components/TitleRow/TitleRow'; export { default as Toggle } from './src/components/Toggle/Toggle'; export { default as ToggleSmall } from './src/components/Toggle/ToggleSmall'; export { default as Tooltip } from './src/components/Tooltip/Tooltip'; +export { default as TextMagnifier } from './src/components/TextMagnifier/TextMagnifier'; // Globals export { default as context } from './src/globals/context'; diff --git a/packages/@lightningjs/ui-components/src/components/Surface/Surface.js b/packages/@lightningjs/ui-components/src/components/Surface/Surface.js index 42921405b..3be9f6814 100644 --- a/packages/@lightningjs/ui-components/src/components/Surface/Surface.js +++ b/packages/@lightningjs/ui-components/src/components/Surface/Surface.js @@ -53,6 +53,11 @@ export default class Surface extends Base { } _update() { + this.patch({ + x: this.style.x, + w: this.w, + mountX: 0.5 + }); this._updateLayout(); this._updateScale(); } diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js new file mode 100644 index 000000000..2b09e1266 --- /dev/null +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js @@ -0,0 +1,59 @@ +/** + * Copyright 2023 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import Surface from '../Surface/Surface'; +// import ScrollWrapper from '../ScrollWrapper/ScrollWrapper.js'; +import * as styles from './TextMagnifier.styles.js'; + +export default class TextMagnifier extends Surface { + static get __componentName() { + return 'TextMagnifier'; + } + + static get __themeStyle() { + return styles; + } + + _construct() { + this._tone = 'inverse'; + super._construct(); + } + + set mode(v) { } + + get mode() { + return 'unfocused'; + } + + _update() { + this.patch({ + y: this.stage.h / this.stage.getRenderPrecision() + this.style.radius, + mountY: 1 + }); + + super._update(); + } + + // set content(text) { + // let trimmedText = text.trim(); + // if (trimmedText.charAt(trimmedText.length - 1) === '0') { + // trimmedText = trimmedText.slice(0, -1); + // } + // this.tag('Text').content = trimmedText.split(',').filter(Boolean).join(','); + // } +} diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.stories.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.stories.js new file mode 100644 index 000000000..f6734cab6 --- /dev/null +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.stories.js @@ -0,0 +1,43 @@ +/** + * Copyright 2023 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import lng from '@lightningjs/core'; +import { default as TextMagnifierComponent } from '.'; +import { context } from '@lightningjs/ui-components/src'; + +export default { + title: 'Components/TextMagnifier' +}; + +export const TextMagnifier = () => + class TextMagnifier extends lng.Component { + static _template() { + return { + TextMagnifier: { + type: TextMagnifierComponent + } + }; + } + + _init() { + this.parent.x = -context.theme.layout.marginX; + this.parent.y = -context.theme.layout.marginY; + + super._init(); + } + }; diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js new file mode 100644 index 000000000..582b11421 --- /dev/null +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js @@ -0,0 +1,28 @@ +/** + * Copyright 2023 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export const base = theme => { + return { + radius: theme.radius.lg, + textStyle: theme.typography.display1, + color: theme.color.fillNeutral, + w: theme.layout.screenW - theme.layout.safe, + x: (theme.layout.screenW - theme.layout.safe) / 2 + theme.layout.safe / 2, + h: theme.typography.headline1.lineHeight + theme.layout.marginY * 2 + }; +}; diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/index.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/index.js new file mode 100644 index 000000000..77bde66f6 --- /dev/null +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/index.js @@ -0,0 +1,19 @@ +/** + * Copyright 2023 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +export { default as default } from './TextMagnifier'; diff --git a/packages/@lightningjs/ui-components/src/mixins/withThemeStyles/index.js b/packages/@lightningjs/ui-components/src/mixins/withThemeStyles/index.js index 97a8b769e..7d0105f5a 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withThemeStyles/index.js +++ b/packages/@lightningjs/ui-components/src/mixins/withThemeStyles/index.js @@ -139,7 +139,7 @@ export default function withThemeStyles(Base, mixinStyle = {}) { if ( !Object.keys(this._styleManager.props).length || JSON.stringify(this._styleManager.props) === - JSON.stringify(this._prevComponentConfigProps) + JSON.stringify(this._prevComponentConfigProps) ) { return; } @@ -147,9 +147,9 @@ export default function withThemeStyles(Base, mixinStyle = {}) { // Compare current properties with previous configuration and get the payload const payload = this._prevComponentConfigProps ? mergeObjectsWithSecondDominant( - this._prevComponentConfigProps || {}, - this._styleManager.props || {} - ) + this._prevComponentConfigProps || {}, + this._styleManager.props || {} + ) : this._styleManager.props || {}; // Store a deep copy of the current properties for future comparison From 75643315ddffd8137569f8ec9e123ed505429528 Mon Sep 17 00:00:00 2001 From: Josh Howenstine Date: Mon, 14 Oct 2024 13:22:38 -0700 Subject: [PATCH 02/11] fix: updates to text magnifier component --- .../components/ScrollWrapper/ScrollWrapper.js | 6 +- .../components/TextMagnifier/TextMagnifier.js | 60 ++++++++++++++----- .../TextMagnifier/TextMagnifier.stories.js | 3 +- .../TextMagnifier/TextMagnifier.styles.js | 17 ++++++ .../src/mixins/withThemeStyles/index.js | 8 +-- 5 files changed, 72 insertions(+), 22 deletions(-) diff --git a/packages/@lightningjs/ui-components/src/components/ScrollWrapper/ScrollWrapper.js b/packages/@lightningjs/ui-components/src/components/ScrollWrapper/ScrollWrapper.js index b8c726f36..4578887c0 100644 --- a/packages/@lightningjs/ui-components/src/components/ScrollWrapper/ScrollWrapper.js +++ b/packages/@lightningjs/ui-components/src/components/ScrollWrapper/ScrollWrapper.js @@ -150,9 +150,9 @@ export default class ScrollWrapper extends Base { rtt: true, shader: shouldFade ? { - type: lng.shaders.FadeOut, - bottom: this.style.fadeHeight - } + type: lng.shaders.FadeOut, + bottom: this.style.fadeHeight + } : undefined }); } diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js index 2b09e1266..a6ead024d 100644 --- a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js @@ -17,7 +17,7 @@ */ import Surface from '../Surface/Surface'; -// import ScrollWrapper from '../ScrollWrapper/ScrollWrapper.js'; +import ScrollWrapper from '../ScrollWrapper/ScrollWrapper.js'; import * as styles from './TextMagnifier.styles.js'; export default class TextMagnifier extends Surface { @@ -29,31 +29,63 @@ export default class TextMagnifier extends Surface { return styles; } + static _template() { + return { + ...super._template(), + ScrollWrapper: { + type: ScrollWrapper + } + }; + } + _construct() { - this._tone = 'inverse'; + this._location = 'top'; super._construct(); } - set mode(v) { } + set mode(value) { + // Mode is always unfocused + } get mode() { return 'unfocused'; } + static get properties() { + return ['location']; + } + + get content() { + return this._content; + } + + set content(value) { + this._createScrollWrapper(); + this.tag('ScrollWrapper').content = value; + this._content = value; + } + _update() { - this.patch({ - y: this.stage.h / this.stage.getRenderPrecision() + this.style.radius, - mountY: 1 - }); + if (this.location === 'top') { + this.y = -this.style.radius; + } else { + this.patch({ + y: this.stage.h / this.stage.getRenderPrecision() + this.style.radius, + mountY: 1 + }); + } + this._createScrollWrapper(); super._update(); } - // set content(text) { - // let trimmedText = text.trim(); - // if (trimmedText.charAt(trimmedText.length - 1) === '0') { - // trimmedText = trimmedText.slice(0, -1); - // } - // this.tag('Text').content = trimmedText.split(',').filter(Boolean).join(','); - // } + _createScrollWrapper() { + this.tag('ScrollWrapper').patch({ + type: ScrollWrapper, + w: this.style.w - this.style.gutterX * 2, + h: this.style.h - this.style.gutterY * 2, + y: this.style.gutterY, + style: { textStyle: this.style.textStyle } + }); + } } diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.stories.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.stories.js index f6734cab6..611fcfe0f 100644 --- a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.stories.js +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.stories.js @@ -29,7 +29,8 @@ export const TextMagnifier = () => static _template() { return { TextMagnifier: { - type: TextMagnifierComponent + type: TextMagnifierComponent, + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec aliquet fermentum massa, nec scelerisque magna sodales viverra. Fusce mauris neque, aliquam vitae hendrerit vitae, tempor eu nunc. Vestibulum ligula tellus, feugiat facilisis diam non, scelerisque eleifend justo. Sed fermentum dui velit, eu imperdiet enim pretium vitae. Sed molestie, ex nec lacinia ornare, turpis urna egestas justo, sit amet blandit sapien turpis egestas dui. Sed vel pharetra elit. Quisque auctor risus a elit posuere fermentum. Integer interdum enim vitae arcu faucibus vulputate. Pellentesque sed dolor quis erat venenatis scelerisque in faucibus lectus. Mauris ac semper ante, at lobortis nunc.' } }; } diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js index 582b11421..c46eb3774 100644 --- a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js @@ -21,8 +21,25 @@ export const base = theme => { radius: theme.radius.lg, textStyle: theme.typography.display1, color: theme.color.fillNeutral, + gutterX: theme.layout.gutterX, + gutterY: theme.layout.safe, w: theme.layout.screenW - theme.layout.safe, x: (theme.layout.screenW - theme.layout.safe) / 2 + theme.layout.safe / 2, h: theme.typography.headline1.lineHeight + theme.layout.marginY * 2 }; }; + +export const tone = theme => ({ + neutral: { + backgroundColor: theme.color.fillNeutral, + textStyle: { textColor: theme.color.textInverse } + }, + inverse: { + backgroundColor: theme.color.fillInverse, + textStyle: { textColor: theme.color.textNeutral } + }, + brand: { + backgroundColor: theme.color.fillBrand, + textStyle: { textColor: theme.color.textNeutral } + } +}); diff --git a/packages/@lightningjs/ui-components/src/mixins/withThemeStyles/index.js b/packages/@lightningjs/ui-components/src/mixins/withThemeStyles/index.js index 7d0105f5a..97a8b769e 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withThemeStyles/index.js +++ b/packages/@lightningjs/ui-components/src/mixins/withThemeStyles/index.js @@ -139,7 +139,7 @@ export default function withThemeStyles(Base, mixinStyle = {}) { if ( !Object.keys(this._styleManager.props).length || JSON.stringify(this._styleManager.props) === - JSON.stringify(this._prevComponentConfigProps) + JSON.stringify(this._prevComponentConfigProps) ) { return; } @@ -147,9 +147,9 @@ export default function withThemeStyles(Base, mixinStyle = {}) { // Compare current properties with previous configuration and get the payload const payload = this._prevComponentConfigProps ? mergeObjectsWithSecondDominant( - this._prevComponentConfigProps || {}, - this._styleManager.props || {} - ) + this._prevComponentConfigProps || {}, + this._styleManager.props || {} + ) : this._styleManager.props || {}; // Store a deep copy of the current properties for future comparison From 6c7fa53f7ed6583f9aaeb722facd9a7bba0c10ea Mon Sep 17 00:00:00 2001 From: Josh Howenstine Date: Mon, 14 Oct 2024 16:14:33 -0700 Subject: [PATCH 03/11] fix: remove base updates --- packages/@lightningjs/ui-components/src/components/Base/Base.js | 2 +- .../ui-components/src/components/TextMagnifier/TextMagnifier.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/@lightningjs/ui-components/src/components/Base/Base.js b/packages/@lightningjs/ui-components/src/components/Base/Base.js index 219d73b66..293c37f95 100644 --- a/packages/@lightningjs/ui-components/src/components/Base/Base.js +++ b/packages/@lightningjs/ui-components/src/components/Base/Base.js @@ -49,7 +49,7 @@ class Base extends lng.Component { }); } - _update() {} + _update() { } _focus() { this._updateShouldSmooth(); diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js index a6ead024d..ad23ec1ed 100644 --- a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js @@ -74,6 +74,7 @@ export default class TextMagnifier extends Surface { mountY: 1 }); } + //this.application.focusPath[this.application.focusPath.length - 1].core.renderContext this._createScrollWrapper(); super._update(); From f61154bc91d03e9a6dde68364f009de1f7f6683b Mon Sep 17 00:00:00 2001 From: Josh Howenstine Date: Mon, 14 Oct 2024 17:56:07 -0700 Subject: [PATCH 04/11] fix: add TextMagnifier to withAnnouncer --- .../ui-components/src/components/Base/Base.js | 2 +- .../components/ScrollWrapper/ScrollWrapper.js | 6 +-- .../src/components/Surface/Surface.js | 5 -- .../components/TextMagnifier/TextMagnifier.js | 5 +- .../TextMagnifier/TextMagnifier.stories.js | 3 +- .../src/mixins/withAnnouncer/index.js | 51 +++++++++++++++++-- 6 files changed, 55 insertions(+), 17 deletions(-) diff --git a/packages/@lightningjs/ui-components/src/components/Base/Base.js b/packages/@lightningjs/ui-components/src/components/Base/Base.js index 293c37f95..219d73b66 100644 --- a/packages/@lightningjs/ui-components/src/components/Base/Base.js +++ b/packages/@lightningjs/ui-components/src/components/Base/Base.js @@ -49,7 +49,7 @@ class Base extends lng.Component { }); } - _update() { } + _update() {} _focus() { this._updateShouldSmooth(); diff --git a/packages/@lightningjs/ui-components/src/components/ScrollWrapper/ScrollWrapper.js b/packages/@lightningjs/ui-components/src/components/ScrollWrapper/ScrollWrapper.js index 4578887c0..b8c726f36 100644 --- a/packages/@lightningjs/ui-components/src/components/ScrollWrapper/ScrollWrapper.js +++ b/packages/@lightningjs/ui-components/src/components/ScrollWrapper/ScrollWrapper.js @@ -150,9 +150,9 @@ export default class ScrollWrapper extends Base { rtt: true, shader: shouldFade ? { - type: lng.shaders.FadeOut, - bottom: this.style.fadeHeight - } + type: lng.shaders.FadeOut, + bottom: this.style.fadeHeight + } : undefined }); } diff --git a/packages/@lightningjs/ui-components/src/components/Surface/Surface.js b/packages/@lightningjs/ui-components/src/components/Surface/Surface.js index 3be9f6814..42921405b 100644 --- a/packages/@lightningjs/ui-components/src/components/Surface/Surface.js +++ b/packages/@lightningjs/ui-components/src/components/Surface/Surface.js @@ -53,11 +53,6 @@ export default class Surface extends Base { } _update() { - this.patch({ - x: this.style.x, - w: this.w, - mountX: 0.5 - }); this._updateLayout(); this._updateScale(); } diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js index ad23ec1ed..85b6177f8 100644 --- a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js @@ -67,13 +67,14 @@ export default class TextMagnifier extends Surface { _update() { if (this.location === 'top') { - this.y = -this.style.radius; + this.y = -(this.style.radius + 8); } else { this.patch({ - y: this.stage.h / this.stage.getRenderPrecision() + this.style.radius, + y: this.stage.h + this.style.radius + 8, mountY: 1 }); } + //this.application.focusPath[this.application.focusPath.length - 1].core.renderContext this._createScrollWrapper(); diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.stories.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.stories.js index 611fcfe0f..ad8a5da25 100644 --- a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.stories.js +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.stories.js @@ -30,7 +30,8 @@ export const TextMagnifier = () => return { TextMagnifier: { type: TextMagnifierComponent, - content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec aliquet fermentum massa, nec scelerisque magna sodales viverra. Fusce mauris neque, aliquam vitae hendrerit vitae, tempor eu nunc. Vestibulum ligula tellus, feugiat facilisis diam non, scelerisque eleifend justo. Sed fermentum dui velit, eu imperdiet enim pretium vitae. Sed molestie, ex nec lacinia ornare, turpis urna egestas justo, sit amet blandit sapien turpis egestas dui. Sed vel pharetra elit. Quisque auctor risus a elit posuere fermentum. Integer interdum enim vitae arcu faucibus vulputate. Pellentesque sed dolor quis erat venenatis scelerisque in faucibus lectus. Mauris ac semper ante, at lobortis nunc.' + content: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec aliquet fermentum massa, nec scelerisque magna sodales viverra. Fusce mauris neque, aliquam vitae hendrerit vitae, tempor eu nunc. Vestibulum ligula tellus, feugiat facilisis diam non, scelerisque eleifend justo. Sed fermentum dui velit, eu imperdiet enim pretium vitae. Sed molestie, ex nec lacinia ornare, turpis urna egestas justo, sit amet blandit sapien turpis egestas dui. Sed vel pharetra elit. Quisque auctor risus a elit posuere fermentum. Integer interdum enim vitae arcu faucibus vulputate. Pellentesque sed dolor quis erat venenatis scelerisque in faucibus lectus. Mauris ac semper ante, at lobortis nunc.' } }; } diff --git a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js index 4e9c0bbc2..bd2214eed 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js +++ b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js @@ -19,6 +19,7 @@ import Speech from './Speech'; import { translateAbbrev } from './abbreviations'; import { debounce } from '../../utils'; +import TextMagnifier from '../../components/TextMagnifier/TextMagnifier'; export { generateAbbrevConfig, defaultAbbrevConfig } from './abbreviations'; @@ -51,8 +52,8 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { if (announcerOptions.abbreviationsConfig.abbreviationsPattern) { toSpeak = Array.isArray(toAnnounce) ? toAnnounce.map(phrase => - translateAbbrev(phrase, announcerOptions.abbreviationsConfig) - ) + translateAbbrev(phrase, announcerOptions.abbreviationsConfig) + ) : translateAbbrev(toAnnounce, announcerOptions.abbreviationsConfig); } const speech = speak(toSpeak, options.language); @@ -121,6 +122,16 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { return this._announcerEnabled; } + set textMagnifierEnabled(val) { + this._textMagnifierEnabled = val; + this._focusChange(); + } + + get textMagnifierEnabled() { + return true + return this._textMagnifierEnabled; + } + _focusChange() { if (!this._resetFocusTimer) { return; @@ -145,6 +156,34 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { this._lastFocusPath = focusPath.slice(0); // Provide hook for focus diff for things like TextBanner this.focusDiffHook = focusDiff; + if (this.textMagnifierEnabled) { + const focusedElement = this.focusDiffHook[this.focusDiffHook.length - 1]; + + // Check if focusedElement exists and has the properties you need. + if (!focusedElement) return; + + const { title, description, announce, core } = focusedElement; + const focusText = title || description || announce || ''; // Simplified focusText selection + + const { py: focusedY } = core.renderContext; + const { h: screenHeight } = this.stage; + + const stickToTop = focusedY > screenHeight / 2; + + // Initialize patch only once to avoid re-creating it in conditionals. + const patch = { + TextMagnifier: { + type: this.tag('TextMagnifier') ? undefined : TextMagnifier, // Add type if TextMagnifier is not already tagged + location: stickToTop ? 'top' : 'bottom', + content: Array.isArray(focusText) + ? focusText.filter(Boolean).join('. ').replace(/\.$/, "") + : focusText.replace(/\.$/, "") + } + }; + + this.patch(patch); + } + if (!this.announcerEnabled) { return; @@ -180,9 +219,11 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { if (toAnnounce.length) { this.$announcerCancel(); - this._currentlySpeaking = this._voiceOut( - toAnnounce.reduce((acc, val) => acc.concat(val), []) - ); + if (this.announcerEnabled) { + this._currentlySpeaking = this._voiceOut( + toAnnounce.reduce((acc, val) => acc.concat(val), []) + ); + } } } From 3a32a2058f4851433c349d27011f316ad735c537 Mon Sep 17 00:00:00 2001 From: Josh Howenstine Date: Mon, 14 Oct 2024 22:07:42 -0700 Subject: [PATCH 05/11] fix: clean up TextMagnifier code --- .../components/TextMagnifier/TextMagnifier.js | 76 ++++++++++--------- .../TextMagnifier/TextMagnifier.styles.js | 9 +-- .../src/mixins/withAnnouncer/index.js | 15 ++-- .../.storybook/addons/constants.js | 1 + .../.storybook/addons/toolbars/Announce.js | 32 +++++++- .../.storybook/addons/toolbars/index.js | 2 +- 6 files changed, 85 insertions(+), 50 deletions(-) diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js index 85b6177f8..41888bc36 100644 --- a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js @@ -29,30 +29,16 @@ export default class TextMagnifier extends Surface { return styles; } - static _template() { - return { - ...super._template(), - ScrollWrapper: { - type: ScrollWrapper - } - }; - } - - _construct() { - this._location = 'top'; - super._construct(); - } - - set mode(value) { - // Mode is always unfocused + static get properties() { + return ['location']; } get mode() { return 'unfocused'; } - static get properties() { - return ['location']; + set mode(value) { + // Disable Mode } get content() { @@ -60,34 +46,52 @@ export default class TextMagnifier extends Surface { } set content(value) { - this._createScrollWrapper(); - this.tag('ScrollWrapper').content = value; - this._content = value; + if (this._content !== value) { + this._content = value; + this._updateScrollWrapper(); + } } - _update() { - if (this.location === 'top') { - this.y = -(this.style.radius + 8); - } else { - this.patch({ - y: this.stage.h + this.style.radius + 8, - mountY: 1 - }); - } + _construct() { + super._construct(); + this._location = 'top'; + this._content = null; + } - //this.application.focusPath[this.application.focusPath.length - 1].core.renderContext + static _template() { + return { + ...super._template(), + ScrollWrapper: { + type: ScrollWrapper + } + }; + } + + _update() { + const stageWidth = this.stage.w / this.stage.getRenderPrecision(); + this.patch({ + w: stageWidth - this.style.marginX * 2, + h: this.style.h, + x: this.style.marginX, + mountY: this.location === 'top' ? 0 : 1, + y: + this.location === 'top' + ? -this.style.radius + : this.stage.h / this.stage.getRenderPrecision() + this.style.radius + }); - this._createScrollWrapper(); + this._updateScrollWrapper(); super._update(); } - _createScrollWrapper() { + _updateScrollWrapper() { this.tag('ScrollWrapper').patch({ - type: ScrollWrapper, - w: this.style.w - this.style.gutterX * 2, + w: this.w - this.style.gutterX * 2, h: this.style.h - this.style.gutterY * 2, y: this.style.gutterY, - style: { textStyle: this.style.textStyle } + style: { textStyle: this.style.textStyle }, + content: this._content, + alpha: this.content && this.content.length ? 1 : 0 }); } } diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js index c46eb3774..4d3d56221 100644 --- a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js @@ -18,14 +18,13 @@ export const base = theme => { return { - radius: theme.radius.lg, - textStyle: theme.typography.display1, color: theme.color.fillNeutral, gutterX: theme.layout.gutterX, gutterY: theme.layout.safe, - w: theme.layout.screenW - theme.layout.safe, - x: (theme.layout.screenW - theme.layout.safe) / 2 + theme.layout.safe / 2, - h: theme.typography.headline1.lineHeight + theme.layout.marginY * 2 + h: theme.typography.headline1.lineHeight + theme.layout.marginY * 2, + marginX: theme.layout.safe, + radius: theme.radius.lg, + textStyle: theme.typography.display1 }; }; diff --git a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js index bd2214eed..9753ec774 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js +++ b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js @@ -128,7 +128,7 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { } get textMagnifierEnabled() { - return true + return true; return this._textMagnifierEnabled; } @@ -157,13 +157,14 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { // Provide hook for focus diff for things like TextBanner this.focusDiffHook = focusDiff; if (this.textMagnifierEnabled) { - const focusedElement = this.focusDiffHook[this.focusDiffHook.length - 1]; + const focusedElement = + this.focusDiffHook[this.focusDiffHook.length - 1]; // Check if focusedElement exists and has the properties you need. if (!focusedElement) return; const { title, description, announce, core } = focusedElement; - const focusText = title || description || announce || ''; // Simplified focusText selection + const focusText = title || description || announce || ''; // Simplified focusText selection const { py: focusedY } = core.renderContext; const { h: screenHeight } = this.stage; @@ -173,18 +174,18 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { // Initialize patch only once to avoid re-creating it in conditionals. const patch = { TextMagnifier: { - type: this.tag('TextMagnifier') ? undefined : TextMagnifier, // Add type if TextMagnifier is not already tagged + type: this.tag('TextMagnifier') ? undefined : TextMagnifier, // Add type if TextMagnifier is not already tagged location: stickToTop ? 'top' : 'bottom', content: Array.isArray(focusText) - ? focusText.filter(Boolean).join('. ').replace(/\.$/, "") - : focusText.replace(/\.$/, "") + ? focusText.filter(Boolean).join('. ').replace(/\.$/, '') + : focusText.replace(/\.$/, ''), + zIndex: 9999 } }; this.patch(patch); } - if (!this.announcerEnabled) { return; } diff --git a/packages/apps/lightning-ui-docs/.storybook/addons/constants.js b/packages/apps/lightning-ui-docs/.storybook/addons/constants.js index 77572d9ac..be3b00f5d 100644 --- a/packages/apps/lightning-ui-docs/.storybook/addons/constants.js +++ b/packages/apps/lightning-ui-docs/.storybook/addons/constants.js @@ -21,6 +21,7 @@ export const ADDON_ID = 'lui-addons'; // specfic add-ons export const DOWNLOAD_ID = `${ADDON_ID}/downloadbutton`; export const ANNOUNCE_ID = `${ADDON_ID}/announcetoggle`; +export const MAGNIFIER_ID = `${ADDON_ID}/magnifiertoggle`; export const GRIDOVERLAY_ID = `${ADDON_ID}/gridoverlaypanel`; export const THEMEPANEL_ID = `${ADDON_ID}/themepanel`; export const COMPONENTSTYLES_ID = `${ADDON_ID}/componentstylespanel`; diff --git a/packages/apps/lightning-ui-docs/.storybook/addons/toolbars/Announce.js b/packages/apps/lightning-ui-docs/.storybook/addons/toolbars/Announce.js index 0c81f119c..2c0ab26f3 100644 --- a/packages/apps/lightning-ui-docs/.storybook/addons/toolbars/Announce.js +++ b/packages/apps/lightning-ui-docs/.storybook/addons/toolbars/Announce.js @@ -19,7 +19,7 @@ import React, { memo, useCallback, useEffect } from 'react'; import { useGlobals, useStorybookApi } from '@storybook/manager-api'; import { Icons, IconButton } from '@storybook/components'; -import { ADDON_ID, ANNOUNCE_ID } from '../constants'; +import { ADDON_ID, ANNOUNCE_ID, MAGNIFIER_ID } from '../constants'; export const Announce = memo(function MyAddonSelector() { const [{ announce }, updateGlobals] = useGlobals(); @@ -50,3 +50,33 @@ export const Announce = memo(function MyAddonSelector() { ); }); + +export const Magnifier = memo(function MyAddonSelector() { + const [{ announce }, updateGlobals] = useGlobals(); + const api = useStorybookApi(); + const isActive = [true, 'true'].includes(announce); + const toggleAnnounce = useCallback(() => { + updateGlobals({ + announce: !isActive + }); + }, [isActive]); + + useEffect(() => { + api.setAddonShortcut(ADDON_ID, { + label: 'Announce Toggle [0]', + actionName: 'Announce', + action: toggleAnnounce + }); + }, [toggleAnnounce, api]); + + return ( + + + + ); +}); diff --git a/packages/apps/lightning-ui-docs/.storybook/addons/toolbars/index.js b/packages/apps/lightning-ui-docs/.storybook/addons/toolbars/index.js index 1305aa61b..ccba37673 100644 --- a/packages/apps/lightning-ui-docs/.storybook/addons/toolbars/index.js +++ b/packages/apps/lightning-ui-docs/.storybook/addons/toolbars/index.js @@ -16,7 +16,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -export { Announce } from './Announce'; +export { Announce, Magnifier } from './Announce'; export { StageColor } from './StageColor'; export { default as ThemeDownload } from './ThemeDownload'; export { default as ThemePicker } from './ThemePicker'; From 4df95a1aeecf0d84f330c339897425220c5ff6a7 Mon Sep 17 00:00:00 2001 From: Josh Howenstine Date: Mon, 14 Oct 2024 22:29:09 -0700 Subject: [PATCH 06/11] fix: stop debounce of textMagnifier --- .../src/mixins/withAnnouncer/index.js | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js index 9753ec774..518f0590f 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js +++ b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js @@ -52,8 +52,8 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { if (announcerOptions.abbreviationsConfig.abbreviationsPattern) { toSpeak = Array.isArray(toAnnounce) ? toAnnounce.map(phrase => - translateAbbrev(phrase, announcerOptions.abbreviationsConfig) - ) + translateAbbrev(phrase, announcerOptions.abbreviationsConfig) + ) : translateAbbrev(toAnnounce, announcerOptions.abbreviationsConfig); } const speech = speak(toSpeak, options.language); @@ -140,6 +140,41 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { this._resetFocusTimer(); this.$announcerCancel(); this._debounceAnnounceFocusChanges(); + this._updateTextMagnifier(); + } + + _updateTextMagnifier() { + if (!this.textMagnifierEnabled) return; + + const focusPath = this.application.focusPath || []; + const lastFocusPath = this._lastFocusPath || []; + const focusDiff = focusPath.filter(elm => !lastFocusPath.includes(elm)); + + this._lastFocusPath = [...focusPath]; // Shallow copy of focusPath + this.focusDiffHook = focusDiff; + + const focusedElement = focusDiff[focusDiff.length - 1]; + if (!focusedElement) return; + + const { title, description, announce, core } = focusedElement; + const focusText = title || description || announce || ''; + + const { py: focusedY } = core.renderContext; + const stickToTop = focusedY > this.stage.h / 2; + + const textMagnifier = this.tag('TextMagnifier'); + const content = Array.isArray(focusText) + ? focusText.filter(Boolean).join('. ').replace(/\.$/, '') + : focusText.replace(/\.$/, ''); + + this.patch({ + TextMagnifier: { + type: textMagnifier ? undefined : TextMagnifier, // Set type only if not already tagged + location: stickToTop ? 'top' : 'bottom', + content, + zIndex: 9999 + } + }); } _announceFocusChanges() { @@ -153,39 +188,6 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { return; } - this._lastFocusPath = focusPath.slice(0); - // Provide hook for focus diff for things like TextBanner - this.focusDiffHook = focusDiff; - if (this.textMagnifierEnabled) { - const focusedElement = - this.focusDiffHook[this.focusDiffHook.length - 1]; - - // Check if focusedElement exists and has the properties you need. - if (!focusedElement) return; - - const { title, description, announce, core } = focusedElement; - const focusText = title || description || announce || ''; // Simplified focusText selection - - const { py: focusedY } = core.renderContext; - const { h: screenHeight } = this.stage; - - const stickToTop = focusedY > screenHeight / 2; - - // Initialize patch only once to avoid re-creating it in conditionals. - const patch = { - TextMagnifier: { - type: this.tag('TextMagnifier') ? undefined : TextMagnifier, // Add type if TextMagnifier is not already tagged - location: stickToTop ? 'top' : 'bottom', - content: Array.isArray(focusText) - ? focusText.filter(Boolean).join('. ').replace(/\.$/, '') - : focusText.replace(/\.$/, ''), - zIndex: 9999 - } - }; - - this.patch(patch); - } - if (!this.announcerEnabled) { return; } From e4fde777c28cb68fe9b71da60a5f6c60efb130c3 Mon Sep 17 00:00:00 2001 From: Josh Howenstine Date: Tue, 15 Oct 2024 09:14:27 -0700 Subject: [PATCH 07/11] fix: add text magnifier button and unit tests --- .../src/mixins/withAnnouncer/index.js | 19 +++-- .../withAnnouncer/withAnnouncer.test.js | 73 ++++++++++++++++++- .../addons/decorators/withLightning.js | 11 +-- .../.storybook/addons/toolbars/Announce.js | 21 +++--- .../lightning-ui-docs/.storybook/manager.js | 8 ++ .../lightning-ui-docs/.storybook/preview.js | 3 + 6 files changed, 112 insertions(+), 23 deletions(-) diff --git a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js index 518f0590f..e0c1fef31 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js +++ b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js @@ -124,11 +124,10 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { set textMagnifierEnabled(val) { this._textMagnifierEnabled = val; - this._focusChange(); + this._updateTextMagnifier(true); } get textMagnifierEnabled() { - return true; return this._textMagnifierEnabled; } @@ -136,18 +135,24 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { if (!this._resetFocusTimer) { return; } - this._resetFocusTimer(); this.$announcerCancel(); this._debounceAnnounceFocusChanges(); this._updateTextMagnifier(); } - _updateTextMagnifier() { - if (!this.textMagnifierEnabled) return; + _updateTextMagnifier(force = false) { + if (!this.textMagnifierEnabled) { + if (this.tag('TextMagnifier')) { + this.patch({ + TextMagnifier: undefined + }); + } + return; + } const focusPath = this.application.focusPath || []; - const lastFocusPath = this._lastFocusPath || []; + const lastFocusPath = force ? [] : this._lastFocusPath || []; const focusDiff = focusPath.filter(elm => !lastFocusPath.includes(elm)); this._lastFocusPath = [...focusPath]; // Shallow copy of focusPath @@ -169,7 +174,7 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { this.patch({ TextMagnifier: { - type: textMagnifier ? undefined : TextMagnifier, // Set type only if not already tagged + type: textMagnifier ? undefined : TextMagnifier, location: stickToTop ? 'top' : 'bottom', content, zIndex: 9999 diff --git a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/withAnnouncer.test.js b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/withAnnouncer.test.js index 86a65d706..ac34420ae 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/withAnnouncer.test.js +++ b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/withAnnouncer.test.js @@ -83,7 +83,7 @@ class Row extends lng.Component { } } -class Item extends lng.Component {} +class Item extends lng.Component { } const Items = { Items: { @@ -503,4 +503,75 @@ describe('AppAnnouncer', () => { expect(speak).toHaveBeenCalledWith('CT', language); }); }); + + describe('TextMagnifier', () => { + let announcer, testRenderer; + + beforeEach(() => { + jest.clearAllMocks(); + testRenderer = TestRenderer.create(Component); + announcer = testRenderer.getInstance(); + }); + + it('should enable TextMagnifier when textMagnifierEnabled is true', () => { + announcer.textMagnifierEnabled = true; + announcer._updateTextMagnifier(true); + + const textMagnifier = announcer.tag('TextMagnifier'); + expect(textMagnifier).toBeDefined(); + }); + + it('should disable TextMagnifier when textMagnifierEnabled is false', () => { + announcer.textMagnifierEnabled = false; + announcer._updateTextMagnifier(true); + + const textMagnifier = announcer.tag('TextMagnifier'); + expect(textMagnifier).toBeUndefined(); + }); + + it('should update the content of the TextMagnifier with the focused component title or announce', () => { + announcer.textMagnifierEnabled = true; + testRenderer.keyPress('Right'); + announcer._updateTextMagnifier(true); + + const textMagnifier = announcer.tag('TextMagnifier'); + const expectedContent = 'Transformers'; + expect(textMagnifier.content).toEqual(expectedContent); + }); + + it.skip('should stick the TextMagnifier to the top when focusedY is greater than half the stage height', () => { + announcer.textMagnifierEnabled = true; + + const focusedElement = announcer.application.focusPath[0]; + focusedElement.core.renderContext = { py: announcer.stage.h / 2 + 1 }; // Bottom half of the screen + announcer._updateTextMagnifier(true); + + const textMagnifier = announcer.tag('TextMagnifier'); + expect(textMagnifier.location).toEqual('top'); + }); + + it.skip('should stick the TextMagnifier to the bottom when focusedY is less than or equal to half the stage height', () => { + announcer.textMagnifierEnabled = true; + + const focusedElement = announcer.application.focusPath[0]; + focusedElement.core.renderContext = { py: announcer.stage.h / 2 - 1 }; // Top half of the screen + announcer._updateTextMagnifier(true); + + const textMagnifier = announcer.tag('TextMagnifier'); + expect(textMagnifier.location).toEqual('bottom'); + }); + + it.skip('should update TextMagnifier content with concatenated announce values when an array is passed', () => { + announcer.textMagnifierEnabled = true; + const toAnnounce = ['Item 1', 'Context 1']; + + announcer._voiceOut(toAnnounce); + announcer._updateTextMagnifier(true); + + const textMagnifier = announcer.tag('TextMagnifier'); + const expectedContent = 'Item 1. Context 1'; + expect(textMagnifier.content).toEqual(expectedContent); + }); + }); + }); diff --git a/packages/apps/lightning-ui-docs/.storybook/addons/decorators/withLightning.js b/packages/apps/lightning-ui-docs/.storybook/addons/decorators/withLightning.js index 8d5ebf7c2..bc04f2f87 100644 --- a/packages/apps/lightning-ui-docs/.storybook/addons/decorators/withLightning.js +++ b/packages/apps/lightning-ui-docs/.storybook/addons/decorators/withLightning.js @@ -83,6 +83,7 @@ export const withLightning = ( const app = createApp({ theme: globals.LUITheme }); clearInspector(); app.announcerEnabled = globals.announce; + app.textMagnifierEnabled = globals.magnifier; app.debug = globals.announce; // toggle stage color !globals.stageColor @@ -176,12 +177,12 @@ export const withLightning = ( app.tag('StoryComponent').patch( parameters.storyDetails ? { - x: context.theme.layout.marginX - } + x: context.theme.layout.marginX + } : { - x: context.theme.layout.marginX, - y: context.theme.layout.marginY - } + x: context.theme.layout.marginX, + y: context.theme.layout.marginY + } ); }); if (!app.tag('GridOverlay')) { diff --git a/packages/apps/lightning-ui-docs/.storybook/addons/toolbars/Announce.js b/packages/apps/lightning-ui-docs/.storybook/addons/toolbars/Announce.js index 2c0ab26f3..89d8eab07 100644 --- a/packages/apps/lightning-ui-docs/.storybook/addons/toolbars/Announce.js +++ b/packages/apps/lightning-ui-docs/.storybook/addons/toolbars/Announce.js @@ -52,31 +52,32 @@ export const Announce = memo(function MyAddonSelector() { }); export const Magnifier = memo(function MyAddonSelector() { - const [{ announce }, updateGlobals] = useGlobals(); + const [{ magnifier }, updateGlobals] = useGlobals(); const api = useStorybookApi(); - const isActive = [true, 'true'].includes(announce); - const toggleAnnounce = useCallback(() => { + const isActive = [true, 'true'].includes(magnifier); + const toggleMagnifier = useCallback(() => { + console.log('here') updateGlobals({ - announce: !isActive + magnifier: !isActive }); }, [isActive]); useEffect(() => { api.setAddonShortcut(ADDON_ID, { - label: 'Announce Toggle [0]', - actionName: 'Announce', - action: toggleAnnounce + label: 'Magnifier Toggle [0]', + actionName: 'Magnifier', + action: toggleMagnifier }); - }, [toggleAnnounce, api]); + }, [toggleMagnifier, api]); return ( - + ); }); diff --git a/packages/apps/lightning-ui-docs/.storybook/manager.js b/packages/apps/lightning-ui-docs/.storybook/manager.js index 7e4f008d9..825ffcf88 100644 --- a/packages/apps/lightning-ui-docs/.storybook/manager.js +++ b/packages/apps/lightning-ui-docs/.storybook/manager.js @@ -21,6 +21,7 @@ import theme from './theme'; import * as ids from './addons/constants'; import { Announce, + Magnifier, StageColor, ThemeDownload, ThemePicker @@ -45,6 +46,13 @@ addons.register(ids.ADDON_ID, () => { match: ({ viewMode }) => viewMode === 'story', // show only in story render: Announce }); + // Magnifier toggle + addons.add(ids.MAGNIFIER_ID, { + type: types.TOOL, + title: 'Magnifier Toggle', + match: ({ viewMode }) => viewMode === 'story', // show only in story + render: Magnifier + }); // Theme Picker addons.add(ids.THEMEPICKER_ID, { type: types.TOOL, diff --git a/packages/apps/lightning-ui-docs/.storybook/preview.js b/packages/apps/lightning-ui-docs/.storybook/preview.js index 7737f6a40..1f0ecec7c 100644 --- a/packages/apps/lightning-ui-docs/.storybook/preview.js +++ b/packages/apps/lightning-ui-docs/.storybook/preview.js @@ -118,6 +118,9 @@ const preview = { announce: { defaultValue: false }, + magnifier: { + defaultValue: false + }, stageColor: { defaultValue: false } From 99b724784437bd529b58221b8a6a74ffc272f7ea Mon Sep 17 00:00:00 2001 From: Josh Howenstine Date: Wed, 16 Oct 2024 07:09:47 -0700 Subject: [PATCH 08/11] fix: start adding new announcer unit tests --- .../src/mixins/withAnnouncer/index.js | 4 +- .../withAnnouncer/withAnnouncer.test.js | 78 +++++++++---------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js index e0c1fef31..6524ac6a9 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js +++ b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js @@ -52,8 +52,8 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { if (announcerOptions.abbreviationsConfig.abbreviationsPattern) { toSpeak = Array.isArray(toAnnounce) ? toAnnounce.map(phrase => - translateAbbrev(phrase, announcerOptions.abbreviationsConfig) - ) + translateAbbrev(phrase, announcerOptions.abbreviationsConfig) + ) : translateAbbrev(toAnnounce, announcerOptions.abbreviationsConfig); } const speech = speak(toSpeak, options.language); diff --git a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/withAnnouncer.test.js b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/withAnnouncer.test.js index ac34420ae..30ce41e94 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/withAnnouncer.test.js +++ b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/withAnnouncer.test.js @@ -521,57 +521,57 @@ describe('AppAnnouncer', () => { expect(textMagnifier).toBeDefined(); }); - it('should disable TextMagnifier when textMagnifierEnabled is false', () => { - announcer.textMagnifierEnabled = false; - announcer._updateTextMagnifier(true); + // it('should disable TextMagnifier when textMagnifierEnabled is false', () => { + // announcer.textMagnifierEnabled = false; + // announcer._updateTextMagnifier(true); - const textMagnifier = announcer.tag('TextMagnifier'); - expect(textMagnifier).toBeUndefined(); - }); + // const textMagnifier = announcer.tag('TextMagnifier'); + // expect(textMagnifier).toBeUndefined(); + // }); - it('should update the content of the TextMagnifier with the focused component title or announce', () => { - announcer.textMagnifierEnabled = true; - testRenderer.keyPress('Right'); - announcer._updateTextMagnifier(true); + // it('should update the content of the TextMagnifier with the focused component title or announce', () => { + // announcer.textMagnifierEnabled = true; + // testRenderer.keyPress('Right'); + // announcer._updateTextMagnifier(true); - const textMagnifier = announcer.tag('TextMagnifier'); - const expectedContent = 'Transformers'; - expect(textMagnifier.content).toEqual(expectedContent); - }); + // const textMagnifier = announcer.tag('TextMagnifier'); + // const expectedContent = 'Transformers'; + // expect(textMagnifier.content).toEqual(expectedContent); + // }); - it.skip('should stick the TextMagnifier to the top when focusedY is greater than half the stage height', () => { - announcer.textMagnifierEnabled = true; + // it.skip('should stick the TextMagnifier to the top when focusedY is greater than half the stage height', () => { + // announcer.textMagnifierEnabled = true; - const focusedElement = announcer.application.focusPath[0]; - focusedElement.core.renderContext = { py: announcer.stage.h / 2 + 1 }; // Bottom half of the screen - announcer._updateTextMagnifier(true); + // const focusedElement = announcer.application.focusPath[0]; + // focusedElement.core.renderContext = { py: announcer.stage.h / 2 + 1 }; // Bottom half of the screen + // announcer._updateTextMagnifier(true); - const textMagnifier = announcer.tag('TextMagnifier'); - expect(textMagnifier.location).toEqual('top'); - }); + // const textMagnifier = announcer.tag('TextMagnifier'); + // expect(textMagnifier.location).toEqual('top'); + // }); - it.skip('should stick the TextMagnifier to the bottom when focusedY is less than or equal to half the stage height', () => { - announcer.textMagnifierEnabled = true; + // it.skip('should stick the TextMagnifier to the bottom when focusedY is less than or equal to half the stage height', () => { + // announcer.textMagnifierEnabled = true; - const focusedElement = announcer.application.focusPath[0]; - focusedElement.core.renderContext = { py: announcer.stage.h / 2 - 1 }; // Top half of the screen - announcer._updateTextMagnifier(true); + // const focusedElement = announcer.application.focusPath[0]; + // focusedElement.core.renderContext = { py: announcer.stage.h / 2 - 1 }; // Top half of the screen + // announcer._updateTextMagnifier(true); - const textMagnifier = announcer.tag('TextMagnifier'); - expect(textMagnifier.location).toEqual('bottom'); - }); + // const textMagnifier = announcer.tag('TextMagnifier'); + // expect(textMagnifier.location).toEqual('bottom'); + // }); - it.skip('should update TextMagnifier content with concatenated announce values when an array is passed', () => { - announcer.textMagnifierEnabled = true; - const toAnnounce = ['Item 1', 'Context 1']; + // it.skip('should update TextMagnifier content with concatenated announce values when an array is passed', () => { + // announcer.textMagnifierEnabled = true; + // const toAnnounce = ['Item 1', 'Context 1']; - announcer._voiceOut(toAnnounce); - announcer._updateTextMagnifier(true); + // announcer._voiceOut(toAnnounce); + // announcer._updateTextMagnifier(true); - const textMagnifier = announcer.tag('TextMagnifier'); - const expectedContent = 'Item 1. Context 1'; - expect(textMagnifier.content).toEqual(expectedContent); - }); + // const textMagnifier = announcer.tag('TextMagnifier'); + // const expectedContent = 'Item 1. Context 1'; + // expect(textMagnifier.content).toEqual(expectedContent); + // }); }); }); From 3e334dba3c45556945e3405d3c84a3b3fed894ea Mon Sep 17 00:00:00 2001 From: Josh Howenstine Date: Wed, 16 Oct 2024 18:31:56 -0700 Subject: [PATCH 09/11] fix: add typescript definitions --- packages/@lightningjs/ui-components/index.js | 1 + .../TextMagnifier/TextMagnifier.d.ts | 20 +++++ .../components/TextMagnifier/TextMagnifier.js | 4 + .../ui-components/src/mixins/index.d.ts | 1 + .../src/mixins/withAnnouncer/index.js | 69 +++------------- .../withAnnouncer/withAnnouncer.test.js | 73 +---------------- .../src/mixins/withTextMagnifier/index.d.ts | 17 ++++ .../src/mixins/withTextMagnifier/index.js | 79 +++++++++++++++++++ .../addons/decorators/withLightning.js | 3 + packages/apps/lightning-ui-docs/index.js | 7 +- 10 files changed, 139 insertions(+), 135 deletions(-) create mode 100644 packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.d.ts create mode 100644 packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.d.ts create mode 100644 packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.js diff --git a/packages/@lightningjs/ui-components/index.js b/packages/@lightningjs/ui-components/index.js index 040f140bf..819213cf2 100644 --- a/packages/@lightningjs/ui-components/index.js +++ b/packages/@lightningjs/ui-components/index.js @@ -103,6 +103,7 @@ export { generateAbbrevConfig, defaultAbbrevConfig } from './src/mixins/withAnnouncer'; +export { default as withTextMagnifier } from './src/mixins/withTextMagnifier'; export { default as Speech } from './src/mixins/withAnnouncer/Speech'; export { default as withClassCache } from './src/mixins/withClassCache'; export { default as withHandleKey } from './src/mixins/withHandleKey'; diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.d.ts b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.d.ts new file mode 100644 index 000000000..749dbe9b2 --- /dev/null +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.d.ts @@ -0,0 +1,20 @@ +import Surface, { SurfaceStyle } from '../Surface'; +import { StylePartial } from '../../types/lui'; + +type TextMagnifierStyle = SurfaceStyle & { + color: string; + gutterX: number; + gutterY: number; + h: number; + marginX: number; + radius: number | string; + textStyle: any; +}; + +export default class TextMagnifier extends Surface { + get style(): TextMagnifierStyle; + set style(v: StylePartial); + location: 'top' | 'bottom'; + mode: string; + content: string | null; +} diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js index 41888bc36..d1e659720 100644 --- a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js @@ -67,6 +67,10 @@ export default class TextMagnifier extends Surface { }; } + _focusChange() { + debugger; + } + _update() { const stageWidth = this.stage.w / this.stage.getRenderPrecision(); this.patch({ diff --git a/packages/@lightningjs/ui-components/src/mixins/index.d.ts b/packages/@lightningjs/ui-components/src/mixins/index.d.ts index 20bc80c94..a88219872 100644 --- a/packages/@lightningjs/ui-components/src/mixins/index.d.ts +++ b/packages/@lightningjs/ui-components/src/mixins/index.d.ts @@ -35,3 +35,4 @@ export { default as withSelections } from './withSelections'; export { default as withTags } from './withTags'; export { default as withThemeStyles } from './withThemeStyles'; export { default as withUpdates } from './withUpdates'; +export { default as withTextMagnifier } from './withTextMagnifier'; diff --git a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js index 6524ac6a9..4e9c0bbc2 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js +++ b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/index.js @@ -19,7 +19,6 @@ import Speech from './Speech'; import { translateAbbrev } from './abbreviations'; import { debounce } from '../../utils'; -import TextMagnifier from '../../components/TextMagnifier/TextMagnifier'; export { generateAbbrevConfig, defaultAbbrevConfig } from './abbreviations'; @@ -52,8 +51,8 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { if (announcerOptions.abbreviationsConfig.abbreviationsPattern) { toSpeak = Array.isArray(toAnnounce) ? toAnnounce.map(phrase => - translateAbbrev(phrase, announcerOptions.abbreviationsConfig) - ) + translateAbbrev(phrase, announcerOptions.abbreviationsConfig) + ) : translateAbbrev(toAnnounce, announcerOptions.abbreviationsConfig); } const speech = speak(toSpeak, options.language); @@ -122,64 +121,14 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { return this._announcerEnabled; } - set textMagnifierEnabled(val) { - this._textMagnifierEnabled = val; - this._updateTextMagnifier(true); - } - - get textMagnifierEnabled() { - return this._textMagnifierEnabled; - } - _focusChange() { if (!this._resetFocusTimer) { return; } + this._resetFocusTimer(); this.$announcerCancel(); this._debounceAnnounceFocusChanges(); - this._updateTextMagnifier(); - } - - _updateTextMagnifier(force = false) { - if (!this.textMagnifierEnabled) { - if (this.tag('TextMagnifier')) { - this.patch({ - TextMagnifier: undefined - }); - } - return; - } - - const focusPath = this.application.focusPath || []; - const lastFocusPath = force ? [] : this._lastFocusPath || []; - const focusDiff = focusPath.filter(elm => !lastFocusPath.includes(elm)); - - this._lastFocusPath = [...focusPath]; // Shallow copy of focusPath - this.focusDiffHook = focusDiff; - - const focusedElement = focusDiff[focusDiff.length - 1]; - if (!focusedElement) return; - - const { title, description, announce, core } = focusedElement; - const focusText = title || description || announce || ''; - - const { py: focusedY } = core.renderContext; - const stickToTop = focusedY > this.stage.h / 2; - - const textMagnifier = this.tag('TextMagnifier'); - const content = Array.isArray(focusText) - ? focusText.filter(Boolean).join('. ').replace(/\.$/, '') - : focusText.replace(/\.$/, ''); - - this.patch({ - TextMagnifier: { - type: textMagnifier ? undefined : TextMagnifier, - location: stickToTop ? 'top' : 'bottom', - content, - zIndex: 9999 - } - }); } _announceFocusChanges() { @@ -193,6 +142,10 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { return; } + this._lastFocusPath = focusPath.slice(0); + // Provide hook for focus diff for things like TextBanner + this.focusDiffHook = focusDiff; + if (!this.announcerEnabled) { return; } @@ -227,11 +180,9 @@ export default function withAnnouncer(Base, speak = Speech, options = {}) { if (toAnnounce.length) { this.$announcerCancel(); - if (this.announcerEnabled) { - this._currentlySpeaking = this._voiceOut( - toAnnounce.reduce((acc, val) => acc.concat(val), []) - ); - } + this._currentlySpeaking = this._voiceOut( + toAnnounce.reduce((acc, val) => acc.concat(val), []) + ); } } diff --git a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/withAnnouncer.test.js b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/withAnnouncer.test.js index 30ce41e94..86a65d706 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/withAnnouncer.test.js +++ b/packages/@lightningjs/ui-components/src/mixins/withAnnouncer/withAnnouncer.test.js @@ -83,7 +83,7 @@ class Row extends lng.Component { } } -class Item extends lng.Component { } +class Item extends lng.Component {} const Items = { Items: { @@ -503,75 +503,4 @@ describe('AppAnnouncer', () => { expect(speak).toHaveBeenCalledWith('CT', language); }); }); - - describe('TextMagnifier', () => { - let announcer, testRenderer; - - beforeEach(() => { - jest.clearAllMocks(); - testRenderer = TestRenderer.create(Component); - announcer = testRenderer.getInstance(); - }); - - it('should enable TextMagnifier when textMagnifierEnabled is true', () => { - announcer.textMagnifierEnabled = true; - announcer._updateTextMagnifier(true); - - const textMagnifier = announcer.tag('TextMagnifier'); - expect(textMagnifier).toBeDefined(); - }); - - // it('should disable TextMagnifier when textMagnifierEnabled is false', () => { - // announcer.textMagnifierEnabled = false; - // announcer._updateTextMagnifier(true); - - // const textMagnifier = announcer.tag('TextMagnifier'); - // expect(textMagnifier).toBeUndefined(); - // }); - - // it('should update the content of the TextMagnifier with the focused component title or announce', () => { - // announcer.textMagnifierEnabled = true; - // testRenderer.keyPress('Right'); - // announcer._updateTextMagnifier(true); - - // const textMagnifier = announcer.tag('TextMagnifier'); - // const expectedContent = 'Transformers'; - // expect(textMagnifier.content).toEqual(expectedContent); - // }); - - // it.skip('should stick the TextMagnifier to the top when focusedY is greater than half the stage height', () => { - // announcer.textMagnifierEnabled = true; - - // const focusedElement = announcer.application.focusPath[0]; - // focusedElement.core.renderContext = { py: announcer.stage.h / 2 + 1 }; // Bottom half of the screen - // announcer._updateTextMagnifier(true); - - // const textMagnifier = announcer.tag('TextMagnifier'); - // expect(textMagnifier.location).toEqual('top'); - // }); - - // it.skip('should stick the TextMagnifier to the bottom when focusedY is less than or equal to half the stage height', () => { - // announcer.textMagnifierEnabled = true; - - // const focusedElement = announcer.application.focusPath[0]; - // focusedElement.core.renderContext = { py: announcer.stage.h / 2 - 1 }; // Top half of the screen - // announcer._updateTextMagnifier(true); - - // const textMagnifier = announcer.tag('TextMagnifier'); - // expect(textMagnifier.location).toEqual('bottom'); - // }); - - // it.skip('should update TextMagnifier content with concatenated announce values when an array is passed', () => { - // announcer.textMagnifierEnabled = true; - // const toAnnounce = ['Item 1', 'Context 1']; - - // announcer._voiceOut(toAnnounce); - // announcer._updateTextMagnifier(true); - - // const textMagnifier = announcer.tag('TextMagnifier'); - // const expectedContent = 'Item 1. Context 1'; - // expect(textMagnifier.content).toEqual(expectedContent); - // }); - }); - }); diff --git a/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.d.ts b/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.d.ts new file mode 100644 index 000000000..959bbbeea --- /dev/null +++ b/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.d.ts @@ -0,0 +1,17 @@ +import lng from '@lightningjs/core'; + +// Utility function to check class inheritance +declare function isSubclass(Subclass: any, Superclass: any): boolean; + +// Define the function that takes a base class and returns a new class +export default function withTextMagnifier( + Base: T +): T & { + textMagnifierEnabled: boolean; +}; + +// TextMagnifier Component Definition (optional if it's already defined elsewhere) +export interface TextMagnifier { + location: 'top' | 'bottom'; + content: string +} diff --git a/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.js b/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.js new file mode 100644 index 000000000..be350acee --- /dev/null +++ b/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.js @@ -0,0 +1,79 @@ +import lng from '@lightningjs/core'; +import TextMagnifier from '../../components/TextMagnifier/TextMagnifier'; + +function isSubclass(Subclass, Superclass) { + // Traverse up the prototype chain to check if the Superclass is found + while (Subclass) { + if (Subclass === Superclass) { + return true; + } + Subclass = Object.getPrototypeOf(Subclass); + } + return false; +} + +export default function (Base) { + if (!isSubclass(Base, lng.Application)) { + console.error( + 'withTextMagnifier can only be applied to an application class' + ); + return Base; + } + + return class extends Base { + set textMagnifierEnabled(val) { + this._textMagnifierEnabled = val; + this._updateTextMagnifier(true); + } + + get textMagnifierEnabled() { + return this._textMagnifierEnabled; + } + + get _focusedElement() { + return this.application?.focusPath?.[ + this.application.focusPath.length - 1 + ]; + } + + get _location() { + const { py: focusedY } = this._focusedElement?.core?.renderContext || {}; + return focusedY > this.stage.h / 2 ? 'top' : 'bottom'; + } + + get _focusText() { + const { title, description, announce } = this._focusedElement || {}; + const focusText = title || description || announce || ''; + return Array.isArray(focusText) + ? focusText.filter(Boolean).join('. ').replace(/\.$/, '') + : focusText.replace(/\.$/, ''); + } + + _focusChange() { + this._updateTextMagnifier(); + super._focusChange(); + } + + _updateTextMagnifier() { + if ( + !this.textMagnifierEnabled || + !this._focusedElement || + !this._focusText.length + ) { + if (this.tag('TextMagnifier')) { + this.patch({ TextMagnifier: undefined }); + } + return; + } + + this.patch({ + TextMagnifier: { + type: TextMagnifier, + location: this._location, + content: this._focusText, + zIndex: 9999 + } + }); + } + }; +} diff --git a/packages/apps/lightning-ui-docs/.storybook/addons/decorators/withLightning.js b/packages/apps/lightning-ui-docs/.storybook/addons/decorators/withLightning.js index bc04f2f87..ceb482c13 100644 --- a/packages/apps/lightning-ui-docs/.storybook/addons/decorators/withLightning.js +++ b/packages/apps/lightning-ui-docs/.storybook/addons/decorators/withLightning.js @@ -93,8 +93,10 @@ export const withLightning = ( // // If an update is required patch in the new child element if (shouldTriggerUpdate({ id, args, argTypes, parameters })) { app.childList.clear(); + app.childList.remove() app.childList.a({ StoryComponent: { + ref: 'StoryComponent', type: class extends StoryComponent() { static _states() { return [ @@ -113,6 +115,7 @@ export const withLightning = ( // FIXME: Assess what config.optimization.minimize is doing different in production vs develop - this was prior to v7 upgrade get componentTarget() { // using this check on type Element because production vs develop build issue + console.log(this.childList._items) return this.childList.first instanceof lng.Component ? this.childList.first : this; diff --git a/packages/apps/lightning-ui-docs/index.js b/packages/apps/lightning-ui-docs/index.js index d9abc1e4f..9ebf94bb6 100644 --- a/packages/apps/lightning-ui-docs/index.js +++ b/packages/apps/lightning-ui-docs/index.js @@ -19,6 +19,7 @@ import lng from '@lightningjs/core'; import 'lightningInspect'; import { + withTextMagnifier, withAnnouncer, Speech, pool, @@ -62,10 +63,8 @@ export const createApp = parameters => { window.CONTEXT = context; // Used by addons - window.APP = new (class LightningUIApp extends withAnnouncer( - lng.Application, - Speech, - announcerOptions + window.APP = new (class LightningUIApp extends withTextMagnifier( + lng.Application ) { _construct() { this.announcerTimeout = 15 * 1000; From 224a750441e9c06a3f3eece7590edec33a212688 Mon Sep 17 00:00:00 2001 From: Josh Howenstine Date: Wed, 16 Oct 2024 19:21:05 -0700 Subject: [PATCH 10/11] fix: ts definitions --- .../components/TextMagnifier/TextMagnifier.js | 4 - .../src/mixins/withTextMagnifier/index.d.ts | 24 ++-- .../withTextMagnifier.stories.js | 113 ++++++++++++++++++ packages/apps/lightning-ui-docs/index.js | 6 +- 4 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/withTextMagnifier.stories.js diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js index d1e659720..41888bc36 100644 --- a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js @@ -67,10 +67,6 @@ export default class TextMagnifier extends Surface { }; } - _focusChange() { - debugger; - } - _update() { const stageWidth = this.stage.w / this.stage.getRenderPrecision(); this.patch({ diff --git a/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.d.ts b/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.d.ts index 959bbbeea..f72373bdf 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.d.ts +++ b/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.d.ts @@ -1,17 +1,15 @@ import lng from '@lightningjs/core'; -// Utility function to check class inheritance -declare function isSubclass(Subclass: any, Superclass: any): boolean; - -// Define the function that takes a base class and returns a new class -export default function withTextMagnifier( - Base: T -): T & { - textMagnifierEnabled: boolean; -}; +export interface WithTextMagnifier { + set textMagnifierEnabled(val: boolean); + get textMagnifierEnabled(): boolean; +} -// TextMagnifier Component Definition (optional if it's already defined elsewhere) -export interface TextMagnifier { - location: 'top' | 'bottom'; - content: string +export interface WithTextMagnifierConstructor { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new (...args: any[]): WithTextMagnifier; } + +export default function withAnnouncer( + base: T +): T & WithAnnouncerConstructor; diff --git a/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/withTextMagnifier.stories.js b/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/withTextMagnifier.stories.js new file mode 100644 index 000000000..9d215d808 --- /dev/null +++ b/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/withTextMagnifier.stories.js @@ -0,0 +1,113 @@ +/** + * Copyright 2023 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import lng from '@lightningjs/core'; +import withTextMagnifier from '.'; +import Column from '../../components/Column/Column'; +import Row from '../../components/Row/Row'; +import Button from '../../components/Button/Button'; + +export default { + title: 'Utilities/withTextMagnifier', + component: withTextMagnifier +}; + +const rows = [ + { + type: Row, + w: 1920 - 160, // x offset from preview.js * 2, + h: 100, + itemSpacing: 30, + items: [ + { + type: Button, + title: 'Button 1', + announceContext: '1 of 3' + }, + { + type: Button, + title: 'Button 2', + announceContext: '2 of 3' + }, + { type: Button, title: 'Button 3', announceContext: '3 of 3' } + ] + }, + { + type: Row, + w: 1920 - 160, // x offset from preview.js * 2, + h: 100, + itemSpacing: 30, + items: [ + { + type: Button, + title: 'Button 1', + announceContext: '1 of 3' + }, + { + type: Button, + title: 'Button 2', + announceContext: '2 of 3' + }, + { type: Button, title: 'Button 3', announceContext: '3 of 3' } + ] + }, + { + type: Row, + w: 1920 - 160, // x offset from preview.js * 2, + h: 100, + itemSpacing: 30, + items: [ + { + type: Button, + title: 'Button 1', + announceContext: '1 of 3' + }, + { + type: Button, + title: 'Button 2', + announceContext: '2 of 3' + }, + { type: Button, title: 'Button 3', announceContext: '3 of 3' } + ] + } +]; + +export const Basic = () => + class Basic extends lng.Component { + static _template() { + return { + Column: { + type: Column, + w: 1920 - 160, // x offset from preview.js * 2, + h: 400, + style: { + itemSpacing: 20 + }, + items: rows + } + }; + } + + _init() { + this.fireAncestors('$toggleTextMagnifier', true); + } + + _detach() { + this.fireAncestors('$toggleTextMagnifier', false); + } + }; diff --git a/packages/apps/lightning-ui-docs/index.js b/packages/apps/lightning-ui-docs/index.js index 9ebf94bb6..89fe4187e 100644 --- a/packages/apps/lightning-ui-docs/index.js +++ b/packages/apps/lightning-ui-docs/index.js @@ -64,7 +64,7 @@ export const createApp = parameters => { window.CONTEXT = context; // Used by addons window.APP = new (class LightningUIApp extends withTextMagnifier( - lng.Application + withAnnouncer(lng.Application, Speech, announcerOptions) ) { _construct() { this.announcerTimeout = 15 * 1000; @@ -78,6 +78,10 @@ export const createApp = parameters => { this.emit('storyChanged'); } + $toggleTextMagnifier(value) { + this.textMagnifierEnabled = Boolean(value); + } + _getFocused() { return ((this.childList.first || {}).childList || {}).first || this; } From 7aba0291ffc2b41bc6a9e405432d84d39fea5bab Mon Sep 17 00:00:00 2001 From: Josh Howenstine Date: Fri, 18 Oct 2024 11:45:58 -0700 Subject: [PATCH 11/11] fix: allow surface to override radius logic. Clean up TextMagnifier code --- .../src/components/Surface/Surface.js | 6 +- .../components/TextMagnifier/TextMagnifier.js | 59 ++++++++++++++----- .../TextMagnifier/TextMagnifier.styles.js | 7 +-- .../src/mixins/withTextMagnifier/index.js | 3 +- 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/packages/@lightningjs/ui-components/src/components/Surface/Surface.js b/packages/@lightningjs/ui-components/src/components/Surface/Surface.js index 42921405b..476df526c 100644 --- a/packages/@lightningjs/ui-components/src/components/Surface/Surface.js +++ b/packages/@lightningjs/ui-components/src/components/Surface/Surface.js @@ -52,6 +52,10 @@ export default class Surface extends Base { return this.w; } + get _radius() { + return getMaxRoundRadius(this.style.radius, this.w, this.h); + } + _update() { this._updateLayout(); this._updateScale(); @@ -62,7 +66,7 @@ export default class Surface extends Base { texture: lng.Tools.getRoundRect( this.innerW - 2, // Reference the underscored values here in cause the h or w getters need to be overwritten for alignment - see Tile this.innerH - 2, - getMaxRoundRadius(this.style.radius, this.w, this.h), + this._radius, 0, null, true, diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js index 41888bc36..0cb7b1c18 100644 --- a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.js @@ -37,8 +37,8 @@ export default class TextMagnifier extends Surface { return 'unfocused'; } - set mode(value) { - // Disable Mode + set mode(_) { + // Mode is disabled, no action needed } get content() { @@ -52,6 +52,18 @@ export default class TextMagnifier extends Surface { } } + get _totalHeight() { + const baseHeight = + this.style.textStyle.lineHeight + Math.max(...this._radius) * 2; // Accommodate for all configurations of radii + return Math.max(baseHeight, this.style.textStyle.lineHeight * 2); // Ensure the fade clears + } + + get _radius() { + return this.location === 'top' + ? [0, 0, this.style.radius, this.style.radius] + : [this.style.radius, this.style.radius, 0, 0]; + } + _construct() { super._construct(); this._location = 'top'; @@ -62,22 +74,23 @@ export default class TextMagnifier extends Surface { return { ...super._template(), ScrollWrapper: { - type: ScrollWrapper + type: ScrollWrapper, + autoScroll: true } }; } _update() { - const stageWidth = this.stage.w / this.stage.getRenderPrecision(); + const renderPrecision = this.stage.getRenderPrecision(); + const stageWidth = this.stage.w / renderPrecision; + this.patch({ - w: stageWidth - this.style.marginX * 2, - h: this.style.h, - x: this.style.marginX, + w: stageWidth - this.style.gutterX * 2, + h: this._totalHeight, + x: this.style.gutterX, mountY: this.location === 'top' ? 0 : 1, - y: - this.location === 'top' - ? -this.style.radius - : this.stage.h / this.stage.getRenderPrecision() + this.style.radius + y: this.location === 'top' ? 0 : this.stage.h / renderPrecision, + zIndex: this.style.zIndex }); this._updateScrollWrapper(); @@ -85,13 +98,27 @@ export default class TextMagnifier extends Surface { } _updateScrollWrapper() { + const yCenter = this._totalHeight / 2; + const yOffset = yCenter - this.style.textStyle.fontSize / 2; + const adjustedHeight = this._totalHeight - yOffset; + const radius = Math.max(...this._radius); + const gutterX = this.style.gutterX; + const patchWidth = this.w - Math.max(radius, gutterX) * 2; + const patchX = Math.max(radius, gutterX); + this.tag('ScrollWrapper').patch({ - w: this.w - this.style.gutterX * 2, - h: this.style.h - this.style.gutterY * 2, - y: this.style.gutterY, - style: { textStyle: this.style.textStyle }, + alpha: this._content && this._content.length ? 1 : 0, content: this._content, - alpha: this.content && this.content.length ? 1 : 0 + h: adjustedHeight, + w: patchWidth, + x: patchX, + y: yOffset, + style: { + textStyle: this.style.textStyle, + fadeHeight: this.style.textStyle.lineHeight, + contentMarginTop: 0, + contentMarginLeft: 0 + } }); } } diff --git a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js index 4d3d56221..aeaff4f65 100644 --- a/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js +++ b/packages/@lightningjs/ui-components/src/components/TextMagnifier/TextMagnifier.styles.js @@ -19,12 +19,11 @@ export const base = theme => { return { color: theme.color.fillNeutral, - gutterX: theme.layout.gutterX, + gutterX: theme.layout.gutterX * 2, gutterY: theme.layout.safe, - h: theme.typography.headline1.lineHeight + theme.layout.marginY * 2, - marginX: theme.layout.safe, radius: theme.radius.lg, - textStyle: theme.typography.display1 + textStyle: theme.typography.display1, + zIndex: 9999 }; }; diff --git a/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.js b/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.js index be350acee..78316057f 100644 --- a/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.js +++ b/packages/@lightningjs/ui-components/src/mixins/withTextMagnifier/index.js @@ -70,8 +70,7 @@ export default function (Base) { TextMagnifier: { type: TextMagnifier, location: this._location, - content: this._focusText, - zIndex: 9999 + content: this._focusText } }); }