@ng-native/components 0.5.0 → 0.6.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.
@@ -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"]}
@@ -275,6 +275,10 @@ export declare class VirtualList<T> extends ScrollViewProps {
275
275
  private readonly extent;
276
276
  /** A fixed-height row and the gap after it. */
277
277
  private stride;
278
+ /** ScrollView.js's `contentContainerHorizontal`: the header, rows and footer side by side. */
279
+ protected readonly content: import("@angular/core").Signal<{
280
+ flexDirection: string;
281
+ } | undefined>;
278
282
  protected readonly canvas: import("@angular/core").Signal<{
279
283
  width: number;
280
284
  flexDirection: string;
@@ -25,8 +25,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
25
25
  type: Directive,
26
26
  args: [{ selector: 'ng-template[virtualListSeparator]' }]
27
27
  }] });
28
- /** A parked slot: out of layout, out of sight, and out of the accessibility tree. */
29
- const PARKED = Object.freeze({ display: 'none' });
28
+ /**
29
+ * A parked slot: out of the flow, out of sight and out of reach, with its view kept to be used
30
+ * again. Not `display: none`, which is an element with no view. A screen reader passes over a
31
+ * view with no opacity, and `VirtualListRow` says so outright where a row uses it. Moved off the
32
+ * list as well, since `pointer-events: none` is CSS's: something inside a row with a
33
+ * `pointer-events` of its own would still take a touch where the row was left.
34
+ */
35
+ const PARKED = Object.freeze({
36
+ position: 'absolute',
37
+ opacity: 0,
38
+ pointerEvents: 'none',
39
+ transform: 'translateX(-100000px)',
40
+ });
30
41
  /** How many hidden slots of each row type are kept ready for reuse. */
31
42
  const PARK_LIMIT = 6;
32
43
  /** The style of a row that sizes itself and needs nothing else: in flow, where it falls. */
