@limetech/lime-elements
Version:
776 lines (775 loc) • 33.8 kB
JavaScript
import { h, Host } from "@stencil/core";
import translate from "../../global/translations";
import { buildSplitLines, computeDiff, normalizeForDiff } from "./diff-engine";
import { tokenize } from "./syntax-highlighter";
import { buildSearchRegex, navigateMatchIndex } from "./search-utils";
import { extractRemovedContent, extractRemovedContentFromSplit, } from "./content-utils";
/**
* Displays a visual diff between two text values, modeled on
* GitHub's code difference view.
*
* Supports unified and split (side-by-side) views with line numbers,
* color-coded additions and removals, word-level inline highlighting,
* and collapsible unchanged context sections.
*
* @beta
* @exampleComponent limel-example-code-diff-basic
* @exampleComponent limel-example-code-diff-headings
* @exampleComponent limel-example-code-diff-json
* @exampleComponent limel-example-code-diff-split
* @exampleComponent limel-example-code-diff-line-wrap
* @exampleComponent limel-example-code-diff-expand
*/
export class CodeDiff {
constructor() {
/**
* The "before" value to compare.
* Can be a string or an object (which will be serialized to JSON).
*/
this.oldValue = '';
/**
* The "after" value to compare.
* Can be a string or an object (which will be serialized to JSON).
*/
this.newValue = '';
/**
* The layout of the diff view.
* - `unified` — single column with interleaved additions and removals
* - `split` — side-by-side comparison with old on left, new on right
*/
this.layout = 'unified';
/**
* Number of unchanged context lines to display around each change.
*/
this.contextLines = 3;
/**
* When `true`, long lines are wrapped instead of causing
* horizontal scrolling. Useful when comparing prose or
* config files with long values.
*/
this.lineWrapping = true;
/**
* When `true`, JSON values are parsed, keys are sorted,
* and indentation is normalized before diffing.
* This eliminates noise from formatting or key-order differences.
*/
this.reformatJson = false;
/**
* Defines the language for translations.
* Will translate all visible labels and announcements.
*/
this.translationLanguage = 'en';
this.diffResult = {
hunks: [],
additions: 0,
deletions: 0,
allLines: [],
};
this.liveAnnouncement = '';
this.copyState = 'idle';
this.searchVisible = false;
this.searchTerm = '';
this.currentMatchIndex = 0;
this.focusedRowIndex = -1;
this.normalizedOldText = '';
/**
* Render-time counter that increments for each search match
* found while rendering removed lines. Used to determine which
* match is the "current" one for navigation highlighting.
*/
this.searchMatchCounter = 0;
/**
* Total search matches found during the last render pass.
*/
this.totalSearchMatches = 0;
/**
* Whether the current render is inside a removed line,
* so search highlighting knows when to activate.
*/
this.isRenderingRemovedLine = false;
/**
* Cached search regex for the current render pass.
* Built once in render() and reused across all renderSearchableText calls.
*/
this.activeSearchRegex = null;
this.prevSearchVisible = false;
}
componentWillLoad() {
this.recomputeDiff();
}
componentDidRender() {
var _a, _b;
if (this.searchVisible && !this.prevSearchVisible) {
(_a = this.searchInputEl) === null || _a === void 0 ? void 0 : _a.focus();
}
this.prevSearchVisible = this.searchVisible;
if (this.searchTerm && this.totalSearchMatches > 0) {
const current = (_b = this.host.shadowRoot) === null || _b === void 0 ? void 0 : _b.querySelector('.search-match--current');
current === null || current === void 0 ? void 0 : current.scrollIntoView({ block: 'center', behavior: 'smooth' });
}
}
render() {
this.searchMatchCounter = 0;
this.activeSearchRegex = buildSearchRegex(this.searchTerm);
const diffContent = this.renderDiff();
// Capture total matches after rendering completes
this.totalSearchMatches = this.searchMatchCounter;
const lineNumberWidth = this.computeLineNumberWidth();
return (h(Host, { key: '8122ede0d323b9021dae44cd5f65703d03083617', style: { '--limel-line-number-min-width': lineNumberWidth } }, this.renderHeader(), this.renderScreenReaderSummary(), this.searchVisible && this.renderSearchBar(), h("div", { key: 'fefe6e4898cbc82b6eb87f7dff7af71a14c7c91e', class: "diff-body", role: "table", "aria-label": this.getTranslation('code-diff.table-label'), tabindex: "0", onKeyDown: (event) => this.handleKeyDown(event) }, diffContent), h("div", { key: '2af1c7b29f2060a58af13d67fb174cb4de085efb', class: "screen-reader-only", role: "status", "aria-live": "polite", "aria-atomic": "true" }, this.liveAnnouncement)));
}
watchInputs() {
this.recomputeDiff();
}
recomputeDiff() {
const oldText = normalizeForDiff(this.oldValue, this.reformatJson);
const newText = normalizeForDiff(this.newValue, this.reformatJson);
this.normalizedOldText = oldText;
this.diffResult = computeDiff(oldText, newText, this.contextLines);
this.focusedRowIndex = -1;
}
formatSrSummary() {
const { additions, deletions } = this.diffResult;
if (additions === 0 && deletions === 0) {
return null;
}
const parts = [];
if (additions > 0) {
const key = additions === 1
? 'code-diff.diff-addition'
: 'code-diff.diff-additions';
parts.push(this.getTranslation(key, { count: additions }));
}
if (deletions > 0) {
const key = deletions === 1
? 'code-diff.diff-deletion'
: 'code-diff.diff-deletions';
parts.push(this.getTranslation(key, { count: deletions }));
}
return this.getTranslation('code-diff.diff-summary', {
parts: parts.join(', '),
});
}
renderScreenReaderSummary() {
const summary = this.formatSrSummary();
return (h("div", { class: "screen-reader-only", role: "status", "aria-live": "polite" }, summary !== null && summary !== void 0 ? summary : this.getTranslation('code-diff.no-differences-found')));
}
handleKeyDown(event) {
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') {
return;
}
event.preventDefault();
const rows = this.getDiffRows();
if (rows.length === 0) {
return;
}
if (event.key === 'ArrowDown') {
this.focusedRowIndex = Math.min(this.focusedRowIndex + 1, rows.length - 1);
}
else {
this.focusedRowIndex = Math.max(this.focusedRowIndex - 1, 0);
}
this.updateRowFocus(rows);
}
getDiffRows() {
var _a;
const body = (_a = this.host.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('.diff-body');
if (!body) {
return [];
}
return [
...body.querySelectorAll('.diff-line:not(.diff-line--collapsed)'),
];
}
updateRowFocus(rows) {
for (const row of rows) {
row.removeAttribute('tabindex');
row.classList.remove('diff-line--focused');
}
const target = rows[this.focusedRowIndex];
if (target) {
target.setAttribute('tabindex', '-1');
target.classList.add('diff-line--focused');
target.focus();
this.announceLine(target);
}
}
announceLine(row) {
var _a, _b;
let lineType = this.getTranslation('code-diff.line-context');
if (row.classList.contains('diff-line--added')) {
lineType = this.getTranslation('code-diff.line-added');
}
else if (row.classList.contains('diff-line--removed')) {
lineType = this.getTranslation('code-diff.line-removed');
}
const content = (_b = (_a = row.querySelector('.line-content, .split-content')) === null || _a === void 0 ? void 0 : _a.textContent) !== null && _b !== void 0 ? _b : '';
const trimmed = content.length > 80 ? content.slice(0, 80) + '…' : content;
this.liveAnnouncement = `${lineType}: ${trimmed}`;
}
renderHeader() {
var _a, _b;
const oldHeading = (_a = this.oldHeading) !== null && _a !== void 0 ? _a : this.getTranslation('code-diff.old-heading');
const newHeading = (_b = this.newHeading) !== null && _b !== void 0 ? _b : this.getTranslation('code-diff.new-heading');
const { additions, deletions } = this.diffResult;
const hasDiff = additions > 0 || deletions > 0;
return (h("div", { class: "diff-header" }, h("div", { class: "diff-header__labels" }, h("span", { class: "diff-header__old" }, oldHeading), h("span", { class: "diff-header__new" }, newHeading)), h("div", { class: "diff-header__actions" }, h("div", { class: "diff-header__stats" }, additions > 0 && (h("span", { class: "stat stat--added" }, "+", additions)), deletions > 0 && (h("span", { class: "stat stat--removed" }, "-", deletions))), hasDiff && this.renderCopyButton(), deletions > 0 && this.renderSearchToggle())));
}
renderCopyButton() {
const label = this.copyState === 'copied'
? this.getTranslation('code-diff.copied')
: this.getTranslation('code-diff.copy-old-version');
const icon = this.copyState === 'copied' ? 'checkmark' : 'copy';
return (h("limel-icon-button", { label: label, icon: icon, onClick: () => this.copyToClipboard(this.normalizedOldText) }));
}
async copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
this.copyState = 'copied';
this.liveAnnouncement = this.getTranslation('code-diff.copied-to-clipboard');
setTimeout(() => {
this.copyState = 'idle';
}, 2000);
}
catch (_a) {
// Clipboard API may fail in insecure contexts
}
}
renderSearchToggle() {
return (h("limel-icon-button", { class: { 'search-toggle--active': this.searchVisible }, label: this.getTranslation('code-diff.search'), icon: "search", onClick: () => this.toggleSearch() }));
}
renderSearchBar() {
const matchInfo = this.totalSearchMatches === 0
? this.getTranslation('code-diff.no-matches')
: this.getTranslation('code-diff.match-count', {
current: this.currentMatchIndex + 1,
total: this.totalSearchMatches,
});
return (h("div", { class: "search-bar" }, h("limel-input-field", { class: "search-bar__input", type: "search", placeholder: this.getTranslation('code-diff.search') + '…', value: this.searchTerm, onChange: (e) => this.onSearchInput(e), onKeyDown: (e) => this.onSearchKeyDown(e), ref: (el) => (this.searchInputEl = el) }), h("span", { class: "search-bar__count" }, matchInfo), h("limel-action-bar", { actions: this.getSearchActions(), onItemSelected: (e) => this.onSearchAction(e) })));
}
toggleSearch() {
this.searchVisible = !this.searchVisible;
if (!this.searchVisible) {
this.searchTerm = '';
this.currentMatchIndex = 0;
}
}
onSearchInput(event) {
this.searchTerm = event.detail;
this.currentMatchIndex = 0;
}
onSearchKeyDown(event) {
if (event.key === 'Enter') {
event.preventDefault();
if (event.shiftKey) {
this.navigateSearch(-1);
}
else {
this.navigateSearch(1);
}
}
else if (event.key === 'Escape') {
this.toggleSearch();
}
}
computeLineNumberWidth() {
const maxLineNumber = this.diffResult.allLines.length;
const digits = String(maxLineNumber).length;
return `calc(${digits}ch + 2 * var(--limel-code-diff-line-number-padding))`;
}
getSearchActions() {
const noMatches = this.totalSearchMatches === 0;
return [
{
text: this.getTranslation('code-diff.previous-match'),
icon: '-lime-caret-top',
iconOnly: true,
disabled: noMatches,
value: 'prev',
},
{
text: this.getTranslation('code-diff.next-match'),
icon: '-lime-caret-bottom',
iconOnly: true,
disabled: noMatches,
value: 'next',
},
{
text: this.getTranslation('code-diff.close-search'),
icon: 'cancel',
iconOnly: true,
value: 'close',
},
];
}
onSearchAction(event) {
const { value } = event.detail;
if (value === 'prev') {
this.navigateSearch(-1);
}
else if (value === 'next') {
this.navigateSearch(1);
}
else if (value === 'close') {
this.toggleSearch();
}
}
navigateSearch(direction) {
this.currentMatchIndex = navigateMatchIndex(this.currentMatchIndex, direction, this.totalSearchMatches);
}
renderDiff() {
const { hunks, collapsedAfter } = this.diffResult;
if (hunks.length === 0) {
return (h("div", { class: "diff-empty" }, this.getTranslation('code-diff.no-differences')));
}
const lineRenderer = this.layout === 'split'
? (hunk) => this.renderSplitHunkRows(hunk)
: (hunk) => this.renderHunkLines(hunk);
return this.renderHunks(hunks, collapsedAfter, lineRenderer);
}
renderHunks(hunks, collapsedAfter, lineRenderer) {
const elements = [];
for (const [i, hunk] of hunks.entries()) {
if (hunk.collapsedBefore) {
elements.push(this.renderCollapsedRow(hunk.collapsedBefore, i));
}
elements.push(...lineRenderer(hunk));
}
if (collapsedAfter) {
elements.push(this.renderCollapsedAfterRow(collapsedAfter));
}
return elements;
}
renderHunkLines(hunk) {
const elements = [];
let i = 0;
while (i < hunk.lines.length) {
const line = hunk.lines[i];
if (line.type === 'context') {
elements.push(this.renderLine(line));
i++;
continue;
}
// Collect consecutive changed lines as a change block
const blockLines = [];
while (i < hunk.lines.length && hunk.lines[i].type !== 'context') {
blockLines.push(hunk.lines[i]);
i++;
}
elements.push(this.renderChangeBlock(blockLines));
}
return elements;
}
renderChangeBlock(lines) {
const removedContent = extractRemovedContent(lines);
return (h("div", { class: "change-group" }, lines.map((line) => this.renderLine(line)), removedContent && this.renderBlockCopyButton(removedContent)));
}
renderLine(line) {
var _a, _b;
const lineClass = {
'diff-line': true,
[`diff-line--${line.type}`]: true,
};
const indicatorMap = {
added: '+',
removed: '-',
context: ' ',
};
const indicator = indicatorMap[line.type];
return (h("div", { class: lineClass, role: "row" }, h("span", { class: "line-number line-number--old", role: "cell", "aria-label": line.oldLineNumber
? this.getTranslation('code-diff.old-line', {
number: line.oldLineNumber,
})
: undefined }, (_a = line.oldLineNumber) !== null && _a !== void 0 ? _a : ''), h("span", { class: "line-number line-number--new", role: "cell", "aria-label": line.newLineNumber
? this.getTranslation('code-diff.new-line', {
number: line.newLineNumber,
})
: undefined }, (_b = line.newLineNumber) !== null && _b !== void 0 ? _b : ''), h("span", { class: "line-indicator", role: "cell" }, indicator), h("span", { class: "line-content", role: "cell" }, this.renderContent(line))));
}
renderSplitHunkRows(hunk) {
var _a, _b, _c, _d;
const splitRows = buildSplitLines(hunk.lines);
const elements = [];
let i = 0;
while (i < splitRows.length) {
const row = splitRows[i];
const isContext = ((_a = row.left) === null || _a === void 0 ? void 0 : _a.type) === 'context' && ((_b = row.right) === null || _b === void 0 ? void 0 : _b.type) === 'context';
if (isContext) {
elements.push(this.renderSplitRow(row));
i++;
continue;
}
// Collect consecutive changed rows
const blockRows = [];
while (i < splitRows.length) {
const r = splitRows[i];
const rIsContext = ((_c = r.left) === null || _c === void 0 ? void 0 : _c.type) === 'context' && ((_d = r.right) === null || _d === void 0 ? void 0 : _d.type) === 'context';
if (rIsContext) {
break;
}
blockRows.push(r);
i++;
}
elements.push(this.renderSplitChangeBlock(blockRows));
}
return elements;
}
renderSplitChangeBlock(rows) {
const removedContent = extractRemovedContentFromSplit(rows);
return (h("div", { class: "change-group" }, rows.map((row) => this.renderSplitRow(row)), removedContent && this.renderBlockCopyButton(removedContent)));
}
renderSplitRow(row) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const leftType = (_b = (_a = row.left) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'empty';
const rightType = (_d = (_c = row.right) === null || _c === void 0 ? void 0 : _c.type) !== null && _d !== void 0 ? _d : 'empty';
const oldLineLabel = ((_e = row.left) === null || _e === void 0 ? void 0 : _e.oldLineNumber)
? this.getTranslation('code-diff.old-line', {
number: row.left.oldLineNumber,
})
: undefined;
const newLineLabel = ((_f = row.right) === null || _f === void 0 ? void 0 : _f.newLineNumber)
? this.getTranslation('code-diff.new-line', {
number: row.right.newLineNumber,
})
: undefined;
return (h("div", { class: "diff-line diff-line--split", role: "row" }, h("span", { class: "line-number line-number--old", role: "cell", "aria-label": oldLineLabel }, (_h = (_g = row.left) === null || _g === void 0 ? void 0 : _g.oldLineNumber) !== null && _h !== void 0 ? _h : ''), h("span", { class: `split-content split-content--left split-content--${leftType}`, role: "cell" }, row.left ? this.renderContent(row.left) : ''), h("span", { class: "line-number line-number--new", role: "cell", "aria-label": newLineLabel }, (_k = (_j = row.right) === null || _j === void 0 ? void 0 : _j.newLineNumber) !== null && _k !== void 0 ? _k : ''), h("span", { class: `split-content split-content--right split-content--${rightType}`, role: "cell" }, row.right ? this.renderContent(row.right) : '')));
}
renderBlockCopyButton(removedContent) {
return (h("limel-icon-button", { class: "change-group__copy", elevated: true, label: this.getTranslation('code-diff.copy-change'), icon: "copy", onClick: () => this.copyToClipboard(removedContent) }));
}
renderContent(line) {
this.isRenderingRemovedLine =
line.type === 'removed' && this.searchTerm.length > 0;
if (!line.segments || line.segments.length === 0) {
return this.renderSyntaxTokens(line.content);
}
return line.segments.map((segment) => this.renderSegment(segment, line.type));
}
renderSegment(segment, lineType) {
const content = this.renderSyntaxTokens(segment.value);
if (segment.type === 'equal') {
return content;
}
const segmentClass = lineType === 'removed' ? 'segment--removed' : 'segment--added';
return h("mark", { class: segmentClass }, content);
}
renderSyntaxTokens(text) {
const tokens = tokenize(text, this.language);
if (tokens.length === 1 && tokens[0].type === 'plain') {
return this.renderSearchableText(text);
}
return tokens.map((token) => this.renderSyntaxToken(token));
}
renderSyntaxToken(token) {
const text = this.renderSearchableText(token.value);
if (token.type === 'plain') {
return text;
}
return h("span", { class: `syntax--${token.type}` }, text);
}
renderSearchableText(text) {
if (!this.isRenderingRemovedLine || !this.activeSearchRegex) {
return text;
}
const parts = text.split(this.activeSearchRegex);
if (parts.length === 1) {
return text;
}
return parts.map((part, i) => {
// Odd indices are the captured matches from split
if (i % 2 === 0) {
return part;
}
const matchIndex = this.searchMatchCounter++;
const isCurrent = matchIndex === this.currentMatchIndex;
const cls = {
'search-match': true,
'search-match--current': isCurrent,
};
return (h("mark", { key: `match-${matchIndex}`, class: cls }, part));
});
}
renderCollapsedRow(count, hunkIndex) {
return (h("div", { class: "diff-line diff-line--collapsed", role: "row" }, h("button", { class: "expand-button", type: "button", onClick: () => this.expandHunk(hunkIndex), "aria-label": this.getTranslation('code-diff.show-hidden-lines', {
count,
}) }, this.getTranslation('code-diff.hidden-lines', { count }))));
}
renderCollapsedAfterRow(count) {
return (h("div", { class: "diff-line diff-line--collapsed", role: "row" }, h("button", { class: "expand-button", type: "button", onClick: () => this.expandAfter(), "aria-label": this.getTranslation('code-diff.show-hidden-lines', {
count,
}) }, this.getTranslation('code-diff.hidden-lines', { count }))));
}
expandHunk(hunkIndex) {
const hunks = [...this.diffResult.hunks];
const hunk = hunks[hunkIndex];
const prevHunkEnd = hunkIndex > 0
? hunks[hunkIndex - 1].startIndex +
hunks[hunkIndex - 1].lines.length
: 0;
const hiddenLines = this.diffResult.allLines.slice(prevHunkEnd, hunk.startIndex);
hunks[hunkIndex] = Object.assign(Object.assign({}, hunk), { lines: [...hiddenLines, ...hunk.lines], collapsedBefore: undefined, startIndex: prevHunkEnd });
this.diffResult = Object.assign(Object.assign({}, this.diffResult), { hunks });
this.liveAnnouncement = this.getTranslation('code-diff.expanded-lines');
}
expandAfter() {
const hunks = [...this.diffResult.hunks];
const lastIndex = hunks.length - 1;
const lastHunk = hunks[lastIndex];
const lastHunkEnd = lastHunk.startIndex + lastHunk.lines.length;
const hiddenLines = this.diffResult.allLines.slice(lastHunkEnd);
hunks[lastIndex] = Object.assign(Object.assign({}, lastHunk), { lines: [...lastHunk.lines, ...hiddenLines] });
this.diffResult = Object.assign(Object.assign({}, this.diffResult), { hunks, collapsedAfter: undefined });
this.liveAnnouncement = this.getTranslation('code-diff.expanded-lines-end');
}
getTranslation(key, params) {
return translate.get(key, this.translationLanguage, params);
}
static get is() { return "limel-code-diff"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() {
return {
"$": ["code-diff.scss"]
};
}
static get styleUrls() {
return {
"$": ["code-diff.css"]
};
}
static get properties() {
return {
"oldValue": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string | object",
"resolved": "object | string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The \"before\" value to compare.\nCan be a string or an object (which will be serialized to JSON)."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "old-value",
"defaultValue": "''"
},
"newValue": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string | object",
"resolved": "object | string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The \"after\" value to compare.\nCan be a string or an object (which will be serialized to JSON)."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "new-value",
"defaultValue": "''"
},
"oldHeading": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Heading for the original (before) version, displayed in the diff header.\nDefaults to `\"Original\"`, localized via `translationLanguage`."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "old-heading"
},
"newHeading": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Heading for the modified (after) version, displayed in the diff header.\nDefaults to `\"Modified\"`, localized via `translationLanguage`."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "new-heading"
},
"layout": {
"type": "string",
"mutable": false,
"complexType": {
"original": "'unified' | 'split'",
"resolved": "\"split\" | \"unified\"",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The layout of the diff view.\n- `unified` \u2014 single column with interleaved additions and removals\n- `split` \u2014 side-by-side comparison with old on left, new on right"
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "layout",
"defaultValue": "'unified'"
},
"contextLines": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Number of unchanged context lines to display around each change."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "context-lines",
"defaultValue": "3"
},
"lineWrapping": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "When `true`, long lines are wrapped instead of causing\nhorizontal scrolling. Useful when comparing prose or\nconfig files with long values."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "line-wrapping",
"defaultValue": "true"
},
"language": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Language for syntax highlighting.\nCurrently supports `\"json\"`. When set, code tokens are\ncolorized (strings, numbers, keys, etc.) alongside the\ndiff highlighting."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "language"
},
"reformatJson": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "When `true`, JSON values are parsed, keys are sorted,\nand indentation is normalized before diffing.\nThis eliminates noise from formatting or key-order differences."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "reformat-json",
"defaultValue": "false"
},
"translationLanguage": {
"type": "string",
"mutable": false,
"complexType": {
"original": "Languages",
"resolved": "\"da\" | \"de\" | \"en\" | \"fi\" | \"fr\" | \"nb\" | \"nl\" | \"no\" | \"sv\"",
"references": {
"Languages": {
"location": "import",
"path": "../date-picker/date.types",
"id": "src/components/date-picker/date.types.ts::Languages",
"referenceLocation": "Languages"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Defines the language for translations.\nWill translate all visible labels and announcements."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "translation-language",
"defaultValue": "'en'"
}
};
}
static get states() {
return {
"diffResult": {},
"liveAnnouncement": {},
"copyState": {},
"searchVisible": {},
"searchTerm": {},
"currentMatchIndex": {}
};
}
static get elementRef() { return "host"; }
static get watchers() {
return [{
"propName": "oldValue",
"methodName": "watchInputs"
}, {
"propName": "newValue",
"methodName": "watchInputs"
}, {
"propName": "contextLines",
"methodName": "watchInputs"
}, {
"propName": "reformatJson",
"methodName": "watchInputs"
}, {
"propName": "layout",
"methodName": "watchInputs"
}];
}
}