-
-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add web component renderer (#35)
- Loading branch information
Showing
9 changed files
with
438 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import type { ShikiMagicMove } from '../../../src/web-component/ShikiMagicMove' | ||
import type { RendererFactory, RendererFactoryResult, RendererUpdatePayload } from './types' | ||
|
||
import '../../../src/web-component/ShikiMagicMove' | ||
|
||
export const createRendererWebComponent: RendererFactory = (options): RendererFactoryResult => { | ||
let app: ShikiMagicMove | undefined | ||
|
||
return { | ||
mount: async (element, payload) => { | ||
app = document.createElement('shiki-magic-move') | ||
|
||
app.addEventListener('onStart', options.onStart ?? (() => {})) | ||
app.addEventListener('onEnd', options.onEnd ?? (() => {})) | ||
|
||
Object.keys(payload).forEach((prop) => { | ||
// eslint-disable-next-line ts/ban-ts-comment | ||
// @ts-ignore | ||
app[prop as keyof RendererUpdatePayload] = payload[prop as keyof RendererUpdatePayload] | ||
}) | ||
|
||
element.appendChild(app) | ||
|
||
// eslint-disable-next-line no-console | ||
console.log('Web Component renderer mounted') | ||
}, | ||
|
||
update: (payload) => { | ||
Object.keys(payload).forEach((prop) => { | ||
// eslint-disable-next-line ts/ban-ts-comment | ||
// @ts-ignore | ||
app[prop as keyof RendererUpdatePayload] = payload[prop as keyof RendererUpdatePayload] | ||
}) | ||
}, | ||
|
||
dispose: () => { | ||
if (!app) | ||
return | ||
app.remove() | ||
app = undefined | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './web-component/index' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import type { HighlighterCore } from 'shiki/core' | ||
import type { KeyedTokensInfo, MagicMoveDifferOptions, MagicMoveRenderOptions } from '../types' | ||
import type { ShikiMagicMoveRenderer } from './ShikiMagicMoveRenderer' | ||
import { codeToKeyedTokens, createMagicMoveMachine } from '../core' | ||
|
||
import './ShikiMagicMoveRenderer' | ||
|
||
export class ShikiMagicMove extends HTMLElement { | ||
private _highlighter!: HighlighterCore | ||
private _lang!: string | ||
private _theme!: string | ||
private _code!: string | ||
private _class?: string | ||
private _options?: MagicMoveRenderOptions & MagicMoveDifferOptions | ||
|
||
get highlighter() { | ||
return this._highlighter | ||
} | ||
|
||
set highlighter(value: HighlighterCore) { | ||
this._highlighter = value | ||
this.propertyChangedCallback() | ||
} | ||
|
||
get lang() { | ||
return this._lang | ||
} | ||
|
||
set lang(value: string) { | ||
this._lang = value | ||
this.propertyChangedCallback() | ||
} | ||
|
||
get theme() { | ||
return this._theme | ||
} | ||
|
||
set theme(value: string) { | ||
this._theme = value | ||
this.propertyChangedCallback() | ||
} | ||
|
||
get code() { | ||
return this._code | ||
} | ||
|
||
set code(value: string) { | ||
this._code = value | ||
this.propertyChangedCallback() | ||
} | ||
|
||
get class() { | ||
return this._class | ||
} | ||
|
||
set class(value: string | undefined) { | ||
this._class = value | ||
this.propertyChangedCallback() | ||
} | ||
|
||
get options() { | ||
return this._options | ||
} | ||
|
||
set options(value: MagicMoveRenderOptions & MagicMoveDifferOptions | undefined) { | ||
this._options = value | ||
this.propertyChangedCallback() | ||
} | ||
|
||
private machine?: { | ||
commit: (code: string, override?: MagicMoveDifferOptions) => { | ||
current: KeyedTokensInfo | ||
previous: KeyedTokensInfo | ||
} | ||
} | ||
|
||
private renderer?: ShikiMagicMoveRenderer | ||
|
||
private result?: { current: KeyedTokensInfo, previous: KeyedTokensInfo } | ||
|
||
private batchUpdate = false | ||
|
||
private hasUpdated = false | ||
|
||
constructor() { | ||
super() | ||
} | ||
|
||
connectedCallback() { | ||
this.renderer = document.createElement('shiki-magic-move-renderer') as ShikiMagicMoveRenderer | ||
this.renderer.addEventListener('onStart', () => this.dispatchEvent(new CustomEvent('onStart'))) | ||
this.renderer.addEventListener('onEnd', () => this.dispatchEvent(new CustomEvent('onEnd'))) | ||
|
||
this.machine = createMagicMoveMachine( | ||
code => codeToKeyedTokens( | ||
this.highlighter, | ||
code, | ||
{ | ||
lang: this.lang, | ||
theme: this.theme, | ||
}, | ||
this.options?.lineNumbers, | ||
), | ||
this.options, | ||
) | ||
|
||
this.updateRenderer() | ||
|
||
this.appendChild(this.renderer) | ||
} | ||
|
||
propertyChangedCallback() { | ||
if (!this.batchUpdate) { | ||
this.batchUpdate = true | ||
|
||
setTimeout(() => { | ||
this.batchUpdate = false | ||
|
||
if (!this.hasUpdated) { | ||
this.hasUpdated = true | ||
return | ||
} | ||
|
||
this.updateRenderer() | ||
}, 0) | ||
} | ||
} | ||
|
||
updateRenderer() { | ||
if (this.machine && this.renderer) { | ||
this.result = this.machine.commit(this.code, this.options) | ||
|
||
this.renderer.tokens = this.result!.current | ||
this.renderer.previous = this.result!.previous | ||
this.renderer.options = this.options | ||
this.renderer.class = this.class ?? '' | ||
} | ||
} | ||
} | ||
|
||
customElements.define('shiki-magic-move', ShikiMagicMove) | ||
|
||
declare global { | ||
interface HTMLElementTagNameMap { | ||
'shiki-magic-move': ShikiMagicMove | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import type { KeyedTokensInfo, MagicMoveDifferOptions, MagicMoveRenderOptions } from '../types' | ||
import type { ShikiMagicMoveRenderer } from './ShikiMagicMoveRenderer' | ||
|
||
import { syncTokenKeys, toKeyedTokens } from '../core' | ||
|
||
export class ShikiMagicMovePrecompiled extends HTMLElement { | ||
private _steps: KeyedTokensInfo[] = [] | ||
private _step: number = 0 | ||
private _animated: boolean = true | ||
private _options?: MagicMoveRenderOptions & MagicMoveDifferOptions | ||
|
||
get steps() { | ||
return this._steps | ||
} | ||
|
||
set steps(value: KeyedTokensInfo[]) { | ||
this._steps = value | ||
this.propertyChangedCallback() | ||
} | ||
|
||
get step() { | ||
return this._step | ||
} | ||
|
||
set step(value: number) { | ||
this._step = value | ||
this.propertyChangedCallback() | ||
} | ||
|
||
get animated() { | ||
return this._animated | ||
} | ||
|
||
set animated(value: boolean) { | ||
this._animated = value | ||
this.propertyChangedCallback() | ||
} | ||
|
||
get options(): MagicMoveRenderOptions & MagicMoveDifferOptions | undefined { | ||
return this._options | ||
} | ||
|
||
set options(value: MagicMoveRenderOptions & MagicMoveDifferOptions) { | ||
this._options = value | ||
this.propertyChangedCallback() | ||
} | ||
|
||
private renderer?: ShikiMagicMoveRenderer | ||
|
||
private previous: KeyedTokensInfo = toKeyedTokens('', []) | ||
|
||
private batchUpdate = false | ||
|
||
private hasUpdated = false | ||
|
||
constructor() { | ||
super() | ||
} | ||
|
||
connectedCallback() { | ||
this.renderer = document.createElement('shiki-magic-move-renderer') as ShikiMagicMoveRenderer | ||
this.renderer.addEventListener('onStart', () => this.dispatchEvent(new CustomEvent('onStart'))) | ||
this.renderer.addEventListener('onEnd', () => this.dispatchEvent(new CustomEvent('onEnd'))) | ||
|
||
this.updateRenderer() | ||
|
||
this.appendChild(this.renderer) | ||
} | ||
|
||
propertyChangedCallback() { | ||
if (!this.batchUpdate) { | ||
this.batchUpdate = true | ||
|
||
setTimeout(() => { | ||
this.batchUpdate = false | ||
|
||
if (!this.hasUpdated) { | ||
this.hasUpdated = true | ||
return | ||
} | ||
|
||
this.updateRenderer() | ||
}, 0) | ||
} | ||
} | ||
|
||
updateRenderer() { | ||
const result = syncTokenKeys( | ||
this.previous, | ||
this.steps[Math.min(this.step, this.steps.length - 1)], | ||
this.options, | ||
) | ||
this.previous = result.to | ||
|
||
this.renderer!.tokens = result.to | ||
this.renderer!.previous = result.from | ||
this.renderer!.options = this.options | ||
this.renderer!.animated = this.animated | ||
} | ||
} | ||
|
||
customElements.define('shiki-magic-move-precompiled', ShikiMagicMovePrecompiled) | ||
|
||
declare global { | ||
interface HTMLElementTagNameMap { | ||
'shiki-magic-move-precompiled': ShikiMagicMovePrecompiled | ||
} | ||
} |
Oops, something went wrong.