diff --git a/.changeset/selfish-apes-breathe.md b/.changeset/selfish-apes-breathe.md new file mode 100644 index 00000000000..46717e5ad0c --- /dev/null +++ b/.changeset/selfish-apes-breathe.md @@ -0,0 +1,5 @@ +--- +"@hashicorp/design-system-components": patch +--- + +`CodeBlock` - Fixed issues with line numbers when line wrapping is present and when the number of lines changes dynamically; line highlighting when the Code Block is hidden from view initially such as when used inside a Tabs component; and line highlighting when hasLineNumbers is false. diff --git a/packages/components/src/components/hds/code-block/index.hbs b/packages/components/src/components/hds/code-block/index.hbs index ea0531a04b4..931ef1dd859 100644 --- a/packages/components/src/components/hds/code-block/index.hbs +++ b/packages/components/src/components/hds/code-block/index.hbs @@ -3,7 +3,7 @@ SPDX-License-Identifier: MPL-2.0 }} -
+ >
{{~this._prismCode~}}
diff --git a/packages/components/src/components/hds/code-block/index.ts b/packages/components/src/components/hds/code-block/index.ts
index 07bd750aa4c..a2cbbd70f46 100644
--- a/packages/components/src/components/hds/code-block/index.ts
+++ b/packages/components/src/components/hds/code-block/index.ts
@@ -10,6 +10,7 @@ import { assert } from '@ember/debug';
import { next, schedule } from '@ember/runloop';
import { htmlSafe } from '@ember/template';
import { guidFor } from '@ember/object/internals';
+import { modifier } from 'ember-modifier';
import Prism from 'prismjs';
@@ -75,6 +76,34 @@ export default class HdsCodeBlock extends Component {
// Generates a unique ID for the code content
private _preCodeId = 'pre-code-' + guidFor(this);
+ private _preCodeElement!: HTMLPreElement;
+ private _observer!: ResizeObserver;
+
+ // If a code block is hidden from view, and made visible after load, the Prism code needs to be re-run
+ private _setUpCodeObserver = modifier((element: HTMLElement) => {
+ this._preCodeElement = element.querySelector(
+ '.hds-code-block__code'
+ ) as HTMLPreElement;
+ this._observer = new ResizeObserver((entries) => {
+ entries.forEach((entry) => {
+ if (entry.contentBoxSize) {
+ this._updateCodeHeights();
+ this._updatePrismPlugins();
+ }
+ });
+ });
+ this._observer.observe(element);
+
+ return () => {
+ this._observer.disconnect();
+ };
+ });
+
+ private _setUpCodeBlockCode = modifier((element: HTMLElement) => {
+ this._isExpanded = false; // reset expanded state on updates
+ this.setPrismCode(element);
+ return () => {};
+ });
// code text content for the CodeBlock
get code(): string {
@@ -145,6 +174,14 @@ export default class HdsCodeBlock extends Component {
this._prismCode = htmlSafe(Prism.util.encode(code).toString());
}
+ // Existing line numbers must be removed in order to be updated correctly
+ const lineNumbers = element.querySelector(
+ '.line-numbers-rows'
+ ) as HTMLElement;
+ if (lineNumbers) {
+ element.removeChild(lineNumbers);
+ }
+
// Force prism-line-numbers plugin initialization, required for Prism.highlight usage
// See https://github.com/PrismJS/prism/issues/1234
Prism.hooks.run('complete', {
@@ -152,28 +189,38 @@ export default class HdsCodeBlock extends Component {
element,
});
- // Get the actual height & the content height of the preCodeElement
// eslint-disable-next-line ember/no-runloop
schedule('afterRender', (): void => {
- const preCodeElement = document.getElementById(this._preCodeId);
- this._codeContentHeight = preCodeElement?.scrollHeight ?? 0;
- this._codeContainerHeight = preCodeElement?.clientHeight ?? 0;
+ this._updateCodeHeights();
+ // we need to delay re-evaluating the context for prism plugins for as much as possible, and `afterRender` is the 'latest' we can use in the component lifecycle
+ this._updatePrismPlugins();
});
-
- // Force prism-line-highlight plugin initialization
- // Context: https://github.com/hashicorp/design-system/pull/1749#discussion_r1374288785
- if (this.args.highlightLines) {
- // we need to delay re-evaluating the context for prism-line-highlight for as much as possible, and `afterRender` is the 'latest' we can use in the component lifecycle
- // eslint-disable-next-line ember/no-runloop
- schedule('afterRender', (): void => {
- // we piggy-back on the plugin's `resize` event listener to trigger a new call of the `highlightLines` function: https://github.com/PrismJS/prism/blob/master/plugins/line-highlight/prism-line-highlight.js#L337
- if (window) window.dispatchEvent(new Event('resize'));
- });
- }
});
}
}
+ private _updateCodeHeights(): void {
+ if (!this._isExpanded) {
+ // Get the actual height & the content height of the preCodeElement
+ this._codeContentHeight = this._preCodeElement?.scrollHeight ?? 0;
+ this._codeContainerHeight = this._preCodeElement?.clientHeight ?? 0;
+ }
+ }
+
+ private _updatePrismPlugins(): void {
+ if (this.hasLineNumbers && Prism?.plugins?.['lineNumbers']) {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
+ Prism.plugins['lineNumbers'].resize(this._preCodeElement);
+ }
+
+ // Force prism-line-highlight plugin initialization
+ // Context: https://github.com/hashicorp/design-system/pull/1749#discussion_r1374288785
+ if (this.args.highlightLines) {
+ // we piggy-back on the plugin's `resize` event listener to trigger a new call of the `highlightLines` function: https://github.com/PrismJS/prism/blob/master/plugins/line-highlight/prism-line-highlight.js#L337
+ if (window) window.dispatchEvent(new Event('resize'));
+ }
+ }
+
@action
toggleExpanded(): void {
this._isExpanded = !this._isExpanded;
diff --git a/packages/components/src/styles/components/code-block/index.scss b/packages/components/src/styles/components/code-block/index.scss
index c995c208ac2..c5f24efc996 100644
--- a/packages/components/src/styles/components/code-block/index.scss
+++ b/packages/components/src/styles/components/code-block/index.scss
@@ -147,6 +147,7 @@ $hds-code-block-code-footer-height: 48px;
}
code {
+ position: relative;
display: inline-block;
padding-right: $hds-code-block-code-padding;
}
@@ -220,6 +221,13 @@ $hds-code-block-code-footer-height: 48px;
position: relative;
// reserve space for line numbers
padding-left: calc(#{$hds-code-block-line-numbers-width} + #{$hds-code-block-code-padding});
+
+ // When line numbers are enabled, line highlighing is calculated based on the pre element instead of the code element
+ // To ensure the offset is correct, we need to set the position of the code element to static
+ // Source: https://github.com/PrismJS/prism/blob/v2/src/plugins/line-highlight/prism-line-highlight.ts#L92
+ code {
+ position: static;
+ }
}
.hds-code-block__overlay-footer {
@@ -250,15 +258,17 @@ $hds-code-block-code-footer-height: 48px;
}
}
}
+
+ .line-highlight {
+ left: 0;
+ }
}
// Highlighted Lines
.line-highlight {
position: absolute;
right: 0;
- left: 0;
- // Note: position seems off by a few px although not sure why
- margin-top: -3px;
+ left: -$hds-code-block-code-padding;
background-color: var(--hds-code-block-color-line-highlight);
border: solid var(--hds-code-block-color-line-highlight-border);
border-width: 1px 0 1px 4px;
diff --git a/showcase/app/controllers/components/code-block.js b/showcase/app/controllers/components/code-block.js
index 4748833804f..45ea5c04b91 100644
--- a/showcase/app/controllers/components/code-block.js
+++ b/showcase/app/controllers/components/code-block.js
@@ -31,6 +31,25 @@ export default class CodeBlockController extends Controller {
@tracked isModalActive = false;
@tracked declaration = 'let';
@tracked input = '';
+ @tracked value_demo1 = this.value_start_demo1;
+
+ value_start_demo1 = `package main
+import 'fmt'
+func main() {
+ res = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam'
+ fm.Println(res)
+}`;
+
+ value_new_demo1 = `package main
+import 'fmt'
+func main() {
+ res = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam'
+ fm.Println(res)
+}
+func main2() {
+ res = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam'
+ fm.Println(res)
+}`;
constructor() {
super(...arguments);
@@ -76,4 +95,13 @@ export default class CodeBlockController extends Controller {
deactivateModal() {
this.isModalActive = false;
}
+
+ @action
+ onUpdateClickDemo1() {
+ if (this.value_demo1 === this.value_start_demo1) {
+ this.value_demo1 = this.value_new_demo1;
+ } else {
+ this.value_demo1 = this.value_start_demo1;
+ }
+ }
}
diff --git a/showcase/app/templates/components/code-block.hbs b/showcase/app/templates/components/code-block.hbs
index 1108807b4e8..fae8c9f4c9f 100644
--- a/showcase/app/templates/components/code-block.hbs
+++ b/showcase/app/templates/components/code-block.hbs
@@ -659,6 +659,7 @@ console.log(`I am ${codeLang} code`);"
@language="go"
@highlightLines="2, 4"
@hasLineWrapping={{true}}
+ @maxHeight="130px"
@value="package main
import 'fmt'
func main() {
@@ -720,5 +721,15 @@ func main() {
{{/if}}
+
+
+
+
\ No newline at end of file