@ng-native/components 0.5.0 → 0.7.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/html-entities.d.ts +7 -0
- package/dist/html-entities.js +59 -0
- package/dist/html-entities.js.map +1 -0
- package/dist/markdown-blocks.d.ts +79 -0
- package/dist/markdown-blocks.js +212 -0
- package/dist/markdown-blocks.js.map +1 -0
- package/dist/markdown.d.ts +63 -0
- package/dist/markdown.js +338 -0
- package/dist/markdown.js.map +1 -0
- package/dist/virtual-list.d.ts +16 -0
- package/dist/virtual-list.js +91 -36
- package/dist/virtual-list.js.map +1 -1
- package/package.json +14 -5
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown, drawn as native views and text.
|
|
3
|
+
*
|
|
4
|
+
* Its own entry point because it parses with `marked`, an optional peer dependency an app installs
|
|
5
|
+
* only when it renders Markdown: the barrel never imports it, so an app that does not is not asked
|
|
6
|
+
* for it.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { Markdown } from '@ng-native/components/markdown';
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
import { Component, Injector, computed, inject, input, linkedSignal, output } from '@angular/core';
|
|
13
|
+
import { DeepLinks } from '@ng-native/device';
|
|
14
|
+
import { nativePlatform } from '@ng-native/fabric';
|
|
15
|
+
import { lexer } from 'marked';
|
|
16
|
+
import { Image } from "./image.js";
|
|
17
|
+
import { markdownBlocks, opensOnPress, } from "./markdown-blocks.js";
|
|
18
|
+
import { ScrollView } from "./scroll-view.js";
|
|
19
|
+
import { TemplateSlot } from "./template-slot.js";
|
|
20
|
+
import { Text } from "./text.js";
|
|
21
|
+
import { View } from "./view.js";
|
|
22
|
+
import * as i0 from "@angular/core";
|
|
23
|
+
const ELEMENTS = [
|
|
24
|
+
...['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'blockquote', 'ul', 'ol', 'li', 'marker'],
|
|
25
|
+
...['pre', 'code', 'strong', 'em', 'del', 'a', 'hr', 'img', 'table', 'tr', 'th', 'td'],
|
|
26
|
+
];
|
|
27
|
+
const DEFAULT_CLASSES = Object.fromEntries(ELEMENTS.map((element) => [element, `md-${element}`]));
|
|
28
|
+
/** Every image address in `blocks`, however deep in quotes and lists. */
|
|
29
|
+
function imageSources(blocks, into = new Set()) {
|
|
30
|
+
for (const block of blocks) {
|
|
31
|
+
if (block.kind === 'image')
|
|
32
|
+
into.add(block.src);
|
|
33
|
+
else if (block.kind === 'blockquote')
|
|
34
|
+
imageSources(block.children, into);
|
|
35
|
+
else if (block.kind === 'list') {
|
|
36
|
+
for (const item of block.items)
|
|
37
|
+
imageSources(item.children, into);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return into;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A Markdown document as `<text>`, `<view>`, `<image>` and `<scroll-view>`: headings, paragraphs,
|
|
44
|
+
* emphasis, inline code, links, quotes, ordered, unordered and task lists, code blocks, rules,
|
|
45
|
+
* tables and images.
|
|
46
|
+
*
|
|
47
|
+
* `source` is parsed with `marked.lexer`; `tokens` takes tokens lexed already, and wins when both
|
|
48
|
+
* are set. A paragraph is one `<text>` with a nested `<text>` for each emphasis, code span and
|
|
49
|
+
* link, so it wraps as one run; an image is lifted out to a block of its own.
|
|
50
|
+
*
|
|
51
|
+
* Nothing in the document is interpreted. Raw HTML is drawn as the text it is written as, a link
|
|
52
|
+
* is followed only to `http`, `https`, `mailto`, `tel` or a relative target, and an image is
|
|
53
|
+
* loaded only over `http` or `https`; anything else is drawn as its text.
|
|
54
|
+
*
|
|
55
|
+
* Each element carries a default class, `md-h1`, `md-p`, `md-a` and so on, styled in light and
|
|
56
|
+
* dark. `classes` replaces one by its element's name, so a global stylesheet or Tailwind styles it
|
|
57
|
+
* instead: `{ h1: 'text-3xl font-bold' }`. Put the default back beside your own to keep both.
|
|
58
|
+
*
|
|
59
|
+
* A pressed link is `(linkPress)`. An absolute target is then opened with `DeepLinks.open` unless
|
|
60
|
+
* a handler calls `preventDefault()`; a relative one is only reported, for the app to route.
|
|
61
|
+
*/
|
|
62
|
+
export class Markdown {
|
|
63
|
+
injector = inject(Injector);
|
|
64
|
+
/** Which monospace font code is drawn in, as the platform names it. */
|
|
65
|
+
platform = nativePlatform();
|
|
66
|
+
/** The Markdown to draw. */
|
|
67
|
+
source = input(/* @ts-ignore */
|
|
68
|
+
...(ngDevMode ? [undefined, { debugName: "source" }] : /* istanbul ignore next */ []));
|
|
69
|
+
/** Tokens `marked.lexer` made already, drawn in place of `source`. */
|
|
70
|
+
tokens = input(/* @ts-ignore */
|
|
71
|
+
...(ngDevMode ? [undefined, { debugName: "tokens" }] : /* istanbul ignore next */ []));
|
|
72
|
+
/** A class for an element, in place of its default. See the class note. */
|
|
73
|
+
classes = input({}, /* @ts-ignore */
|
|
74
|
+
...(ngDevMode ? [{ debugName: "classes" }] : /* istanbul ignore next */ []));
|
|
75
|
+
/** A link was pressed. See the class note. */
|
|
76
|
+
linkPress = output();
|
|
77
|
+
blocks = computed(() => markdownBlocks(this.tokens() ?? lexer(this.source() ?? '')), /* @ts-ignore */
|
|
78
|
+
...(ngDevMode ? [{ debugName: "blocks" }] : /* istanbul ignore next */ []));
|
|
79
|
+
classOf = computed(() => ({ ...DEFAULT_CLASSES, ...this.classes() }), /* @ts-ignore */
|
|
80
|
+
...(ngDevMode ? [{ debugName: "classOf" }] : /* istanbul ignore next */ []));
|
|
81
|
+
/**
|
|
82
|
+
* Each image's width over its height, once it has loaded, so it is drawn at the page's width and
|
|
83
|
+
* its own shape. An image the document no longer has is forgotten when the document changes.
|
|
84
|
+
*/
|
|
85
|
+
ratios = linkedSignal({ ...(ngDevMode ? { debugName: "ratios" } : /* istanbul ignore next */ {}), source: this.blocks,
|
|
86
|
+
computation: (blocks, previous) => {
|
|
87
|
+
const kept = new Map();
|
|
88
|
+
if (!previous)
|
|
89
|
+
return kept;
|
|
90
|
+
const present = imageSources(blocks);
|
|
91
|
+
for (const [src, ratio] of previous.value)
|
|
92
|
+
if (present.has(src))
|
|
93
|
+
kept.set(src, ratio);
|
|
94
|
+
return kept;
|
|
95
|
+
} });
|
|
96
|
+
classFor(element) {
|
|
97
|
+
return this.classOf()[element];
|
|
98
|
+
}
|
|
99
|
+
sizeImage(src, event) {
|
|
100
|
+
const source = event.nativeEvent?.source;
|
|
101
|
+
if (!source)
|
|
102
|
+
return;
|
|
103
|
+
const { width, height } = source;
|
|
104
|
+
if (!(width > 0 && height > 0))
|
|
105
|
+
return;
|
|
106
|
+
this.ratios.update((ratios) => new Map(ratios).set(src, width / height));
|
|
107
|
+
}
|
|
108
|
+
followLink(link) {
|
|
109
|
+
let prevented = false;
|
|
110
|
+
this.linkPress.emit({
|
|
111
|
+
href: link.href,
|
|
112
|
+
title: link.title,
|
|
113
|
+
preventDefault: () => {
|
|
114
|
+
prevented = true;
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
if (!prevented && opensOnPress(link.href))
|
|
118
|
+
this.injector.get(DeepLinks).open(link.href);
|
|
119
|
+
}
|
|
120
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: Markdown, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
121
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.0", type: Markdown, isStandalone: true, selector: "markdown", inputs: { source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null }, tokens: { classPropertyName: "tokens", publicName: "tokens", isSignal: true, isRequired: false, transformFunction: null }, classes: { classPropertyName: "classes", publicName: "classes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { linkPress: "linkPress" }, host: { properties: { "class.md-ios": "platform === 'ios'", "class.md-android": "platform === 'android'" } }, ngImport: i0, template: `
|
|
122
|
+
<ng-template #blockTemplate let-list>
|
|
123
|
+
@for (block of list; track $index) {
|
|
124
|
+
@switch (block.kind) {
|
|
125
|
+
@case ('heading') {
|
|
126
|
+
<text [class]="classFor(block.element)" accessibilityRole="header"
|
|
127
|
+
><ng-container *templateSlot="inlineTemplate; context: { $implicit: block.children }"
|
|
128
|
+
/></text>
|
|
129
|
+
}
|
|
130
|
+
@case ('paragraph') {
|
|
131
|
+
<text [class]="classFor('p')"
|
|
132
|
+
><ng-container *templateSlot="inlineTemplate; context: { $implicit: block.children }"
|
|
133
|
+
/></text>
|
|
134
|
+
}
|
|
135
|
+
@case ('image') {
|
|
136
|
+
<image
|
|
137
|
+
[src]="block.src"
|
|
138
|
+
[alt]="block.alt || undefined"
|
|
139
|
+
resizeMode="contain"
|
|
140
|
+
[class]="classFor('img')"
|
|
141
|
+
[style.aspectRatio]="ratios().get(block.src) ?? null"
|
|
142
|
+
[style.height]="ratios().has(block.src) ? 'auto' : null"
|
|
143
|
+
(load)="sizeImage(block.src, $any($event))"
|
|
144
|
+
/>
|
|
145
|
+
}
|
|
146
|
+
@case ('blockquote') {
|
|
147
|
+
<view [class]="classFor('blockquote')">
|
|
148
|
+
<ng-container *templateSlot="blockTemplate; context: { $implicit: block.children }" />
|
|
149
|
+
</view>
|
|
150
|
+
}
|
|
151
|
+
@case ('list') {
|
|
152
|
+
<view [class]="classFor(block.element)">
|
|
153
|
+
@for (item of block.items; track $index) {
|
|
154
|
+
<view [class]="classFor('li')">
|
|
155
|
+
<text [class]="classFor('marker')">{{ item.marker }}</text>
|
|
156
|
+
<view class="md-li-body">
|
|
157
|
+
<ng-container
|
|
158
|
+
*templateSlot="blockTemplate; context: { $implicit: item.children }"
|
|
159
|
+
/>
|
|
160
|
+
</view>
|
|
161
|
+
</view>
|
|
162
|
+
}
|
|
163
|
+
</view>
|
|
164
|
+
}
|
|
165
|
+
@case ('code') {
|
|
166
|
+
<scroll-view horizontal [class]="classFor('pre')">
|
|
167
|
+
<text selectable class="md-pre-text">{{ block.text }}</text>
|
|
168
|
+
</scroll-view>
|
|
169
|
+
}
|
|
170
|
+
@case ('hr') {
|
|
171
|
+
<view [class]="classFor('hr')"></view>
|
|
172
|
+
}
|
|
173
|
+
@case ('table') {
|
|
174
|
+
<view [class]="classFor('table')">
|
|
175
|
+
<view [class]="classFor('tr')">
|
|
176
|
+
@for (cell of block.header; track $index) {
|
|
177
|
+
<text [class]="classFor('th')"
|
|
178
|
+
><ng-container *templateSlot="inlineTemplate; context: { $implicit: cell }"
|
|
179
|
+
/></text>
|
|
180
|
+
}
|
|
181
|
+
</view>
|
|
182
|
+
@for (row of block.rows; track $index) {
|
|
183
|
+
<view [class]="classFor('tr')">
|
|
184
|
+
@for (cell of row; track $index) {
|
|
185
|
+
<text [class]="classFor('td')"
|
|
186
|
+
><ng-container *templateSlot="inlineTemplate; context: { $implicit: cell }"
|
|
187
|
+
/></text>
|
|
188
|
+
}
|
|
189
|
+
</view>
|
|
190
|
+
}
|
|
191
|
+
</view>
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
</ng-template>
|
|
196
|
+
|
|
197
|
+
<ng-template #inlineTemplate let-runs>
|
|
198
|
+
@for (run of runs; track $index) {
|
|
199
|
+
@switch (run.kind) {
|
|
200
|
+
@case ('text') {
|
|
201
|
+
<ng-container>{{ run.text }}</ng-container>
|
|
202
|
+
}
|
|
203
|
+
@case ('code') {
|
|
204
|
+
<text [class]="classFor('code')">{{ run.text }}</text>
|
|
205
|
+
}
|
|
206
|
+
@case ('link') {
|
|
207
|
+
<text
|
|
208
|
+
[class]="classFor('a')"
|
|
209
|
+
pressable
|
|
210
|
+
[accessibilityHint]="run.title ?? undefined"
|
|
211
|
+
(press)="followLink(run)"
|
|
212
|
+
><ng-container *templateSlot="inlineTemplate; context: { $implicit: run.children }"
|
|
213
|
+
/></text>
|
|
214
|
+
}
|
|
215
|
+
@default {
|
|
216
|
+
<text [class]="classFor(run.kind)"
|
|
217
|
+
><ng-container *templateSlot="inlineTemplate; context: { $implicit: run.children }"
|
|
218
|
+
/></text>
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
</ng-template>
|
|
223
|
+
|
|
224
|
+
<ng-container *templateSlot="blockTemplate; context: { $implicit: blocks() }" />
|
|
225
|
+
`, isInline: true, styles: ["/* What a document looks like with no `classes` of the app's, in light and in dark. */\n\n:host {\n gap: 12px;\n color: #1c1c1e;\n}\n\n.md-h1,\n.md-h2,\n.md-h3,\n.md-h4,\n.md-h5,\n.md-h6 {\n font-weight: 700;\n}\n\n.md-h1 {\n font-size: 28px;\n line-height: 34px;\n}\n\n.md-h2 {\n font-size: 22px;\n line-height: 28px;\n}\n\n.md-h3 {\n font-size: 20px;\n line-height: 25px;\n}\n\n.md-h4 {\n font-size: 17px;\n line-height: 22px;\n}\n\n.md-h5 {\n font-size: 15px;\n line-height: 20px;\n}\n\n.md-h6 {\n font-size: 13px;\n line-height: 18px;\n color: #6c6c70;\n}\n\n.md-p,\n.md-marker,\n.md-th,\n.md-td {\n font-size: 16px;\n line-height: 23px;\n}\n\n.md-strong {\n font-weight: 700;\n}\n\n.md-em {\n font-style: italic;\n}\n\n.md-del {\n text-decoration-line: line-through;\n}\n\n.md-a {\n color: #0a66d8;\n text-decoration-line: underline;\n}\n\n.md-code {\n background-color: rgba(120, 120, 128, 0.16);\n}\n\n.md-code,\n.md-pre {\n font-family: Courier New;\n}\n\n:host(.md-ios) .md-code,\n:host(.md-ios) .md-pre {\n font-family: Menlo;\n}\n\n:host(.md-android) .md-code,\n:host(.md-android) .md-pre {\n font-family: monospace;\n}\n\n.md-blockquote {\n gap: 8px;\n padding-left: 12px;\n border-left-width: 3px;\n border-left-color: #d1d1d6;\n color: #6c6c70;\n}\n\n.md-ul,\n.md-ol {\n gap: 4px;\n}\n\n.md-li {\n flex-direction: row;\n gap: 8px;\n}\n\n.md-marker {\n min-width: 16px;\n}\n\n.md-li-body {\n flex: 1;\n gap: 4px;\n}\n\n.md-pre {\n flex-grow: 0;\n border-radius: 8px;\n background-color: #f2f2f7;\n}\n\n.md-pre-text {\n padding: 12px;\n font-size: 14px;\n line-height: 20px;\n}\n\n.md-hr {\n height: 1px;\n background-color: #d1d1d6;\n}\n\n.md-img {\n width: 100%;\n height: 200px;\n border-radius: 8px;\n}\n\n.md-table {\n border-width: 1px;\n border-color: #d1d1d6;\n border-radius: 8px;\n overflow: hidden;\n}\n\n.md-tr {\n flex-direction: row;\n}\n\n.md-th,\n.md-td {\n flex: 1;\n padding: 6px 8px;\n}\n\n.md-td {\n border-top-width: 1px;\n border-top-color: #d1d1d6;\n}\n\n.md-th {\n font-weight: 600;\n}\n\n@media (prefers-color-scheme: dark) {\n :host {\n color: #f2f2f7;\n }\n\n .md-h6,\n .md-blockquote {\n color: #98989f;\n }\n\n .md-a {\n color: #4c9bff;\n }\n\n .md-code {\n background-color: rgba(120, 120, 128, 0.32);\n }\n\n .md-pre {\n background-color: #1c1c1e;\n }\n\n .md-blockquote,\n .md-table,\n .md-td {\n border-color: #38383a;\n }\n\n .md-hr {\n background-color: #38383a;\n }\n}\n"], dependencies: [{ kind: "directive", type: Image, selector: "image", inputs: ["source", "src", "srcSet", "defaultSource", "loadingIndicatorSource", "resizeMode", "resizeMethod", "resizeMultiplier", "blurRadius", "capInsets", "tintColor", "fadeDuration", "progressiveRenderingEnabled", "alt", "crossOrigin", "referrerPolicy"] }, { kind: "component", type: ScrollView, selector: "scroll-view", inputs: ["contentContainerStyle", "contentContainerClass", "horizontal", "contentOffset", "maintainVisibleContentPosition", "keyboardShouldPersistTaps", "nestedScrollEnabled", "stickyHeaderIndices"], outputs: ["contentSizeChange"], exportAs: ["scrollView"] }, { kind: "directive", type: TemplateSlot, selector: "[templateSlot]", inputs: ["templateSlot", "templateSlotContext"] }, { kind: "directive", type: Text, selector: "text", inputs: ["numberOfLines", "ellipsizeMode", "selectable", "selectionColor", "allowFontScaling", "maxFontSizeMultiplier", "dynamicTypeRamp", "adjustsFontSizeToFit", "minimumFontScale", "lineBreakStrategyIOS", "textBreakStrategy", "dataDetectorType", "android_hyphenationFrequency", "pressable", "suppressHighlighting"], exportAs: ["text"] }, { kind: "directive", type: View, selector: "view" }] });
|
|
226
|
+
}
|
|
227
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: Markdown, decorators: [{
|
|
228
|
+
type: Component,
|
|
229
|
+
args: [{ selector: 'markdown', imports: [Image, ScrollView, TemplateSlot, Text, View], template: `
|
|
230
|
+
<ng-template #blockTemplate let-list>
|
|
231
|
+
@for (block of list; track $index) {
|
|
232
|
+
@switch (block.kind) {
|
|
233
|
+
@case ('heading') {
|
|
234
|
+
<text [class]="classFor(block.element)" accessibilityRole="header"
|
|
235
|
+
><ng-container *templateSlot="inlineTemplate; context: { $implicit: block.children }"
|
|
236
|
+
/></text>
|
|
237
|
+
}
|
|
238
|
+
@case ('paragraph') {
|
|
239
|
+
<text [class]="classFor('p')"
|
|
240
|
+
><ng-container *templateSlot="inlineTemplate; context: { $implicit: block.children }"
|
|
241
|
+
/></text>
|
|
242
|
+
}
|
|
243
|
+
@case ('image') {
|
|
244
|
+
<image
|
|
245
|
+
[src]="block.src"
|
|
246
|
+
[alt]="block.alt || undefined"
|
|
247
|
+
resizeMode="contain"
|
|
248
|
+
[class]="classFor('img')"
|
|
249
|
+
[style.aspectRatio]="ratios().get(block.src) ?? null"
|
|
250
|
+
[style.height]="ratios().has(block.src) ? 'auto' : null"
|
|
251
|
+
(load)="sizeImage(block.src, $any($event))"
|
|
252
|
+
/>
|
|
253
|
+
}
|
|
254
|
+
@case ('blockquote') {
|
|
255
|
+
<view [class]="classFor('blockquote')">
|
|
256
|
+
<ng-container *templateSlot="blockTemplate; context: { $implicit: block.children }" />
|
|
257
|
+
</view>
|
|
258
|
+
}
|
|
259
|
+
@case ('list') {
|
|
260
|
+
<view [class]="classFor(block.element)">
|
|
261
|
+
@for (item of block.items; track $index) {
|
|
262
|
+
<view [class]="classFor('li')">
|
|
263
|
+
<text [class]="classFor('marker')">{{ item.marker }}</text>
|
|
264
|
+
<view class="md-li-body">
|
|
265
|
+
<ng-container
|
|
266
|
+
*templateSlot="blockTemplate; context: { $implicit: item.children }"
|
|
267
|
+
/>
|
|
268
|
+
</view>
|
|
269
|
+
</view>
|
|
270
|
+
}
|
|
271
|
+
</view>
|
|
272
|
+
}
|
|
273
|
+
@case ('code') {
|
|
274
|
+
<scroll-view horizontal [class]="classFor('pre')">
|
|
275
|
+
<text selectable class="md-pre-text">{{ block.text }}</text>
|
|
276
|
+
</scroll-view>
|
|
277
|
+
}
|
|
278
|
+
@case ('hr') {
|
|
279
|
+
<view [class]="classFor('hr')"></view>
|
|
280
|
+
}
|
|
281
|
+
@case ('table') {
|
|
282
|
+
<view [class]="classFor('table')">
|
|
283
|
+
<view [class]="classFor('tr')">
|
|
284
|
+
@for (cell of block.header; track $index) {
|
|
285
|
+
<text [class]="classFor('th')"
|
|
286
|
+
><ng-container *templateSlot="inlineTemplate; context: { $implicit: cell }"
|
|
287
|
+
/></text>
|
|
288
|
+
}
|
|
289
|
+
</view>
|
|
290
|
+
@for (row of block.rows; track $index) {
|
|
291
|
+
<view [class]="classFor('tr')">
|
|
292
|
+
@for (cell of row; track $index) {
|
|
293
|
+
<text [class]="classFor('td')"
|
|
294
|
+
><ng-container *templateSlot="inlineTemplate; context: { $implicit: cell }"
|
|
295
|
+
/></text>
|
|
296
|
+
}
|
|
297
|
+
</view>
|
|
298
|
+
}
|
|
299
|
+
</view>
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
</ng-template>
|
|
304
|
+
|
|
305
|
+
<ng-template #inlineTemplate let-runs>
|
|
306
|
+
@for (run of runs; track $index) {
|
|
307
|
+
@switch (run.kind) {
|
|
308
|
+
@case ('text') {
|
|
309
|
+
<ng-container>{{ run.text }}</ng-container>
|
|
310
|
+
}
|
|
311
|
+
@case ('code') {
|
|
312
|
+
<text [class]="classFor('code')">{{ run.text }}</text>
|
|
313
|
+
}
|
|
314
|
+
@case ('link') {
|
|
315
|
+
<text
|
|
316
|
+
[class]="classFor('a')"
|
|
317
|
+
pressable
|
|
318
|
+
[accessibilityHint]="run.title ?? undefined"
|
|
319
|
+
(press)="followLink(run)"
|
|
320
|
+
><ng-container *templateSlot="inlineTemplate; context: { $implicit: run.children }"
|
|
321
|
+
/></text>
|
|
322
|
+
}
|
|
323
|
+
@default {
|
|
324
|
+
<text [class]="classFor(run.kind)"
|
|
325
|
+
><ng-container *templateSlot="inlineTemplate; context: { $implicit: run.children }"
|
|
326
|
+
/></text>
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
</ng-template>
|
|
331
|
+
|
|
332
|
+
<ng-container *templateSlot="blockTemplate; context: { $implicit: blocks() }" />
|
|
333
|
+
`, host: {
|
|
334
|
+
'[class.md-ios]': "platform === 'ios'",
|
|
335
|
+
'[class.md-android]': "platform === 'android'",
|
|
336
|
+
}, styles: ["/* What a document looks like with no `classes` of the app's, in light and in dark. */\n\n:host {\n gap: 12px;\n color: #1c1c1e;\n}\n\n.md-h1,\n.md-h2,\n.md-h3,\n.md-h4,\n.md-h5,\n.md-h6 {\n font-weight: 700;\n}\n\n.md-h1 {\n font-size: 28px;\n line-height: 34px;\n}\n\n.md-h2 {\n font-size: 22px;\n line-height: 28px;\n}\n\n.md-h3 {\n font-size: 20px;\n line-height: 25px;\n}\n\n.md-h4 {\n font-size: 17px;\n line-height: 22px;\n}\n\n.md-h5 {\n font-size: 15px;\n line-height: 20px;\n}\n\n.md-h6 {\n font-size: 13px;\n line-height: 18px;\n color: #6c6c70;\n}\n\n.md-p,\n.md-marker,\n.md-th,\n.md-td {\n font-size: 16px;\n line-height: 23px;\n}\n\n.md-strong {\n font-weight: 700;\n}\n\n.md-em {\n font-style: italic;\n}\n\n.md-del {\n text-decoration-line: line-through;\n}\n\n.md-a {\n color: #0a66d8;\n text-decoration-line: underline;\n}\n\n.md-code {\n background-color: rgba(120, 120, 128, 0.16);\n}\n\n.md-code,\n.md-pre {\n font-family: Courier New;\n}\n\n:host(.md-ios) .md-code,\n:host(.md-ios) .md-pre {\n font-family: Menlo;\n}\n\n:host(.md-android) .md-code,\n:host(.md-android) .md-pre {\n font-family: monospace;\n}\n\n.md-blockquote {\n gap: 8px;\n padding-left: 12px;\n border-left-width: 3px;\n border-left-color: #d1d1d6;\n color: #6c6c70;\n}\n\n.md-ul,\n.md-ol {\n gap: 4px;\n}\n\n.md-li {\n flex-direction: row;\n gap: 8px;\n}\n\n.md-marker {\n min-width: 16px;\n}\n\n.md-li-body {\n flex: 1;\n gap: 4px;\n}\n\n.md-pre {\n flex-grow: 0;\n border-radius: 8px;\n background-color: #f2f2f7;\n}\n\n.md-pre-text {\n padding: 12px;\n font-size: 14px;\n line-height: 20px;\n}\n\n.md-hr {\n height: 1px;\n background-color: #d1d1d6;\n}\n\n.md-img {\n width: 100%;\n height: 200px;\n border-radius: 8px;\n}\n\n.md-table {\n border-width: 1px;\n border-color: #d1d1d6;\n border-radius: 8px;\n overflow: hidden;\n}\n\n.md-tr {\n flex-direction: row;\n}\n\n.md-th,\n.md-td {\n flex: 1;\n padding: 6px 8px;\n}\n\n.md-td {\n border-top-width: 1px;\n border-top-color: #d1d1d6;\n}\n\n.md-th {\n font-weight: 600;\n}\n\n@media (prefers-color-scheme: dark) {\n :host {\n color: #f2f2f7;\n }\n\n .md-h6,\n .md-blockquote {\n color: #98989f;\n }\n\n .md-a {\n color: #4c9bff;\n }\n\n .md-code {\n background-color: rgba(120, 120, 128, 0.32);\n }\n\n .md-pre {\n background-color: #1c1c1e;\n }\n\n .md-blockquote,\n .md-table,\n .md-td {\n border-color: #38383a;\n }\n\n .md-hr {\n background-color: #38383a;\n }\n}\n"] }]
|
|
337
|
+
}], propDecorators: { source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], tokens: [{ type: i0.Input, args: [{ isSignal: true, alias: "tokens", required: false }] }], classes: [{ type: i0.Input, args: [{ isSignal: true, alias: "classes", required: false }] }], linkPress: [{ type: i0.Output, args: ["linkPress"] }] } });
|
|
338
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Markdown, drawn as native views and text.\n *\n * Its own entry point because it parses with `marked`, an optional peer dependency an app installs\n * only when it renders Markdown: the barrel never imports it, so an app that does not is not asked\n * for it.\n *\n * ```ts\n * import { Markdown } from '@ng-native/components/markdown';\n * ```\n */\nimport { Component, Injector, computed, inject, input, linkedSignal, output } from '@angular/core';\nimport { DeepLinks } from '@ng-native/device';\nimport { nativePlatform } from '@ng-native/fabric';\nimport { lexer, type Token } from 'marked';\nimport type { ImageLoadEvent } from './events.ts';\nimport { Image } from './image.ts';\nimport {\n markdownBlocks,\n opensOnPress,\n type MarkdownBlock,\n type MarkdownElement,\n type MarkdownInline,\n} from './markdown-blocks.ts';\nimport { ScrollView } from './scroll-view.ts';\nimport { TemplateSlot } from './template-slot.ts';\nimport { Text } from './text.ts';\nimport { View } from './view.ts';\n\nexport type { MarkdownElement } from './markdown-blocks.ts';\n\n/** Classes for the elements a document is drawn with, each one in place of the default. */\nexport type MarkdownClasses = Partial<Readonly<Record<MarkdownElement, string>>>;\n\n/** `(linkPress)`: a link was pressed. */\nexport interface MarkdownLinkPress {\n /** The target as written, after its entities are decoded and its scheme was allowed. */\n readonly href: string;\n readonly title: string | null;\n /** Keep the link from being opened, to handle it in the app instead. */\n preventDefault(): void;\n}\n\nconst ELEMENTS: readonly MarkdownElement[] = [\n ...['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'blockquote', 'ul', 'ol', 'li', 'marker'],\n ...['pre', 'code', 'strong', 'em', 'del', 'a', 'hr', 'img', 'table', 'tr', 'th', 'td'],\n] as MarkdownElement[];\n\nconst DEFAULT_CLASSES = Object.fromEntries(\n ELEMENTS.map((element) => [element, `md-${element}`]),\n) as Readonly<Record<MarkdownElement, string>>;\n\ntype MarkdownLink = Extract<MarkdownInline, { kind: 'link' }>;\n\n/** Every image address in `blocks`, however deep in quotes and lists. */\nfunction imageSources(blocks: readonly MarkdownBlock[], into = new Set<string>()): Set<string> {\n for (const block of blocks) {\n if (block.kind === 'image') into.add(block.src);\n else if (block.kind === 'blockquote') imageSources(block.children, into);\n else if (block.kind === 'list') {\n for (const item of block.items) imageSources(item.children, into);\n }\n }\n return into;\n}\n\n/**\n * A Markdown document as `<text>`, `<view>`, `<image>` and `<scroll-view>`: headings, paragraphs,\n * emphasis, inline code, links, quotes, ordered, unordered and task lists, code blocks, rules,\n * tables and images.\n *\n * `source` is parsed with `marked.lexer`; `tokens` takes tokens lexed already, and wins when both\n * are set. A paragraph is one `<text>` with a nested `<text>` for each emphasis, code span and\n * link, so it wraps as one run; an image is lifted out to a block of its own.\n *\n * Nothing in the document is interpreted. Raw HTML is drawn as the text it is written as, a link\n * is followed only to `http`, `https`, `mailto`, `tel` or a relative target, and an image is\n * loaded only over `http` or `https`; anything else is drawn as its text.\n *\n * Each element carries a default class, `md-h1`, `md-p`, `md-a` and so on, styled in light and\n * dark. `classes` replaces one by its element's name, so a global stylesheet or Tailwind styles it\n * instead: `{ h1: 'text-3xl font-bold' }`. Put the default back beside your own to keep both.\n *\n * A pressed link is `(linkPress)`. An absolute target is then opened with `DeepLinks.open` unless\n * a handler calls `preventDefault()`; a relative one is only reported, for the app to route.\n */\n@Component({\n selector: 'markdown',\n imports: [Image, ScrollView, TemplateSlot, Text, View],\n template: `\n <ng-template #blockTemplate let-list>\n @for (block of list; track $index) {\n @switch (block.kind) {\n @case ('heading') {\n <text [class]=\"classFor(block.element)\" accessibilityRole=\"header\"\n ><ng-container *templateSlot=\"inlineTemplate; context: { $implicit: block.children }\"\n /></text>\n }\n @case ('paragraph') {\n <text [class]=\"classFor('p')\"\n ><ng-container *templateSlot=\"inlineTemplate; context: { $implicit: block.children }\"\n /></text>\n }\n @case ('image') {\n <image\n [src]=\"block.src\"\n [alt]=\"block.alt || undefined\"\n resizeMode=\"contain\"\n [class]=\"classFor('img')\"\n [style.aspectRatio]=\"ratios().get(block.src) ?? null\"\n [style.height]=\"ratios().has(block.src) ? 'auto' : null\"\n (load)=\"sizeImage(block.src, $any($event))\"\n />\n }\n @case ('blockquote') {\n <view [class]=\"classFor('blockquote')\">\n <ng-container *templateSlot=\"blockTemplate; context: { $implicit: block.children }\" />\n </view>\n }\n @case ('list') {\n <view [class]=\"classFor(block.element)\">\n @for (item of block.items; track $index) {\n <view [class]=\"classFor('li')\">\n <text [class]=\"classFor('marker')\">{{ item.marker }}</text>\n <view class=\"md-li-body\">\n <ng-container\n *templateSlot=\"blockTemplate; context: { $implicit: item.children }\"\n />\n </view>\n </view>\n }\n </view>\n }\n @case ('code') {\n <scroll-view horizontal [class]=\"classFor('pre')\">\n <text selectable class=\"md-pre-text\">{{ block.text }}</text>\n </scroll-view>\n }\n @case ('hr') {\n <view [class]=\"classFor('hr')\"></view>\n }\n @case ('table') {\n <view [class]=\"classFor('table')\">\n <view [class]=\"classFor('tr')\">\n @for (cell of block.header; track $index) {\n <text [class]=\"classFor('th')\"\n ><ng-container *templateSlot=\"inlineTemplate; context: { $implicit: cell }\"\n /></text>\n }\n </view>\n @for (row of block.rows; track $index) {\n <view [class]=\"classFor('tr')\">\n @for (cell of row; track $index) {\n <text [class]=\"classFor('td')\"\n ><ng-container *templateSlot=\"inlineTemplate; context: { $implicit: cell }\"\n /></text>\n }\n </view>\n }\n </view>\n }\n }\n }\n </ng-template>\n\n <ng-template #inlineTemplate let-runs>\n @for (run of runs; track $index) {\n @switch (run.kind) {\n @case ('text') {\n <ng-container>{{ run.text }}</ng-container>\n }\n @case ('code') {\n <text [class]=\"classFor('code')\">{{ run.text }}</text>\n }\n @case ('link') {\n <text\n [class]=\"classFor('a')\"\n pressable\n [accessibilityHint]=\"run.title ?? undefined\"\n (press)=\"followLink(run)\"\n ><ng-container *templateSlot=\"inlineTemplate; context: { $implicit: run.children }\"\n /></text>\n }\n @default {\n <text [class]=\"classFor(run.kind)\"\n ><ng-container *templateSlot=\"inlineTemplate; context: { $implicit: run.children }\"\n /></text>\n }\n }\n }\n </ng-template>\n\n <ng-container *templateSlot=\"blockTemplate; context: { $implicit: blocks() }\" />\n `,\n styleUrl: './markdown.css',\n host: {\n '[class.md-ios]': \"platform === 'ios'\",\n '[class.md-android]': \"platform === 'android'\",\n },\n})\nexport class Markdown {\n private readonly injector = inject(Injector);\n /** Which monospace font code is drawn in, as the platform names it. */\n protected readonly platform = nativePlatform();\n\n /** The Markdown to draw. */\n readonly source = input<string>();\n /** Tokens `marked.lexer` made already, drawn in place of `source`. */\n readonly tokens = input<readonly Token[]>();\n /** A class for an element, in place of its default. See the class note. */\n readonly classes = input<MarkdownClasses>({});\n /** A link was pressed. See the class note. */\n readonly linkPress = output<MarkdownLinkPress>();\n\n protected readonly blocks = computed(() =>\n markdownBlocks(this.tokens() ?? lexer(this.source() ?? '')),\n );\n\n private readonly classOf = computed(() => ({ ...DEFAULT_CLASSES, ...this.classes() }));\n\n /**\n * Each image's width over its height, once it has loaded, so it is drawn at the page's width and\n * its own shape. An image the document no longer has is forgotten when the document changes.\n */\n protected readonly ratios = linkedSignal<readonly MarkdownBlock[], ReadonlyMap<string, number>>({\n source: this.blocks,\n computation: (blocks, previous) => {\n const kept = new Map<string, number>();\n if (!previous) return kept;\n const present = imageSources(blocks);\n for (const [src, ratio] of previous.value) if (present.has(src)) kept.set(src, ratio);\n return kept;\n },\n });\n\n protected classFor(element: MarkdownElement): string {\n return this.classOf()[element];\n }\n\n protected sizeImage(src: string, event: Partial<ImageLoadEvent>): void {\n const source = event.nativeEvent?.source;\n if (!source) return;\n const { width, height } = source;\n if (!(width > 0 && height > 0)) return;\n this.ratios.update((ratios) => new Map(ratios).set(src, width / height));\n }\n\n protected followLink(link: MarkdownLink): void {\n let prevented = false;\n this.linkPress.emit({\n href: link.href,\n title: link.title,\n preventDefault: () => {\n prevented = true;\n },\n });\n if (!prevented && opensOnPress(link.href)) this.injector.get(DeepLinks).open(link.href);\n }\n}\n"]}
|
package/dist/virtual-list.d.ts
CHANGED
|
@@ -215,6 +215,8 @@ export declare class VirtualList<T> extends ScrollViewProps {
|
|
|
215
215
|
private readonly headerHeight;
|
|
216
216
|
/** What native reported the content measures along the axis, once it has. */
|
|
217
217
|
private contentSize;
|
|
218
|
+
/** The rows' own extent when native reported that, so a report for other rows is not used. */
|
|
219
|
+
private contentSizeFor;
|
|
218
220
|
/** The item count `endReached` last fired for, cleared once the list is away from the end. */
|
|
219
221
|
private endReachedFor;
|
|
220
222
|
/** Slot styles are cached so a scroll does not hand Fabric new prop objects for stable rows. */
|
|
@@ -275,6 +277,10 @@ export declare class VirtualList<T> extends ScrollViewProps {
|
|
|
275
277
|
private readonly extent;
|
|
276
278
|
/** A fixed-height row and the gap after it. */
|
|
277
279
|
private stride;
|
|
280
|
+
/** ScrollView.js's `contentContainerHorizontal`: the header, rows and footer side by side. */
|
|
281
|
+
protected readonly content: import("@angular/core").Signal<{
|
|
282
|
+
flexDirection: string;
|
|
283
|
+
} | undefined>;
|
|
278
284
|
protected readonly canvas: import("@angular/core").Signal<{
|
|
279
285
|
width: number;
|
|
280
286
|
flexDirection: string;
|
|
@@ -366,6 +372,14 @@ export declare class VirtualList<T> extends ScrollViewProps {
|
|
|
366
372
|
/** In development, a list with rows that stays at zero size; see `ZeroSizeWarning`. */
|
|
367
373
|
private readonly zeroSize;
|
|
368
374
|
constructor();
|
|
375
|
+
/** The items the end and viewability were last checked against after a render. */
|
|
376
|
+
private checkedItems;
|
|
377
|
+
/**
|
|
378
|
+
* New items can bring the end within reach or change what is on screen with no scroll: a list
|
|
379
|
+
* shorter than its viewport, a page appended while at the end, a filter. Checked once the rows
|
|
380
|
+
* they make are rendered, and only when they changed, so any other render costs a comparison.
|
|
381
|
+
*/
|
|
382
|
+
private recheckItems;
|
|
369
383
|
/** Whether the viewport has been laid out yet, so a zero in it is a measurement. */
|
|
370
384
|
private viewportMeasured;
|
|
371
385
|
/**
|
|
@@ -491,6 +505,8 @@ export declare class VirtualList<T> extends ScrollViewProps {
|
|
|
491
505
|
private viewableAt;
|
|
492
506
|
/** The last set announced, so the output fires on a change rather than on every frame. */
|
|
493
507
|
private announced;
|
|
508
|
+
/** The items the last set was announced from: new items are a change even at the same indices. */
|
|
509
|
+
private announcedFrom;
|
|
494
510
|
/**
|
|
495
511
|
* Announce a change in what is on screen.
|
|
496
512
|
*
|