Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/selfish-apes-breathe.md
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions packages/components/src/components/hds/code-block/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
SPDX-License-Identifier: MPL-2.0
}}

<div class={{this.classNames}} ...attributes>
<div class={{this.classNames}} ...attributes {{this._setUpCodeObserver}}>
<div class="hds-code-block__header">
{{~yield (hash Title=(component "hds/code-block/title"))~}}
{{~yield (hash Description=(component "hds/code-block/description"))~}}
Expand All @@ -17,7 +17,7 @@
data-start={{@lineNumberStart}}
id={{this._preCodeId}}
tabindex="0"
><code {{did-insert this.setPrismCode}} {{did-update this.setPrismCode this.code @language}}>
><code {{this._setUpCodeBlockCode}}>
{{~this._prismCode~}}
</code></pre>

Expand Down
77 changes: 62 additions & 15 deletions packages/components/src/components/hds/code-block/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

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

// 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 {
Expand Down Expand Up @@ -145,35 +174,53 @@ export default class HdsCodeBlock extends Component<HdsCodeBlockSignature> {
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', {
code,
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;
Expand Down
16 changes: 13 additions & 3 deletions packages/components/src/styles/components/code-block/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ $hds-code-block-code-footer-height: 48px;
}

code {
position: relative;
display: inline-block;
padding-right: $hds-code-block-code-padding;
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
28 changes: 28 additions & 0 deletions showcase/app/controllers/components/code-block.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}
}
11 changes: 11 additions & 0 deletions showcase/app/templates/components/code-block.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -720,5 +721,15 @@ func main() {
</Hds::Modal>
{{/if}}
</SF.Item>
<SF.Item @label="Dynamic updates">
<Hds::CodeBlock @language="go" @highlightLines="2, 4" @maxHeight="180px" @value={{this.value_demo1}} />
<Hds::Button
type="button"
@text="Update"
@isInline={{true}}
{{style marginTop="12px"}}
{{on "click" this.onUpdateClickDemo1}}
/>
</SF.Item>
</Shw::Flex>
</section>