@rhds/elements
Version:
Red Hat Design System Elements
311 lines (308 loc) • 19.9 kB
JavaScript
var _RhCodeBlock_instances, _RhCodeBlock_slots, _RhCodeBlock_ro, _RhCodeBlock_lineHeights, _RhCodeBlock_wrapChanged, _RhCodeBlock_computeLineNumbers, _RhCodeBlock_onActionsClick, _RhCodeBlock_onActionsKeyup, _RhCodeBlock_onCodeAction, _RhCodeBlock_onClickExpand, _RhCodeBlock_copy;
var RhCodeBlock_1;
import { __classPrivateFieldGet, __classPrivateFieldSet, __decorate } from "tslib";
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators/custom-element.js';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { property } from 'lit/decorators/property.js';
import { SlotController } from '@patternfly/pfe-core/controllers/slot-controller.js';
import { colorContextConsumer } from '../../lib/context/color/consumer.js';
import { css } from "lit";
const style = css `:host{--rh-code-block-callout-size:var(--rh-size-icon-02, 24px);--_aspect-ratio:1;--_badge-size:var(--rh-code-block-callout-size);display:block;max-width:1000px;max-height:calc(10 * var(--rh-space-4xl,64px))}:host([full-height]){--_expand-toggle-rotate:0deg;max-height:initial}[hidden]{display:none!important}::slotted(pre){margin:0!important;padding:0!important;background:0 0!important;border:none!important}.shadow-fab{display:flex;align-items:center;justify-content:center;border:none;background:0 0;padding:var(--rh-space-md,8px);border-radius:var(--rh-border-radius-default,3px);width:var(--rh-length-3xl,48px);height:var(--rh-length-3xl,48px)}.shadow-fab:is(:hover,:focus,:active){background:var(--_code-action-hover-focus-active-background,var(--rh-color-surface-light,#e0e0e0))}.shadow-fab svg{width:var(--rh-size-icon-02,24px);height:var(--rh-size-icon-02,24px);color:var(--_code-action-color,var(--rh-color-text-primary-on-light,#151515))}.dark .shadow-fab{--_code-action-color:var(--rh-color-text-primary-on-dark, #ffffff);--_code-action-hover-focus-active-background:var(--rh-color-surface-dark, #383838)}#container,#content,#content-lines,#sizers{max-width:100%}#container{--_code-main-spacer:var(--rh-space-xl, 24px);display:grid;place-items:center;grid-template-columns:auto min-content;grid-template-areas:"code actions" "expand expand";column-gap:var(--_code-main-spacer);padding-inline-start:var(--_code-main-spacer);padding-block-end:var(--_code-main-spacer);border-radius:var(--rh-border-radius-default,3px);background-color:var(--_code-background-color,var(--rh-color-surface-lighter,#f2f2f2));color:var(--_code-color,var(--rh-color-text-primary-on-light,#151515));border:var(--rh-border-width-sm,1px) solid var(--_code-border-color,var(--rh-color-border-subtle-on-light,#c7c7c7));border-block-start-width:var(--rh-code-block-border-block-start-width,var(--rh-border-width-sm,1px))}#container.expandable{padding-block-end:0}#content,#sizers{display:block;font-family:var(--rh-font-family-code, RedHatMono, "Red Hat Mono", "Courier New", Courier, monospace);z-index:1;place-self:start;grid-area:code}#content::slotted(:is(script,pre)),#sizers{display:inline;white-space:var(--_code-white-space,pre);word-wrap:var(--_code-word-wrap,initial);color:inherit}#content::slotted(rh-tag){width:var(--rh-size-icon-06,64px)}#content-lines{display:grid;column-gap:var(--rh-space-lg,16px);grid-area:code;grid-template-areas:"lines code";grid-template-columns:min-content 1fr;grid-template-rows:1fr;position:relative;overflow-y:auto;margin-block-start:var(--_code-main-spacer);width:100%}#sizers{position:absolute;min-width:100%;width:100%;opacity:0;pointer-events:none;z-index:-10000}#line-numbers{pointer-events:none;overflow-y:hidden;margin:0;grid-area:lines;list-style-type:none;padding-inline:0 var(--rh-space-md,8px);text-align:end;font-family:var(--rh-font-family-code, RedHatMono, "Red Hat Mono", "Courier New", Courier, monospace);color:var(--_code-line-numbers-color,var(--rh-color-gray-60,#4d4d4d));font-weight:var(--rh-font-weight-code-regular,400);border-inline-end:var(--rh-border-width-sm,1px) solid var(--_code-line-numbers-border-color,var(--rh-color-border-subtle-on-light,#c7c7c7))}#actions{display:flex;grid-area:actions;gap:var(--rh-space-md,8px);flex-flow:column;margin-block-start:var(--rh-space-lg,16px);margin-inline-end:var(--rh-space-lg,16px);z-index:2;place-self:start center;height:100%;position:relative}#actions rh-tooltip{display:block}#expand{--_code-secondary-spacer:var(--rh-space-md, 8px);display:inline-flex;align-items:center;border:0;background:0 0;grid-area:expand;gap:var(--rh-space-md,8px);inset-block-end:var(--_code-secondary-spacer);margin-block:var(--_code-secondary-spacer);color:var(--_expand-toggle-color,var(--rh-color-text-primary-on-light,#151515));font-family:var(--rh-font-family-body-text, RedHatText, "Red Hat Text", "Noto Sans Arabic", "Noto Sans Hebrew", "Noto Sans JP", "Noto Sans KR", "Noto Sans Malayalam", "Noto Sans SC", "Noto Sans TC", "Noto Sans Thai", Helvetica, Arial, sans-serif);font-size:var(--rh-font-size-body-text-sm, .875rem);font-weight:var(--rh-font-weight-body-text-regular,400);line-height:var(--rh-line-height-body-text, 1.5)}#expand svg{width:11px;height:7px;rotate:var(--_expand-toggle-rotate,180deg);transform:rotate .2s ease-in-out;color:var(--_expand-toggle-icon-color,var(--rh-color-icon-secondary-on-light,#151515))}#container.compact{--_code-main-spacer:var(--rh-space-lg, 16px);--_code-secondary-spacer:var(--rh-space-sm, 6px)}.resizable #content-lines{resize:vertical;overflow-x:scroll}.truncated #content-lines{max-height:calc(8 * var(--rh-font-size-code-md,1rem))}.truncated #content-lines:before{content:"";display:block;position:sticky;z-index:2;inset-block-end:0;inset-inline:0;height:var(--rh-space-3xl,48px);pointer-events:none;grid-column:-1/1;background:var(--_block-end-overflow-gradient,linear-gradient(0deg,#f2f2f2 0,rgba(242,242,242,0) 100%))}:not(.wrap) #actions:before{content:"";display:block;position:absolute;z-index:2;inset-block:0;inset-inline-start:calc(-1 * var(--rh-space-4xl,64px));width:var(--rh-space-4xl,64px);pointer-events:none;background:var(--_inline-end-overflow-gradient,linear-gradient(270deg,#f2f2f2 0,rgba(242,242,242,0) 100%))}:not(.actions) #actions{margin:0}.wrap{--_code-white-space:pre-wrap;--_code-word-wrap:anywhere}.dark{--_code-background-color:var(--rh-color-surface-dark-alt, #292929);--_code-border-color:var(--rh-color-border-subtle-on-dark, #707070);--_code-color:var(--rh-color-text-primary-on-dark, #ffffff);--_code-line-numbers-color:var(--rh-color-text-secondary-on-dark, #c7c7c7);--_code-line-numbers-border-color:var(--rh-color-border-subtle-on-dark, #707070);--_expand-toggle-color:var(--rh-color-text-primary-on-dark, #ffffff);--_expand-toggle-icon-color:var(--rh-color-icon-secondary-on-dark, #ffffff);--_inline-end-overflow-gradient:linear-gradient(270deg, #292929 0%, rgba(41, 41, 41, 0) 100%);--_block-end-overflow-gradient:linear-gradient(0deg, #292929 0%, rgba(41, 41, 41, 0) 100%)}[name=legend]::slotted(dl){display:grid;grid-template-columns:max-content auto;margin-block:var(--rh-space-lg,16px);gap:var(--rh-space-md,8px)}`;
/**
* A code block is formatted text within a container.
* @summary Formats code strings within a container
* @slot - A non-executable script tag containing the sample content. JavaScript
* samples should use the type `text/sample-javascript`. HTML samples
* containing script tags must escape the closing `</script>` tag. Can
* also be a `<pre>` tag.
* @slot action-label-copy - tooltip content for the copy action button
* @slot action-label-wrap - tooltip content for the wrap action button
* @slot show-more - text content for the expandable toggle button when the code
* block is collapsed.
* @slot show-less - text content for the expandable toggle button when the code
* block is expanded.
* @slot legend - `<dl>` element containing rh-badges in the `<dt>`
* and legend text in the `<dd>` elements
*/
let RhCodeBlock = RhCodeBlock_1 = class RhCodeBlock extends LitElement {
constructor() {
super(...arguments);
_RhCodeBlock_instances.add(this);
this.actions = [];
/** When set, the code block displays with compact spacing */
this.compact = false;
/** When set, the code block is resizable */
this.resizable = false;
/** When set, the code block occupies it's full height, without scrolling */
this.fullHeight = false;
/** When set, lines in the code snippet wrap */
this.wrap = false;
_RhCodeBlock_slots.set(this, new SlotController(this, null,
// 'actions',
'action-label-copy', 'action-label-wrap', 'show-more', 'show-less', 'legend'));
_RhCodeBlock_ro.set(this, new ResizeObserver(() => __classPrivateFieldGet(this, _RhCodeBlock_instances, "m", _RhCodeBlock_computeLineNumbers).call(this)));
_RhCodeBlock_lineHeights.set(this, []);
}
connectedCallback() {
super.connectedCallback();
__classPrivateFieldGet(this, _RhCodeBlock_ro, "f").observe(this);
}
disconnectedCallback() {
super.disconnectedCallback();
__classPrivateFieldGet(this, _RhCodeBlock_ro, "f").disconnect();
}
render() {
const { on = '', fullHeight, wrap, resizable, compact } = this;
const expandable = __classPrivateFieldGet(this, _RhCodeBlock_lineHeights, "f").length > 5;
const truncated = expandable && !fullHeight;
const actions = !!this.actions.length;
return html `
<div id="container"
class="${classMap({ [on]: !!on,
actions,
compact,
expandable,
fullHeight,
resizable,
truncated,
wrap })}"
-action="${__classPrivateFieldGet(this, _RhCodeBlock_instances, "m", _RhCodeBlock_onCodeAction)}">
<div id="content-lines">
<div id="sizers" aria-hidden="true"></div>
<ol id="line-numbers" aria-hidden="true">${__classPrivateFieldGet(this, _RhCodeBlock_lineHeights, "f").map((height, i) => html `
<li style="${styleMap({ height })}">${i + 1}</li>`)}
</ol>
<slot id="content" ="${__classPrivateFieldGet(this, _RhCodeBlock_instances, "m", _RhCodeBlock_computeLineNumbers)}"></slot>
</div>
<div id="actions"
="${__classPrivateFieldGet(this, _RhCodeBlock_instances, "m", _RhCodeBlock_onActionsClick)}"
="${__classPrivateFieldGet(this, _RhCodeBlock_instances, "m", _RhCodeBlock_onActionsKeyup)}">
<!-- <slot name="actions"> -->${this.actions.map(x => html `
<rh-tooltip>
<slot slot="content" name="action-label-${x}"></slot>
<button id="action-${x}"
class="shadow-fab"
data-code-block-action="${x}">
${RhCodeBlock_1.actions.get(this.wrap && x === 'wrap' ? 'wrap-active' : x) ?? ''}
</button>
</rh-tooltip>`)}
<!-- </slot> -->
</div>
<button id="expand"
?hidden="${!expandable}"
="${__classPrivateFieldGet(this, _RhCodeBlock_instances, "m", _RhCodeBlock_onClickExpand)}">
<slot name="show-more" ?hidden="${this.fullHeight}">Show more</slot>
<slot name="show-less" ?hidden="${!this.fullHeight}">Show less</slot>
<svg xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 11 7">
<path d="M4.919.239.242 4.847a.801.801 0 0 0 0 1.148l.778.766a.83.83 0 0 0 1.165 0L5.5 3.495 8.815 6.76a.83.83 0 0 0 1.165 0l.778-.766a.802.802 0 0 0 0-1.148L6.08.239a.826.826 0 0 0-1.162 0Z"/>
</svg>
</button>
</div>
<slot name="legend" ?hidden="${__classPrivateFieldGet(this, _RhCodeBlock_slots, "f").isEmpty('legend')}"></slot>
`;
}
firstUpdated() {
__classPrivateFieldGet(this, _RhCodeBlock_instances, "m", _RhCodeBlock_computeLineNumbers).call(this);
}
updated(changed) {
if (changed.has('wrap')) {
__classPrivateFieldGet(this, _RhCodeBlock_instances, "m", _RhCodeBlock_wrapChanged).call(this);
}
}
};
_RhCodeBlock_slots = new WeakMap();
_RhCodeBlock_ro = new WeakMap();
_RhCodeBlock_lineHeights = new WeakMap();
_RhCodeBlock_instances = new WeakSet();
_RhCodeBlock_wrapChanged = function _RhCodeBlock_wrapChanged() {
__classPrivateFieldGet(this, _RhCodeBlock_instances, "m", _RhCodeBlock_computeLineNumbers).call(this);
// TODO: handle slotted fabs
const slot = this.shadowRoot?.querySelector('slot[name="action-label-wrap"]');
for (const el of slot?.assignedElements() ?? []) {
if (el instanceof HTMLElement) {
el.hidden = (el.dataset.codeBlockState !== 'active') === this.wrap;
}
}
this.requestUpdate();
};
_RhCodeBlock_computeLineNumbers = function _RhCodeBlock_computeLineNumbers() {
const slot = this.shadowRoot?.getElementById('content');
const codes = slot.assignedElements().flatMap(x => x instanceof HTMLScriptElement
|| x instanceof HTMLPreElement ? [x]
: []);
const infos = codes.map(element => {
const sizer = document.createElement('span');
sizer.className = 'sizer';
sizer.innerText = '0';
sizer.style.display = 'block';
this.shadowRoot?.getElementById('sizers')?.appendChild(sizer);
return {
lines: element.textContent?.split(/\n(?!$)/g) ?? [],
lineHeights: [],
sizer,
oneLinerHeight: sizer.getBoundingClientRect().height,
};
});
for (const { lines, lineHeights, sizer, oneLinerHeight } of infos) {
lineHeights[lines.length - 1] = undefined; // why?
lines.forEach((line, i) => {
if (line.length > 1) {
const e = sizer.appendChild(document.createElement('span'));
e.style.display = 'block';
e.textContent = line;
}
else {
lineHeights[i] = oneLinerHeight;
}
});
}
for (const { sizer, lineHeights } of infos) {
let childIndex = 0;
for (let i = 0; i < lineHeights.length; i++) {
if (lineHeights[i] === undefined) {
lineHeights[i] = sizer.children[childIndex++].getBoundingClientRect()?.height ?? 0;
}
}
sizer.remove();
}
__classPrivateFieldSet(this, _RhCodeBlock_lineHeights, infos.flatMap(x => x.lineHeights?.map(y => `${y ?? x.oneLinerHeight}px`)), "f");
this.requestUpdate('#linesNumbers', 0);
};
_RhCodeBlock_onActionsClick = function _RhCodeBlock_onActionsClick(event) {
__classPrivateFieldGet(this, _RhCodeBlock_instances, "m", _RhCodeBlock_onCodeAction).call(this, event);
};
_RhCodeBlock_onActionsKeyup = function _RhCodeBlock_onActionsKeyup(event) {
switch (event.key) {
case 'Enter':
case ' ':
event.preventDefault();
__classPrivateFieldGet(this, _RhCodeBlock_instances, "m", _RhCodeBlock_onCodeAction).call(this, event);
}
};
_RhCodeBlock_onCodeAction = function _RhCodeBlock_onCodeAction(event) {
const el = event.composedPath().find((x) => x instanceof HTMLElement && !!x.dataset.codeBlockAction);
if (el) {
switch (el.dataset.codeBlockAction) {
case 'copy':
return __classPrivateFieldGet(this, _RhCodeBlock_instances, "m", _RhCodeBlock_copy).call(this);
case 'wrap':
this.wrap = !this.wrap;
this.requestUpdate();
return;
}
}
};
_RhCodeBlock_onClickExpand = function _RhCodeBlock_onClickExpand() {
this.fullHeight = !this.fullHeight;
};
_RhCodeBlock_copy = async function _RhCodeBlock_copy() {
await navigator.clipboard.writeText(Array.from(this.querySelectorAll('script'), x => x.textContent).join(''));
// TODO: handle slotted fabs
const slot = this.shadowRoot?.querySelector('slot[name="action-label-copy"]');
const tooltip = slot?.closest('rh-tooltip');
tooltip?.hide();
for (const el of slot?.assignedElements() ?? []) {
if (el instanceof HTMLElement) {
el.hidden = el.dataset.codeBlockState !== 'active';
}
}
this.requestUpdate();
tooltip?.show();
await new Promise(r => setTimeout(r, 5000));
tooltip?.hide();
for (const el of slot?.assignedElements() ?? []) {
if (el instanceof HTMLElement) {
el.hidden = el.dataset.codeBlockState === 'active';
}
}
this.requestUpdate();
tooltip?.show();
};
RhCodeBlock.actions = new Map([
['wrap', html `
<svg xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20">
<path d="M19 0c.313.039.781-.077 1 .057V20c-.313-.039-.781.077-1-.057V0ZM10.82 4.992C9.877 4.996 8.31 5.57 8.174 6c1.21.03 2.432-.073 3.635.08 2.181.383 3.677 2.796 3.066 4.922-.41 1.753-2.108 2.995-3.877 3.014L11 14H5.207l2.682-2.682-.707-.707L3.293 14.5l3.889 3.889.707-.707L5.207 15h5.736l.004-.008c1.444.005 2.896-.59 3.832-1.722 1.65-1.82 1.612-4.85-.08-6.63A5 5 0 0 0 11 5a1.948 1.948 0 0 0-.18-.008z"/>
<path d="M4 5h7c-.039.313.077.781-.057 1H4V5ZM0 0c.313.039.781-.077 1 .057V20c-.313-.039-.781.077-1-.057V0Z"/>
</svg>
`],
['wrap-active', html `
<svg xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 21 20">
<path fill="currentColor" d="M12 13h1v7h-1zM12 0h1v7h-1z"/>
<path stroke="currentColor" d="M16.465 6.464 20 10l-3.535 3.536"/>
<path fill="currentColor" d="M3 9.5h17v1H3zM0 0h1v20H0z"/>
</svg>
`],
['copy', html `
<svg xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 20 20">
<path fill="currentColor" d="M12 0H2C.9 0 0 .9 0 2v10h1V2c0-.6.4-1 1-1h10V0z"/>
<path fill="currentColor" d="M18 20H8c-1.1 0-2-.9-2-2V8c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2v10c0 1.1-.9 2-2 2zM8 7c-.6 0-1 .4-1 1v10c0 .6.4 1 1 1h10c.6 0 1-.4 1-1V8c0-.6-.4-1-1-1H8z"/>
</svg>
`],
]);
RhCodeBlock.styles = [style];
__decorate([
property({
reflect: true,
converter: {
fromAttribute(value) {
return ((value ?? '').split(/\s+/) ?? []).map(x => x.trim()).filter(Boolean);
},
toAttribute(value) {
return Array.isArray(value) ? value.join(' ') : '';
},
},
})
], RhCodeBlock.prototype, "actions", void 0);
__decorate([
property({ type: Boolean, reflect: true })
], RhCodeBlock.prototype, "compact", void 0);
__decorate([
property({ type: Boolean, reflect: true })
], RhCodeBlock.prototype, "resizable", void 0);
__decorate([
property({ type: Boolean, reflect: true, attribute: 'full-height' })
], RhCodeBlock.prototype, "fullHeight", void 0);
__decorate([
property({ type: Boolean })
], RhCodeBlock.prototype, "wrap", void 0);
__decorate([
colorContextConsumer()
], RhCodeBlock.prototype, "on", void 0);
RhCodeBlock = RhCodeBlock_1 = __decorate([
customElement('rh-code-block')
], RhCodeBlock);
export { RhCodeBlock };
/**
* TODO: slotted fabs like this:
*
*```html
<rh-tooltip slot="actions">
<span slot="content">Copy to Clipboard</span>
<span slot="content"
hidden
data-code-block-state="active">Copied!</span>
<rh-fab icon="copy"
data-code-block-action="copy"></rh-fab>
</rh-tooltip>
<rh-tooltip slot="actions">
<span slot="content">Toggle linewrap</span>
<span slot="content"
hidden
data-code-block-state="active">Toggle linewrap</span>
<rh-fab icon="copy"
data-code-block-action="copy"></rh-fab>
</rh-tooltip>
````
*
*/
//# sourceMappingURL=rh-code-block.js.map