Skip to content

Commit 76d8575

Browse files
authored
CodeBlock - Fix line wrapping and highlighting rendering (#2856)
1 parent 60a3cb8 commit 76d8575

File tree

6 files changed

+121
-20
lines changed

6 files changed

+121
-20
lines changed

.changeset/selfish-apes-breathe.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hashicorp/design-system-components": patch
3+
---
4+
5+
`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.

packages/components/src/components/hds/code-block/index.hbs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
SPDX-License-Identifier: MPL-2.0
44
}}
55

6-
<div class={{this.classNames}} ...attributes>
6+
<div class={{this.classNames}} ...attributes {{this._setUpCodeObserver}}>
77
<div class="hds-code-block__header">
88
{{~yield (hash Title=(component "hds/code-block/title"))~}}
99
{{~yield (hash Description=(component "hds/code-block/description"))~}}
@@ -17,7 +17,7 @@
1717
data-start={{@lineNumberStart}}
1818
id={{this._preCodeId}}
1919
tabindex="0"
20-
><code {{did-insert this.setPrismCode}} {{did-update this.setPrismCode this.code @language}}>
20+
><code {{this._setUpCodeBlockCode}}>
2121
{{~this._prismCode~}}
2222
</code></pre>
2323

packages/components/src/components/hds/code-block/index.ts

+62-15
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { assert } from '@ember/debug';
1010
import { next, schedule } from '@ember/runloop';
1111
import { htmlSafe } from '@ember/template';
1212
import { guidFor } from '@ember/object/internals';
13+
import { modifier } from 'ember-modifier';
1314

1415
import Prism from 'prismjs';
1516

@@ -75,6 +76,34 @@ export default class HdsCodeBlock extends Component<HdsCodeBlockSignature> {
7576

7677
// Generates a unique ID for the code content
7778
private _preCodeId = 'pre-code-' + guidFor(this);
79+
private _preCodeElement!: HTMLPreElement;
80+
private _observer!: ResizeObserver;
81+
82+
// If a code block is hidden from view, and made visible after load, the Prism code needs to be re-run
83+
private _setUpCodeObserver = modifier((element: HTMLElement) => {
84+
this._preCodeElement = element.querySelector(
85+
'.hds-code-block__code'
86+
) as HTMLPreElement;
87+
this._observer = new ResizeObserver((entries) => {
88+
entries.forEach((entry) => {
89+
if (entry.contentBoxSize) {
90+
this._updateCodeHeights();
91+
this._updatePrismPlugins();
92+
}
93+
});
94+
});
95+
this._observer.observe(element);
96+
97+
return () => {
98+
this._observer.disconnect();
99+
};
100+
});
101+
102+
private _setUpCodeBlockCode = modifier((element: HTMLElement) => {
103+
this._isExpanded = false; // reset expanded state on updates
104+
this.setPrismCode(element);
105+
return () => {};
106+
});
78107

79108
// code text content for the CodeBlock
80109
get code(): string {
@@ -145,35 +174,53 @@ export default class HdsCodeBlock extends Component<HdsCodeBlockSignature> {
145174
this._prismCode = htmlSafe(Prism.util.encode(code).toString());
146175
}
147176

177+
// Existing line numbers must be removed in order to be updated correctly
178+
const lineNumbers = element.querySelector(
179+
'.line-numbers-rows'
180+
) as HTMLElement;
181+
if (lineNumbers) {
182+
element.removeChild(lineNumbers);
183+
}
184+
148185
// Force prism-line-numbers plugin initialization, required for Prism.highlight usage
149186
// See https://github.com/PrismJS/prism/issues/1234
150187
Prism.hooks.run('complete', {
151188
code,
152189
element,
153190
});
154191

155-
// Get the actual height & the content height of the preCodeElement
156192
// eslint-disable-next-line ember/no-runloop
157193
schedule('afterRender', (): void => {
158-
const preCodeElement = document.getElementById(this._preCodeId);
159-
this._codeContentHeight = preCodeElement?.scrollHeight ?? 0;
160-
this._codeContainerHeight = preCodeElement?.clientHeight ?? 0;
194+
this._updateCodeHeights();
195+
// 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
196+
this._updatePrismPlugins();
161197
});
162-
163-
// Force prism-line-highlight plugin initialization
164-
// Context: https://github.com/hashicorp/design-system/pull/1749#discussion_r1374288785
165-
if (this.args.highlightLines) {
166-
// 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
167-
// eslint-disable-next-line ember/no-runloop
168-
schedule('afterRender', (): void => {
169-
// 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
170-
if (window) window.dispatchEvent(new Event('resize'));
171-
});
172-
}
173198
});
174199
}
175200
}
176201

202+
private _updateCodeHeights(): void {
203+
if (!this._isExpanded) {
204+
// Get the actual height & the content height of the preCodeElement
205+
this._codeContentHeight = this._preCodeElement?.scrollHeight ?? 0;
206+
this._codeContainerHeight = this._preCodeElement?.clientHeight ?? 0;
207+
}
208+
}
209+
210+
private _updatePrismPlugins(): void {
211+
if (this.hasLineNumbers && Prism?.plugins?.['lineNumbers']) {
212+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
213+
Prism.plugins['lineNumbers'].resize(this._preCodeElement);
214+
}
215+
216+
// Force prism-line-highlight plugin initialization
217+
// Context: https://github.com/hashicorp/design-system/pull/1749#discussion_r1374288785
218+
if (this.args.highlightLines) {
219+
// 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
220+
if (window) window.dispatchEvent(new Event('resize'));
221+
}
222+
}
223+
177224
@action
178225
toggleExpanded(): void {
179226
this._isExpanded = !this._isExpanded;

packages/components/src/styles/components/code-block/index.scss

+13-3
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ $hds-code-block-code-footer-height: 48px;
147147
}
148148

149149
code {
150+
position: relative;
150151
display: inline-block;
151152
padding-right: $hds-code-block-code-padding;
152153
}
@@ -220,6 +221,13 @@ $hds-code-block-code-footer-height: 48px;
220221
position: relative;
221222
// reserve space for line numbers
222223
padding-left: calc(#{$hds-code-block-line-numbers-width} + #{$hds-code-block-code-padding});
224+
225+
// When line numbers are enabled, line highlighing is calculated based on the pre element instead of the code element
226+
// To ensure the offset is correct, we need to set the position of the code element to static
227+
// Source: https://github.com/PrismJS/prism/blob/v2/src/plugins/line-highlight/prism-line-highlight.ts#L92
228+
code {
229+
position: static;
230+
}
223231
}
224232

225233
.hds-code-block__overlay-footer {
@@ -250,15 +258,17 @@ $hds-code-block-code-footer-height: 48px;
250258
}
251259
}
252260
}
261+
262+
.line-highlight {
263+
left: 0;
264+
}
253265
}
254266

255267
// Highlighted Lines
256268
.line-highlight {
257269
position: absolute;
258270
right: 0;
259-
left: 0;
260-
// Note: position seems off by a few px although not sure why
261-
margin-top: -3px;
271+
left: -$hds-code-block-code-padding;
262272
background-color: var(--hds-code-block-color-line-highlight);
263273
border: solid var(--hds-code-block-color-line-highlight-border);
264274
border-width: 1px 0 1px 4px;

showcase/app/controllers/components/code-block.js

+28
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,25 @@ export default class CodeBlockController extends Controller {
3131
@tracked isModalActive = false;
3232
@tracked declaration = 'let';
3333
@tracked input = '';
34+
@tracked value_demo1 = this.value_start_demo1;
35+
36+
value_start_demo1 = `package main
37+
import 'fmt'
38+
func main() {
39+
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'
40+
fm.Println(res)
41+
}`;
42+
43+
value_new_demo1 = `package main
44+
import 'fmt'
45+
func main() {
46+
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'
47+
fm.Println(res)
48+
}
49+
func main2() {
50+
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'
51+
fm.Println(res)
52+
}`;
3453

3554
constructor() {
3655
super(...arguments);
@@ -76,4 +95,13 @@ export default class CodeBlockController extends Controller {
7695
deactivateModal() {
7796
this.isModalActive = false;
7897
}
98+
99+
@action
100+
onUpdateClickDemo1() {
101+
if (this.value_demo1 === this.value_start_demo1) {
102+
this.value_demo1 = this.value_new_demo1;
103+
} else {
104+
this.value_demo1 = this.value_start_demo1;
105+
}
106+
}
79107
}

showcase/app/templates/components/code-block.hbs

+11
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,7 @@ console.log(`I am ${codeLang} code`);"
659659
@language="go"
660660
@highlightLines="2, 4"
661661
@hasLineWrapping={{true}}
662+
@maxHeight="130px"
662663
@value="package main
663664
import 'fmt'
664665
func main() {
@@ -720,5 +721,15 @@ func main() {
720721
</Hds::Modal>
721722
{{/if}}
722723
</SF.Item>
724+
<SF.Item @label="Dynamic updates">
725+
<Hds::CodeBlock @language="go" @highlightLines="2, 4" @maxHeight="180px" @value={{this.value_demo1}} />
726+
<Hds::Button
727+
type="button"
728+
@text="Update"
729+
@isInline={{true}}
730+
{{style marginTop="12px"}}
731+
{{on "click" this.onUpdateClickDemo1}}
732+
/>
733+
</SF.Item>
723734
</Shw::Flex>
724735
</section>

0 commit comments

Comments
 (0)