@@ -309,6 +320,9 @@ export class VirtualList extends ScrollViewProps {
309
320
  stride() {
310
321
  return this.itemHeight() + this.rowGap();
311
322
  }
323
+ /** ScrollView.js's `contentContainerHorizontal`: the header, rows and footer side by side. */
324
+ content = computed(() => this.horizontal() ? { flexDirection: 'row' } : undefined, /* @ts-ignore */
325
+ ...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
312
326
  canvas = computed(() => this.horizontal() ? { width: this.extent(), flexDirection: 'row' } : { height: this.extent() }, /* @ts-ignore */
313
327
  ...(ngDevMode ? [{ debugName: "canvas" }] : /* istanbul ignore next */ []));
314
328
  /**
@@ -1140,23 +1154,27 @@ export class VirtualList extends ScrollViewProps {
1140
1154
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: VirtualList, deps: [], target: i0.ɵɵFactoryTarget.Component });
1141
1155
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.0", type: VirtualList, isStandalone: true, selector: "virtual-list", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: false, transformFunction: null }, estimatedItemHeight: { classPropertyName: "estimatedItemHeight", publicName: "estimatedItemHeight", isSignal: true, isRequired: false, transformFunction: null }, contentPadding: { classPropertyName: "contentPadding", publicName: "contentPadding", isSignal: true, isRequired: false, transformFunction: null }, rowGap: { classPropertyName: "rowGap", publicName: "rowGap", isSignal: true, isRequired: false, transformFunction: null }, keyExtractor: { classPropertyName: "keyExtractor", publicName: "keyExtractor", isSignal: true, isRequired: false, transformFunction: null }, itemType: { classPropertyName: "itemType", publicName: "itemType", isSignal: true, isRequired: false, transformFunction: null }, maintainVisibleContentPosition: { classPropertyName: "maintainVisibleContentPosition", publicName: "maintainVisibleContentPosition", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, endReachedThreshold: { classPropertyName: "endReachedThreshold", publicName: "endReachedThreshold", isSignal: true, isRequired: false, transformFunction: null }, horizontal: { classPropertyName: "horizontal", publicName: "horizontal", isSignal: true, isRequired: false, transformFunction: null }, inverted: { classPropertyName: "inverted", publicName: "inverted", isSignal: true, isRequired: false, transformFunction: null }, stickyIndices: { classPropertyName: "stickyIndices", publicName: "stickyIndices", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, keyboardShouldPersistTaps: { classPropertyName: "keyboardShouldPersistTaps", publicName: "keyboardShouldPersistTaps", isSignal: true, isRequired: false, transformFunction: null }, itemVisiblePercentThreshold: { classPropertyName: "itemVisiblePercentThreshold", publicName: "itemVisiblePercentThreshold", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { endReached: "endReached", viewableItemsChanged: "viewableItemsChanged" }, host: { listeners: { "scroll": "onScroll($event)", "layout": "onLayout($event)", "contentSizeChange": "onContentSize($event)" }, properties: { "flexDirection": "horizontal() ? 'row' : 'column'", "horizontal": "horizontal()", "nestedScrollEnabled": "nestedScroll()", "transform": "flip()", "scrollEventThrottle": "scrollEventThrottle()" } }, queries: [{ propertyName: "separator", first: true, predicate: VirtualListSeparator, descendants: true, isSignal: true }, { propertyName: "refreshControl", first: true, predicate: RefreshControl, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "canvasView", first: true, predicate: ["rows"], descendants: true, read: ElementRef, isSignal: true }], exportAs: ["virtualList"], usesInheritance: true, ngImport: i0, template: `
1142
1156
  <ng-content select="refresh-control" />
1143
- <view [style]="headerStyle()" (layout)="onHeaderLayout($event)"
1144
- ><ng-content select="[listHeader]"
1145
- /></view>
1146
- <view #rows [style]="canvas()" collapsable="false">
1147
- @if (measuring()) {
1148
- <view [style]="spacer()"></view>
1149
- }
1150
- <ng-content />
1151
- @if (separator(); as separator) {
1152
- @for (gap of gaps(); track gap.index) {
1153
- <view [style]="gap.style"
1154
- ><ng-container [templateSlot]="separator.template" [templateSlotContext]="gap.context"
1155
- /></view>
1157
+ <view [style]="content()" collapsable="false">
1158
+ <view [style]="headerStyle()" (layout)="onHeaderLayout($event)"
1159
+ ><ng-content select="[listHeader]"
1160
+ /></view>
1161
+ <view #rows [style]="canvas()" collapsable="false">
1162
+ @if (measuring()) {
1163
+ <view [style]="spacer()"></view>
1156
1164
  }
1157
- }
1165
+ <ng-content />
1166
+ @if (separator(); as separator) {
1167
+ @for (gap of gaps(); track gap.index) {
1168
+ <view [style]="gap.style"
1169
+ ><ng-container
1170
+ [templateSlot]="separator.template"
1171
+ [templateSlotContext]="gap.context"
1172
+ /></view>
1173
+ }
1174
+ }
1175
+ </view>
1176
+ <view [style]="footerStyle()"><ng-content select="[listFooter]" /></view>
1158
1177
  </view>
1159
- <view [style]="footerStyle()"><ng-content select="[listFooter]" /></view>
1160
1178
  `, isInline: true, dependencies: [{ kind: "directive", type: TemplateSlot, selector: "[templateSlot]", inputs: ["templateSlot", "templateSlotContext"] }, { kind: "directive", type: View, selector: "view" }] });
1161
1179
  }
1162
1180
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: VirtualList, decorators: [{
@@ -1165,29 +1183,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
1165
1183
  selector: 'virtual-list',
1166
1184
  exportAs: 'virtualList',
1167
1185
  imports: [TemplateSlot, View],
1186
+ // One content view holds the header, the rows and the footer, as ScrollView.js's content
1187
+ // container does: Android's ScrollView takes one child and throws on a second. It is never
1188
+ // collapsed, or Fabric would flatten it away and hand the scroll view all three again.
1168
1189
  // See ScrollView: the canvas carries only a height, so Fabric would flatten it away and drags
1169
1190
  // over the gaps between rows would not scroll. Separators come after the rows so they draw over
1170
1191
  // the slot they share. The spacer is there only when rows are in flow.
1171
1192
  // A refresh control comes first, as the scroll view's own child; see ScrollView.
1172
1193
  template: `
1173
1194
  <ng-content select="refresh-control" />
1174
- <view [style]="headerStyle()" (layout)="onHeaderLayout($event)"
1175
- ><ng-content select="[listHeader]"
1176
- /></view>
1177
- <view #rows [style]="canvas()" collapsable="false">
1178
- @if (measuring()) {
1179
- <view [style]="spacer()"></view>
1180
- }
1181
- <ng-content />
1182
- @if (separator(); as separator) {
1183
- @for (gap of gaps(); track gap.index) {
1184
- <view [style]="gap.style"
1185
- ><ng-container [templateSlot]="separator.template" [templateSlotContext]="gap.context"
1186
- /></view>
1195
+ <view [style]="content()" collapsable="false">
1196
+ <view [style]="headerStyle()" (layout)="onHeaderLayout($event)"
1197
+ ><ng-content select="[listHeader]"
1198
+ /></view>
1199
+ <view #rows [style]="canvas()" collapsable="false">
1200
+ @if (measuring()) {
1201
+ <view [style]="spacer()"></view>
1202
+ }
1203
+ <ng-content />
1204
+ @if (separator(); as separator) {
1205
+ @for (gap of gaps(); track gap.index) {
1206
+ <view [style]="gap.style"
1207
+ ><ng-container
1208
+ [templateSlot]="separator.template"
1209
+ [templateSlotContext]="gap.context"
1210
+ /></view>
1211
+ }
1187
1212
  }
1188
- }
1213
+ </view>
1214
+ <view [style]="footerStyle()"><ng-content select="[listFooter]" /></view>
1189
1215
  </view>
1190
- <view [style]="footerStyle()"><ng-content select="[listFooter]" /></view>
1191
1216
  `,
1192
1217
  host: {
1193
1218
  // ScrollView.js's `baseHorizontal`: a column would stack the header, canvas and footer, and
@@ -1223,7 +1248,7 @@ export class VirtualListRow {
1223
1248
  this.list.measureRow(this.row(), event.nativeEvent?.layout);
1224
1249
  }
1225
1250
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: VirtualListRow, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1226
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.2.0", type: VirtualListRow, isStandalone: true, selector: "[virtualListRow]", inputs: { row: { classPropertyName: "row", publicName: "virtualListRow", isSignal: true, isRequired: true, transformFunction: null } }, host: { listeners: { "layout": "onLayout($event)" }, properties: { "style": "row().style" } }, ngImport: i0 });
1251
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.2.0", type: VirtualListRow, isStandalone: true, selector: "[virtualListRow]", inputs: { row: { classPropertyName: "row", publicName: "virtualListRow", isSignal: true, isRequired: true, transformFunction: null } }, host: { listeners: { "layout": "onLayout($event)" }, properties: { "style": "row().style", "attr.aria-hidden": "row().parked ? true : null" } }, ngImport: i0 });
1227
1252
  }
1228
1253
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: VirtualListRow, decorators: [{
1229
1254
  type: Directive,
@@ -1231,6 +1256,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
1231
1256
  selector: '[virtualListRow]',
1232
1257
  host: {
1233
1258
  '[style]': 'row().style',
1259
+ // A parked row is not read out: see `PARKED`.
1260
+ '[attr.aria-hidden]': 'row().parked ? true : null',
1234
1261
  '(layout)': 'onLayout($event)',
1235
1262
  },
1236
1263
  }]