@ng-native/components 0.15.0 → 0.17.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.
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard-dock.js","sourceRoot":"","sources":["../src/keyboard-dock.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,UAAU,EACV,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,MAAM,EACN,KAAK,EACL,MAAM,EACN,SAAS,GAEV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EACL,SAAS,EACT,cAAc,EACd,gBAAgB,GAIjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;;AAE1C;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,cAAc,CAAU,qBAAqB,EAAE;IACpF,UAAU,EAAE,MAAM;IAClB,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK;CACrB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,yBAAyB;IACvC,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC1D,CAAC;AAED,+FAA+F;AAC/F,MAAM,aAAa,GAAG;IACpB,MAAM,EAAE,CAAC,gBAAgB,EAAE,2BAA2B,EAAE,mBAAmB,CAAC;IAC5E,IAAI,EAAE,CAAC,QAAQ,CAAC;CACjB,CAAC;AAEF,+FAA+F;AAC/F,MAAM,GAAG,GAAG,MAAM,CAAC;AAEnB,+EAA+E;AAC/E,MAAM,WAAW,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AAIlE,6FAA6F;AAK7F,MAAM,OAAO,sBAAsB;IACjC;QACE,gBAAgB,CAAC,0BAA0B,EAAE,wBAAwB,CAAC,CAAC;QACvE,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC,CAAC;IAC1D,CAAC;uGAJU,sBAAsB;2FAAtB,sBAAsB;;2FAAtB,sBAAsB;kBAJlC,SAAS;mBAAC;oBACT,QAAQ,EAAE,0BAA0B;oBACpC,IAAI,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE;iBAC9B;;AAQD;;;GAGG;AAKH,MAAM,OAAO,mBAAmB;IACrB,MAAM,GAAG,KAAK;0FAAU,CAAC;IACzB,iBAAiB,GAAG,KAAK;qGAAU,CAAC;IAE7C;QACE,gBAAgB,CAAC,uBAAuB,EAAE,qBAAqB,CAAC,CAAC;QACjE,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC,CAAC;IAC1D,CAAC;uGAPU,mBAAmB;2FAAnB,mBAAmB;;2FAAnB,mBAAmB;kBAJ/B,SAAS;mBAAC;oBACT,QAAQ,EAAE,uBAAuB;oBACjC,IAAI,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,qBAAqB,EAAE,qBAAqB,EAAE;iBAC/E;;AAWD;;;;;;;;;;;;;;;;;;;GAmBG;AAoCH,MAAM,OAAO,YAAa,SAAQ,QAAQ;IACxC,qFAAqF;IAC5E,eAAe,GAAG,KAAK;mGAAU,CAAC;IAE1B,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC5B,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC1B,IAAI,GAAG,EAAE,CAAC;IACV,SAAS,GAAG,WAAW,CAAC;IAE3C,8FAA8F;IAC3E,KAAK,GAAG,cAAc,EAAE,KAAK,KAAK,IAAI,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAEpE,KAAK,GAAG,YAAY,CAAC,SAAS,6EAAI,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,GAAG,CAAC;IACzE,UAAU,GAAG,YAAY,CAAC,SAAS,kFAAI,WAAW,EAAE,IAAI,GAAG,CAAC;IAE7E,wFAAwF;IACvE,UAAU,GAAG,MAAM,CAAgB,IAAI;mFAAC,CAAC;IAC1D,4FAA4F;IACzE,SAAS,GAAG,MAAM,CAAC,CAAC;kFAAC,CAAC;IACzC,yFAAyF;IACxE,UAAU,GAAG,MAAM,CAAC,CAAC;mFAAC,CAAC;IAExC,gGAAgG;IAC/E,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM;8EAAC,CAAC;IAE9D,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,CAC/B,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gFAC/D,CAAC;IAEiB,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE;QACzC,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;QACpC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACpD,IAAI,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAC/B,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;QAC1D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;IACrC,CAAC;gFAAC,CAAC;IAEH;;;OAGG;IACgB,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,EAAE,aAAqC,CAAC;QACjE,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAC;QACtC,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,EAAE,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;QAC3C,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;QACpB,MAAM,EAAE,GAAG,iBAAiB,EAAE,KAAK,EAAE,CAAC;QACtC,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;QAC3D,OAAO,EAAE,CAAC;IACZ,CAAC;gFAAC,CAAC;IAEH,iGAAiG;IACxF,SAAS,GAAG,QAAQ,CAAc,GAAG,EAAE;QAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QAC3B,OAAO,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IAC5D,CAAC;kFAAC,CAAC;IAEK,MAAM,GAAoB,IAAI,CAAC;IAEtB,OAAO,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;IAEnD;QACE,KAAK,EAAE,CAAC;QACR,IAAI,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/B,4FAA4F;QAC5F,kCAAkC;QAClC,MAAM,KAAK,GAAG,MAAM,CAClB,GAAG,EAAE;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YAC/B,SAAS,CAAC,GAAG,EAAE;gBACb,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;oBAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YACtE,CAAC,CAAC,CAAC;QACL,CAAC,6EACC,QAAQ,EAAE,MAAM,CAAC,mBAAmB,CAAC,EAAE,aAAa,EAAE,IAAI,GAC7D,CAAC;QACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACtD,CAAC;IAED;;;OAGG;IACH,IAAI,CAAC,IAAc,EAAE,QAAuB,IAAI;QAC9C,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;IAC/F,CAAC;IAEO,OAAO;QACb,IAAI,KAAK,GAAuB,IAAI,CAAC;QACrC,iBAAiB,CAAC,GAAG,EAAE;YACrB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAC/B,SAAS,CAAC,GAAG,EAAE;gBACb,IAAI,KAAK;oBAAE,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACtC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC3B,IAAI,GAAG;oBAAE,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClC,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;IAED,sEAAsE;IAC9D,cAAc;QACpB,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC;QAC1F,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,gEAAgE;IACxD,OAAO;QACb,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,CAAC;IAES,cAAc,CAAC,KAAc;QACrC,MAAM,MAAM,GAAI,KAA2B,CAAC,WAAW,EAAE,MAAM,CAAC;QAChE,IAAI,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACxD,CAAC;IAES,WAAW,CAAC,KAAyD;QAC7E,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC;QACjD,IAAI,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvD,CAAC;IAES,QAAQ;QAChB,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACzF,CAAC;uGAhIU,YAAY;2FAAZ,YAAY,iaAYe,SAAS,2BAA6B,UAAU,0EAC3C,SAAS,mHA1C1C;;;;;;;;;;;;;;;;;;;;;;GAsBT,4DAzEU,sBAAsB,qEAetB,mBAAmB,2GAiCyB,YAAY,4GAAE,IAAI;;2FAgC9D,YAAY;kBAnCxB,SAAS;mBAAC;oBACT,QAAQ,EAAE,eAAe;oBACzB,QAAQ,EAAE,cAAc;oBACxB,OAAO,EAAE,CAAC,sBAAsB,EAAE,mBAAmB,EAAE,YAAY,EAAE,IAAI,CAAC;oBAC1E,gGAAgG;oBAChG,kEAAkE;oBAClE,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;GAsBT;oBACD,IAAI,EAAE;wBACJ,uBAAuB,EAAE,WAAW;wBACpC,yBAAyB,EAAE,mBAAmB;wBAC9C,UAAU,EAAE,YAAY;qBACzB;iBACF;kOAauC,SAAS,QAAE,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,yFAC7C,SAAS,QAAE,EAAE,WAAW,EAAE,IAAI,EAAE;AAsH7E,IAAI,KAAK,GAAG,CAAC,CAAC;AAEd;;;;;;;;;;GAUG;AAKH,MAAM,OAAO,YAAY;IACd,IAAI,GAAG,KAAK,CAAC,QAAQ,2EAAiB,KAAK,EAAE,cAAc,GAAG,CAAC;IAErD,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;QACtC,OAAO,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3C,CAAC;gFAAC,CAAC;IAEH;QACE,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC;QAC1D,IAAI,KAAK,GAAuB,IAAI,CAAC;QACrC,iBAAiB,CAAC,GAAG,EAAE;YACrB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YAC/B,SAAS,CAAC,GAAG,EAAE;gBACb,IAAI,CAAC,KAAK;oBAAE,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBAC7C,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;gBACrB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACxB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;uGAtBU,YAAY;2FAAZ,YAAY;;2FAAZ,YAAY;kBAJxB,SAAS;mBAAC;oBACT,QAAQ,EAAE,gBAAgB;oBAC1B,IAAI,EAAE,EAAE,uBAAuB,EAAE,WAAW,EAAE;iBAC/C","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_MOTION, 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\n/** Over the dock, out of the way of the layout, so its bottom edge is the dock's. */\nconst OVER_DOCK = { position: 'absolute', top: 0, bottom: 0, left: 0, width: 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 * The native view `KEYBOARD_MOTION` names, which reports how far the keyboard covers the dock at\n * every frame of its motion. See `KeyboardDock`.\n */\n@Directive({ selector: 'keyboard-motion-view' })\nexport class KeyboardMotionView {\n constructor() {\n const motion = inject(KEYBOARD_MOTION);\n if (motion) registerViewName('keyboard-motion-view', motion.viewName);\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 * On Android with `@ng-native/expo`'s `provideNativeKeyboard()`, the bar is moved the same way, by\n * a view of that package's that reports how far the keyboard covers the dock at every frame. It is\n * measured on screen, so a dock that is not at the bottom of the window, or is in a form sheet\n * react-native-screens raises with the keyboard, rises only as far as it still must. Android does\n * not use react-native-keyboard-controller: its view adds the navigation bar as margins around the\n * whole app's content, which an app drawn edge to edge does not want.\n *\n * Otherwise the bar is padded by as much of the keyboard as covers it, which it learns as the\n * keyboard starts to move; on Android it jumps into place then, rather than moving with the\n * keyboard. A window the system resized for the keyboard already puts it clear, and then it is not\n * 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, KeyboardMotionView, 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 @if (motion) {\n <keyboard-motion-view\n [style]=\"overDock\"\n (keyboardMoveStart)=\"onKeyboardMove($event)\"\n (keyboardMoveEnd)=\"onKeyboardMove($event)\"\n />\n } @else {\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 }\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 protected readonly overDock = OVER_DOCK;\n\n /** On Android, the view that reports the keyboard's motion, where the build has one. */\n protected readonly motion = nativePlatform() === 'android' ? inject(KEYBOARD_MOTION) : null;\n\n /**\n * Native moves the bar: on iOS with the controller, on Android with the motion view. Read once:\n * neither changes under an app.\n */\n protected readonly lifts =\n this.motion !== null || (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 /**\n * The keyboard's height over the dock as native last reported it: the controller's keyboard\n * height, excluding the drag area, or the motion view's cover of the dock.\n */\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 or the motion view, the one whose events carry the keyboard. */\n private controllerView(): HostNode {\n this.source ??= this.node.childList.find((child) => child.nodeKind === '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.childList.filter((child) => child.nodeKind === '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,5 +1,5 @@
1
1
  import { type Token } from 'marked';
2
- import type { ImageLoadEvent } from './events.ts';
2
+ import type { ImageLoadEvent, LayoutEvent } from './events.ts';
3
3
  import { blockKey, type MarkdownBlock, type MarkdownElement, type MarkdownInline } from './markdown-blocks.ts';
4
4
  import * as i0 from "@angular/core";
5
5
  export type { MarkdownElement } from './markdown-blocks.ts';
@@ -16,6 +16,14 @@ export interface MarkdownLinkPress {
16
16
  type MarkdownLink = Extract<MarkdownInline, {
17
17
  kind: 'link';
18
18
  }>;
19
+ /** A loaded image's own width in points, and its width over its height. */
20
+ interface ImageSize {
21
+ readonly width: number;
22
+ readonly ratio: number;
23
+ }
24
+ type ImageBlock = Extract<MarkdownBlock, {
25
+ kind: 'image';
26
+ }>;
19
27
  /**
20
28
  * A Markdown document as `<text>`, `<view>`, `<image>` and `<scroll-view>`: headings, paragraphs,
21
29
  * emphasis, inline code, links, quotes, ordered, unordered and task lists, code blocks, rules,
@@ -75,14 +83,31 @@ export declare class Markdown {
75
83
  protected readonly blocks: import("@angular/core").WritableSignal<readonly MarkdownBlock[]>;
76
84
  private readonly classOf;
77
85
  /**
78
- * Each image's width over its height, once it has loaded, so it is drawn at the page's width and
79
- * its own shape. An image the document no longer has is forgotten when the document changes.
86
+ * Each image's own width and its width over its height, once it has loaded, so it is drawn at its
87
+ * own size and shape, as a browser draws it, and no wider than the document. The width reaches
88
+ * the image as `--md-img-width`, which `.md-img` reads, so a class of the app's own still sets
89
+ * the width. An image the document no longer has is forgotten when the document changes.
90
+ */
91
+ protected readonly sizes: import("@angular/core").WritableSignal<ReadonlyMap<string, ImageSize>>;
92
+ /**
93
+ * The width each image is laid out at where that is less than its own, narrowed by
94
+ * `max-width`. Yoga takes an image's height from its width before `max-width` narrows it, where
95
+ * CSS takes it after, so a narrowed image is given the height its ratio makes of the width it
96
+ * has. An image no longer in the document is forgotten when the document changes.
80
97
  */
81
- protected readonly ratios: import("@angular/core").WritableSignal<ReadonlyMap<string, number>>;
98
+ protected readonly narrowed: import("@angular/core").WritableSignal<ReadonlyMap<{
99
+ readonly kind: "image";
100
+ readonly src: string;
101
+ readonly alt: string;
102
+ }, number>>;
82
103
  private lex;
83
104
  /** `tokens`' blocks, converting and sharing only those of tokens the stream had not kept. */
84
105
  private streamedBlocks;
85
106
  protected classFor(element: MarkdownElement): string;
107
+ protected imageWidth(src: string): string | null;
108
+ /** The class's height until the image loads, then its ratio's, or a narrowed image's own. */
109
+ protected imageHeight(block: ImageBlock): number | 'auto' | null;
110
+ protected layOutImage(block: ImageBlock, event: Partial<LayoutEvent>): void;
86
111
  protected sizeImage(src: string, event: Partial<ImageLoadEvent>): void;
87
112
  protected followLink(link: MarkdownLink): void;
88
113
  static ɵfac: i0.ɵɵFactoryDeclaration<Markdown, never>;
package/dist/markdown.js CHANGED
@@ -27,16 +27,16 @@ const ELEMENTS = [
27
27
  ...['pre', 'code', 'strong', 'em', 'del', 'a', 'hr', 'img', 'table', 'tr', 'th', 'td'],
28
28
  ];
29
29
  const DEFAULT_CLASSES = Object.fromEntries(ELEMENTS.map((element) => [element, `md-${element}`]));
30
- /** Every image address in `blocks`, however deep in quotes and lists. */
31
- function imageSources(blocks, into = new Set()) {
30
+ /** Every image in `blocks`, however deep in quotes and lists. */
31
+ function imageBlocks(blocks, into = new Set()) {
32
32
  for (const block of blocks) {
33
33
  if (block.kind === 'image')
34
- into.add(block.src);
34
+ into.add(block);
35
35
  else if (block.kind === 'blockquote')
36
- imageSources(block.children, into);
36
+ imageBlocks(block.children, into);
37
37
  else if (block.kind === 'list') {
38
38
  for (const item of block.items)
39
- imageSources(item.children, into);
39
+ imageBlocks(item.children, into);
40
40
  }
41
41
  }
42
42
  return into;
@@ -113,18 +113,37 @@ export class Markdown {
113
113
  classOf = computed(() => ({ ...DEFAULT_CLASSES, ...this.classes() }), /* @ts-ignore */
114
114
  ...(ngDevMode ? [{ debugName: "classOf" }] : /* istanbul ignore next */ []));
115
115
  /**
116
- * Each image's width over its height, once it has loaded, so it is drawn at the page's width and
117
- * its own shape. An image the document no longer has is forgotten when the document changes.
116
+ * Each image's own width and its width over its height, once it has loaded, so it is drawn at its
117
+ * own size and shape, as a browser draws it, and no wider than the document. The width reaches
118
+ * the image as `--md-img-width`, which `.md-img` reads, so a class of the app's own still sets
119
+ * the width. An image the document no longer has is forgotten when the document changes.
118
120
  */
119
- ratios = linkedSignal({ ...(ngDevMode ? { debugName: "ratios" } : /* istanbul ignore next */ {}), source: this.blocks,
121
+ sizes = linkedSignal({ ...(ngDevMode ? { debugName: "sizes" } : /* istanbul ignore next */ {}), source: this.blocks,
120
122
  computation: (blocks, previous) => {
121
123
  const kept = new Map();
122
124
  if (!previous)
123
125
  return kept;
124
- const present = imageSources(blocks);
125
- for (const [src, ratio] of previous.value)
126
+ const present = new Set([...imageBlocks(blocks)].map((block) => block.src));
127
+ for (const [src, size] of previous.value)
126
128
  if (present.has(src))
127
- kept.set(src, ratio);
129
+ kept.set(src, size);
130
+ return kept;
131
+ } });
132
+ /**
133
+ * The width each image is laid out at where that is less than its own, narrowed by
134
+ * `max-width`. Yoga takes an image's height from its width before `max-width` narrows it, where
135
+ * CSS takes it after, so a narrowed image is given the height its ratio makes of the width it
136
+ * has. An image no longer in the document is forgotten when the document changes.
137
+ */
138
+ narrowed = linkedSignal({ ...(ngDevMode ? { debugName: "narrowed" } : /* istanbul ignore next */ {}), source: this.blocks,
139
+ computation: (blocks, previous) => {
140
+ const kept = new Map();
141
+ if (!previous)
142
+ return kept;
143
+ const present = imageBlocks(blocks);
144
+ for (const [block, width] of previous.value)
145
+ if (present.has(block))
146
+ kept.set(block, width);
128
147
  return kept;
129
148
  } });
130
149
  lex(source, streaming) {
@@ -156,6 +175,35 @@ export class Markdown {
156
175
  classFor(element) {
157
176
  return this.classOf()[element];
158
177
  }
178
+ imageWidth(src) {
179
+ const size = this.sizes().get(src);
180
+ return size ? `${size.width}px` : null;
181
+ }
182
+ /** The class's height until the image loads, then its ratio's, or a narrowed image's own. */
183
+ imageHeight(block) {
184
+ const size = this.sizes().get(block.src);
185
+ if (!size)
186
+ return null;
187
+ const width = this.narrowed().get(block);
188
+ return width === undefined ? 'auto' : width / size.ratio;
189
+ }
190
+ layOutImage(block, event) {
191
+ const width = event.nativeEvent?.layout?.width;
192
+ const size = this.sizes().get(block.src);
193
+ if (width === undefined || !size)
194
+ return;
195
+ const narrowed = width < size.width ? width : undefined;
196
+ if (this.narrowed().get(block) === narrowed)
197
+ return;
198
+ this.narrowed.update((all) => {
199
+ const next = new Map(all);
200
+ if (narrowed === undefined)
201
+ next.delete(block);
202
+ else
203
+ next.set(block, narrowed);
204
+ return next;
205
+ });
206
+ }
159
207
  sizeImage(src, event) {
160
208
  const source = event.nativeEvent?.source;
161
209
  if (!source)
@@ -163,7 +211,9 @@ export class Markdown {
163
211
  const { width, height } = source;
164
212
  if (!(width > 0 && height > 0))
165
213
  return;
166
- this.ratios.update((ratios) => new Map(ratios).set(src, width / height));
214
+ // A url's or a data url's scale is 1, so the load event's pixels are the points a browser's
215
+ // CSS pixels are.
216
+ this.sizes.update((sizes) => new Map(sizes).set(src, { width, ratio: width / height }));
167
217
  }
168
218
  followLink(link) {
169
219
  let prevented = false;
@@ -198,9 +248,11 @@ export class Markdown {
198
248
  [alt]="block.alt || undefined"
199
249
  resizeMode="contain"
200
250
  [class]="classFor('img')"
201
- [style.aspectRatio]="ratios().get(block.src) ?? null"
202
- [style.height]="ratios().has(block.src) ? 'auto' : null"
251
+ [style.--md-img-width]="imageWidth(block.src)"
252
+ [style.aspectRatio]="sizes().get(block.src)?.ratio ?? null"
253
+ [style.height]="imageHeight(block)"
203
254
  (load)="sizeImage(block.src, $any($event))"
255
+ (layout)="layOutImage(block, $any($event))"
204
256
  />
205
257
  }
206
258
  @case ('blockquote') {
@@ -297,7 +349,7 @@ export class Markdown {
297
349
  </ng-template>
298
350
 
299
351
  <ng-container *templateSlot="blockTemplate; context: { $implicit: blocks() }" />
300
- `, 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/* The text hugs its content inside the column's least width, so it can report a narrower one. */\n.md-cell {\n align-items: flex-start;\n}\n\n.md-th,\n.md-td {\n max-width: 280px;\n padding: 6px 8px;\n}\n\n.md-cell-body {\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-cell-body {\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: "directive", type: MarkdownColumns, selector: "[markdownColumns]", inputs: ["markdownColumns"], exportAs: ["markdownColumns"] }, { 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" }] });
352
+ `, 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: var(--md-img-width, 100%);\n max-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/* The text hugs its content inside the column's least width, so it can report a narrower one. */\n.md-cell {\n align-items: flex-start;\n}\n\n.md-th,\n.md-td {\n max-width: 280px;\n padding: 6px 8px;\n}\n\n.md-cell-body {\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-cell-body {\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: "directive", type: MarkdownColumns, selector: "[markdownColumns]", inputs: ["markdownColumns"], exportAs: ["markdownColumns"] }, { 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" }] });
301
353
  }
302
354
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: Markdown, decorators: [{
303
355
  type: Component,
@@ -321,9 +373,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
321
373
  [alt]="block.alt || undefined"
322
374
  resizeMode="contain"
323
375
  [class]="classFor('img')"
324
- [style.aspectRatio]="ratios().get(block.src) ?? null"
325
- [style.height]="ratios().has(block.src) ? 'auto' : null"
376
+ [style.--md-img-width]="imageWidth(block.src)"
377
+ [style.aspectRatio]="sizes().get(block.src)?.ratio ?? null"
378
+ [style.height]="imageHeight(block)"
326
379
  (load)="sizeImage(block.src, $any($event))"
380
+ (layout)="layOutImage(block, $any($event))"
327
381
  />
328
382
  }
329
383
  @case ('blockquote') {
@@ -423,6 +477,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
423
477
  `, host: {
424
478
  '[class.md-ios]': "platform === 'ios'",
425
479
  '[class.md-android]': "platform === 'android'",
426
- }, 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/* The text hugs its content inside the column's least width, so it can report a narrower one. */\n.md-cell {\n align-items: flex-start;\n}\n\n.md-th,\n.md-td {\n max-width: 280px;\n padding: 6px 8px;\n}\n\n.md-cell-body {\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-cell-body {\n border-color: #38383a;\n }\n\n .md-hr {\n background-color: #38383a;\n }\n}\n"] }]
480
+ }, 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: var(--md-img-width, 100%);\n max-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/* The text hugs its content inside the column's least width, so it can report a narrower one. */\n.md-cell {\n align-items: flex-start;\n}\n\n.md-th,\n.md-td {\n max-width: 280px;\n padding: 6px 8px;\n}\n\n.md-cell-body {\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-cell-body {\n border-color: #38383a;\n }\n\n .md-hr {\n background-color: #38383a;\n }\n}\n"] }]
427
481
  }], propDecorators: { source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], tokens: [{ type: i0.Input, args: [{ isSignal: true, alias: "tokens", required: false }] }], streaming: [{ type: i0.Input, args: [{ isSignal: true, alias: "streaming", required: false }] }], classes: [{ type: i0.Input, args: [{ isSignal: true, alias: "classes", required: false }] }], linkPress: [{ type: i0.Output, args: ["linkPress"] }] } });
428
482
  //# sourceMappingURL=markdown.js.map
@@ -1 +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 {\n Component,\n Injector,\n booleanAttribute,\n computed,\n inject,\n input,\n linkedSignal,\n output,\n} 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 blockKey,\n markdownBlocks,\n opensOnPress,\n shareBlocks,\n type MarkdownBlock,\n type MarkdownElement,\n type MarkdownInline,\n} from './markdown-blocks.ts';\nimport { MarkdownColumns } from './markdown-columns.ts';\nimport { MarkdownStream } from './markdown-stream.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 * For a source that grows as it streams in, an answer arriving a few words at a time, set\n * `streaming` until it is complete. The blocks before the last one are then kept as they are and\n * only the last is lexed and converted again, unless a chunk defines or changes a link reference,\n * which can change a block already kept, and the whole source is lexed again. Syntax still open at its end, a code span, an emphasis, a link,\n * an image or a table row, is held back until it closes. Of a code fence only an unfinished\n * opening line is held back, and its code then streams as it arrives. Clearing `streaming` draws\n * the whole source as written, and `tokens` are always drawn as given. A block that did not\n * change keeps its views, streaming or not.\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, MarkdownColumns, ScrollView, TemplateSlot, Text, View],\n template: `\n <ng-template #blockTemplate let-list>\n @for (block of list; track blockKey(block)) {\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 <scroll-view horizontal>\n <view\n [class]=\"classFor('table')\"\n [markdownColumns]=\"block\"\n #columns=\"markdownColumns\"\n >\n <view [class]=\"classFor('tr')\">\n @for (cell of block.header; track $index; let column = $index) {\n <view class=\"md-cell\" [style.minWidth]=\"columns.width(column)\">\n <text\n [class]=\"classFor('th')\"\n (layout)=\"columns.measure(0, column, $any($event))\"\n ><ng-container *templateSlot=\"inlineTemplate; context: { $implicit: cell }\"\n /></text>\n </view>\n }\n </view>\n @for (row of block.rows; track $index; let r = $index) {\n <view [class]=\"classFor('tr')\">\n @for (cell of row; track $index; let column = $index) {\n <view class=\"md-cell md-cell-body\" [style.minWidth]=\"columns.width(column)\">\n <text\n [class]=\"classFor('td')\"\n (layout)=\"columns.measure(r + 1, column, $any($event))\"\n ><ng-container\n *templateSlot=\"inlineTemplate; context: { $implicit: cell }\"\n /></text>\n </view>\n }\n </view>\n }\n </view>\n </scroll-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 /**\n * Whether `source` is still arriving, a chunk at a time. While it is, only its last block is\n * lexed again, unless a link reference changes, and syntax still open at its end is held back.\n * See the class note.\n */\n readonly streaming = input(false, { transform: booleanAttribute });\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 private readonly stream = new MarkdownStream();\n protected readonly blockKey = blockKey;\n\n /**\n * The blocks each token the stream kept was drawn as. A kept token is the same object from one\n * chunk to the next, so its blocks are not converted or compared again; a source lexed whole\n * makes new tokens, and so new blocks.\n */\n private readonly streamed = new WeakMap<Token, readonly MarkdownBlock[]>();\n\n /** The document's blocks, with each one that did not change kept, so it is not drawn again. */\n protected readonly blocks = linkedSignal({\n source: () => {\n const tokens = this.tokens();\n if (tokens) return { tokens, streamed: false };\n const streamed = this.streaming();\n return { tokens: this.lex(this.source() ?? '', streamed), streamed };\n },\n computation: ({ tokens, streamed }, previous?: { value: readonly MarkdownBlock[] }) =>\n streamed\n ? this.streamedBlocks(tokens, previous?.value)\n : shareBlocks(previous?.value, markdownBlocks(tokens)),\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 private lex(source: string, streaming: boolean): readonly Token[] {\n return streaming ? this.stream.lex(source) : lexer(source);\n }\n\n /** `tokens`' blocks, converting and sharing only those of tokens the stream had not kept. */\n private streamedBlocks(\n tokens: readonly Token[],\n previous: readonly MarkdownBlock[] | undefined,\n ): readonly MarkdownBlock[] {\n const kept: MarkdownBlock[] = [];\n let at = 0;\n for (; at < tokens.length; at++) {\n const blocks = this.streamed.get(tokens[at]!);\n if (!blocks) break;\n kept.push(...blocks);\n }\n const rest = tokens.slice(at);\n const converted = rest.map((token) => markdownBlocks([token]));\n // The blocks drawn after the kept ones, when those are still the ones drawn first.\n const drawn = !kept.length || previous?.[kept.length - 1] === kept.at(-1);\n const was = drawn ? previous?.slice(kept.length) : undefined;\n const shared = shareBlocks(was, converted.flat());\n let from = 0;\n rest.forEach((token, i) => {\n const count = converted[i]!.length;\n this.streamed.set(token, shared.slice(from, (from += count)));\n });\n return [...kept, ...shared];\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"]}
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 {\n Component,\n Injector,\n booleanAttribute,\n computed,\n inject,\n input,\n linkedSignal,\n output,\n} from '@angular/core';\nimport { DeepLinks } from '@ng-native/device';\nimport { nativePlatform } from '@ng-native/fabric';\nimport { lexer, type Token } from 'marked';\nimport type { ImageLoadEvent, LayoutEvent } from './events.ts';\nimport { Image } from './image.ts';\nimport {\n blockKey,\n markdownBlocks,\n opensOnPress,\n shareBlocks,\n type MarkdownBlock,\n type MarkdownElement,\n type MarkdownInline,\n} from './markdown-blocks.ts';\nimport { MarkdownColumns } from './markdown-columns.ts';\nimport { MarkdownStream } from './markdown-stream.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/** A loaded image's own width in points, and its width over its height. */\ninterface ImageSize {\n readonly width: number;\n readonly ratio: number;\n}\n\ntype ImageBlock = Extract<MarkdownBlock, { kind: 'image' }>;\n\n/** Every image in `blocks`, however deep in quotes and lists. */\nfunction imageBlocks(\n blocks: readonly MarkdownBlock[],\n into = new Set<ImageBlock>(),\n): Set<ImageBlock> {\n for (const block of blocks) {\n if (block.kind === 'image') into.add(block);\n else if (block.kind === 'blockquote') imageBlocks(block.children, into);\n else if (block.kind === 'list') {\n for (const item of block.items) imageBlocks(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 * For a source that grows as it streams in, an answer arriving a few words at a time, set\n * `streaming` until it is complete. The blocks before the last one are then kept as they are and\n * only the last is lexed and converted again, unless a chunk defines or changes a link reference,\n * which can change a block already kept, and the whole source is lexed again. Syntax still open at its end, a code span, an emphasis, a link,\n * an image or a table row, is held back until it closes. Of a code fence only an unfinished\n * opening line is held back, and its code then streams as it arrives. Clearing `streaming` draws\n * the whole source as written, and `tokens` are always drawn as given. A block that did not\n * change keeps its views, streaming or not.\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, MarkdownColumns, ScrollView, TemplateSlot, Text, View],\n template: `\n <ng-template #blockTemplate let-list>\n @for (block of list; track blockKey(block)) {\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.--md-img-width]=\"imageWidth(block.src)\"\n [style.aspectRatio]=\"sizes().get(block.src)?.ratio ?? null\"\n [style.height]=\"imageHeight(block)\"\n (load)=\"sizeImage(block.src, $any($event))\"\n (layout)=\"layOutImage(block, $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 <scroll-view horizontal>\n <view\n [class]=\"classFor('table')\"\n [markdownColumns]=\"block\"\n #columns=\"markdownColumns\"\n >\n <view [class]=\"classFor('tr')\">\n @for (cell of block.header; track $index; let column = $index) {\n <view class=\"md-cell\" [style.minWidth]=\"columns.width(column)\">\n <text\n [class]=\"classFor('th')\"\n (layout)=\"columns.measure(0, column, $any($event))\"\n ><ng-container *templateSlot=\"inlineTemplate; context: { $implicit: cell }\"\n /></text>\n </view>\n }\n </view>\n @for (row of block.rows; track $index; let r = $index) {\n <view [class]=\"classFor('tr')\">\n @for (cell of row; track $index; let column = $index) {\n <view class=\"md-cell md-cell-body\" [style.minWidth]=\"columns.width(column)\">\n <text\n [class]=\"classFor('td')\"\n (layout)=\"columns.measure(r + 1, column, $any($event))\"\n ><ng-container\n *templateSlot=\"inlineTemplate; context: { $implicit: cell }\"\n /></text>\n </view>\n }\n </view>\n }\n </view>\n </scroll-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 /**\n * Whether `source` is still arriving, a chunk at a time. While it is, only its last block is\n * lexed again, unless a link reference changes, and syntax still open at its end is held back.\n * See the class note.\n */\n readonly streaming = input(false, { transform: booleanAttribute });\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 private readonly stream = new MarkdownStream();\n protected readonly blockKey = blockKey;\n\n /**\n * The blocks each token the stream kept was drawn as. A kept token is the same object from one\n * chunk to the next, so its blocks are not converted or compared again; a source lexed whole\n * makes new tokens, and so new blocks.\n */\n private readonly streamed = new WeakMap<Token, readonly MarkdownBlock[]>();\n\n /** The document's blocks, with each one that did not change kept, so it is not drawn again. */\n protected readonly blocks = linkedSignal({\n source: () => {\n const tokens = this.tokens();\n if (tokens) return { tokens, streamed: false };\n const streamed = this.streaming();\n return { tokens: this.lex(this.source() ?? '', streamed), streamed };\n },\n computation: ({ tokens, streamed }, previous?: { value: readonly MarkdownBlock[] }) =>\n streamed\n ? this.streamedBlocks(tokens, previous?.value)\n : shareBlocks(previous?.value, markdownBlocks(tokens)),\n });\n\n private readonly classOf = computed(() => ({ ...DEFAULT_CLASSES, ...this.classes() }));\n\n /**\n * Each image's own width and its width over its height, once it has loaded, so it is drawn at its\n * own size and shape, as a browser draws it, and no wider than the document. The width reaches\n * the image as `--md-img-width`, which `.md-img` reads, so a class of the app's own still sets\n * the width. An image the document no longer has is forgotten when the document changes.\n */\n protected readonly sizes = linkedSignal<readonly MarkdownBlock[], ReadonlyMap<string, ImageSize>>(\n {\n source: this.blocks,\n computation: (blocks, previous) => {\n const kept = new Map<string, ImageSize>();\n if (!previous) return kept;\n const present = new Set([...imageBlocks(blocks)].map((block) => block.src));\n for (const [src, size] of previous.value) if (present.has(src)) kept.set(src, size);\n return kept;\n },\n },\n );\n\n /**\n * The width each image is laid out at where that is less than its own, narrowed by\n * `max-width`. Yoga takes an image's height from its width before `max-width` narrows it, where\n * CSS takes it after, so a narrowed image is given the height its ratio makes of the width it\n * has. An image no longer in the document is forgotten when the document changes.\n */\n protected readonly narrowed = linkedSignal<\n readonly MarkdownBlock[],\n ReadonlyMap<ImageBlock, number>\n >({\n source: this.blocks,\n computation: (blocks, previous) => {\n const kept = new Map<ImageBlock, number>();\n if (!previous) return kept;\n const present = imageBlocks(blocks);\n for (const [block, width] of previous.value) if (present.has(block)) kept.set(block, width);\n return kept;\n },\n });\n\n private lex(source: string, streaming: boolean): readonly Token[] {\n return streaming ? this.stream.lex(source) : lexer(source);\n }\n\n /** `tokens`' blocks, converting and sharing only those of tokens the stream had not kept. */\n private streamedBlocks(\n tokens: readonly Token[],\n previous: readonly MarkdownBlock[] | undefined,\n ): readonly MarkdownBlock[] {\n const kept: MarkdownBlock[] = [];\n let at = 0;\n for (; at < tokens.length; at++) {\n const blocks = this.streamed.get(tokens[at]!);\n if (!blocks) break;\n kept.push(...blocks);\n }\n const rest = tokens.slice(at);\n const converted = rest.map((token) => markdownBlocks([token]));\n // The blocks drawn after the kept ones, when those are still the ones drawn first.\n const drawn = !kept.length || previous?.[kept.length - 1] === kept.at(-1);\n const was = drawn ? previous?.slice(kept.length) : undefined;\n const shared = shareBlocks(was, converted.flat());\n let from = 0;\n rest.forEach((token, i) => {\n const count = converted[i]!.length;\n this.streamed.set(token, shared.slice(from, (from += count)));\n });\n return [...kept, ...shared];\n }\n\n protected classFor(element: MarkdownElement): string {\n return this.classOf()[element];\n }\n\n protected imageWidth(src: string): string | null {\n const size = this.sizes().get(src);\n return size ? `${size.width}px` : null;\n }\n\n /** The class's height until the image loads, then its ratio's, or a narrowed image's own. */\n protected imageHeight(block: ImageBlock): number | 'auto' | null {\n const size = this.sizes().get(block.src);\n if (!size) return null;\n const width = this.narrowed().get(block);\n return width === undefined ? 'auto' : width / size.ratio;\n }\n\n protected layOutImage(block: ImageBlock, event: Partial<LayoutEvent>): void {\n const width = event.nativeEvent?.layout?.width;\n const size = this.sizes().get(block.src);\n if (width === undefined || !size) return;\n const narrowed = width < size.width ? width : undefined;\n if (this.narrowed().get(block) === narrowed) return;\n this.narrowed.update((all) => {\n const next = new Map(all);\n if (narrowed === undefined) next.delete(block);\n else next.set(block, narrowed);\n return next;\n });\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 // A url's or a data url's scale is 1, so the load event's pixels are the points a browser's\n // CSS pixels are.\n this.sizes.update((sizes) => new Map(sizes).set(src, { width, ratio: 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"]}
@@ -41,7 +41,7 @@ export class StickyHeaders {
41
41
  scroll;
42
42
  /** Find the sticky children again, after a pass that may have changed them. */
43
43
  track(content, indices) {
44
- const children = (content?.children ?? []).filter((child) => child.kind === 'element');
44
+ const children = (content?.childList ?? []).filter((child) => child.nodeKind === 'element');
45
45
  const headers = indices.flatMap((index) => children[index] ?? []);
46
46
  for (const node of this.headers) {
47
47
  if (!headers.includes(node))
@@ -1 +1 @@
1
- {"version":3,"file":"sticky-headers.js","sourceRoot":"","sources":["../src/sticky-headers.ts"],"names":[],"mappings":"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","sourcesContent":["import { type HostEngine, type HostNode, type ScrollDrive, pinnedRange } from '@ng-native/fabric';\n\ninterface Extent {\n readonly start: number;\n readonly size: number;\n}\n\n/**\n * How long scrolling has to pause before a natively moved header's translate is written, so the\n * shadow tree, which a touch is hit-tested against, catches up with where native drew it. RN's\n * `ScrollViewStickyHeader` debounces the same write, by 64 ms on iOS.\n */\nexport const STICKY_SETTLE_MS = 64;\n\ntype LayoutEvent = {\n nativeEvent?: { layout?: { x?: number; y?: number; width?: number; height?: number } };\n};\n\n/**\n * `stickyHeaderIndices` for `<scroll-view>`, done the way RN's `ScrollView.js` does it.\n *\n * There is no native prop for it on either platform. RN wraps each sticky child in\n * `ScrollViewStickyHeader`, which translates it by the scroll offset past its own position and\n * stops where the next sticky child would push it off. The same sums run here, over the content\n * view's element children: control-flow anchors are not children in RN's sense and are skipped,\n * so an index counts what is committed.\n *\n * Each sticky child's position comes from its own layout event, and the translation is written\n * as its `transform` prop, with RN's `zIndex: 10` so it draws over the content scrolling under\n * it. A style of the caller's own that sets either wins, as it would in RN.\n *\n * Where the host can, native moves each one on every frame the scroll view moves, as RN's\n * animated interpolation does, and the translation is written only once scrolling pauses: a\n * translate written from each scroll event lands late, and on a fast fling the header shudders.\n */\nexport class StickyHeaders {\n private readonly engine: HostEngine;\n private readonly horizontal: () => boolean;\n private headers: HostNode[] = [];\n private readonly extents = new Map<HostNode, Extent>();\n private readonly listening = new Map<HostNode, () => void>();\n /** The translation last written per header, so an unchanged one is not written again. */\n private readonly written = new Map<HostNode, number>();\n private readonly drives = new Map<HostNode, ScrollDrive>();\n private settleTimer: ReturnType<typeof setTimeout> | undefined;\n private offset = 0;\n\n constructor(engine: HostEngine, scroll: HostNode, horizontal: () => boolean) {\n this.engine = engine;\n this.scroll = scroll;\n this.horizontal = horizontal;\n }\n\n private readonly scroll: HostNode;\n\n /** Find the sticky children again, after a pass that may have changed them. */\n track(content: HostNode | undefined, indices: readonly number[]): void {\n const children = (content?.children ?? []).filter((child) => child.kind === 'element');\n const headers = indices.flatMap((index) => children[index] ?? []);\n for (const node of this.headers) {\n if (!headers.includes(node)) this.release(node);\n }\n for (const node of headers) {\n if (this.listening.has(node)) continue;\n this.engine.setProp(node, 'zIndex', 10);\n this.listening.set(\n node,\n this.engine.setEventListener(node, 'topLayout', (event) => this.measured(node, event)),\n );\n }\n this.headers = headers;\n this.apply();\n }\n\n scrolled(offset: number): void {\n this.offset = offset;\n if (!this.engine.drivesScroll) return this.apply();\n clearTimeout(this.settleTimer);\n this.settleTimer = setTimeout(() => this.apply(), STICKY_SETTLE_MS);\n }\n\n destroy(): void {\n clearTimeout(this.settleTimer);\n for (const node of this.headers) this.release(node);\n this.headers = [];\n }\n\n private measured(node: HostNode, event: unknown): void {\n const layout = (event as LayoutEvent).nativeEvent?.layout;\n if (!layout) return;\n const horizontal = this.horizontal();\n this.extents.set(node, {\n start: (horizontal ? layout.x : layout.y) ?? 0,\n size: (horizontal ? layout.width : layout.height) ?? 0,\n });\n this.apply();\n }\n\n private release(node: HostNode): void {\n this.listening.get(node)?.();\n this.listening.delete(node);\n this.extents.delete(node);\n this.written.delete(node);\n this.drives.get(node)?.stop();\n this.drives.delete(node);\n this.engine.setProp(node, 'zIndex', undefined);\n this.engine.setProp(node, 'transform', undefined);\n }\n\n private drive(node: HostNode, start: number, stop: number): void {\n if (!this.engine.drivesScroll) return;\n const range = pinnedRange(start, stop);\n const drive = this.drives.get(node);\n if (drive) return drive.update(range);\n const axis = this.horizontal() ? 'x' : 'y';\n const next = this.engine.driveByScroll(node, this.scroll, axis, range);\n if (next) this.drives.set(node, next);\n }\n\n /** `ScrollViewStickyHeader`'s interpolation, as plain arithmetic. */\n private apply(): void {\n const key = this.horizontal() ? 'translateX' : 'translateY';\n this.headers.forEach((node, at) => {\n const own = this.extents.get(node);\n if (!own) return;\n const next = this.extents.get(this.headers[at + 1]!);\n const stop = next ? next.start - own.size : Infinity;\n this.drive(node, own.start, stop);\n const shift = Math.min(Math.max(0, this.offset - own.start), Math.max(0, stop - own.start));\n if (this.written.get(node) === shift) return;\n this.written.set(node, shift);\n this.engine.setProp(node, 'transform', shift > 0 ? [{ [key]: shift }] : undefined);\n });\n }\n}\n"]}
1
+ {"version":3,"file":"sticky-headers.js","sourceRoot":"","sources":["../src/sticky-headers.ts"],"names":[],"mappings":"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","sourcesContent":["import { type HostEngine, type HostNode, type ScrollDrive, pinnedRange } from '@ng-native/fabric';\n\ninterface Extent {\n readonly start: number;\n readonly size: number;\n}\n\n/**\n * How long scrolling has to pause before a natively moved header's translate is written, so the\n * shadow tree, which a touch is hit-tested against, catches up with where native drew it. RN's\n * `ScrollViewStickyHeader` debounces the same write, by 64 ms on iOS.\n */\nexport const STICKY_SETTLE_MS = 64;\n\ntype LayoutEvent = {\n nativeEvent?: { layout?: { x?: number; y?: number; width?: number; height?: number } };\n};\n\n/**\n * `stickyHeaderIndices` for `<scroll-view>`, done the way RN's `ScrollView.js` does it.\n *\n * There is no native prop for it on either platform. RN wraps each sticky child in\n * `ScrollViewStickyHeader`, which translates it by the scroll offset past its own position and\n * stops where the next sticky child would push it off. The same sums run here, over the content\n * view's element children: control-flow anchors are not children in RN's sense and are skipped,\n * so an index counts what is committed.\n *\n * Each sticky child's position comes from its own layout event, and the translation is written\n * as its `transform` prop, with RN's `zIndex: 10` so it draws over the content scrolling under\n * it. A style of the caller's own that sets either wins, as it would in RN.\n *\n * Where the host can, native moves each one on every frame the scroll view moves, as RN's\n * animated interpolation does, and the translation is written only once scrolling pauses: a\n * translate written from each scroll event lands late, and on a fast fling the header shudders.\n */\nexport class StickyHeaders {\n private readonly engine: HostEngine;\n private readonly horizontal: () => boolean;\n private headers: HostNode[] = [];\n private readonly extents = new Map<HostNode, Extent>();\n private readonly listening = new Map<HostNode, () => void>();\n /** The translation last written per header, so an unchanged one is not written again. */\n private readonly written = new Map<HostNode, number>();\n private readonly drives = new Map<HostNode, ScrollDrive>();\n private settleTimer: ReturnType<typeof setTimeout> | undefined;\n private offset = 0;\n\n constructor(engine: HostEngine, scroll: HostNode, horizontal: () => boolean) {\n this.engine = engine;\n this.scroll = scroll;\n this.horizontal = horizontal;\n }\n\n private readonly scroll: HostNode;\n\n /** Find the sticky children again, after a pass that may have changed them. */\n track(content: HostNode | undefined, indices: readonly number[]): void {\n const children = (content?.childList ?? []).filter((child) => child.nodeKind === 'element');\n const headers = indices.flatMap((index) => children[index] ?? []);\n for (const node of this.headers) {\n if (!headers.includes(node)) this.release(node);\n }\n for (const node of headers) {\n if (this.listening.has(node)) continue;\n this.engine.setProp(node, 'zIndex', 10);\n this.listening.set(\n node,\n this.engine.setEventListener(node, 'topLayout', (event) => this.measured(node, event)),\n );\n }\n this.headers = headers;\n this.apply();\n }\n\n scrolled(offset: number): void {\n this.offset = offset;\n if (!this.engine.drivesScroll) return this.apply();\n clearTimeout(this.settleTimer);\n this.settleTimer = setTimeout(() => this.apply(), STICKY_SETTLE_MS);\n }\n\n destroy(): void {\n clearTimeout(this.settleTimer);\n for (const node of this.headers) this.release(node);\n this.headers = [];\n }\n\n private measured(node: HostNode, event: unknown): void {\n const layout = (event as LayoutEvent).nativeEvent?.layout;\n if (!layout) return;\n const horizontal = this.horizontal();\n this.extents.set(node, {\n start: (horizontal ? layout.x : layout.y) ?? 0,\n size: (horizontal ? layout.width : layout.height) ?? 0,\n });\n this.apply();\n }\n\n private release(node: HostNode): void {\n this.listening.get(node)?.();\n this.listening.delete(node);\n this.extents.delete(node);\n this.written.delete(node);\n this.drives.get(node)?.stop();\n this.drives.delete(node);\n this.engine.setProp(node, 'zIndex', undefined);\n this.engine.setProp(node, 'transform', undefined);\n }\n\n private drive(node: HostNode, start: number, stop: number): void {\n if (!this.engine.drivesScroll) return;\n const range = pinnedRange(start, stop);\n const drive = this.drives.get(node);\n if (drive) return drive.update(range);\n const axis = this.horizontal() ? 'x' : 'y';\n const next = this.engine.driveByScroll(node, this.scroll, axis, range);\n if (next) this.drives.set(node, next);\n }\n\n /** `ScrollViewStickyHeader`'s interpolation, as plain arithmetic. */\n private apply(): void {\n const key = this.horizontal() ? 'translateX' : 'translateY';\n this.headers.forEach((node, at) => {\n const own = this.extents.get(node);\n if (!own) return;\n const next = this.extents.get(this.headers[at + 1]!);\n const stop = next ? next.start - own.size : Infinity;\n this.drive(node, own.start, stop);\n const shift = Math.min(Math.max(0, this.offset - own.start), Math.max(0, stop - own.start));\n if (this.written.get(node) === shift) return;\n this.written.set(node, shift);\n this.engine.setProp(node, 'transform', shift > 0 ? [{ [key]: shift }] : undefined);\n });\n }\n}\n"]}
package/dist/view-base.js CHANGED
@@ -147,7 +147,7 @@ export class ViewBase {
147
147
  * off the node where the host put it there, which is one property read against an injector
148
148
  * lookup that walks every ancestor element, for every element on screen.
149
149
  */
150
- engine = this.node.host ?? inject(HostEngine);
150
+ engine = this.node.engine ?? inject(HostEngine);
151
151
  /**
152
152
  * See `contributeAccessibility`. Empty for every element nothing is composed onto. Read, never
153
153
  * cached, from `ngOnInit` onwards: by then every directive on the node has been constructed and