@ng-native/components 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/keyboard-dock.d.ts +5 -2
- package/dist/keyboard-dock.js +5 -2
- package/dist/keyboard-dock.js.map +1 -1
- package/dist/markdown-blocks.d.ts +18 -1
- package/dist/markdown-blocks.js +112 -4
- package/dist/markdown-blocks.js.map +1 -1
- package/dist/markdown-columns.d.ts +24 -0
- package/dist/markdown-columns.js +58 -0
- package/dist/markdown-columns.js.map +1 -0
- package/dist/markdown-stream.d.ts +31 -0
- package/dist/markdown-stream.js +282 -0
- package/dist/markdown-stream.js.map +1 -0
- package/dist/markdown.d.ts +59 -7
- package/dist/markdown.js +201 -57
- package/dist/markdown.js.map +1 -1
- package/dist/text-input.d.ts +6 -1
- package/dist/text-input.js +8 -2
- package/dist/text-input.js.map +1 -1
- package/dist/written-props.js +1 -0
- package/dist/written-props.js.map +1 -1
- package/package.json +3 -3
package/dist/keyboard-dock.d.ts
CHANGED
|
@@ -43,8 +43,11 @@ export declare class KeyboardGestureArea {
|
|
|
43
43
|
* content above with it.
|
|
44
44
|
*
|
|
45
45
|
* Without it, and on Android, the bar is padded by as much of the keyboard as covers it, which it
|
|
46
|
-
* learns as the keyboard starts to move;
|
|
47
|
-
* puts it clear, and then
|
|
46
|
+
* learns as the keyboard starts to move; on Android it jumps into place then, rather than moving
|
|
47
|
+
* with the keyboard. A window the system resized for the keyboard already puts it clear, and then
|
|
48
|
+
* it is not padded at all. Android does not use the controller: a dock in a form sheet would be
|
|
49
|
+
* lifted twice, by the sheet and by the keyboard, and the controller adds the system bars as
|
|
50
|
+
* margins around the app's content unless it is told the app is edge-to-edge.
|
|
48
51
|
*
|
|
49
52
|
* `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own
|
|
50
53
|
* place, for content above that keeps clear of it with padding or an inset rather than moving.
|
package/dist/keyboard-dock.js
CHANGED
|
@@ -83,8 +83,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
|
|
|
83
83
|
* content above with it.
|
|
84
84
|
*
|
|
85
85
|
* Without it, and on Android, the bar is padded by as much of the keyboard as covers it, which it
|
|
86
|
-
* learns as the keyboard starts to move;
|
|
87
|
-
* puts it clear, and then
|
|
86
|
+
* learns as the keyboard starts to move; on Android it jumps into place then, rather than moving
|
|
87
|
+
* with the keyboard. A window the system resized for the keyboard already puts it clear, and then
|
|
88
|
+
* it is not padded at all. Android does not use the controller: a dock in a form sheet would be
|
|
89
|
+
* lifted twice, by the sheet and by the keyboard, and the controller adds the system bars as
|
|
90
|
+
* margins around the app's content unless it is told the app is edge-to-edge.
|
|
88
91
|
*
|
|
89
92
|
* `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own
|
|
90
93
|
* place, for content above that keeps clear of it with padding or an inset rather than moving.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard-dock.js","sourceRoot":"","sources":["../src/keyboard-dock.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n DestroyRef,\n Directive,\n ElementRef,\n EnvironmentInjector,\n InjectionToken,\n afterRenderEffect,\n computed,\n contentChild,\n effect,\n inject,\n input,\n signal,\n untracked,\n type Provider,\n} from '@angular/core';\nimport { Keyboard, SCREEN_IN_FRONT, SafeArea } from '@ng-native/device';\nimport {\n claimHost,\n nativePlatform,\n registerViewName,\n type HostNode,\n type ScrollDrive,\n type ScrollRange,\n} from '@ng-native/fabric';\nimport { TemplateSlot } from './template-slot.ts';\nimport { TextInput } from './text-input.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\n\n/**\n * Whether react-native-keyboard-controller is installed, which is what lets a `<keyboard-dock>`\n * on iOS follow the keyboard frame by frame. See `provideKeyboardController`.\n */\nexport const KEYBOARD_CONTROLLER = new InjectionToken<boolean>('KEYBOARD_CONTROLLER', {\n providedIn: 'root',\n factory: () => false,\n});\n\n/**\n * Say that react-native-keyboard-controller is installed, for `<keyboard-dock>` to use on iOS.\n * Nothing is imported from it: the dock drives its native views directly.\n */\nexport function provideKeyboardController(): Provider {\n return { provide: KEYBOARD_CONTROLLER, useValue: true };\n}\n\n/** What carries the keyboard's height, frame by frame, in react-native-keyboard-controller. */\nconst KEYBOARD_FEED = {\n events: ['onKeyboardMove', 'onKeyboardMoveInteractive', 'onKeyboardMoveEnd'],\n path: ['height'],\n};\n\n/** Far enough that no keyboard is taller; native interpolation takes numbers, not infinity. */\nconst FAR = 10_000;\n\n/** A native view the dock renders for itself, out of the way of the layout. */\nconst OUT_OF_FLOW = { position: 'absolute', width: 0, height: 0 };\n\ntype KeyboardMoveEvent = { nativeEvent?: { height?: number } };\n\n/** react-native-keyboard-controller's view that reports the keyboard. See `KeyboardDock`. */\n@Directive({\n selector: 'keyboard-controller-view',\n host: { '[enabled]': 'true' },\n})\nexport class KeyboardControllerView {\n constructor() {\n registerViewName('keyboard-controller-view', 'KeyboardControllerView');\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * react-native-keyboard-controller's drag area. On iOS it registers `offset` for the field with\n * `textInputNativeID`, so an interactive dismissal takes the keyboard from that far above it.\n */\n@Directive({\n selector: 'keyboard-gesture-area',\n host: { '[offset]': 'offset()', '[textInputNativeID]': 'textInputNativeID()' },\n})\nexport class KeyboardGestureArea {\n readonly offset = input<number>();\n readonly textInputNativeID = input<string>();\n\n constructor() {\n registerViewName('keyboard-gesture-area', 'KeyboardGestureArea');\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * A bar that sits on the keyboard: a chat's composer, a comment box, a row of formatting keys.\n * At the bottom of its parent, clear of the home indicator or navigation bar, while the keyboard\n * is down; on top of it while it is up.\n *\n * On iOS with react-native-keyboard-controller (`provideKeyboardController()`), the bar is moved\n * by the keyboard's height on the native side, frame by frame, the way UIKit carries an input\n * accessory: as the keyboard rises and falls, and while a finger drags it down through a scroll\n * view with `keyboardDismissMode=\"interactive\"`, where iOS reports no keyboard frames to\n * JavaScript at all. The drag takes the keyboard from the top of the bar rather than the top of\n * the keyboard, as Messages does, and a short one springs it back up. `keyboardLift` moves\n * content above with it.\n *\n * Without it, and on Android, the bar is padded by as much of the keyboard as covers it, which it\n * learns as the keyboard starts to move; a window the system resized for the keyboard already\n * puts it clear, and then it is not padded at all.\n *\n * `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own\n * place, for content above that keeps clear of it with padding or an inset rather than moving.\n */\n@Component({\n selector: 'keyboard-dock',\n exportAs: 'keyboardDock',\n imports: [KeyboardControllerView, KeyboardGestureArea, TemplateSlot, View],\n // One slot, stamped in whichever branch applies: two `<ng-content>`s of the same selector would\n // be given the content at compile time, whichever branch renders.\n template: `\n <ng-template #content><ng-content /></ng-template>\n @if (lifts) {\n <keyboard-controller-view\n [style]=\"outOfFlow\"\n (keyboardMoveStart)=\"onKeyboardMove($event)\"\n (keyboardMoveEnd)=\"onKeyboardMove($event)\"\n />\n <keyboard-gesture-area\n [style]=\"outOfFlow\"\n [offset]=\"barHeight()\"\n [textInputNativeID]=\"inputID()\"\n />\n <view\n collapsable=\"false\"\n [style.backgroundColor]=\"backgroundColor()\"\n (layout)=\"onBarLayout($event)\"\n ><ng-container [templateSlot]=\"content\" [templateSlotContext]=\"none\"\n /></view>\n } @else {\n <ng-container [templateSlot]=\"content\" [templateSlotContext]=\"none\" />\n }\n `,\n host: {\n '[style.paddingBottom]': 'padding()',\n '[style.backgroundColor]': 'backgroundColor()',\n '(layout)': 'onLayout()',\n },\n})\nexport class KeyboardDock extends ViewBase {\n /** The bar's background, under the inset it sits above as well as the bar itself. */\n readonly backgroundColor = input<string>();\n\n private readonly keyboard = inject(Keyboard);\n private readonly safeArea = inject(SafeArea);\n protected readonly none = {};\n protected readonly outOfFlow = OUT_OF_FLOW;\n\n /** iOS with the controller: native moves the bar. Read once: neither changes under an app. */\n protected readonly lifts = nativePlatform() === 'ios' && inject(KEYBOARD_CONTROLLER);\n\n private readonly field = contentChild(TextInput, { descendants: true, read: ElementRef });\n private readonly fieldInput = contentChild(TextInput, { descendants: true });\n\n /** Where the dock's bottom edge is in the window, which the keyboard is compared to. */\n private readonly bottomEdge = signal<number | null>(null);\n /** The bar's own height, measured, which the drag that lowers the keyboard starts above. */\n protected readonly barHeight = signal(0);\n /** The keyboard's height as the controller last reported it, excluding the drag area. */\n private readonly controlled = signal(0);\n\n /** The inset under the bar while the keyboard is down: the home indicator or navigation bar. */\n private readonly inset = computed(() => this.safeArea.insets().bottom);\n\n readonly covered = computed(() =>\n this.lifts ? Math.max(0, this.controlled() - this.inset()) : 0,\n );\n\n protected readonly padding = computed(() => {\n if (this.lifts) return this.inset();\n const { height, screenY } = this.keyboard.metrics();\n if (height === 0) return this.inset();\n const edge = this.bottomEdge();\n if (edge === null || screenY === undefined) return height;\n return Math.max(0, edge - screenY);\n });\n\n /**\n * The field the drag area belongs to, by `nativeID`, which the controller finds it by. A field\n * without one is given one.\n */\n protected readonly inputID = computed(() => {\n const input = this.fieldInput();\n const node = this.field()?.nativeElement as HostNode | undefined;\n if (!input || !node) return undefined;\n const own = input.nativeID() ?? input.id();\n if (own) return own;\n const id = `keyboard-dock-${++docks}`;\n untracked(() => this.engine.setProp(node, 'nativeID', id));\n return id;\n });\n\n /** The bar, and anything `keyboardLift` moves, rises by the keyboard less the inset under it. */\n readonly liftRange = computed<ScrollRange>(() => {\n const inset = this.inset();\n return { input: [inset, inset + FAR], output: [0, -FAR] };\n });\n\n private source: HostNode | null = null;\n\n private readonly inFront = inject(SCREEN_IN_FRONT);\n\n constructor() {\n super();\n if (this.lifts) this.liftBar();\n // A root effect, because a covered screen's views are detached from change detection, and a\n // view's own effects wait for it.\n const front = effect(\n () => {\n const inFront = this.inFront();\n untracked(() => {\n if (!inFront && this.keyboard.height() > 0) this.keyboard.dismiss();\n });\n },\n { injector: inject(EnvironmentInjector), manualCleanup: true },\n );\n inject(DestroyRef).onDestroy(() => front.destroy());\n }\n\n /**\n * Move a view with the keyboard on the native side, as the bar is moved: content above the bar\n * that should rise with it. Null when native does not move the bar. See `keyboardLift`.\n */\n lift(view: HostNode, shift: number | null = null): ScrollDrive | null {\n if (!this.lifts) return null;\n const source = this.controllerView();\n const range = this.liftRange();\n return this.engine.driveByEvent(view, source, KEYBOARD_FEED, 'translateY', range, [], shift);\n }\n\n private liftBar(): void {\n let drive: ScrollDrive | null = null;\n afterRenderEffect(() => {\n const range = this.liftRange();\n untracked(() => {\n if (drive) return drive.update(range);\n const bar = this.barView();\n if (bar) drive = this.lift(bar);\n });\n });\n inject(DestroyRef).onDestroy(() => drive?.stop());\n }\n\n /** The controller's view, the one whose events carry the keyboard. */\n private controllerView(): HostNode {\n this.source ??= this.node.children.find((child) => child.kind === 'element') ?? this.node;\n return this.source;\n }\n\n /** The view holding the content, the last of the dock's own. */\n private barView(): HostNode | undefined {\n return this.node.children.filter((child) => child.kind === 'element').at(-1);\n }\n\n protected onKeyboardMove(event: unknown): void {\n const height = (event as KeyboardMoveEvent).nativeEvent?.height;\n if (height !== undefined) this.controlled.set(height);\n }\n\n protected onBarLayout(event: { nativeEvent?: { layout?: { height?: number } } }): void {\n const height = event.nativeEvent?.layout?.height;\n if (height !== undefined) this.barHeight.set(height);\n }\n\n protected onLayout(): void {\n if (this.lifts) return;\n this.engine.measure(this.node, (frame) => this.bottomEdge.set(frame.y + frame.height));\n }\n}\n\nlet docks = 0;\n\n/**\n * Moves its view with a `<keyboard-dock>`'s bar: `<view [keyboardLift]=\"dock\">` around a chat's\n * transcript keeps the newest message on top of the bar as the keyboard rises, falls and is\n * dragged. Put it inside a view that clips, so what rises past the top is hidden rather than\n * drawn over what is above. Does nothing where native does not move the bar.\n *\n * Once the keyboard has moved, the view is padded by as much of it as covers the view, and moved\n * back down by the same amount, which leaves it where it was on screen: since iOS 26 a scroll view\n * the keyboard overlaps in layout draws a frosted edge effect over the part it thinks is covered,\n * and a transform does not change what it thinks.\n */\n@Directive({\n selector: '[keyboardLift]',\n host: { '[style.paddingBottom]': 'padding()' },\n})\nexport class KeyboardLift {\n readonly dock = input.required<KeyboardDock>({ alias: 'keyboardLift' });\n\n protected readonly padding = computed(() => {\n const covered = this.dock().covered();\n return covered > 0 ? covered : undefined;\n });\n\n constructor() {\n const view = inject(ElementRef).nativeElement as HostNode;\n let drive: ScrollDrive | null = null;\n afterRenderEffect(() => {\n const dock = this.dock();\n const range = dock.liftRange();\n const covered = dock.covered();\n untracked(() => {\n if (!drive) drive = dock.lift(view, covered);\n drive?.update(range);\n drive?.shift(covered);\n });\n });\n inject(DestroyRef).onDestroy(() => drive?.stop());\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"keyboard-dock.js","sourceRoot":"","sources":["../src/keyboard-dock.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n DestroyRef,\n Directive,\n ElementRef,\n EnvironmentInjector,\n InjectionToken,\n afterRenderEffect,\n computed,\n contentChild,\n effect,\n inject,\n input,\n signal,\n untracked,\n type Provider,\n} from '@angular/core';\nimport { Keyboard, SCREEN_IN_FRONT, SafeArea } from '@ng-native/device';\nimport {\n claimHost,\n nativePlatform,\n registerViewName,\n type HostNode,\n type ScrollDrive,\n type ScrollRange,\n} from '@ng-native/fabric';\nimport { TemplateSlot } from './template-slot.ts';\nimport { TextInput } from './text-input.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\n\n/**\n * Whether react-native-keyboard-controller is installed, which is what lets a `<keyboard-dock>`\n * on iOS follow the keyboard frame by frame. See `provideKeyboardController`.\n */\nexport const KEYBOARD_CONTROLLER = new InjectionToken<boolean>('KEYBOARD_CONTROLLER', {\n providedIn: 'root',\n factory: () => false,\n});\n\n/**\n * Say that react-native-keyboard-controller is installed, for `<keyboard-dock>` to use on iOS.\n * Nothing is imported from it: the dock drives its native views directly.\n */\nexport function provideKeyboardController(): Provider {\n return { provide: KEYBOARD_CONTROLLER, useValue: true };\n}\n\n/** What carries the keyboard's height, frame by frame, in react-native-keyboard-controller. */\nconst KEYBOARD_FEED = {\n events: ['onKeyboardMove', 'onKeyboardMoveInteractive', 'onKeyboardMoveEnd'],\n path: ['height'],\n};\n\n/** Far enough that no keyboard is taller; native interpolation takes numbers, not infinity. */\nconst FAR = 10_000;\n\n/** A native view the dock renders for itself, out of the way of the layout. */\nconst OUT_OF_FLOW = { position: 'absolute', width: 0, height: 0 };\n\ntype KeyboardMoveEvent = { nativeEvent?: { height?: number } };\n\n/** react-native-keyboard-controller's view that reports the keyboard. See `KeyboardDock`. */\n@Directive({\n selector: 'keyboard-controller-view',\n host: { '[enabled]': 'true' },\n})\nexport class KeyboardControllerView {\n constructor() {\n registerViewName('keyboard-controller-view', 'KeyboardControllerView');\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * react-native-keyboard-controller's drag area. On iOS it registers `offset` for the field with\n * `textInputNativeID`, so an interactive dismissal takes the keyboard from that far above it.\n */\n@Directive({\n selector: 'keyboard-gesture-area',\n host: { '[offset]': 'offset()', '[textInputNativeID]': 'textInputNativeID()' },\n})\nexport class KeyboardGestureArea {\n readonly offset = input<number>();\n readonly textInputNativeID = input<string>();\n\n constructor() {\n registerViewName('keyboard-gesture-area', 'KeyboardGestureArea');\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * A bar that sits on the keyboard: a chat's composer, a comment box, a row of formatting keys.\n * At the bottom of its parent, clear of the home indicator or navigation bar, while the keyboard\n * is down; on top of it while it is up.\n *\n * On iOS with react-native-keyboard-controller (`provideKeyboardController()`), the bar is moved\n * by the keyboard's height on the native side, frame by frame, the way UIKit carries an input\n * accessory: as the keyboard rises and falls, and while a finger drags it down through a scroll\n * view with `keyboardDismissMode=\"interactive\"`, where iOS reports no keyboard frames to\n * JavaScript at all. The drag takes the keyboard from the top of the bar rather than the top of\n * the keyboard, as Messages does, and a short one springs it back up. `keyboardLift` moves\n * content above with it.\n *\n * Without it, and on Android, the bar is padded by as much of the keyboard as covers it, which it\n * learns as the keyboard starts to move; on Android it jumps into place then, rather than moving\n * with the keyboard. A window the system resized for the keyboard already puts it clear, and then\n * it is not padded at all. Android does not use the controller: a dock in a form sheet would be\n * lifted twice, by the sheet and by the keyboard, and the controller adds the system bars as\n * margins around the app's content unless it is told the app is edge-to-edge.\n *\n * `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own\n * place, for content above that keeps clear of it with padding or an inset rather than moving.\n */\n@Component({\n selector: 'keyboard-dock',\n exportAs: 'keyboardDock',\n imports: [KeyboardControllerView, KeyboardGestureArea, TemplateSlot, View],\n // One slot, stamped in whichever branch applies: two `<ng-content>`s of the same selector would\n // be given the content at compile time, whichever branch renders.\n template: `\n <ng-template #content><ng-content /></ng-template>\n @if (lifts) {\n <keyboard-controller-view\n [style]=\"outOfFlow\"\n (keyboardMoveStart)=\"onKeyboardMove($event)\"\n (keyboardMoveEnd)=\"onKeyboardMove($event)\"\n />\n <keyboard-gesture-area\n [style]=\"outOfFlow\"\n [offset]=\"barHeight()\"\n [textInputNativeID]=\"inputID()\"\n />\n <view\n collapsable=\"false\"\n [style.backgroundColor]=\"backgroundColor()\"\n (layout)=\"onBarLayout($event)\"\n ><ng-container [templateSlot]=\"content\" [templateSlotContext]=\"none\"\n /></view>\n } @else {\n <ng-container [templateSlot]=\"content\" [templateSlotContext]=\"none\" />\n }\n `,\n host: {\n '[style.paddingBottom]': 'padding()',\n '[style.backgroundColor]': 'backgroundColor()',\n '(layout)': 'onLayout()',\n },\n})\nexport class KeyboardDock extends ViewBase {\n /** The bar's background, under the inset it sits above as well as the bar itself. */\n readonly backgroundColor = input<string>();\n\n private readonly keyboard = inject(Keyboard);\n private readonly safeArea = inject(SafeArea);\n protected readonly none = {};\n protected readonly outOfFlow = OUT_OF_FLOW;\n\n /** iOS with the controller: native moves the bar. Read once: neither changes under an app. */\n protected readonly lifts = nativePlatform() === 'ios' && inject(KEYBOARD_CONTROLLER);\n\n private readonly field = contentChild(TextInput, { descendants: true, read: ElementRef });\n private readonly fieldInput = contentChild(TextInput, { descendants: true });\n\n /** Where the dock's bottom edge is in the window, which the keyboard is compared to. */\n private readonly bottomEdge = signal<number | null>(null);\n /** The bar's own height, measured, which the drag that lowers the keyboard starts above. */\n protected readonly barHeight = signal(0);\n /** The keyboard's height as the controller last reported it, excluding the drag area. */\n private readonly controlled = signal(0);\n\n /** The inset under the bar while the keyboard is down: the home indicator or navigation bar. */\n private readonly inset = computed(() => this.safeArea.insets().bottom);\n\n readonly covered = computed(() =>\n this.lifts ? Math.max(0, this.controlled() - this.inset()) : 0,\n );\n\n protected readonly padding = computed(() => {\n if (this.lifts) return this.inset();\n const { height, screenY } = this.keyboard.metrics();\n if (height === 0) return this.inset();\n const edge = this.bottomEdge();\n if (edge === null || screenY === undefined) return height;\n return Math.max(0, edge - screenY);\n });\n\n /**\n * The field the drag area belongs to, by `nativeID`, which the controller finds it by. A field\n * without one is given one.\n */\n protected readonly inputID = computed(() => {\n const input = this.fieldInput();\n const node = this.field()?.nativeElement as HostNode | undefined;\n if (!input || !node) return undefined;\n const own = input.nativeID() ?? input.id();\n if (own) return own;\n const id = `keyboard-dock-${++docks}`;\n untracked(() => this.engine.setProp(node, 'nativeID', id));\n return id;\n });\n\n /** The bar, and anything `keyboardLift` moves, rises by the keyboard less the inset under it. */\n readonly liftRange = computed<ScrollRange>(() => {\n const inset = this.inset();\n return { input: [inset, inset + FAR], output: [0, -FAR] };\n });\n\n private source: HostNode | null = null;\n\n private readonly inFront = inject(SCREEN_IN_FRONT);\n\n constructor() {\n super();\n if (this.lifts) this.liftBar();\n // A root effect, because a covered screen's views are detached from change detection, and a\n // view's own effects wait for it.\n const front = effect(\n () => {\n const inFront = this.inFront();\n untracked(() => {\n if (!inFront && this.keyboard.height() > 0) this.keyboard.dismiss();\n });\n },\n { injector: inject(EnvironmentInjector), manualCleanup: true },\n );\n inject(DestroyRef).onDestroy(() => front.destroy());\n }\n\n /**\n * Move a view with the keyboard on the native side, as the bar is moved: content above the bar\n * that should rise with it. Null when native does not move the bar. See `keyboardLift`.\n */\n lift(view: HostNode, shift: number | null = null): ScrollDrive | null {\n if (!this.lifts) return null;\n const source = this.controllerView();\n const range = this.liftRange();\n return this.engine.driveByEvent(view, source, KEYBOARD_FEED, 'translateY', range, [], shift);\n }\n\n private liftBar(): void {\n let drive: ScrollDrive | null = null;\n afterRenderEffect(() => {\n const range = this.liftRange();\n untracked(() => {\n if (drive) return drive.update(range);\n const bar = this.barView();\n if (bar) drive = this.lift(bar);\n });\n });\n inject(DestroyRef).onDestroy(() => drive?.stop());\n }\n\n /** The controller's view, the one whose events carry the keyboard. */\n private controllerView(): HostNode {\n this.source ??= this.node.children.find((child) => child.kind === 'element') ?? this.node;\n return this.source;\n }\n\n /** The view holding the content, the last of the dock's own. */\n private barView(): HostNode | undefined {\n return this.node.children.filter((child) => child.kind === 'element').at(-1);\n }\n\n protected onKeyboardMove(event: unknown): void {\n const height = (event as KeyboardMoveEvent).nativeEvent?.height;\n if (height !== undefined) this.controlled.set(height);\n }\n\n protected onBarLayout(event: { nativeEvent?: { layout?: { height?: number } } }): void {\n const height = event.nativeEvent?.layout?.height;\n if (height !== undefined) this.barHeight.set(height);\n }\n\n protected onLayout(): void {\n if (this.lifts) return;\n this.engine.measure(this.node, (frame) => this.bottomEdge.set(frame.y + frame.height));\n }\n}\n\nlet docks = 0;\n\n/**\n * Moves its view with a `<keyboard-dock>`'s bar: `<view [keyboardLift]=\"dock\">` around a chat's\n * transcript keeps the newest message on top of the bar as the keyboard rises, falls and is\n * dragged. Put it inside a view that clips, so what rises past the top is hidden rather than\n * drawn over what is above. Does nothing where native does not move the bar.\n *\n * Once the keyboard has moved, the view is padded by as much of it as covers the view, and moved\n * back down by the same amount, which leaves it where it was on screen: since iOS 26 a scroll view\n * the keyboard overlaps in layout draws a frosted edge effect over the part it thinks is covered,\n * and a transform does not change what it thinks.\n */\n@Directive({\n selector: '[keyboardLift]',\n host: { '[style.paddingBottom]': 'padding()' },\n})\nexport class KeyboardLift {\n readonly dock = input.required<KeyboardDock>({ alias: 'keyboardLift' });\n\n protected readonly padding = computed(() => {\n const covered = this.dock().covered();\n return covered > 0 ? covered : undefined;\n });\n\n constructor() {\n const view = inject(ElementRef).nativeElement as HostNode;\n let drive: ScrollDrive | null = null;\n afterRenderEffect(() => {\n const dock = this.dock();\n const range = dock.liftRange();\n const covered = dock.covered();\n untracked(() => {\n if (!drive) drive = dock.lift(view, covered);\n drive?.update(range);\n drive?.shift(covered);\n });\n });\n inject(DestroyRef).onDestroy(() => drive?.stop());\n }\n}\n"]}
|
|
@@ -73,7 +73,24 @@ export declare function schemeOf(url: string): string | null;
|
|
|
73
73
|
export declare function linkTarget(href: string): string | null;
|
|
74
74
|
/** Whether `href` is absolute in a scheme a link may be followed to, and so is opened on press. */
|
|
75
75
|
export declare function opensOnPress(href: string): boolean;
|
|
76
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* An image's address if it may be loaded: `http`, `https`, or a picture written into the document
|
|
78
|
+
* as a `data:` url of a raster type. A pasted screenshot arrives that way from a web editor, and
|
|
79
|
+
* a native image only decodes such a picture: nothing in it runs. Any other `data:` url, an SVG
|
|
80
|
+
* or HTML among them, is refused, as is every other scheme.
|
|
81
|
+
*/
|
|
77
82
|
export declare function imageSource(href: string): string | null;
|
|
78
83
|
/** The blocks a document of `tokens` is drawn as. */
|
|
79
84
|
export declare function markdownBlocks(tokens: readonly Token[]): MarkdownBlock[];
|
|
85
|
+
/**
|
|
86
|
+
* A key for `block` that stays the same while it is drawn: a block kept from the document before
|
|
87
|
+
* keeps its own, and one {@link shareBlocks} made in place of an edited one takes that one's.
|
|
88
|
+
*/
|
|
89
|
+
export declare function blockKey(block: MarkdownBlock): number;
|
|
90
|
+
/**
|
|
91
|
+
* `next`, with every part of it that equals the same part of `previous` replaced by that part, so
|
|
92
|
+
* a block that did not change is the object drawn already and is not drawn again. The two are
|
|
93
|
+
* matched from each end, so a block added or removed in the middle leaves those either side of it
|
|
94
|
+
* kept; what is left between is matched by position.
|
|
95
|
+
*/
|
|
96
|
+
export declare function shareBlocks(previous: readonly MarkdownBlock[] | undefined, next: readonly MarkdownBlock[]): readonly MarkdownBlock[];
|
package/dist/markdown-blocks.js
CHANGED
|
@@ -55,12 +55,27 @@ export function opensOnPress(href) {
|
|
|
55
55
|
const scheme = schemeOf(href.trim());
|
|
56
56
|
return scheme !== null && LINK_SCHEMES.has(scheme);
|
|
57
57
|
}
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* An image's address if it may be loaded: `http`, `https`, or a picture written into the document
|
|
60
|
+
* as a `data:` url of a raster type. A pasted screenshot arrives that way from a web editor, and
|
|
61
|
+
* a native image only decodes such a picture: nothing in it runs. Any other `data:` url, an SVG
|
|
62
|
+
* or HTML among them, is refused, as is every other scheme.
|
|
63
|
+
*/
|
|
59
64
|
export function imageSource(href) {
|
|
60
65
|
const source = href.trim();
|
|
61
66
|
const scheme = schemeOf(source);
|
|
62
|
-
|
|
67
|
+
if (scheme === null)
|
|
68
|
+
return null;
|
|
69
|
+
if (IMAGE_SCHEMES.has(scheme))
|
|
70
|
+
return source;
|
|
71
|
+
if (scheme !== 'data')
|
|
72
|
+
return null;
|
|
73
|
+
// A native loader may not decode a url with whitespace in it, so a wrapped payload is joined.
|
|
74
|
+
const joined = source.replace(/\s+/g, '');
|
|
75
|
+
return DATA_IMAGE.test(joined) ? joined : null;
|
|
63
76
|
}
|
|
77
|
+
/** A base64 `data:` url of a raster picture: PNG, JPEG, GIF or WebP. */
|
|
78
|
+
const DATA_IMAGE = /^data:image\/(?:png|jpe?g|gif|webp);base64,[a-z0-9+/=]+$/i;
|
|
64
79
|
/** A link's or an image's target with its entities decoded, or null when it is not a string. */
|
|
65
80
|
function hrefOf(token) {
|
|
66
81
|
const href = token.href;
|
|
@@ -76,7 +91,8 @@ const BLOCKS = {
|
|
|
76
91
|
heading: (token) => {
|
|
77
92
|
const heading = token;
|
|
78
93
|
const depth = Math.min(Math.max(heading.depth, 1), 6);
|
|
79
|
-
|
|
94
|
+
const children = trimmedEnd(inlines(heading.tokens));
|
|
95
|
+
return [{ kind: 'heading', element: HEADINGS[depth - 1], children }];
|
|
80
96
|
},
|
|
81
97
|
paragraph: (token) => paragraphs(token.tokens),
|
|
82
98
|
text: (token) => {
|
|
@@ -143,7 +159,7 @@ function paragraphs(tokens) {
|
|
|
143
159
|
let run = [];
|
|
144
160
|
const flush = () => {
|
|
145
161
|
if (run.some((one) => one.kind !== 'text' || one.text.trim())) {
|
|
146
|
-
out.push({ kind: 'paragraph', children: run });
|
|
162
|
+
out.push({ kind: 'paragraph', children: trimmedEnd(run) });
|
|
147
163
|
}
|
|
148
164
|
run = [];
|
|
149
165
|
};
|
|
@@ -160,6 +176,21 @@ function paragraphs(tokens) {
|
|
|
160
176
|
flush();
|
|
161
177
|
return out;
|
|
162
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Runs without the whitespace at their end, which a paragraph does not draw. The engine works out
|
|
181
|
+
* that trim when a run changes, not when a run arrives after it, so a run that ended a streaming
|
|
182
|
+
* paragraph and stops ending it would otherwise keep its trimmed space: `Run ` and then a code
|
|
183
|
+
* span would read as one word.
|
|
184
|
+
*/
|
|
185
|
+
function trimmedEnd(runs) {
|
|
186
|
+
const last = runs.at(-1);
|
|
187
|
+
if (last?.kind !== 'text')
|
|
188
|
+
return runs;
|
|
189
|
+
const text = last.text.replace(/[ \t\n\r\f]+$/, '');
|
|
190
|
+
if (text === last.text)
|
|
191
|
+
return runs;
|
|
192
|
+
return text ? [...runs.slice(0, -1), { kind: 'text', text }] : runs.slice(0, -1);
|
|
193
|
+
}
|
|
163
194
|
function inlines(tokens) {
|
|
164
195
|
return (tokens ?? []).flatMap(inline);
|
|
165
196
|
}
|
|
@@ -209,4 +240,81 @@ function inline(token) {
|
|
|
209
240
|
return draw(token);
|
|
210
241
|
return token.raw ? [{ kind: 'text', text: token.raw }] : [];
|
|
211
242
|
}
|
|
243
|
+
const keys = new WeakMap();
|
|
244
|
+
let lastKey = 0;
|
|
245
|
+
/**
|
|
246
|
+
* A key for `block` that stays the same while it is drawn: a block kept from the document before
|
|
247
|
+
* keeps its own, and one {@link shareBlocks} made in place of an edited one takes that one's.
|
|
248
|
+
*/
|
|
249
|
+
export function blockKey(block) {
|
|
250
|
+
let key = keys.get(block);
|
|
251
|
+
if (key === undefined)
|
|
252
|
+
keys.set(block, (key = ++lastKey));
|
|
253
|
+
return key;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* `next`, with every part of it that equals the same part of `previous` replaced by that part, so
|
|
257
|
+
* a block that did not change is the object drawn already and is not drawn again. The two are
|
|
258
|
+
* matched from each end, so a block added or removed in the middle leaves those either side of it
|
|
259
|
+
* kept; what is left between is matched by position.
|
|
260
|
+
*/
|
|
261
|
+
export function shareBlocks(previous, next) {
|
|
262
|
+
return previous ? share(previous, next) : next;
|
|
263
|
+
}
|
|
264
|
+
function share(previous, next) {
|
|
265
|
+
if (previous === next || typeof previous !== 'object' || typeof next !== 'object')
|
|
266
|
+
return next;
|
|
267
|
+
if (previous === null || next === null)
|
|
268
|
+
return next;
|
|
269
|
+
if (Array.isArray(previous) !== Array.isArray(next))
|
|
270
|
+
return next;
|
|
271
|
+
if (Array.isArray(previous))
|
|
272
|
+
return shareArray(previous, next);
|
|
273
|
+
return shareObject(previous, next);
|
|
274
|
+
}
|
|
275
|
+
function shareArray(previous, next) {
|
|
276
|
+
const out = next.slice();
|
|
277
|
+
let start = 0;
|
|
278
|
+
while (start < out.length && start < previous.length) {
|
|
279
|
+
const kept = share(previous[start], out[start]);
|
|
280
|
+
if (kept !== previous[start])
|
|
281
|
+
break;
|
|
282
|
+
out[start++] = kept;
|
|
283
|
+
}
|
|
284
|
+
let end = 0;
|
|
285
|
+
while (end < out.length - start && end < previous.length - start) {
|
|
286
|
+
const at = out.length - 1 - end;
|
|
287
|
+
const was = previous.length - 1 - end;
|
|
288
|
+
const kept = share(previous[was], out[at]);
|
|
289
|
+
if (kept !== previous[was])
|
|
290
|
+
break;
|
|
291
|
+
out[at] = kept;
|
|
292
|
+
end++;
|
|
293
|
+
}
|
|
294
|
+
for (let i = start; i < out.length - end && i < previous.length - end; i++) {
|
|
295
|
+
out[i] = share(previous[i], out[i]);
|
|
296
|
+
}
|
|
297
|
+
const same = out.length === previous.length && out.every((one, i) => one === previous[i]);
|
|
298
|
+
return same ? previous : out;
|
|
299
|
+
}
|
|
300
|
+
function shareObject(previous, next) {
|
|
301
|
+
if (previous['kind'] !== next['kind'])
|
|
302
|
+
return next;
|
|
303
|
+
const names = Object.keys(next);
|
|
304
|
+
let same = names.length === Object.keys(previous).length;
|
|
305
|
+
const out = {};
|
|
306
|
+
for (const name of names) {
|
|
307
|
+
const was = previous[name];
|
|
308
|
+
const kept = Object.hasOwn(previous, name) ? share(was, next[name]) : next[name];
|
|
309
|
+
if (kept !== was)
|
|
310
|
+
same = false;
|
|
311
|
+
out[name] = kept;
|
|
312
|
+
}
|
|
313
|
+
if (same)
|
|
314
|
+
return previous;
|
|
315
|
+
const key = keys.get(previous);
|
|
316
|
+
if (key !== undefined)
|
|
317
|
+
keys.set(out, key);
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
212
320
|
//# sourceMappingURL=markdown-blocks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-blocks.js","sourceRoot":"","sources":["../src/markdown-blocks.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Marked's tokens, turned into the blocks `<markdown>` draws: the entities decoded, the links and\n * images held to the schemes they may use, raw HTML made plain text, and an image lifted out of\n * its paragraph, since a `<text>` cannot hold one.\n *\n * Plain functions with no Angular in them, so the policy is the same wherever the tokens came from:\n * `marked.lexer` on the device, or a build step that lexed them ahead of time.\n */\nimport type { Token, Tokens } from 'marked';\nimport { HTML_ENTITIES } from './html-entities.ts';\n\n/** The elements a document is drawn with, by the HTML names the Markdown would have produced. */\nexport type MarkdownElement =\n | 'h1'\n | 'h2'\n | 'h3'\n | 'h4'\n | 'h5'\n | 'h6'\n | 'p'\n | 'blockquote'\n | 'ul'\n | 'ol'\n | 'li'\n | 'marker'\n | 'pre'\n | 'code'\n | 'strong'\n | 'em'\n | 'del'\n | 'a'\n | 'hr'\n | 'img'\n | 'table'\n | 'tr'\n | 'th'\n | 'td';\n\nexport type MarkdownInline =\n | { readonly kind: 'text'; readonly text: string }\n | { readonly kind: 'strong' | 'em' | 'del'; readonly children: readonly MarkdownInline[] }\n | { readonly kind: 'code'; readonly text: string }\n | {\n readonly kind: 'link';\n readonly href: string;\n readonly title: string | null;\n readonly children: readonly MarkdownInline[];\n };\n\nexport interface MarkdownListItem {\n readonly marker: string;\n readonly children: readonly MarkdownBlock[];\n}\n\nexport type MarkdownBlock =\n | {\n readonly kind: 'heading';\n readonly element: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';\n readonly children: readonly MarkdownInline[];\n }\n | { readonly kind: 'paragraph'; readonly children: readonly MarkdownInline[] }\n | { readonly kind: 'image'; readonly src: string; readonly alt: string }\n | { readonly kind: 'blockquote'; readonly children: readonly MarkdownBlock[] }\n | {\n readonly kind: 'list';\n readonly element: 'ul' | 'ol';\n readonly items: readonly MarkdownListItem[];\n }\n | { readonly kind: 'code'; readonly text: string }\n | { readonly kind: 'hr' }\n | {\n readonly kind: 'table';\n readonly header: readonly (readonly MarkdownInline[])[];\n readonly rows: readonly (readonly (readonly MarkdownInline[])[])[];\n };\n\nlet namedEntities: Map<string, string> | undefined;\n\n/** The character HTML names `name`, from its full table, built on the first name looked up. */\nfunction namedEntity(name: string): string | undefined {\n if (!namedEntities) {\n namedEntities = new Map();\n const pairs = HTML_ENTITIES.split(' ');\n for (let i = 0; i + 1 < pairs.length; i += 2) namedEntities.set(pairs[i]!, pairs[i + 1]!);\n }\n return namedEntities.get(name);\n}\n\nconst ENTITY = /&(?:#(\\d{1,7})|#[xX]([0-9a-fA-F]{1,6})|([a-zA-Z][a-zA-Z0-9]{1,31}));/g;\n\n/**\n * The text an HTML reader would show for `text`, in one pass so `&lt;` is `<` and not `<`.\n * A reference to no character, or to one outside Unicode, is the replacement character, as HTML\n * makes it; a name HTML does not have stays as written.\n */\nexport function decodeEntities(text: string): string {\n if (!text.includes('&')) return text;\n return text.replace(ENTITY, (whole, decimal?: string, hex?: string, name?: string) => {\n if (name !== undefined) return namedEntity(name) ?? whole;\n const code = decimal !== undefined ? Number(decimal) : parseInt(hex!, 16);\n const valid = code > 0 && code <= 0x10ffff && (code < 0xd800 || code > 0xdfff);\n return valid ? String.fromCodePoint(code) : '�';\n });\n}\n\n/**\n * What a URL parser ignores while it reads a scheme: the C0 controls and the space, and the C1\n * controls and DEL beside them. `java\\tscript:` is `javascript:` to the thing that opens it.\n */\nconst IGNORED_IN_SCHEME = /[\\u0000- \\u007f-\\u009f]/g;\nconst SCHEME = /^([a-z][a-z0-9+.-]*):/;\n\n/** The scheme `url` would be opened with, lowercased, or null for a relative one. */\nexport function schemeOf(url: string): string | null {\n return SCHEME.exec(url.replace(IGNORED_IN_SCHEME, '').toLowerCase())?.[1] ?? null;\n}\n\nconst LINK_SCHEMES = new Set(['http', 'https', 'mailto', 'tel']);\nconst IMAGE_SCHEMES = new Set(['http', 'https']);\n\n/**\n * A link's target if it may be followed: `http`, `https`, `mailto`, `tel`, or relative, which is\n * the app's to resolve. Anything else, `javascript:`, `data:`, `vbscript:` and `file:` among\n * them, is null, and the link is drawn as its text. The scheme is read after the trim, so\n * whitespace the trim strips cannot hide one.\n */\nexport function linkTarget(href: string): string | null {\n const target = href.trim();\n const scheme = schemeOf(target);\n return scheme === null || LINK_SCHEMES.has(scheme) ? target : null;\n}\n\n/** Whether `href` is absolute in a scheme a link may be followed to, and so is opened on press. */\nexport function opensOnPress(href: string): boolean {\n const scheme = schemeOf(href.trim());\n return scheme !== null && LINK_SCHEMES.has(scheme);\n}\n\n/** An image's address if it may be loaded: `http` or `https` only. */\nexport function imageSource(href: string): string | null {\n const source = href.trim();\n const scheme = schemeOf(source);\n return scheme !== null && IMAGE_SCHEMES.has(scheme) ? source : null;\n}\n\n/** A link's or an image's target with its entities decoded, or null when it is not a string. */\nfunction hrefOf(token: Token): string | null {\n const href: unknown = (token as Tokens.Link).href;\n return typeof href === 'string' ? decodeEntities(href) : null;\n}\n\nconst HEADINGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;\n\n/** The blocks a document of `tokens` is drawn as. */\nexport function markdownBlocks(tokens: readonly Token[]): MarkdownBlock[] {\n return tokens.flatMap(block);\n}\n\n/** What each kind of block token is drawn as. A kind not here is drawn as its source text. */\nconst BLOCKS: Readonly<Record<string, (token: Token) => MarkdownBlock[]>> = {\n heading: (token) => {\n const heading = token as Tokens.Heading;\n const depth = Math.min(Math.max(heading.depth, 1), 6);\n return [{ kind: 'heading', element: HEADINGS[depth - 1]!, children: inlines(heading.tokens) }];\n },\n paragraph: (token) => paragraphs((token as Tokens.Paragraph).tokens),\n text: (token) => {\n const text = token as Tokens.Text;\n return text.tokens\n ? paragraphs(text.tokens)\n : [{ kind: 'paragraph', children: [plain(softBreaks(text.text))] }];\n },\n blockquote: (token) => [\n { kind: 'blockquote', children: markdownBlocks((token as Tokens.Blockquote).tokens) },\n ],\n list: (token) => [list(token as Tokens.List)],\n code: (token) => [{ kind: 'code', text: indented((token as Tokens.Code).text) }],\n hr: () => [{ kind: 'hr' }],\n table: (token) => {\n const table = token as Tokens.Table;\n return [\n {\n kind: 'table',\n header: table.header.map((cell) => inlines(cell.tokens)),\n rows: table.rows.map((row) => row.map((cell) => inlines(cell.tokens))),\n },\n ];\n },\n html: (token) => literal((token as Tokens.HTML).text),\n space: () => [],\n def: () => [],\n checkbox: () => [],\n};\n\nfunction block(token: Token): MarkdownBlock[] {\n const draw = Object.hasOwn(BLOCKS, token.type) ? BLOCKS[token.type]! : null;\n return draw ? draw(token) : literal(token.raw ?? '');\n}\n\n/**\n * A code block's text with its first line's indent made of no-break spaces: a paragraph loses the\n * spaces at its start, and a block's indent is part of the code.\n */\nfunction indented(text: string): string {\n return text.replace(/^ +/, (spaces) => '\\u00a0'.repeat(spaces.length));\n}\n\n/** Text drawn as written, which is what raw HTML and a token nothing here knows become. */\nfunction literal(text: string): MarkdownBlock[] {\n const trimmed = text.replace(/\\n+$/, '');\n return trimmed.trim() ? [{ kind: 'paragraph', children: [{ kind: 'text', text: trimmed }] }] : [];\n}\n\nfunction list(token: Tokens.List): MarkdownBlock {\n const start = typeof token.start === 'number' ? token.start : 1;\n return {\n kind: 'list',\n element: token.ordered ? 'ol' : 'ul',\n items: token.items.map((item, index) => ({\n marker: item.task ? (item.checked ? '☑' : '☐') : token.ordered ? `${start + index}.` : '•',\n children: markdownBlocks(item.tokens),\n })),\n };\n}\n\n/**\n * A paragraph's runs, split around the images at its top level: an image is a block of its own,\n * and the text either side of it a paragraph each. An image whose address is refused, and one\n * inside a link or an emphasis, is its alt text instead.\n */\nfunction paragraphs(tokens: readonly Token[]): MarkdownBlock[] {\n const out: MarkdownBlock[] = [];\n let run: MarkdownInline[] = [];\n const flush = () => {\n if (run.some((one) => one.kind !== 'text' || one.text.trim())) {\n out.push({ kind: 'paragraph', children: run });\n }\n run = [];\n };\n for (const token of tokens) {\n const href = token.type === 'image' ? hrefOf(token) : null;\n const src = href === null ? null : imageSource(href);\n if (src === null) {\n run.push(...inline(token));\n continue;\n }\n flush();\n out.push({ kind: 'image', src, alt: decodeEntities((token as Tokens.Image).text) });\n }\n flush();\n return out;\n}\n\nfunction inlines(tokens: readonly Token[] | undefined): MarkdownInline[] {\n return (tokens ?? []).flatMap(inline);\n}\n\n/**\n * Text with each line break the source wrapped it at made a space, as CommonMark reads a soft\n * break, with the spaces around it. A hard break is a `br` token of its own, and stays one.\n */\nfunction softBreaks(text: string): string {\n return text.replace(/ *\\n */g, ' ');\n}\n\nfunction plain(text: string): MarkdownInline {\n return { kind: 'text', text: decodeEntities(text) };\n}\n\nfunction emphasis(token: Token): MarkdownInline[] {\n const kind = token.type as 'strong' | 'em' | 'del';\n return [{ kind, children: inlines((token as Tokens.Strong).tokens) }];\n}\n\n/** What each kind of inline token is drawn as. A kind not here is drawn as its source text. */\nconst INLINES: Readonly<Record<string, (token: Token) => MarkdownInline[]>> = {\n text: (token) => {\n const text = token as Tokens.Text;\n return text.tokens ? inlines(text.tokens) : [plain(softBreaks(text.text))];\n },\n escape: (token) => [plain((token as Tokens.Escape).text)],\n strong: emphasis,\n em: emphasis,\n del: emphasis,\n codespan: (token) => [{ kind: 'code', text: (token as Tokens.Codespan).text }],\n br: () => [{ kind: 'text', text: '\\n' }],\n link: (token) => {\n const link = token as Tokens.Link;\n const children = inlines(link.tokens);\n const decoded = hrefOf(link);\n const href = decoded === null ? null : linkTarget(decoded);\n if (href === null) return children;\n const title = link.title ? decodeEntities(link.title) : null;\n return [{ kind: 'link', href, title, children }];\n },\n image: (token) => [plain((token as Tokens.Image).text)],\n html: (token) => [{ kind: 'text', text: (token as Tokens.HTML).text }],\n checkbox: () => [],\n};\n\nfunction inline(token: Token): MarkdownInline[] {\n const draw = Object.hasOwn(INLINES, token.type) ? INLINES[token.type]! : null;\n if (draw) return draw(token);\n return token.raw ? [{ kind: 'text', text: token.raw }] : [];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"markdown-blocks.js","sourceRoot":"","sources":["../src/markdown-blocks.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Marked's tokens, turned into the blocks `<markdown>` draws: the entities decoded, the links and\n * images held to the schemes they may use, raw HTML made plain text, and an image lifted out of\n * its paragraph, since a `<text>` cannot hold one.\n *\n * Plain functions with no Angular in them, so the policy is the same wherever the tokens came from:\n * `marked.lexer` on the device, or a build step that lexed them ahead of time.\n */\nimport type { Token, Tokens } from 'marked';\nimport { HTML_ENTITIES } from './html-entities.ts';\n\n/** The elements a document is drawn with, by the HTML names the Markdown would have produced. */\nexport type MarkdownElement =\n | 'h1'\n | 'h2'\n | 'h3'\n | 'h4'\n | 'h5'\n | 'h6'\n | 'p'\n | 'blockquote'\n | 'ul'\n | 'ol'\n | 'li'\n | 'marker'\n | 'pre'\n | 'code'\n | 'strong'\n | 'em'\n | 'del'\n | 'a'\n | 'hr'\n | 'img'\n | 'table'\n | 'tr'\n | 'th'\n | 'td';\n\nexport type MarkdownInline =\n | { readonly kind: 'text'; readonly text: string }\n | { readonly kind: 'strong' | 'em' | 'del'; readonly children: readonly MarkdownInline[] }\n | { readonly kind: 'code'; readonly text: string }\n | {\n readonly kind: 'link';\n readonly href: string;\n readonly title: string | null;\n readonly children: readonly MarkdownInline[];\n };\n\nexport interface MarkdownListItem {\n readonly marker: string;\n readonly children: readonly MarkdownBlock[];\n}\n\nexport type MarkdownBlock =\n | {\n readonly kind: 'heading';\n readonly element: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';\n readonly children: readonly MarkdownInline[];\n }\n | { readonly kind: 'paragraph'; readonly children: readonly MarkdownInline[] }\n | { readonly kind: 'image'; readonly src: string; readonly alt: string }\n | { readonly kind: 'blockquote'; readonly children: readonly MarkdownBlock[] }\n | {\n readonly kind: 'list';\n readonly element: 'ul' | 'ol';\n readonly items: readonly MarkdownListItem[];\n }\n | { readonly kind: 'code'; readonly text: string }\n | { readonly kind: 'hr' }\n | {\n readonly kind: 'table';\n readonly header: readonly (readonly MarkdownInline[])[];\n readonly rows: readonly (readonly (readonly MarkdownInline[])[])[];\n };\n\nlet namedEntities: Map<string, string> | undefined;\n\n/** The character HTML names `name`, from its full table, built on the first name looked up. */\nfunction namedEntity(name: string): string | undefined {\n if (!namedEntities) {\n namedEntities = new Map();\n const pairs = HTML_ENTITIES.split(' ');\n for (let i = 0; i + 1 < pairs.length; i += 2) namedEntities.set(pairs[i]!, pairs[i + 1]!);\n }\n return namedEntities.get(name);\n}\n\nconst ENTITY = /&(?:#(\\d{1,7})|#[xX]([0-9a-fA-F]{1,6})|([a-zA-Z][a-zA-Z0-9]{1,31}));/g;\n\n/**\n * The text an HTML reader would show for `text`, in one pass so `&lt;` is `<` and not `<`.\n * A reference to no character, or to one outside Unicode, is the replacement character, as HTML\n * makes it; a name HTML does not have stays as written.\n */\nexport function decodeEntities(text: string): string {\n if (!text.includes('&')) return text;\n return text.replace(ENTITY, (whole, decimal?: string, hex?: string, name?: string) => {\n if (name !== undefined) return namedEntity(name) ?? whole;\n const code = decimal !== undefined ? Number(decimal) : parseInt(hex!, 16);\n const valid = code > 0 && code <= 0x10ffff && (code < 0xd800 || code > 0xdfff);\n return valid ? String.fromCodePoint(code) : '�';\n });\n}\n\n/**\n * What a URL parser ignores while it reads a scheme: the C0 controls and the space, and the C1\n * controls and DEL beside them. `java\\tscript:` is `javascript:` to the thing that opens it.\n */\nconst IGNORED_IN_SCHEME = /[\\u0000- \\u007f-\\u009f]/g;\nconst SCHEME = /^([a-z][a-z0-9+.-]*):/;\n\n/** The scheme `url` would be opened with, lowercased, or null for a relative one. */\nexport function schemeOf(url: string): string | null {\n return SCHEME.exec(url.replace(IGNORED_IN_SCHEME, '').toLowerCase())?.[1] ?? null;\n}\n\nconst LINK_SCHEMES = new Set(['http', 'https', 'mailto', 'tel']);\nconst IMAGE_SCHEMES = new Set(['http', 'https']);\n\n/**\n * A link's target if it may be followed: `http`, `https`, `mailto`, `tel`, or relative, which is\n * the app's to resolve. Anything else, `javascript:`, `data:`, `vbscript:` and `file:` among\n * them, is null, and the link is drawn as its text. The scheme is read after the trim, so\n * whitespace the trim strips cannot hide one.\n */\nexport function linkTarget(href: string): string | null {\n const target = href.trim();\n const scheme = schemeOf(target);\n return scheme === null || LINK_SCHEMES.has(scheme) ? target : null;\n}\n\n/** Whether `href` is absolute in a scheme a link may be followed to, and so is opened on press. */\nexport function opensOnPress(href: string): boolean {\n const scheme = schemeOf(href.trim());\n return scheme !== null && LINK_SCHEMES.has(scheme);\n}\n\n/**\n * An image's address if it may be loaded: `http`, `https`, or a picture written into the document\n * as a `data:` url of a raster type. A pasted screenshot arrives that way from a web editor, and\n * a native image only decodes such a picture: nothing in it runs. Any other `data:` url, an SVG\n * or HTML among them, is refused, as is every other scheme.\n */\nexport function imageSource(href: string): string | null {\n const source = href.trim();\n const scheme = schemeOf(source);\n if (scheme === null) return null;\n if (IMAGE_SCHEMES.has(scheme)) return source;\n if (scheme !== 'data') return null;\n // A native loader may not decode a url with whitespace in it, so a wrapped payload is joined.\n const joined = source.replace(/\\s+/g, '');\n return DATA_IMAGE.test(joined) ? joined : null;\n}\n\n/** A base64 `data:` url of a raster picture: PNG, JPEG, GIF or WebP. */\nconst DATA_IMAGE = /^data:image\\/(?:png|jpe?g|gif|webp);base64,[a-z0-9+/=]+$/i;\n\n/** A link's or an image's target with its entities decoded, or null when it is not a string. */\nfunction hrefOf(token: Token): string | null {\n const href: unknown = (token as Tokens.Link).href;\n return typeof href === 'string' ? decodeEntities(href) : null;\n}\n\nconst HEADINGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;\n\n/** The blocks a document of `tokens` is drawn as. */\nexport function markdownBlocks(tokens: readonly Token[]): MarkdownBlock[] {\n return tokens.flatMap(block);\n}\n\n/** What each kind of block token is drawn as. A kind not here is drawn as its source text. */\nconst BLOCKS: Readonly<Record<string, (token: Token) => MarkdownBlock[]>> = {\n heading: (token) => {\n const heading = token as Tokens.Heading;\n const depth = Math.min(Math.max(heading.depth, 1), 6);\n const children = trimmedEnd(inlines(heading.tokens));\n return [{ kind: 'heading', element: HEADINGS[depth - 1]!, children }];\n },\n paragraph: (token) => paragraphs((token as Tokens.Paragraph).tokens),\n text: (token) => {\n const text = token as Tokens.Text;\n return text.tokens\n ? paragraphs(text.tokens)\n : [{ kind: 'paragraph', children: [plain(softBreaks(text.text))] }];\n },\n blockquote: (token) => [\n { kind: 'blockquote', children: markdownBlocks((token as Tokens.Blockquote).tokens) },\n ],\n list: (token) => [list(token as Tokens.List)],\n code: (token) => [{ kind: 'code', text: indented((token as Tokens.Code).text) }],\n hr: () => [{ kind: 'hr' }],\n table: (token) => {\n const table = token as Tokens.Table;\n return [\n {\n kind: 'table',\n header: table.header.map((cell) => inlines(cell.tokens)),\n rows: table.rows.map((row) => row.map((cell) => inlines(cell.tokens))),\n },\n ];\n },\n html: (token) => literal((token as Tokens.HTML).text),\n space: () => [],\n def: () => [],\n checkbox: () => [],\n};\n\nfunction block(token: Token): MarkdownBlock[] {\n const draw = Object.hasOwn(BLOCKS, token.type) ? BLOCKS[token.type]! : null;\n return draw ? draw(token) : literal(token.raw ?? '');\n}\n\n/**\n * A code block's text with its first line's indent made of no-break spaces: a paragraph loses the\n * spaces at its start, and a block's indent is part of the code.\n */\nfunction indented(text: string): string {\n return text.replace(/^ +/, (spaces) => '\\u00a0'.repeat(spaces.length));\n}\n\n/** Text drawn as written, which is what raw HTML and a token nothing here knows become. */\nfunction literal(text: string): MarkdownBlock[] {\n const trimmed = text.replace(/\\n+$/, '');\n return trimmed.trim() ? [{ kind: 'paragraph', children: [{ kind: 'text', text: trimmed }] }] : [];\n}\n\nfunction list(token: Tokens.List): MarkdownBlock {\n const start = typeof token.start === 'number' ? token.start : 1;\n return {\n kind: 'list',\n element: token.ordered ? 'ol' : 'ul',\n items: token.items.map((item, index) => ({\n marker: item.task ? (item.checked ? '☑' : '☐') : token.ordered ? `${start + index}.` : '•',\n children: markdownBlocks(item.tokens),\n })),\n };\n}\n\n/**\n * A paragraph's runs, split around the images at its top level: an image is a block of its own,\n * and the text either side of it a paragraph each. An image whose address is refused, and one\n * inside a link or an emphasis, is its alt text instead.\n */\nfunction paragraphs(tokens: readonly Token[]): MarkdownBlock[] {\n const out: MarkdownBlock[] = [];\n let run: MarkdownInline[] = [];\n const flush = () => {\n if (run.some((one) => one.kind !== 'text' || one.text.trim())) {\n out.push({ kind: 'paragraph', children: trimmedEnd(run) });\n }\n run = [];\n };\n for (const token of tokens) {\n const href = token.type === 'image' ? hrefOf(token) : null;\n const src = href === null ? null : imageSource(href);\n if (src === null) {\n run.push(...inline(token));\n continue;\n }\n flush();\n out.push({ kind: 'image', src, alt: decodeEntities((token as Tokens.Image).text) });\n }\n flush();\n return out;\n}\n\n/**\n * Runs without the whitespace at their end, which a paragraph does not draw. The engine works out\n * that trim when a run changes, not when a run arrives after it, so a run that ended a streaming\n * paragraph and stops ending it would otherwise keep its trimmed space: `Run ` and then a code\n * span would read as one word.\n */\nfunction trimmedEnd(runs: MarkdownInline[]): MarkdownInline[] {\n const last = runs.at(-1);\n if (last?.kind !== 'text') return runs;\n const text = last.text.replace(/[ \\t\\n\\r\\f]+$/, '');\n if (text === last.text) return runs;\n return text ? [...runs.slice(0, -1), { kind: 'text', text }] : runs.slice(0, -1);\n}\n\nfunction inlines(tokens: readonly Token[] | undefined): MarkdownInline[] {\n return (tokens ?? []).flatMap(inline);\n}\n\n/**\n * Text with each line break the source wrapped it at made a space, as CommonMark reads a soft\n * break, with the spaces around it. A hard break is a `br` token of its own, and stays one.\n */\nfunction softBreaks(text: string): string {\n return text.replace(/ *\\n */g, ' ');\n}\n\nfunction plain(text: string): MarkdownInline {\n return { kind: 'text', text: decodeEntities(text) };\n}\n\nfunction emphasis(token: Token): MarkdownInline[] {\n const kind = token.type as 'strong' | 'em' | 'del';\n return [{ kind, children: inlines((token as Tokens.Strong).tokens) }];\n}\n\n/** What each kind of inline token is drawn as. A kind not here is drawn as its source text. */\nconst INLINES: Readonly<Record<string, (token: Token) => MarkdownInline[]>> = {\n text: (token) => {\n const text = token as Tokens.Text;\n return text.tokens ? inlines(text.tokens) : [plain(softBreaks(text.text))];\n },\n escape: (token) => [plain((token as Tokens.Escape).text)],\n strong: emphasis,\n em: emphasis,\n del: emphasis,\n codespan: (token) => [{ kind: 'code', text: (token as Tokens.Codespan).text }],\n br: () => [{ kind: 'text', text: '\\n' }],\n link: (token) => {\n const link = token as Tokens.Link;\n const children = inlines(link.tokens);\n const decoded = hrefOf(link);\n const href = decoded === null ? null : linkTarget(decoded);\n if (href === null) return children;\n const title = link.title ? decodeEntities(link.title) : null;\n return [{ kind: 'link', href, title, children }];\n },\n image: (token) => [plain((token as Tokens.Image).text)],\n html: (token) => [{ kind: 'text', text: (token as Tokens.HTML).text }],\n checkbox: () => [],\n};\n\nfunction inline(token: Token): MarkdownInline[] {\n const draw = Object.hasOwn(INLINES, token.type) ? INLINES[token.type]! : null;\n if (draw) return draw(token);\n return token.raw ? [{ kind: 'text', text: token.raw }] : [];\n}\n\nconst keys = new WeakMap<object, number>();\nlet lastKey = 0;\n\n/**\n * A key for `block` that stays the same while it is drawn: a block kept from the document before\n * keeps its own, and one {@link shareBlocks} made in place of an edited one takes that one's.\n */\nexport function blockKey(block: MarkdownBlock): number {\n let key = keys.get(block);\n if (key === undefined) keys.set(block, (key = ++lastKey));\n return key;\n}\n\n/**\n * `next`, with every part of it that equals the same part of `previous` replaced by that part, so\n * a block that did not change is the object drawn already and is not drawn again. The two are\n * matched from each end, so a block added or removed in the middle leaves those either side of it\n * kept; what is left between is matched by position.\n */\nexport function shareBlocks(\n previous: readonly MarkdownBlock[] | undefined,\n next: readonly MarkdownBlock[],\n): readonly MarkdownBlock[] {\n return previous ? (share(previous, next) as readonly MarkdownBlock[]) : next;\n}\n\nfunction share(previous: unknown, next: unknown): unknown {\n if (previous === next || typeof previous !== 'object' || typeof next !== 'object') return next;\n if (previous === null || next === null) return next;\n if (Array.isArray(previous) !== Array.isArray(next)) return next;\n if (Array.isArray(previous)) return shareArray(previous, next as unknown[]);\n return shareObject(previous as Record<string, unknown>, next as Record<string, unknown>);\n}\n\nfunction shareArray(previous: readonly unknown[], next: readonly unknown[]): readonly unknown[] {\n const out = next.slice();\n let start = 0;\n while (start < out.length && start < previous.length) {\n const kept = share(previous[start], out[start]);\n if (kept !== previous[start]) break;\n out[start++] = kept;\n }\n let end = 0;\n while (end < out.length - start && end < previous.length - start) {\n const at = out.length - 1 - end;\n const was = previous.length - 1 - end;\n const kept = share(previous[was], out[at]);\n if (kept !== previous[was]) break;\n out[at] = kept;\n end++;\n }\n for (let i = start; i < out.length - end && i < previous.length - end; i++) {\n out[i] = share(previous[i], out[i]);\n }\n const same = out.length === previous.length && out.every((one, i) => one === previous[i]);\n return same ? previous : out;\n}\n\nfunction shareObject(\n previous: Record<string, unknown>,\n next: Record<string, unknown>,\n): Record<string, unknown> {\n if (previous['kind'] !== next['kind']) return next;\n const names = Object.keys(next);\n let same = names.length === Object.keys(previous).length;\n const out: Record<string, unknown> = {};\n for (const name of names) {\n const was = previous[name];\n const kept = Object.hasOwn(previous, name) ? share(was, next[name]) : next[name];\n if (kept !== was) same = false;\n out[name] = kept;\n }\n if (same) return previous;\n const key = keys.get(previous);\n if (key !== undefined) keys.set(out, key);\n return out;\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { LayoutEvent } from './events.ts';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export declare class MarkdownColumns {
|
|
4
|
+
/** The table whose columns are lined up. */
|
|
5
|
+
readonly table: import("@angular/core").InputSignal<{
|
|
6
|
+
readonly kind: "table";
|
|
7
|
+
readonly header: readonly (readonly import("./markdown-blocks.ts").MarkdownInline[])[];
|
|
8
|
+
readonly rows: readonly (readonly (readonly import("./markdown-blocks.ts").MarkdownInline[])[])[];
|
|
9
|
+
}>;
|
|
10
|
+
/**
|
|
11
|
+
* The measured cells and each column's widest. A new table keeps only the cells of the rows
|
|
12
|
+
* and columns it still has, so one that goes and comes back starts unmeasured.
|
|
13
|
+
*/
|
|
14
|
+
private readonly columns;
|
|
15
|
+
/** The least width of a cell in `column`, or null until one of its cells is measured. */
|
|
16
|
+
width(column: number): number | null;
|
|
17
|
+
/**
|
|
18
|
+
* Records the width the text of the cell at `row` (0 for the header) and `column` was laid out
|
|
19
|
+
* at. Only a column whose widest cell narrows is gone over again.
|
|
20
|
+
*/
|
|
21
|
+
measure(row: number, column: number, event: Partial<LayoutEvent>): void;
|
|
22
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<MarkdownColumns, never>;
|
|
23
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<MarkdownColumns, "[markdownColumns]", ["markdownColumns"], { "table": { "alias": "markdownColumns"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The columns of a Markdown table, each as wide as its widest cell.
|
|
3
|
+
*
|
|
4
|
+
* Yoga has no table layout, so a table is rows of cells, and a cell is as wide as its own text.
|
|
5
|
+
* Each cell's text reports its width with `(layout)`; the widest in a column becomes the least
|
|
6
|
+
* width of every cell in it, which lines the column up. The text is measured inside that least
|
|
7
|
+
* width rather than at it, so a cell that narrows, or a row or column that goes, narrows its
|
|
8
|
+
* column again.
|
|
9
|
+
*/
|
|
10
|
+
import { Directive, input, linkedSignal } from '@angular/core';
|
|
11
|
+
import * as i0 from "@angular/core";
|
|
12
|
+
const widest = (column) => column.reduce((most, width) => Math.max(most, width), 0);
|
|
13
|
+
export class MarkdownColumns {
|
|
14
|
+
/** The table whose columns are lined up. */
|
|
15
|
+
table = input.required({ ...(ngDevMode ? { debugName: "table" } : /* istanbul ignore next */ {}), alias: 'markdownColumns' });
|
|
16
|
+
/**
|
|
17
|
+
* The measured cells and each column's widest. A new table keeps only the cells of the rows
|
|
18
|
+
* and columns it still has, so one that goes and comes back starts unmeasured.
|
|
19
|
+
*/
|
|
20
|
+
columns = linkedSignal({ ...(ngDevMode ? { debugName: "columns" } : /* istanbul ignore next */ {}), source: this.table,
|
|
21
|
+
computation: ({ header, rows }, previous) => {
|
|
22
|
+
const cells = header.map((_, column) => (previous?.value.cells[column] ?? []).slice(0, rows.length + 1));
|
|
23
|
+
return { cells, widths: cells.map(widest) };
|
|
24
|
+
} });
|
|
25
|
+
/** The least width of a cell in `column`, or null until one of its cells is measured. */
|
|
26
|
+
width(column) {
|
|
27
|
+
return this.columns().widths[column] || null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Records the width the text of the cell at `row` (0 for the header) and `column` was laid out
|
|
31
|
+
* at. Only a column whose widest cell narrows is gone over again.
|
|
32
|
+
*/
|
|
33
|
+
measure(row, column, event) {
|
|
34
|
+
const width = event.nativeEvent?.layout.width;
|
|
35
|
+
if (width === undefined || row > this.table().rows.length)
|
|
36
|
+
return;
|
|
37
|
+
const { cells, widths } = this.columns();
|
|
38
|
+
const measured = cells[column];
|
|
39
|
+
if (!measured || measured[row] === width)
|
|
40
|
+
return;
|
|
41
|
+
const was = measured[row];
|
|
42
|
+
measured[row] = width;
|
|
43
|
+
const most = widths[column];
|
|
44
|
+
const next = width >= most ? width : was === most ? widest(measured) : most;
|
|
45
|
+
if (next === most)
|
|
46
|
+
return;
|
|
47
|
+
const changed = [...widths];
|
|
48
|
+
changed[column] = next;
|
|
49
|
+
this.columns.set({ cells, widths: changed });
|
|
50
|
+
}
|
|
51
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: MarkdownColumns, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
52
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.2.0", type: MarkdownColumns, isStandalone: true, selector: "[markdownColumns]", inputs: { table: { classPropertyName: "table", publicName: "markdownColumns", isSignal: true, isRequired: true, transformFunction: null } }, exportAs: ["markdownColumns"], ngImport: i0 });
|
|
53
|
+
}
|
|
54
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: MarkdownColumns, decorators: [{
|
|
55
|
+
type: Directive,
|
|
56
|
+
args: [{ selector: '[markdownColumns]', exportAs: 'markdownColumns' }]
|
|
57
|
+
}], propDecorators: { table: [{ type: i0.Input, args: [{ isSignal: true, alias: "markdownColumns", required: true }] }] } });
|
|
58
|
+
//# sourceMappingURL=markdown-columns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-columns.js","sourceRoot":"","sources":["../src/markdown-columns.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;;AAa/D,MAAM,MAAM,GAAG,CAAC,MAAyB,EAAU,EAAE,CACnD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;AAG3D,MAAM,OAAO,eAAe;IAC1B,4CAA4C;IACnC,KAAK,GAAG,KAAK,CAAC,QAAQ,4EAAkB,KAAK,EAAE,iBAAiB,GAAG,CAAC;IAE7E;;;OAGG;IACc,OAAO,GAAG,YAAY,8EACrC,MAAM,EAAE,IAAI,CAAC,KAAK;QAClB,WAAW,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE;YAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CACrC,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAChE,CAAC;YACF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9C,CAAC,GACD,CAAC;IAEH,yFAAyF;IACzF,KAAK,CAAC,MAAc;QAClB,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;IAC/C,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,GAAW,EAAE,MAAc,EAAE,KAA2B;QAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC;QAC9C,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QAClE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,KAAK;YAAE,OAAO;QACjD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC1B,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtB,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5E,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO;QAC1B,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;QAC5B,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;QACvB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/C,CAAC;uGAzCU,eAAe;2FAAf,eAAe;;2FAAf,eAAe;kBAD3B,SAAS;mBAAC,EAAE,QAAQ,EAAE,mBAAmB,EAAE,QAAQ,EAAE,iBAAiB,EAAE","sourcesContent":["/**\n * The columns of a Markdown table, each as wide as its widest cell.\n *\n * Yoga has no table layout, so a table is rows of cells, and a cell is as wide as its own text.\n * Each cell's text reports its width with `(layout)`; the widest in a column becomes the least\n * width of every cell in it, which lines the column up. The text is measured inside that least\n * width rather than at it, so a cell that narrows, or a row or column that goes, narrows its\n * column again.\n */\nimport { Directive, input, linkedSignal } from '@angular/core';\nimport type { LayoutEvent } from './events.ts';\nimport type { MarkdownBlock } from './markdown-blocks.ts';\n\ntype MarkdownTable = Extract<MarkdownBlock, { kind: 'table' }>;\n\ninterface Columns {\n /** Each cell's text width, by column and then row, with the header as row 0. */\n readonly cells: number[][];\n /** Each column's widest cell. */\n readonly widths: readonly number[];\n}\n\nconst widest = (column: readonly number[]): number =>\n column.reduce((most, width) => Math.max(most, width), 0);\n\n@Directive({ selector: '[markdownColumns]', exportAs: 'markdownColumns' })\nexport class MarkdownColumns {\n /** The table whose columns are lined up. */\n readonly table = input.required<MarkdownTable>({ alias: 'markdownColumns' });\n\n /**\n * The measured cells and each column's widest. A new table keeps only the cells of the rows\n * and columns it still has, so one that goes and comes back starts unmeasured.\n */\n private readonly columns = linkedSignal<MarkdownTable, Columns>({\n source: this.table,\n computation: ({ header, rows }, previous) => {\n const cells = header.map((_, column) =>\n (previous?.value.cells[column] ?? []).slice(0, rows.length + 1),\n );\n return { cells, widths: cells.map(widest) };\n },\n });\n\n /** The least width of a cell in `column`, or null until one of its cells is measured. */\n width(column: number): number | null {\n return this.columns().widths[column] || null;\n }\n\n /**\n * Records the width the text of the cell at `row` (0 for the header) and `column` was laid out\n * at. Only a column whose widest cell narrows is gone over again.\n */\n measure(row: number, column: number, event: Partial<LayoutEvent>): void {\n const width = event.nativeEvent?.layout.width;\n if (width === undefined || row > this.table().rows.length) return;\n const { cells, widths } = this.columns();\n const measured = cells[column];\n if (!measured || measured[row] === width) return;\n const was = measured[row];\n measured[row] = width;\n const most = widths[column]!;\n const next = width >= most ? width : was === most ? widest(measured) : most;\n if (next === most) return;\n const changed = [...widths];\n changed[column] = next;\n this.columns.set({ cells, widths: changed });\n }\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Markdown source that grows as it streams in, lexed a chunk at a time: the blocks before the
|
|
3
|
+
* last one are kept, only the last is lexed again, and syntax still open at its end is held back
|
|
4
|
+
* until it closes, so a half-written code span, emphasis, link, image, table row or code fence
|
|
5
|
+
* line never shows as the characters it is written with.
|
|
6
|
+
*
|
|
7
|
+
* Plain functions and a class with no Angular in them, beside `markdown-blocks.ts`.
|
|
8
|
+
*/
|
|
9
|
+
import { type Token } from 'marked';
|
|
10
|
+
/**
|
|
11
|
+
* Lexes a source that grows, keeping the blocks before the last one from the lex before. A source
|
|
12
|
+
* that does not start with the one before is lexed whole, as is one whose new tail defines a link
|
|
13
|
+
* reference or changes one, since that can change a block already kept.
|
|
14
|
+
*/
|
|
15
|
+
export declare class MarkdownStream {
|
|
16
|
+
private source;
|
|
17
|
+
/** The blocks before the last one, whose raw text is the start of the source exactly. */
|
|
18
|
+
private settled;
|
|
19
|
+
/** How much of the source the settled blocks are. */
|
|
20
|
+
private settledLength;
|
|
21
|
+
/** The link references the settled blocks define. */
|
|
22
|
+
private settledLinks;
|
|
23
|
+
/** The link references the whole source defines. */
|
|
24
|
+
private links;
|
|
25
|
+
lex(text: string): Token[];
|
|
26
|
+
/**
|
|
27
|
+
* Keeps the tail's blocks before its last one as settled, as far as their raw text is the
|
|
28
|
+
* source's: marked trims some blocks' raw text, and the lex after starts again from there.
|
|
29
|
+
*/
|
|
30
|
+
private settle;
|
|
31
|
+
}
|