ngwr 14.5.0 → 14.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/avatar/styles/_index.scss +46 -0
- package/fesm2022/ngwr-avatar.mjs +58 -7
- package/fesm2022/ngwr-avatar.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +16 -5
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n.mjs +13 -0
- package/fesm2022/ngwr-i18n.mjs.map +1 -1
- package/fesm2022/ngwr-image-cropper-testing.mjs +34 -3
- package/fesm2022/ngwr-image-cropper-testing.mjs.map +1 -1
- package/fesm2022/ngwr-image-cropper.mjs +98 -5
- package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
- package/fesm2022/ngwr-pagination.mjs +1 -1
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-schema-form.mjs +1 -1
- package/fesm2022/ngwr-schema-form.mjs.map +1 -1
- package/fesm2022/ngwr-select-testing.mjs +21 -2
- package/fesm2022/ngwr-select-testing.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +233 -35
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-table.mjs +10 -2
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/image-cropper/styles/_index.scss +7 -0
- package/llms-full.txt +5 -5
- package/llms.txt +12 -3
- package/mcp/providers.js +8 -2
- package/package.json +1 -1
- package/schematics/migrations/v14/index.js +90 -34
- package/schematics/migrations/v7/index.js +15 -4
- package/schematics/migrations/v8/index.js +13 -2
- package/schematics/migrations/v9/index.js +19 -10
- package/schematics/use/symbol-map.json +1 -0
- package/select/styles/_index.scss +34 -0
- package/skills/ngwr/SKILL.md +9 -4
- package/skills/ngwr/references/catalog.md +1 -1
- package/skills/ngwr/references/setup.md +2 -2
- package/table/styles/_index.scss +39 -12
- package/types/ngwr-avatar.d.ts +36 -3
- package/types/ngwr-i18n-en.d.ts +16 -5
- package/types/ngwr-i18n.d.ts +13 -0
- package/types/ngwr-image-cropper-testing.d.ts +25 -3
- package/types/ngwr-image-cropper.d.ts +88 -2
- package/types/ngwr-select-testing.d.ts +18 -1
- package/types/ngwr-select.d.ts +181 -10
- package/types/ngwr-table.d.ts +8 -0
- package/types/ngwr-version.d.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-image-cropper.mjs","sources":["../../../projects/lib/image-cropper/image-cropper.ts","../../../projects/lib/image-cropper/image-cropper.html","../../../projects/lib/image-cropper/ngwr-image-cropper.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n DestroyRef,\n type ElementRef,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n output,\n signal,\n viewChild,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { clamp, randomId } from 'ngwr/utils';\n\nimport type { WrCropHandle, WrCropRect, WrImageOutputType } from './interfaces';\n\n/**\n * Stands in for the consumer input `useI18nText` expects. None of this\n * component's three strings has a `*Label` input behind it — the catalog is the\n * override channel — so they share one empty override.\n */\nconst NO_OVERRIDE = signal<string | null>(null).asReadonly();\n\n/** Keyboard step in display pixels, and what Shift promotes it to. */\nconst KEY_STEP = 1;\nconst KEY_STEP_COARSE = 10;\n\ninterface RectPx {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/**\n * Image crop UI. Pass `[src]` (URL, `File`, or `Blob`); the user drags\n * the crop window or any of its eight handles. Optionally lock the crop\n * to a fixed `[aspectRatio]`.\n *\n * The crop window is also the one tab stop: arrow keys move it, Alt with an\n * arrow key resizes it, and Shift makes either step ten pixels instead of one.\n *\n * `(cropped)` fires after each drag end — or each run of arrow keys — with a\n * freshly-rendered `Blob` of the cropped region. For one-off reads use\n * `toBlob()` / `toDataUrl()`.\n *\n * @example\n * ```html\n * <wr-image-cropper\n * [src]=\"file\"\n * [aspectRatio]=\"1\"\n * (cropped)=\"onBlob($event)\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/image-cropper\n */\n@Component({\n selector: 'wr-image-cropper',\n templateUrl: './image-cropper.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-image-cropper' },\n})\nexport class WrImageCropper {\n /** Image source — URL string, `File`, or `Blob`. */\n readonly src = input<string | File | Blob | null>(null);\n\n /** Aspect ratio (width / height). `null` = free. @default null */\n readonly aspectRatio = input<number | null>(null);\n\n /** Minimum crop width in display pixels. @default 32 */\n readonly minWidth = input(32, { transform: (v: unknown): number => Math.max(8, coerceNumberProperty(v, 32)) });\n\n /** Minimum crop height in display pixels. @default 32 */\n readonly minHeight = input(32, { transform: (v: unknown): number => Math.max(8, coerceNumberProperty(v, 32)) });\n\n /** Default output type for `(cropped)`. @default 'image/png' */\n readonly outputType = input<WrImageOutputType>('image/png');\n\n /** JPEG / WebP quality for `(cropped)` in [0, 1]. @default 0.92 */\n readonly outputQuality = input(0.92);\n\n /** Emits a Blob after each drag end. */\n readonly cropped = output<Blob>();\n\n protected readonly imgEl = viewChild.required<ElementRef<HTMLImageElement>>('img');\n\n /** Resolved object URL for `src` (so File / Blob render in `<img>`). */\n protected readonly emptyText = useI18nText(NO_OVERRIDE, 'imageCropper.empty', 'No image');\n\n /** Accessible name of the crop window — the component's only tab stop. */\n protected readonly resolvedWindowLabel = useI18nText(NO_OVERRIDE, 'imageCropper.window', 'Crop region');\n\n /** The key model, referenced by the window's `aria-describedby`. */\n protected readonly resolvedKeyHelp = useI18nText(\n NO_OVERRIDE,\n 'imageCropper.keyHelp',\n 'Arrow keys move the crop. Hold Alt with an arrow key to resize it, and Shift for larger steps. ' +\n 'The crop is announced as left, top, width and height in image pixels.'\n );\n\n /** Id linking the crop window to the help text. Random rather than counted:\n * the crop UI only exists after a real image load, so it never prerenders. */\n protected readonly helpId = randomId('wr-image-cropper-help');\n\n /** Live-region text — written by the keyboard path only (see `onWindowKeydown`). */\n protected readonly announcement = signal<string>('');\n\n protected readonly objectUrl = signal<string | null>(null);\n\n /** Previous object URL we created — kept off-signal so the resolve\n * effect doesn't depend on its own writes (which would loop). */\n private previousObjectUrl: string | null = null;\n\n /** Natural (source) pixel dimensions. */\n protected readonly natural = signal<{ w: number; h: number }>({ w: 0, h: 0 });\n\n /** Display (rendered) pixel dimensions. */\n protected readonly display = signal<{ w: number; h: number }>({ w: 0, h: 0 });\n\n /** Crop rect in display coordinates. */\n protected readonly cropDisplay = signal<RectPx>({ x: 0, y: 0, w: 0, h: 0 });\n\n /** Watches the rendered image box — see `watchResize`. */\n private resizeObserver: ResizeObserver | null = null;\n\n /** Currently active drag handle. */\n private active: WrCropHandle | null = null;\n private startPointer: { x: number; y: number } = { x: 0, y: 0 };\n private startRect: RectPx = { x: 0, y: 0, w: 0, h: 0 };\n\n private readonly destroyRef = inject(DestroyRef);\n\n /** Resolved crop rect in natural (source) pixel coordinates. */\n readonly cropRect = computed<WrCropRect>(() => {\n const display = this.display();\n const natural = this.natural();\n const c = this.cropDisplay();\n if (display.w === 0 || display.h === 0) return { x: 0, y: 0, width: 0, height: 0 };\n const sx = natural.w / display.w;\n const sy = natural.h / display.h;\n return {\n x: Math.round(c.x * sx),\n y: Math.round(c.y * sy),\n width: Math.round(c.w * sx),\n height: Math.round(c.h * sy),\n };\n });\n\n constructor() {\n // Resolve the source into a usable URL the <img> can render.\n // We only read `src()` here — `previousObjectUrl` is a plain field, so\n // writing to `objectUrl` doesn't re-trigger this effect (and hang the\n // tab on file upload).\n effect(() => {\n const src = this.src();\n if (this.previousObjectUrl) {\n URL.revokeObjectURL(this.previousObjectUrl);\n this.previousObjectUrl = null;\n }\n // The crop is measured against the image that WAS showing, and `cropRect` is a\n // public signal a consumer can read at any moment. Left in place it kept\n // reporting a rect scaled to the previous image for the whole gap between a new\n // `src` and its load event — long enough to crop the wrong thing.\n this.resetGeometry();\n if (!src) {\n this.objectUrl.set(null);\n return;\n }\n if (typeof src === 'string') {\n this.objectUrl.set(src);\n } else {\n const url = URL.createObjectURL(src);\n this.previousObjectUrl = url;\n this.objectUrl.set(url);\n }\n });\n\n this.destroyRef.onDestroy(() => {\n if (this.previousObjectUrl) URL.revokeObjectURL(this.previousObjectUrl);\n this.resizeObserver?.disconnect();\n });\n }\n\n // Image load\n\n /**\n * Back to \"nothing measured yet\", which is also what an absent `src` means.\n *\n * `display` is the field that gates everything downstream — the crop UI renders on\n * `display().w > 0` and `cropRect` returns zeros without it — so it is the only one\n * a test can observe. The other two are reset for coherence: the resize observer\n * below also writes `display`, and left alone they would resurrect a crop measured\n * against the previous image (which is why `onResize` refuses to run on a zeroed\n * `display` — see there).\n */\n private resetGeometry(): void {\n this.natural.set({ w: 0, h: 0 });\n this.display.set({ w: 0, h: 0 });\n this.cropDisplay.set({ x: 0, y: 0, w: 0, h: 0 });\n }\n\n protected onImageLoad(): void {\n const img = this.imgEl().nativeElement;\n const rect = img.getBoundingClientRect();\n const display = { w: rect.width, h: rect.height };\n // Some SVGs and odd images report zero natural dimensions — fall back\n // to the rendered size so the crop math stays well-defined.\n const natural = {\n w: img.naturalWidth || display.w,\n h: img.naturalHeight || display.h,\n };\n this.natural.set(natural);\n this.display.set(display);\n this.cropDisplay.set(this.initialCrop(display));\n this.watchResize(img);\n }\n\n /**\n * Keep `display` on the box the image is actually rendered at.\n *\n * The stylesheet sizes the image responsively — `max-width: 100%`,\n * `max-height: 70dvh`, `height: auto` — so the single measurement taken in the\n * `(load)` handler stops being true as soon as the container or the viewport\n * changes. Everything the user sees and touches is in DISPLAY pixels: the crop\n * window's inline `left` / `top` / `width` / `height`, the backdrop cut-out, and\n * the bounds `applyMove` / `applyResize` clamp against. Measured once, a narrowed\n * window leaves the crop painted past the edge of the image and draggable off it.\n *\n * Re-observed per load rather than once, because a new `src` may mount a new\n * `<img>`; the old observation would then be on a detached element.\n */\n private watchResize(el: HTMLImageElement): void {\n if (typeof ResizeObserver === 'undefined') return;\n this.resizeObserver?.disconnect();\n // Re-read the same way `onImageLoad` does rather than take the entry's\n // `contentRect`, so the two measurements can't disagree about the box.\n const ro = new ResizeObserver(() => this.onResize(el));\n ro.observe(el);\n this.resizeObserver = ro;\n }\n\n /**\n * Rescale the crop by however much the image did, so the user keeps the region\n * they picked instead of having it re-centred: `cropRect()` — the source-pixel\n * value a consumer acts on — comes out the same across the resize.\n */\n private onResize(el: HTMLImageElement): void {\n const previous = this.display();\n // Nothing measured yet, or measured and then dropped by `resetGeometry` while a\n // new `src` loads. There is no crop to carry over, and the load will measure.\n if (previous.w === 0 || previous.h === 0) return;\n const rect = el.getBoundingClientRect();\n // A hidden image (a closed tab, `display: none`) reports zeros. Scaling by that\n // would collapse the crop and there would be nothing to scale back from, so hold\n // the last real box until it is shown again.\n if (rect.width === 0 || rect.height === 0) return;\n if (rect.width === previous.w && rect.height === previous.h) return;\n\n const sx = rect.width / previous.w;\n const sy = rect.height / previous.h;\n const c = this.cropDisplay();\n this.display.set({ w: rect.width, h: rect.height });\n this.cropDisplay.set({ x: c.x * sx, y: c.y * sy, w: c.w * sx, h: c.h * sy });\n }\n\n /** Compute a sensible initial crop — centered, respecting aspectRatio. */\n private initialCrop(display: { w: number; h: number }): RectPx {\n const ratio = this.aspectRatio();\n let w = display.w * 0.6;\n let h = display.h * 0.6;\n if (ratio && ratio > 0) {\n const candidate = w / ratio;\n if (candidate > h) w = h * ratio;\n else h = w / ratio;\n }\n return {\n x: (display.w - w) / 2,\n y: (display.h - h) / 2,\n w,\n h,\n };\n }\n\n // Drag handlers\n\n protected readonly handles: readonly WrCropHandle[] = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];\n\n protected onPointerDown(handle: WrCropHandle, event: PointerEvent): void {\n // Any pointerdown used to start a drag, so the right button moved the crop window and\n // a second finger could take over a drag already in progress.\n if (event.button !== 0 || !event.isPrimary) return;\n event.preventDefault();\n event.stopPropagation();\n this.active = handle;\n this.startPointer = { x: event.clientX, y: event.clientY };\n this.startRect = { ...this.cropDisplay() };\n (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);\n }\n\n protected onPointerMove(event: PointerEvent): void {\n if (!this.active) return;\n const dx = event.clientX - this.startPointer.x;\n const dy = event.clientY - this.startPointer.y;\n if (this.active === 'move') this.applyMove(dx, dy);\n else this.applyResize(this.active, dx, dy);\n }\n\n protected onPointerUp(event: PointerEvent): void {\n if (!this.active) return;\n (event.currentTarget as HTMLElement).releasePointerCapture(event.pointerId);\n this.active = null;\n void this.emitCropped();\n }\n\n // Keyboard\n\n /** Whether a key has moved the crop since the last `keyup`. */\n private keyboardDirty = false;\n\n /**\n * The component's only keyboard path. Arrow keys move the crop window; Alt\n * with an arrow key resizes it from the east / south edge; Shift makes either\n * one ten display pixels instead of one — the coarse modifier `wr-slider`,\n * `wr-knob` and `wr-splitter` all use, which is why Alt and not Shift carries\n * the mode switch here.\n *\n * Nothing mirrors under `dir=\"rtl\"`: the image is not flipped and the window\n * is placed with a physical `left`, so ArrowLeft moves the crop visually left\n * in both directions.\n *\n * Both branches go through `applyMove` / `applyResize`, so the `aspectRatio`\n * lock, the `minWidth` / `minHeight` floors and the canvas bounds hold for a\n * keystroke exactly as they do for a drag. Those two measure from `startRect`\n * rather than from the live rect, so it is re-seeded per keystroke — a 1px\n * delta against the rect from the first press would make a held arrow stall\n * after one pixel instead of accumulating.\n */\n protected onWindowKeydown(event: KeyboardEvent): void {\n const step = event.shiftKey ? KEY_STEP_COARSE : KEY_STEP;\n let dx = 0;\n let dy = 0;\n switch (event.key) {\n case 'ArrowLeft':\n dx = -step;\n break;\n case 'ArrowRight':\n dx = step;\n break;\n case 'ArrowUp':\n dy = -step;\n break;\n case 'ArrowDown':\n dy = step;\n break;\n default:\n return;\n }\n\n event.preventDefault();\n this.startRect = { ...this.cropDisplay() };\n if (event.altKey) this.applyResize(dx !== 0 ? 'e' : 's', dx, dy);\n else this.applyMove(dx, dy);\n\n this.keyboardDirty = true;\n const c = this.cropRect();\n // Bare numbers on purpose — `resolvedKeyHelp` has already told the reader\n // what the four are, and a sentence here would be a translated string.\n this.announcement.set(`${c.x}, ${c.y}, ${c.width} × ${c.height}`);\n }\n\n /**\n * Emit once per gesture, not once per keystroke. A held arrow repeats its\n * keydown many times a second and each `emitCropped` renders a canvas and\n * encodes a Blob; `keyup` fires once when the run ends, which is the keyboard's\n * equivalent of the `pointerup` the drag path emits on.\n */\n protected onWindowKeyup(): void {\n if (!this.keyboardDirty) return;\n this.keyboardDirty = false;\n void this.emitCropped();\n }\n\n private applyMove(dx: number, dy: number): void {\n const display = this.display();\n const r = this.startRect;\n const next = {\n x: clamp(r.x + dx, 0, display.w - r.w),\n y: clamp(r.y + dy, 0, display.h - r.h),\n w: r.w,\n h: r.h,\n };\n this.cropDisplay.set(next);\n }\n\n private applyResize(handle: WrCropHandle, dx: number, dy: number): void {\n const display = this.display();\n const min = { w: this.minWidth(), h: this.minHeight() };\n const ratio = this.aspectRatio();\n const r = this.startRect;\n let x = r.x;\n let y = r.y;\n let w = r.w;\n let h = r.h;\n\n if (handle.includes('e')) w = clamp(r.w + dx, min.w, display.w - r.x);\n if (handle.includes('w')) {\n const nextW = clamp(r.w - dx, min.w, r.x + r.w);\n x = r.x + (r.w - nextW);\n w = nextW;\n }\n if (handle.includes('s')) h = clamp(r.h + dy, min.h, display.h - r.y);\n if (handle.includes('n')) {\n const nextH = clamp(r.h - dy, min.h, r.y + r.h);\n y = r.y + (r.h - nextH);\n h = nextH;\n }\n\n if (ratio && ratio > 0) {\n // Lock the orthogonal axis to maintain ratio. For corner handles,\n // pick whichever delta drives the larger change so we don't bounce.\n if (handle === 'e' || handle === 'w') {\n const newH = w / ratio;\n const dh = newH - r.h;\n if (handle === 'e' || handle === 'w') {\n y = r.y - dh / 2;\n h = newH;\n }\n } else if (handle === 'n' || handle === 's') {\n const newW = h * ratio;\n const dw = newW - r.w;\n x = r.x - dw / 2;\n w = newW;\n } else {\n // Corner — drive by width, then derive height.\n const newH = w / ratio;\n if (handle.includes('n')) y = r.y + r.h - newH;\n h = newH;\n }\n // Re-clamp after ratio adjustment so we never spill out of the canvas.\n x = clamp(x, 0, display.w - w);\n y = clamp(y, 0, display.h - h);\n w = clamp(w, min.w, display.w - x);\n h = clamp(h, min.h, display.h - y);\n }\n\n this.cropDisplay.set({ x, y, w, h });\n }\n\n // Public API\n\n /** Render the current crop as a Blob. */\n async toBlob(type: WrImageOutputType = this.outputType(), quality: number = this.outputQuality()): Promise<Blob> {\n const canvas = this.toCanvas();\n return new Promise<Blob>((resolve, reject) => {\n canvas.toBlob(\n blob => {\n if (blob) resolve(blob);\n else reject(new Error('toBlob returned null'));\n },\n type,\n quality\n );\n });\n }\n\n /** Render the current crop as a data URL. */\n toDataUrl(type: WrImageOutputType = this.outputType(), quality: number = this.outputQuality()): string {\n return this.toCanvas().toDataURL(type, quality);\n }\n\n /** Re-emit the crop without waiting for a drag (e.g. after a programmatic change). */\n async refresh(): Promise<void> {\n await this.emitCropped();\n }\n\n // Internals\n\n private toCanvas(): HTMLCanvasElement {\n const img = this.imgEl().nativeElement;\n const c = this.cropRect();\n const canvas = document.createElement('canvas');\n canvas.width = Math.max(1, c.width);\n canvas.height = Math.max(1, c.height);\n const ctx = canvas.getContext('2d');\n if (ctx) ctx.drawImage(img, c.x, c.y, c.width, c.height, 0, 0, canvas.width, canvas.height);\n return canvas;\n }\n\n private async emitCropped(): Promise<void> {\n try {\n const blob = await this.toBlob();\n this.cropped.emit(blob);\n } catch {\n // Swallow — invalid state (image not loaded, crop empty, etc.).\n }\n }\n}\n","@if (objectUrl(); as url) {\n <div class=\"wr-image-cropper__canvas\">\n <img #img class=\"wr-image-cropper__image\" [src]=\"url\" alt=\"\" draggable=\"false\" (load)=\"onImageLoad()\" />\n\n @if (display().w > 0) {\n <!-- Semi-transparent backdrop with a cut-out for the crop window. -->\n <div\n class=\"wr-image-cropper__backdrop\"\n [style.clip-path]=\"`polygon(0 0, 100% 0, 100% 100%, 0 100%, 0 ${cropDisplay().y}px, ${cropDisplay().x}px ${cropDisplay().y}px, ${cropDisplay().x}px ${cropDisplay().y + cropDisplay().h}px, ${cropDisplay().x + cropDisplay().w}px ${cropDisplay().y + cropDisplay().h}px, ${cropDisplay().x + cropDisplay().w}px ${cropDisplay().y}px, 0 ${cropDisplay().y}px)`\"\n ></div>\n\n <!--\n Crop window — drag to move, with eight resize handles, and the ONE\n keyboard control the component has (see `onWindowKeydown`). The eight\n handles stay pointer-only and `aria-hidden`: eight extra tab stops per\n cropper would be hostile. The keys resize from the east and south edges\n only, so a west or north drag comes out as a resize plus a move rather\n than as one gesture.\n\n `role=\"group\"` rather than no role at all, because `aria-label` is\n prohibited on a roleless `<div>` and this needs a name; and rather than\n `role=\"application\"`, which would switch the reader out of its own\n browse mode for the whole subtree — far more than one rectangle is worth.\n -->\n <div\n class=\"wr-image-cropper__window\"\n role=\"group\"\n tabindex=\"0\"\n [attr.aria-label]=\"resolvedWindowLabel()\"\n [attr.aria-describedby]=\"helpId\"\n [style.left.px]=\"cropDisplay().x\"\n [style.top.px]=\"cropDisplay().y\"\n [style.width.px]=\"cropDisplay().w\"\n [style.height.px]=\"cropDisplay().h\"\n (pointerdown)=\"onPointerDown('move', $event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n (keydown)=\"onWindowKeydown($event)\"\n (keyup)=\"onWindowKeyup()\"\n >\n @for (h of handles; track h) {\n <span\n aria-hidden=\"true\"\n [class]=\"`wr-image-cropper__handle wr-image-cropper__handle--${h}`\"\n (pointerdown)=\"onPointerDown(h, $event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n ></span>\n }\n </div>\n\n <p class=\"wr-image-cropper__sr-only\" [id]=\"helpId\">{{ resolvedKeyHelp() }}</p>\n\n <!--\n The crop rect in SOURCE pixels, announced after a keyboard change only —\n a pointer drag already gives sighted feedback and would flood the region,\n and the initial crop is not news. Deliberately bare numbers: a sentence\n here would be a translated string the catalog does not carry, and the\n reader has just been told what the four numbers mean by `helpId`.\n -->\n <p class=\"wr-image-cropper__sr-only\" role=\"status\">{{ announcement() }}</p>\n }\n </div>\n} @else {\n <div class=\"wr-image-cropper__empty\">{{ emptyText() }}</div>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-image-cropper.mjs","sources":["../../../projects/lib/image-cropper/image-cropper.ts","../../../projects/lib/image-cropper/image-cropper.html","../../../projects/lib/image-cropper/ngwr-image-cropper.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n DestroyRef,\n type ElementRef,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n output,\n signal,\n viewChild,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { clamp, randomId } from 'ngwr/utils';\n\nimport type { WrCropHandle, WrCropRect, WrImageCropperStatus, WrImageLoadError, WrImageOutputType } from './interfaces';\n\n/**\n * `[maxOutputSize]`'s transform. Anything that is not a positive, finite number —\n * `null`, an empty attribute, `0`, a typo — means \"no cap\" rather than a 1px export:\n * a bad config should cost resolution nobody asked to lose, not the whole image.\n */\nfunction coerceMaxOutputSize(value: unknown): number | null {\n if (value === null || value === undefined || value === '') return null;\n const n = coerceNumberProperty(value, Number.NaN);\n return Number.isFinite(n) && n > 0 ? n : null;\n}\n\n/**\n * Stands in for the consumer input `useI18nText` expects. None of this\n * component's three strings has a `*Label` input behind it — the catalog is the\n * override channel — so they share one empty override.\n */\nconst NO_OVERRIDE = signal<string | null>(null).asReadonly();\n\n/** Keyboard step in display pixels, and what Shift promotes it to. */\nconst KEY_STEP = 1;\nconst KEY_STEP_COARSE = 10;\n\ninterface RectPx {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/**\n * Image crop UI. Pass `[src]` (URL, `File`, or `Blob`); the user drags\n * the crop window or any of its eight handles. Optionally lock the crop\n * to a fixed `[aspectRatio]`.\n *\n * The crop window is also the one tab stop: arrow keys move it, Alt with an\n * arrow key resizes it, and Shift makes either step ten pixels instead of one.\n *\n * `(cropped)` fires after each drag end — or each run of arrow keys — with a\n * freshly-rendered `Blob` of the cropped region. For one-off reads use\n * `toBlob()` / `toDataUrl()`.\n *\n * A source the browser cannot decode fires `(loadError)` once, sets `status()` to\n * `'failed'` and puts `wr-image-cropper--failed` on the host. The component renders\n * no message of its own for it — what went wrong and what to do next is the host's\n * to say.\n *\n * @example\n * ```html\n * <wr-image-cropper\n * [src]=\"file\"\n * [aspectRatio]=\"1\"\n * (cropped)=\"onBlob($event)\"\n * (loadError)=\"onUnreadable($event)\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/image-cropper\n */\n@Component({\n selector: 'wr-image-cropper',\n templateUrl: './image-cropper.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-image-cropper',\n '[class.wr-image-cropper--failed]': \"status() === 'failed'\",\n },\n})\nexport class WrImageCropper {\n /** Image source — URL string, `File`, or `Blob`. */\n readonly src = input<string | File | Blob | null>(null);\n\n /** Aspect ratio (width / height). `null` = free. @default null */\n readonly aspectRatio = input<number | null>(null);\n\n /** Minimum crop width in display pixels. @default 32 */\n readonly minWidth = input(32, { transform: (v: unknown): number => Math.max(8, coerceNumberProperty(v, 32)) });\n\n /** Minimum crop height in display pixels. @default 32 */\n readonly minHeight = input(32, { transform: (v: unknown): number => Math.max(8, coerceNumberProperty(v, 32)) });\n\n /** Default output type for `(cropped)`. @default 'image/png' */\n readonly outputType = input<WrImageOutputType>('image/png');\n\n /** JPEG / WebP quality for `(cropped)` in [0, 1]. @default 0.92 */\n readonly outputQuality = input(0.92);\n\n /**\n * Longest side of the exported image, in pixels — for `(cropped)`, `toBlob()` and\n * `toDataUrl()` alike. A crop larger than this is scaled down to fit, keeping its\n * aspect ratio; a smaller one is exported as it is, never upscaled. The crop itself\n * — `cropRect()`, in source pixels — is not affected. `null` (or anything that is\n * not a positive number) exports at the source image's own resolution.\n * @default null\n */\n readonly maxOutputSize = input<number | null>(null, { transform: coerceMaxOutputSize });\n\n /** Emits a Blob after each drag end. */\n readonly cropped = output<Blob>();\n\n /**\n * Fires once when the browser cannot decode the current `src` — a format it does\n * not support, a broken file, a URL that fails. Nothing is rendered for it; show\n * your own message. A new `src` clears the failure.\n */\n readonly loadError = output<WrImageLoadError>();\n\n protected readonly imgEl = viewChild.required<ElementRef<HTMLImageElement>>('img');\n\n /** Resolved object URL for `src` (so File / Blob render in `<img>`). */\n protected readonly emptyText = useI18nText(NO_OVERRIDE, 'imageCropper.empty', 'No image');\n\n /** Accessible name of the crop window — the component's only tab stop. */\n protected readonly resolvedWindowLabel = useI18nText(NO_OVERRIDE, 'imageCropper.window', 'Crop region');\n\n /** The key model, referenced by the window's `aria-describedby`. */\n protected readonly resolvedKeyHelp = useI18nText(\n NO_OVERRIDE,\n 'imageCropper.keyHelp',\n 'Arrow keys move the crop. Hold Alt with an arrow key to resize it, and Shift for larger steps. ' +\n 'The crop is announced as left, top, width and height in image pixels.'\n );\n\n /** Id linking the crop window to the help text. Random rather than counted:\n * the crop UI only exists after a real image load, so it never prerenders. */\n protected readonly helpId = randomId('wr-image-cropper-help');\n\n /** Live-region text — written by the keyboard path only (see `onWindowKeydown`). */\n protected readonly announcement = signal<string>('');\n\n protected readonly objectUrl = signal<string | null>(null);\n\n /** Previous object URL we created — kept off-signal so the resolve\n * effect doesn't depend on its own writes (which would loop). */\n private previousObjectUrl: string | null = null;\n\n /** Natural (source) pixel dimensions. */\n protected readonly natural = signal<{ w: number; h: number }>({ w: 0, h: 0 });\n\n /** Display (rendered) pixel dimensions. */\n protected readonly display = signal<{ w: number; h: number }>({ w: 0, h: 0 });\n\n /** Crop rect in display coordinates. */\n protected readonly cropDisplay = signal<RectPx>({ x: 0, y: 0, w: 0, h: 0 });\n\n /** Watches the rendered image box — see `watchResize`. */\n private resizeObserver: ResizeObserver | null = null;\n\n /** Currently active drag handle. */\n private active: WrCropHandle | null = null;\n private startPointer: { x: number; y: number } = { x: 0, y: 0 };\n private startRect: RectPx = { x: 0, y: 0, w: 0, h: 0 };\n\n private readonly destroyRef = inject(DestroyRef);\n\n private readonly statusState = signal<WrImageCropperStatus>('empty');\n\n /**\n * Where the source stands: `'empty'`, `'loading'`, `'loaded'` or `'failed'`. Reset\n * to `'loading'` (or `'empty'`) whenever `src` changes, so a failure never outlives\n * the source that caused it.\n */\n readonly status = this.statusState.asReadonly();\n\n /** Resolved crop rect in natural (source) pixel coordinates. */\n readonly cropRect = computed<WrCropRect>(() => {\n const display = this.display();\n const natural = this.natural();\n const c = this.cropDisplay();\n if (display.w === 0 || display.h === 0) return { x: 0, y: 0, width: 0, height: 0 };\n const sx = natural.w / display.w;\n const sy = natural.h / display.h;\n return {\n x: Math.round(c.x * sx),\n y: Math.round(c.y * sy),\n width: Math.round(c.w * sx),\n height: Math.round(c.h * sy),\n };\n });\n\n constructor() {\n // Resolve the source into a usable URL the <img> can render.\n // We only read `src()` here — `previousObjectUrl` is a plain field, so\n // writing to `objectUrl` doesn't re-trigger this effect (and hang the\n // tab on file upload).\n effect(() => {\n const src = this.src();\n if (this.previousObjectUrl) {\n URL.revokeObjectURL(this.previousObjectUrl);\n this.previousObjectUrl = null;\n }\n // The crop is measured against the image that WAS showing, and `cropRect` is a\n // public signal a consumer can read at any moment. Left in place it kept\n // reporting a rect scaled to the previous image for the whole gap between a new\n // `src` and its load event — long enough to crop the wrong thing.\n this.resetGeometry();\n if (!src) {\n this.objectUrl.set(null);\n this.statusState.set('empty');\n return;\n }\n // A new source starts over: a failure belongs to the source that caused it, and\n // left in place it would mark a perfectly good next image as broken until the\n // browser answered for that one too.\n this.statusState.set('loading');\n if (typeof src === 'string') {\n this.objectUrl.set(src);\n } else {\n const url = URL.createObjectURL(src);\n this.previousObjectUrl = url;\n this.objectUrl.set(url);\n }\n });\n\n this.destroyRef.onDestroy(() => {\n if (this.previousObjectUrl) URL.revokeObjectURL(this.previousObjectUrl);\n this.resizeObserver?.disconnect();\n });\n }\n\n // Image load\n\n /**\n * Back to \"nothing measured yet\", which is also what an absent `src` means.\n *\n * `display` is the field that gates everything downstream — the crop UI renders on\n * `display().w > 0` and `cropRect` returns zeros without it — so it is the only one\n * a test can observe. The other two are reset for coherence: the resize observer\n * below also writes `display`, and left alone they would resurrect a crop measured\n * against the previous image (which is why `onResize` refuses to run on a zeroed\n * `display` — see there).\n */\n private resetGeometry(): void {\n this.natural.set({ w: 0, h: 0 });\n this.display.set({ w: 0, h: 0 });\n this.cropDisplay.set({ x: 0, y: 0, w: 0, h: 0 });\n }\n\n protected onImageLoad(): void {\n const img = this.imgEl().nativeElement;\n const rect = img.getBoundingClientRect();\n const display = { w: rect.width, h: rect.height };\n // Some SVGs and odd images report zero natural dimensions — fall back\n // to the rendered size so the crop math stays well-defined.\n const natural = {\n w: img.naturalWidth || display.w,\n h: img.naturalHeight || display.h,\n };\n this.statusState.set('loaded');\n this.natural.set(natural);\n this.display.set(display);\n this.cropDisplay.set(this.initialCrop(display));\n this.watchResize(img);\n }\n\n /**\n * The `<img>` could not decode its source.\n *\n * Before this handler existed a failed decode left the component exactly as it\n * looks while loading — an image with no size, no crop window, nothing emitted —\n * so a host had no way to tell \"still decoding\" from \"never going to\", and a user\n * who picked a `.heic` the browser cannot read saw an empty box and no reason.\n *\n * Once per source: `status` is reset by a new `src` and by nothing else, so a\n * second `error` for the same source says nothing new and is not re-emitted.\n */\n protected onImageError(): void {\n const url = this.objectUrl();\n if (!url || this.statusState() === 'failed') return;\n this.statusState.set('failed');\n this.resetGeometry();\n\n const src = this.src();\n const isBlob = typeof src !== 'string' && src !== null;\n this.loadError.emit({\n url,\n name: typeof File !== 'undefined' && src instanceof File ? src.name : null,\n type: isBlob ? src.type : null,\n size: isBlob ? src.size : null,\n });\n }\n\n /**\n * Keep `display` on the box the image is actually rendered at.\n *\n * The stylesheet sizes the image responsively — `max-width: 100%`,\n * `max-height: 70dvh`, `height: auto` — so the single measurement taken in the\n * `(load)` handler stops being true as soon as the container or the viewport\n * changes. Everything the user sees and touches is in DISPLAY pixels: the crop\n * window's inline `left` / `top` / `width` / `height`, the backdrop cut-out, and\n * the bounds `applyMove` / `applyResize` clamp against. Measured once, a narrowed\n * window leaves the crop painted past the edge of the image and draggable off it.\n *\n * Re-observed per load rather than once, because a new `src` may mount a new\n * `<img>`; the old observation would then be on a detached element.\n */\n private watchResize(el: HTMLImageElement): void {\n if (typeof ResizeObserver === 'undefined') return;\n this.resizeObserver?.disconnect();\n // Re-read the same way `onImageLoad` does rather than take the entry's\n // `contentRect`, so the two measurements can't disagree about the box.\n const ro = new ResizeObserver(() => this.onResize(el));\n ro.observe(el);\n this.resizeObserver = ro;\n }\n\n /**\n * Rescale the crop by however much the image did, so the user keeps the region\n * they picked instead of having it re-centred: `cropRect()` — the source-pixel\n * value a consumer acts on — comes out the same across the resize.\n */\n private onResize(el: HTMLImageElement): void {\n const previous = this.display();\n // Nothing measured yet, or measured and then dropped by `resetGeometry` while a\n // new `src` loads. There is no crop to carry over, and the load will measure.\n if (previous.w === 0 || previous.h === 0) return;\n const rect = el.getBoundingClientRect();\n // A hidden image (a closed tab, `display: none`) reports zeros. Scaling by that\n // would collapse the crop and there would be nothing to scale back from, so hold\n // the last real box until it is shown again.\n if (rect.width === 0 || rect.height === 0) return;\n if (rect.width === previous.w && rect.height === previous.h) return;\n\n const sx = rect.width / previous.w;\n const sy = rect.height / previous.h;\n const c = this.cropDisplay();\n this.display.set({ w: rect.width, h: rect.height });\n this.cropDisplay.set({ x: c.x * sx, y: c.y * sy, w: c.w * sx, h: c.h * sy });\n }\n\n /** Compute a sensible initial crop — centered, respecting aspectRatio. */\n private initialCrop(display: { w: number; h: number }): RectPx {\n const ratio = this.aspectRatio();\n let w = display.w * 0.6;\n let h = display.h * 0.6;\n if (ratio && ratio > 0) {\n const candidate = w / ratio;\n if (candidate > h) w = h * ratio;\n else h = w / ratio;\n }\n return {\n x: (display.w - w) / 2,\n y: (display.h - h) / 2,\n w,\n h,\n };\n }\n\n // Drag handlers\n\n protected readonly handles: readonly WrCropHandle[] = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];\n\n protected onPointerDown(handle: WrCropHandle, event: PointerEvent): void {\n // Any pointerdown used to start a drag, so the right button moved the crop window and\n // a second finger could take over a drag already in progress.\n if (event.button !== 0 || !event.isPrimary) return;\n event.preventDefault();\n event.stopPropagation();\n this.active = handle;\n this.startPointer = { x: event.clientX, y: event.clientY };\n this.startRect = { ...this.cropDisplay() };\n (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);\n }\n\n protected onPointerMove(event: PointerEvent): void {\n if (!this.active) return;\n const dx = event.clientX - this.startPointer.x;\n const dy = event.clientY - this.startPointer.y;\n if (this.active === 'move') this.applyMove(dx, dy);\n else this.applyResize(this.active, dx, dy);\n }\n\n protected onPointerUp(event: PointerEvent): void {\n if (!this.active) return;\n (event.currentTarget as HTMLElement).releasePointerCapture(event.pointerId);\n this.active = null;\n void this.emitCropped();\n }\n\n // Keyboard\n\n /** Whether a key has moved the crop since the last `keyup`. */\n private keyboardDirty = false;\n\n /**\n * The component's only keyboard path. Arrow keys move the crop window; Alt\n * with an arrow key resizes it from the east / south edge; Shift makes either\n * one ten display pixels instead of one — the coarse modifier `wr-slider`,\n * `wr-knob` and `wr-splitter` all use, which is why Alt and not Shift carries\n * the mode switch here.\n *\n * Nothing mirrors under `dir=\"rtl\"`: the image is not flipped and the window\n * is placed with a physical `left`, so ArrowLeft moves the crop visually left\n * in both directions.\n *\n * Both branches go through `applyMove` / `applyResize`, so the `aspectRatio`\n * lock, the `minWidth` / `minHeight` floors and the canvas bounds hold for a\n * keystroke exactly as they do for a drag. Those two measure from `startRect`\n * rather than from the live rect, so it is re-seeded per keystroke — a 1px\n * delta against the rect from the first press would make a held arrow stall\n * after one pixel instead of accumulating.\n */\n protected onWindowKeydown(event: KeyboardEvent): void {\n const step = event.shiftKey ? KEY_STEP_COARSE : KEY_STEP;\n let dx = 0;\n let dy = 0;\n switch (event.key) {\n case 'ArrowLeft':\n dx = -step;\n break;\n case 'ArrowRight':\n dx = step;\n break;\n case 'ArrowUp':\n dy = -step;\n break;\n case 'ArrowDown':\n dy = step;\n break;\n default:\n return;\n }\n\n event.preventDefault();\n this.startRect = { ...this.cropDisplay() };\n if (event.altKey) this.applyResize(dx !== 0 ? 'e' : 's', dx, dy);\n else this.applyMove(dx, dy);\n\n this.keyboardDirty = true;\n const c = this.cropRect();\n // Bare numbers on purpose — `resolvedKeyHelp` has already told the reader\n // what the four are, and a sentence here would be a translated string.\n this.announcement.set(`${c.x}, ${c.y}, ${c.width} × ${c.height}`);\n }\n\n /**\n * Emit once per gesture, not once per keystroke. A held arrow repeats its\n * keydown many times a second and each `emitCropped` renders a canvas and\n * encodes a Blob; `keyup` fires once when the run ends, which is the keyboard's\n * equivalent of the `pointerup` the drag path emits on.\n */\n protected onWindowKeyup(): void {\n if (!this.keyboardDirty) return;\n this.keyboardDirty = false;\n void this.emitCropped();\n }\n\n private applyMove(dx: number, dy: number): void {\n const display = this.display();\n const r = this.startRect;\n const next = {\n x: clamp(r.x + dx, 0, display.w - r.w),\n y: clamp(r.y + dy, 0, display.h - r.h),\n w: r.w,\n h: r.h,\n };\n this.cropDisplay.set(next);\n }\n\n private applyResize(handle: WrCropHandle, dx: number, dy: number): void {\n const display = this.display();\n const min = { w: this.minWidth(), h: this.minHeight() };\n const ratio = this.aspectRatio();\n const r = this.startRect;\n let x = r.x;\n let y = r.y;\n let w = r.w;\n let h = r.h;\n\n if (handle.includes('e')) w = clamp(r.w + dx, min.w, display.w - r.x);\n if (handle.includes('w')) {\n const nextW = clamp(r.w - dx, min.w, r.x + r.w);\n x = r.x + (r.w - nextW);\n w = nextW;\n }\n if (handle.includes('s')) h = clamp(r.h + dy, min.h, display.h - r.y);\n if (handle.includes('n')) {\n const nextH = clamp(r.h - dy, min.h, r.y + r.h);\n y = r.y + (r.h - nextH);\n h = nextH;\n }\n\n if (ratio && ratio > 0) {\n // Lock the orthogonal axis to maintain ratio. For corner handles,\n // pick whichever delta drives the larger change so we don't bounce.\n if (handle === 'e' || handle === 'w') {\n const newH = w / ratio;\n const dh = newH - r.h;\n if (handle === 'e' || handle === 'w') {\n y = r.y - dh / 2;\n h = newH;\n }\n } else if (handle === 'n' || handle === 's') {\n const newW = h * ratio;\n const dw = newW - r.w;\n x = r.x - dw / 2;\n w = newW;\n } else {\n // Corner — drive by width, then derive height.\n const newH = w / ratio;\n if (handle.includes('n')) y = r.y + r.h - newH;\n h = newH;\n }\n // Re-clamp after ratio adjustment so we never spill out of the canvas.\n x = clamp(x, 0, display.w - w);\n y = clamp(y, 0, display.h - h);\n w = clamp(w, min.w, display.w - x);\n h = clamp(h, min.h, display.h - y);\n }\n\n this.cropDisplay.set({ x, y, w, h });\n }\n\n // Public API\n\n /** Render the current crop as a Blob. */\n async toBlob(type: WrImageOutputType = this.outputType(), quality: number = this.outputQuality()): Promise<Blob> {\n const canvas = this.toCanvas();\n return new Promise<Blob>((resolve, reject) => {\n canvas.toBlob(\n blob => {\n if (blob) resolve(blob);\n else reject(new Error('toBlob returned null'));\n },\n type,\n quality\n );\n });\n }\n\n /** Render the current crop as a data URL. */\n toDataUrl(type: WrImageOutputType = this.outputType(), quality: number = this.outputQuality()): string {\n return this.toCanvas().toDataURL(type, quality);\n }\n\n /** Re-emit the crop without waiting for a drag (e.g. after a programmatic change). */\n async refresh(): Promise<void> {\n await this.emitCropped();\n }\n\n // Internals\n\n /**\n * The exported image's pixel size: the crop in source pixels, scaled down so its\n * longer side fits `maxOutputSize`. `Math.min(1, …)` is the never-upscale rule — a\n * crop already inside the cap keeps its own size, which is also the whole of the\n * uncapped path.\n */\n private outputSize(c: WrCropRect): { width: number; height: number } {\n const cap = this.maxOutputSize();\n const longest = Math.max(c.width, c.height);\n const scale = cap === null || longest === 0 ? 1 : Math.min(1, cap / longest);\n return {\n width: Math.max(1, Math.round(c.width * scale)),\n height: Math.max(1, Math.round(c.height * scale)),\n };\n }\n\n private toCanvas(): HTMLCanvasElement {\n const img = this.imgEl().nativeElement;\n const c = this.cropRect();\n const canvas = document.createElement('canvas');\n const size = this.outputSize(c);\n canvas.width = size.width;\n canvas.height = size.height;\n // The SOURCE rect stays the whole crop and the destination is the canvas, so a\n // capped export is the same region drawn smaller rather than a smaller region.\n const ctx = canvas.getContext('2d');\n if (ctx) ctx.drawImage(img, c.x, c.y, c.width, c.height, 0, 0, canvas.width, canvas.height);\n return canvas;\n }\n\n private async emitCropped(): Promise<void> {\n try {\n const blob = await this.toBlob();\n this.cropped.emit(blob);\n } catch {\n // Swallow — invalid state (image not loaded, crop empty, etc.).\n }\n }\n}\n","@if (objectUrl(); as url) {\n <div class=\"wr-image-cropper__canvas\">\n <img\n #img\n class=\"wr-image-cropper__image\"\n [src]=\"url\"\n alt=\"\"\n draggable=\"false\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\"\n />\n\n @if (display().w > 0) {\n <!-- Semi-transparent backdrop with a cut-out for the crop window. -->\n <div\n class=\"wr-image-cropper__backdrop\"\n [style.clip-path]=\"`polygon(0 0, 100% 0, 100% 100%, 0 100%, 0 ${cropDisplay().y}px, ${cropDisplay().x}px ${cropDisplay().y}px, ${cropDisplay().x}px ${cropDisplay().y + cropDisplay().h}px, ${cropDisplay().x + cropDisplay().w}px ${cropDisplay().y + cropDisplay().h}px, ${cropDisplay().x + cropDisplay().w}px ${cropDisplay().y}px, 0 ${cropDisplay().y}px)`\"\n ></div>\n\n <!--\n Crop window — drag to move, with eight resize handles, and the ONE\n keyboard control the component has (see `onWindowKeydown`). The eight\n handles stay pointer-only and `aria-hidden`: eight extra tab stops per\n cropper would be hostile. The keys resize from the east and south edges\n only, so a west or north drag comes out as a resize plus a move rather\n than as one gesture.\n\n `role=\"group\"` rather than no role at all, because `aria-label` is\n prohibited on a roleless `<div>` and this needs a name; and rather than\n `role=\"application\"`, which would switch the reader out of its own\n browse mode for the whole subtree — far more than one rectangle is worth.\n -->\n <div\n class=\"wr-image-cropper__window\"\n role=\"group\"\n tabindex=\"0\"\n [attr.aria-label]=\"resolvedWindowLabel()\"\n [attr.aria-describedby]=\"helpId\"\n [style.left.px]=\"cropDisplay().x\"\n [style.top.px]=\"cropDisplay().y\"\n [style.width.px]=\"cropDisplay().w\"\n [style.height.px]=\"cropDisplay().h\"\n (pointerdown)=\"onPointerDown('move', $event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n (keydown)=\"onWindowKeydown($event)\"\n (keyup)=\"onWindowKeyup()\"\n >\n @for (h of handles; track h) {\n <span\n aria-hidden=\"true\"\n [class]=\"`wr-image-cropper__handle wr-image-cropper__handle--${h}`\"\n (pointerdown)=\"onPointerDown(h, $event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n ></span>\n }\n </div>\n\n <p class=\"wr-image-cropper__sr-only\" [id]=\"helpId\">{{ resolvedKeyHelp() }}</p>\n\n <!--\n The crop rect in SOURCE pixels, announced after a keyboard change only —\n a pointer drag already gives sighted feedback and would flood the region,\n and the initial crop is not news. Deliberately bare numbers: a sentence\n here would be a translated string the catalog does not carry, and the\n reader has just been told what the four numbers mean by `helpId`.\n -->\n <p class=\"wr-image-cropper__sr-only\" role=\"status\">{{ announcement() }}</p>\n }\n </div>\n} @else {\n <div class=\"wr-image-cropper__empty\">{{ emptyText() }}</div>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -322,7 +322,7 @@ class WrPagination {
|
|
|
322
322
|
this.pageSize.set(next);
|
|
323
323
|
}
|
|
324
324
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrPagination, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
325
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: WrPagination, isStandalone: true, selector: "wr-pagination", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, showSizeChanger: { classPropertyName: "showSizeChanger", publicName: "showSizeChanger", isSignal: true, isRequired: false, transformFunction: null }, showTotal: { classPropertyName: "showTotal", publicName: "showTotal", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, prevLabel: { classPropertyName: "prevLabel", publicName: "prevLabel", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, itemsPerPageLabel: { classPropertyName: "itemsPerPageLabel", publicName: "itemsPerPageLabel", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { page: "pageChange", pageSize: "pageSizeChange" }, host: { attributes: { "role": "navigation" }, properties: { "class": "classes()", "attr.aria-label": "navLabel()" } }, ngImport: i0, template: "<div class=\"wr-pagination__inner\">\n @if (showTotal()) {\n <span class=\"wr-pagination__total\">{{ rangeLabel() }}</span>\n }\n\n <div class=\"wr-pagination__nav\">\n <!--\n `wr-pagination__nav-btn` exists so the stylesheet can mirror the arrow\n glyph under `dir=\"rtl\"`. A class rather than `:first-child` /\n `:last-child` on the row: those two positions do hold today \u2014 which is why\n the `--square` end caps use them \u2014 but only while the prev button, the\n compact label and the next button all stay unconditional and in this\n order. A mirror keyed to position would follow a reorder, or land on a\n page cell, without a word.\n\n The two `[disabled]` bindings compare against the RANGE (`<= 1`,\n `>= totalPages()`) rather than for equality with its ends. While `total`\n is 0 the guard effect stops enforcing the upper bound, so the page can\n legitimately sit past `totalPages()` \u2014 and `=== totalPages()` reported\n next as enabled there while `goTo` refused the step. Prev is still\n enabled in that state, correctly: `prevPage()` walks it back into range.\n -->\n <wr-btn\n class=\"wr-pagination__nav-btn\"\n [size]=\"size()\"\n outlined\n [attr.aria-label]=\"resolvedPrev()\"\n [disabled]=\"disabled() || page() <= 1\"\n (click)=\"goTo(prevPage())\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m12 19-7-7 7-7\" />\n <path d=\"M19 12H5\" />\n </svg>\n </wr-btn>\n\n @for (page of pages(); track $index) {\n @if (page === ellipsis) {\n <span class=\"wr-pagination__ellipsis\">{{ ellipsis }}</span>\n } @else {\n <wr-btn\n class=\"wr-pagination__page\"\n [size]=\"size()\"\n [outlined]=\"!isCurrent(page)\"\n [color]=\"isCurrent(page) ? 'primary' : null\"\n [attr.aria-current]=\"isCurrent(page) ? 'page' : null\"\n [attr.aria-label]=\"goToPage({ page })\"\n [disabled]=\"disabled()\"\n (click)=\"goTo(page)\"\n >\n {{ page }}\n </wr-btn>\n }\n }\n\n <!-- Compact pager \u2014 shown only in responsive mode on a narrow box (CSS). -->\n <span class=\"wr-pagination__current\" aria-hidden=\"true\">{{ compactLabel() }}</span>\n\n <wr-btn\n class=\"wr-pagination__nav-btn\"\n [size]=\"size()\"\n outlined\n [attr.aria-label]=\"resolvedNext()\"\n [disabled]=\"disabled() || page() >= totalPages()\"\n (click)=\"goTo(page() + 1)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M5 12h14\" />\n <path d=\"m12 5 7 7-7 7\" />\n </svg>\n </wr-btn>\n </div>\n\n @if (showSizeChanger()) {\n <!--\n `ariaLabel`, not `[attr.aria-label]`: `<wr-select>`'s host carries no role,\n so an attribute there names nothing and never reaches the `role=\"combobox\"`\n button inside \u2014 the size changer announced itself as the catalog's generic\n \"Select\".\n -->\n <wr-select\n class=\"wr-pagination__size\"\n [ariaLabel]=\"resolvedItemsPerPage()\"\n [disabled]=\"disabled()\"\n [value]=\"pageSize()\"\n (valueChange)=\"onSizeChange($event)\"\n >\n @for (option of pageSizeOptions(); track option) {\n <wr-option [value]=\"option\">{{ perPage({ size: option }) }}</wr-option>\n }\n </wr-select>\n }\n</div>\n", dependencies: [{ kind: "component", type: WrButton, selector: "wr-btn, button[wr-btn], a[wr-btn]", inputs: ["color", "size", "shape", "icon", "iconPosition", "disabled", "outlined", "block", "loading", "disabledWhenLoading"] }, { kind: "component", type: WrSelect, selector: "wr-select", inputs: ["placeholder", "clearLabel", "ariaLabel", "disabled", "readonly", "rounded", "size", "responsive", "mode", "searchable", "searchQuery", "options", "displayWith", "loader", "serverSearch", "debounceMs", "minChars", "freeText", "virtualScroll", "rowHeight", "viewportHeight", "overscan", "noResultsText", "loadingText", "loading", "separators", "allowDuplicates", "validate", "clearable", "maxItems", "maxTagCount", "value"], outputs: ["searchQueryChange", "searchChange", "valueChange", "touch"] }, { kind: "component", type: WrOption, selector: "wr-option", inputs: ["value", "disabled"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
325
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: WrPagination, isStandalone: true, selector: "wr-pagination", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, showSizeChanger: { classPropertyName: "showSizeChanger", publicName: "showSizeChanger", isSignal: true, isRequired: false, transformFunction: null }, showTotal: { classPropertyName: "showTotal", publicName: "showTotal", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, prevLabel: { classPropertyName: "prevLabel", publicName: "prevLabel", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, itemsPerPageLabel: { classPropertyName: "itemsPerPageLabel", publicName: "itemsPerPageLabel", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { page: "pageChange", pageSize: "pageSizeChange" }, host: { attributes: { "role": "navigation" }, properties: { "class": "classes()", "attr.aria-label": "navLabel()" } }, ngImport: i0, template: "<div class=\"wr-pagination__inner\">\n @if (showTotal()) {\n <span class=\"wr-pagination__total\">{{ rangeLabel() }}</span>\n }\n\n <div class=\"wr-pagination__nav\">\n <!--\n `wr-pagination__nav-btn` exists so the stylesheet can mirror the arrow\n glyph under `dir=\"rtl\"`. A class rather than `:first-child` /\n `:last-child` on the row: those two positions do hold today \u2014 which is why\n the `--square` end caps use them \u2014 but only while the prev button, the\n compact label and the next button all stay unconditional and in this\n order. A mirror keyed to position would follow a reorder, or land on a\n page cell, without a word.\n\n The two `[disabled]` bindings compare against the RANGE (`<= 1`,\n `>= totalPages()`) rather than for equality with its ends. While `total`\n is 0 the guard effect stops enforcing the upper bound, so the page can\n legitimately sit past `totalPages()` \u2014 and `=== totalPages()` reported\n next as enabled there while `goTo` refused the step. Prev is still\n enabled in that state, correctly: `prevPage()` walks it back into range.\n -->\n <wr-btn\n class=\"wr-pagination__nav-btn\"\n [size]=\"size()\"\n outlined\n [attr.aria-label]=\"resolvedPrev()\"\n [disabled]=\"disabled() || page() <= 1\"\n (click)=\"goTo(prevPage())\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m12 19-7-7 7-7\" />\n <path d=\"M19 12H5\" />\n </svg>\n </wr-btn>\n\n @for (page of pages(); track $index) {\n @if (page === ellipsis) {\n <span class=\"wr-pagination__ellipsis\">{{ ellipsis }}</span>\n } @else {\n <wr-btn\n class=\"wr-pagination__page\"\n [size]=\"size()\"\n [outlined]=\"!isCurrent(page)\"\n [color]=\"isCurrent(page) ? 'primary' : null\"\n [attr.aria-current]=\"isCurrent(page) ? 'page' : null\"\n [attr.aria-label]=\"goToPage({ page })\"\n [disabled]=\"disabled()\"\n (click)=\"goTo(page)\"\n >\n {{ page }}\n </wr-btn>\n }\n }\n\n <!-- Compact pager \u2014 shown only in responsive mode on a narrow box (CSS). -->\n <span class=\"wr-pagination__current\" aria-hidden=\"true\">{{ compactLabel() }}</span>\n\n <wr-btn\n class=\"wr-pagination__nav-btn\"\n [size]=\"size()\"\n outlined\n [attr.aria-label]=\"resolvedNext()\"\n [disabled]=\"disabled() || page() >= totalPages()\"\n (click)=\"goTo(page() + 1)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M5 12h14\" />\n <path d=\"m12 5 7 7-7 7\" />\n </svg>\n </wr-btn>\n </div>\n\n @if (showSizeChanger()) {\n <!--\n `ariaLabel`, not `[attr.aria-label]`: `<wr-select>`'s host carries no role,\n so an attribute there names nothing and never reaches the `role=\"combobox\"`\n button inside \u2014 the size changer announced itself as the catalog's generic\n \"Select\".\n -->\n <wr-select\n class=\"wr-pagination__size\"\n [ariaLabel]=\"resolvedItemsPerPage()\"\n [disabled]=\"disabled()\"\n [value]=\"pageSize()\"\n (valueChange)=\"onSizeChange($event)\"\n >\n @for (option of pageSizeOptions(); track option) {\n <wr-option [value]=\"option\">{{ perPage({ size: option }) }}</wr-option>\n }\n </wr-select>\n }\n</div>\n", dependencies: [{ kind: "component", type: WrButton, selector: "wr-btn, button[wr-btn], a[wr-btn]", inputs: ["color", "size", "shape", "icon", "iconPosition", "disabled", "outlined", "block", "loading", "disabledWhenLoading"] }, { kind: "component", type: WrSelect, selector: "wr-select", inputs: ["placeholder", "clearLabel", "ariaLabel", "disabled", "readonly", "rounded", "size", "responsive", "mode", "searchable", "searchQuery", "options", "displayWith", "loader", "serverSearch", "debounceMs", "minChars", "freeText", "virtualScroll", "rowHeight", "viewportHeight", "overscan", "noResultsText", "loadingText", "loading", "separators", "allowDuplicates", "validate", "clearable", "maxItems", "maxTagCount", "value"], outputs: ["searchQueryChange", "searchChange", "valueChange", "touch"] }, { kind: "component", type: WrOption, selector: "wr-option", inputs: ["value", "disabled", "label"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
326
326
|
}
|
|
327
327
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrPagination, decorators: [{
|
|
328
328
|
type: Component,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-pagination.mjs","sources":["../../../projects/lib/pagination/pagination.ts","../../../projects/lib/pagination/pagination.html","../../../projects/lib/pagination/ngwr-pagination.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, LOCALE_ID, ViewEncapsulation, computed, effect, inject, input, model } from '@angular/core';\n\nimport { WrButton } from 'ngwr/button';\nimport { useI18nFormatter, useI18nText } from 'ngwr/i18n';\nimport { WrOption, WrSelect } from 'ngwr/select';\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrPaginationAlign, WrPaginationShape, WrPaginationSize } from './interfaces';\n\nconst ELLIPSIS = '…' as const;\ntype PageEntry = number | typeof ELLIPSIS;\n\n/**\n * Numbered page navigator with optional total / page-size controls.\n *\n * Two-way binds `page` and `pageSize` via signal `model()` inputs.\n *\n * @example\n * ```html\n * <wr-pagination\n * [total]=\"120\"\n * [(page)]=\"page\"\n * [(pageSize)]=\"size\"\n * showTotal\n * showSizeChanger\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/pagination\n */\n@Component({\n selector: 'wr-pagination',\n templateUrl: './pagination.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()', role: 'navigation', '[attr.aria-label]': 'navLabel()' },\n imports: [WrButton, WrSelect, WrOption],\n})\nexport class WrPagination {\n /**\n * Currently displayed page (1-based). Two-way bindable.\n *\n * `page` rather than `currentPage` since v14, so this and `<wr-table>` — which\n * renders one of these in its own footer — spell the two concepts they share\n * one way each. It also names the output: a `model()` called `X` forces\n * `(XChange)`, so the old name produced `(currentPageChange)` against the\n * table's `(pageChange)`.\n */\n readonly page = model<number>(1);\n\n /** Items per page. Two-way bindable. */\n readonly pageSize = model<number>(10);\n\n /** Total item count across all pages. */\n readonly total = input(0, { transform: numAttr(0) });\n\n /** Options shown in the page-size dropdown. */\n readonly pageSizeOptions = input<readonly number[]>([10, 20, 50, 100]);\n\n /** Render the page-size dropdown. @default false */\n readonly showSizeChanger = input(false, { transform: coerceBooleanProperty });\n\n /** Render the \"X–Y of Z\" total label. @default false */\n readonly showTotal = input(false, { transform: coerceBooleanProperty });\n\n /** Horizontal alignment. @default 'start' */\n readonly align = input<WrPaginationAlign>('start');\n\n /** Size variant — cascades to every internal button. @default 'sm' */\n readonly size = input<WrPaginationSize>('sm');\n\n /** Cell corner treatment. @default 'rounded' */\n readonly shape = input<WrPaginationShape>('rounded');\n\n /** Disable interaction. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Collapse to a compact `‹ page / total ›` pager when the control's own box\n * is too narrow for the full numbered strip (a container query on its own\n * width, not the viewport). @default false\n */\n readonly responsive = input(false, { transform: coerceBooleanProperty });\n\n /** Previous-page button aria-label. Falls back to `pagination.prev`. */\n readonly prevLabel = input<string | null>(null);\n\n /** Next-page button aria-label. Falls back to `pagination.next`. */\n readonly nextLabel = input<string | null>(null);\n\n /** \"Items per page\" label. Falls back to `pagination.itemsPerPage`. */\n readonly itemsPerPageLabel = input<string | null>(null);\n\n /** Accessible name for the `role=\"navigation\"` host. Falls back to `pagination.label`. */\n readonly label = input<string | null>(null);\n\n /** Resolved labels. */\n protected readonly resolvedPrev = useI18nText(this.prevLabel, 'pagination.prev', 'Previous page');\n protected readonly resolvedNext = useI18nText(this.nextLabel, 'pagination.next', 'Next page');\n protected readonly resolvedItemsPerPage = useI18nText(\n this.itemsPerPageLabel,\n 'pagination.itemsPerPage',\n 'Items per page'\n );\n\n /**\n * Page-size option text — interpolates `{{size}}`. Was the template literal\n * `{{ option }} / page`, which no catalog could reach, so a Russian UI read\n * \"25 / page\".\n */\n protected readonly perPage = useI18nFormatter('pagination.perPage', '{{size}} / page');\n\n /** Per-page-button aria-label — interpolates `{{page}}`. */\n protected readonly goToPage = useI18nFormatter('pagination.goToPage', 'Go to page {{page}}');\n\n /** The nav landmark's own accessible name — was a hardcoded English literal. */\n protected readonly navLabel = useI18nText(this.label, 'pagination.label', 'Pagination');\n\n /** Internal: total page count. */\n /**\n * A page size that is not a positive number yields ONE page, not `Infinity`.\n *\n * `pageSize` is a `model()`, so Angular offers no `transform` to coerce it and\n * whatever the host writes arrives raw. At 0 the division is `Infinity`,\n * `Math.max(1, …)` keeps it, and the item list below pushes `total` as the\n * last page — so the strip rendered a clickable cell reading \"Infinity\", and\n * clicking it locked the tab. `NaN` (a `pageSize` bound to an emptied number\n * field) came through untouched for the same reason.\n *\n * One page is the honest answer: a size of zero describes no paging at all.\n * Note this is about `pageSize` and does NOT touch the recorded contract\n * about `total` — pagination still refuses to pull the page DOWN while\n * `total` is at or below zero, because 0 is ambiguous with \"still loading\".\n */\n private readonly usablePageSize = computed(() => {\n const size = this.pageSize();\n if (Number.isFinite(size) && size >= 1) return size;\n // Everything on one page. Deliberately not `Infinity`, which gives the\n // right page COUNT by accident and then puts `Infinity` in the range label\n // the moment `page` is above 1.\n return Math.max(1, this.total());\n });\n\n protected readonly totalPages = computed(() => Math.max(1, Math.ceil(this.total() / this.usablePageSize())));\n\n /**\n * Internal: where the previous arrow goes. Clamped to the end of the valid\n * range rather than being a plain `page() - 1`, because the page can\n * legitimately sit past that end while `total` is 0 (see the guard effect).\n * From page 5 of one page, `goTo(4)` is refused as out of range, so the arrow\n * was enabled and did nothing — and under `responsive` on a narrow box the\n * numbered cells are hidden by a container query, which left no way back at\n * all. `Math.min` only ever moves the target INWARD, so `goTo`'s own\n * past-the-end refusal keeps working as the backstop it is.\n */\n protected readonly prevPage = computed(() => Math.min(this.page() - 1, this.totalPages()));\n\n /**\n * Internal: the \"1–10 of 235\" line, as ONE catalog template.\n *\n * It used to be `` `${start}-${end} ${of} ${total}` ``, which localised the\n * word in the middle and nothing else: the ASCII hyphen between the bounds and\n * the operand order were both frozen in TypeScript, so ja-JP and ar-SA got\n * English word order and no language could change the range separator. The\n * three numbers go through `Intl.NumberFormat` for the same reason the total\n * beside them does — a five-figure total needs the locale's grouping, and\n * ar-SA needs its own digits.\n *\n * The `ofLabel` input is gone with it: an input for the middle word is the\n * concatenation, re-offered as API.\n */\n protected readonly rangeLabel = computed(() => {\n // The same guarded size, or the range label reads \"1 – NaN of 100\".\n const size = this.usablePageSize();\n const start = this.total() === 0 ? 0 : (this.page() - 1) * size + 1;\n const end = Math.min(this.page() * size, this.total());\n return this.rangeText({\n from: this.number(start),\n to: this.number(end),\n total: this.number(this.total()),\n });\n });\n\n /** Internal: the compact pager's `1 / 24`, likewise a catalog template. */\n protected readonly compactLabel = computed(() =>\n this.compactText({ current: this.number(this.page()), total: this.number(this.totalPages()) })\n );\n\n private readonly rangeText = useI18nFormatter('pagination.range', '{{from}}–{{to}} of {{total}}');\n private readonly compactText = useI18nFormatter('pagination.compact', '{{current}} / {{total}}');\n\n private readonly locale = inject(LOCALE_ID);\n\n /**\n * Grouped per `LOCALE_ID`. Only the range and the compact pager go through\n * this — a page NUMBER on a button is an identifier, and `1,024` on a button\n * reads as two things.\n */\n private number(value: number): string {\n return new Intl.NumberFormat(this.locale).format(value);\n }\n\n /**\n * Internal: page list with ellipses. Window of 7 visible page slots.\n *\n * 1 … 5 6 [7] 8 9 … 20\n */\n protected readonly pages = computed<readonly PageEntry[]>(() => {\n const total = this.totalPages();\n const current = this.page();\n\n if (total <= 7) {\n return Array.from({ length: total }, (_, i) => i + 1);\n }\n\n const items: PageEntry[] = [1];\n if (current > 4) items.push(ELLIPSIS);\n\n let start: number;\n let end: number;\n if (current <= 4) {\n start = 2;\n end = 5;\n } else if (current >= total - 3) {\n start = total - 4;\n end = total - 1;\n } else {\n start = current - 2;\n end = current + 2;\n }\n\n for (let i = start; i <= end; i++) items.push(i);\n if (current < total - 3) items.push(ELLIPSIS);\n if (end !== total) items.push(total);\n\n return items;\n });\n\n protected readonly ellipsis = ELLIPSIS;\n\n protected readonly classes = computed(() => {\n const parts = [\n 'wr-pagination',\n `wr-pagination--${this.align()}`,\n `wr-pagination--${this.size()}`,\n `wr-pagination--${this.shape()}`,\n ];\n if (this.disabled()) parts.push('wr-pagination--disabled');\n if (this.responsive()) parts.push('wr-pagination--responsive');\n return parts.join(' ');\n });\n\n protected isCurrent(page: number): boolean {\n return this.page() === page;\n }\n\n protected goTo(page: number | typeof ELLIPSIS): void {\n if (typeof page !== 'number') return;\n if (this.disabled() || page < 1 || page > this.totalPages() || page === this.page()) return;\n this.page.set(page);\n }\n\n /**\n * `wr-select` carries an `unknown` value by design, so the size arrives\n * untyped and is narrowed here. It used to arrive as `any` through\n * `[ngModel]`, which hid the coercion rather than removing the need for it.\n */\n constructor() {\n // `page` is a plain `model` — a `model()` takes no transform — so a\n // shrinking `total` and a host `[(page)]` write both land in it\n // unchecked. The guard used to read and write the page inside `untracked()`\n // and pull DOWNWARDS only, so it fired when `total` / `pageSize` moved and\n // never when the host wrote: past the end no cell carries `aria-current`,\n // the arrow at that end stays enabled and inert (`goTo` refuses the step\n // rather than correcting the stored page), and below 1 `rangeLabel()` counts\n // backwards (\"-9-0 of 120\"). Two-sided and tracked, the same shape\n // `wr-stepper` and `wr-carousel` use for their own host-owned `model()`.\n //\n // Only the UPPER bound is conditional. A `total` at or below 0 is the one\n // value where pulling the page down is destructive rather than cosmetic:\n // there is no `loading` input, so 0 is genuinely ambiguous between \"the\n // list is empty\" and \"the page is still in flight\", and the second is the\n // ordinary state of a server-paged host — Angular's `resource` drops its\n // value whenever the params change, and a params function returning an\n // object literal is never reference-equal, so `total` reads 0 between the\n // click and the response. `totalPages()` is 1 there, so the guard pulled\n // every page above the first back to 1 and emitted a `pageChange`\n // the host could not tell from a click: the pager could not leave page 1,\n // and each click cost two events. A negative `total` is folded into the\n // same branch because it is as meaningless as a zero one.\n //\n // The LOWER bound is unconditional, and gating it on `total` was a mistake\n // in the first cut of this fix: no value of `total` makes page 0 correct,\n // and while it was skipped a host write of -3 survived — `rangeLabel()`\n // guards `start` for an empty total and not `end`, so it read \"0--30 of 0\",\n // and the compact pager read \"-3 / 1\". It is written as one expression\n // rather than an early return so the effect depends on `page()` and\n // `total()` on every run, whichever branch decides the result.\n //\n // What IS still given up while `total` is 0 and the held page is past the\n // first: no cell carries `aria-current`, because `pages` renders the single\n // page and that is not the page the host holds. `rangeLabel()` is not in\n // that list — it already special-cases an empty total and reads \"0-0 of 0\".\n // Nor are the arrows: they compare against the RANGE rather than its ends\n // (see the template), so past the end next renders disabled and prev stays\n // enabled and steps back INTO range via `prevPage`. That is a change from\n // the \"enabled and inert\" behaviour described above, which this component\n // used to accept at both ends.\n effect(() => {\n const page = this.page();\n const floored = Math.max(1, page);\n const clamped = this.total() <= 0 ? floored : Math.min(floored, this.totalPages());\n if (clamped !== page) this.page.set(clamped);\n });\n }\n\n protected onSizeChange(size: unknown): void {\n const next = Number(size);\n if (this.disabled() || !Number.isFinite(next) || next <= 0 || next === this.pageSize()) return;\n // The size and nothing else. This used to clamp the page against the new\n // size too, and it read a value that was already superseded: `pageSize.set`\n // emits `pageSizeChange` synchronously, so by the following statement the\n // host has run its own policy — most tables reset to the first page — and\n // that write has not reached the model yet, it arrives on the next binding\n // pass. So the clamp computed a cap from the PRE-change page and wrote the\n // result over the host's decision: from page 6 at size 10, choosing 25\n // emitted `pageChange(5)` on top of the host's reset to 1, which the\n // host cannot tell from a navigation, and a second request went out. The\n // guard effect in the constructor takes it over and runs once the bindings\n // have settled, so it clamps the page the host actually wants.\n //\n // For a SETTLED `total` that is the whole story. For the server-paged host\n // this fix exists for it is not: the size change invalidates the payload\n // too, so `total` reads 0 and the guard returns early. Measured, from page\n // 6 at size 10 choosing 25 with a host that does not reset — in flight:\n // `navigations []`, page 6, total 0; after settle: `navigations [5]`, page\n // 5. So a non-resetting host issues one doomed request for a page that no\n // longer exists and is corrected only when the answer lands. That is an\n // accepted cost of refusing to write back from transient state, not\n // something the guard covers.\n this.pageSize.set(next);\n }\n}\n","<div class=\"wr-pagination__inner\">\n @if (showTotal()) {\n <span class=\"wr-pagination__total\">{{ rangeLabel() }}</span>\n }\n\n <div class=\"wr-pagination__nav\">\n <!--\n `wr-pagination__nav-btn` exists so the stylesheet can mirror the arrow\n glyph under `dir=\"rtl\"`. A class rather than `:first-child` /\n `:last-child` on the row: those two positions do hold today — which is why\n the `--square` end caps use them — but only while the prev button, the\n compact label and the next button all stay unconditional and in this\n order. A mirror keyed to position would follow a reorder, or land on a\n page cell, without a word.\n\n The two `[disabled]` bindings compare against the RANGE (`<= 1`,\n `>= totalPages()`) rather than for equality with its ends. While `total`\n is 0 the guard effect stops enforcing the upper bound, so the page can\n legitimately sit past `totalPages()` — and `=== totalPages()` reported\n next as enabled there while `goTo` refused the step. Prev is still\n enabled in that state, correctly: `prevPage()` walks it back into range.\n -->\n <wr-btn\n class=\"wr-pagination__nav-btn\"\n [size]=\"size()\"\n outlined\n [attr.aria-label]=\"resolvedPrev()\"\n [disabled]=\"disabled() || page() <= 1\"\n (click)=\"goTo(prevPage())\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m12 19-7-7 7-7\" />\n <path d=\"M19 12H5\" />\n </svg>\n </wr-btn>\n\n @for (page of pages(); track $index) {\n @if (page === ellipsis) {\n <span class=\"wr-pagination__ellipsis\">{{ ellipsis }}</span>\n } @else {\n <wr-btn\n class=\"wr-pagination__page\"\n [size]=\"size()\"\n [outlined]=\"!isCurrent(page)\"\n [color]=\"isCurrent(page) ? 'primary' : null\"\n [attr.aria-current]=\"isCurrent(page) ? 'page' : null\"\n [attr.aria-label]=\"goToPage({ page })\"\n [disabled]=\"disabled()\"\n (click)=\"goTo(page)\"\n >\n {{ page }}\n </wr-btn>\n }\n }\n\n <!-- Compact pager — shown only in responsive mode on a narrow box (CSS). -->\n <span class=\"wr-pagination__current\" aria-hidden=\"true\">{{ compactLabel() }}</span>\n\n <wr-btn\n class=\"wr-pagination__nav-btn\"\n [size]=\"size()\"\n outlined\n [attr.aria-label]=\"resolvedNext()\"\n [disabled]=\"disabled() || page() >= totalPages()\"\n (click)=\"goTo(page() + 1)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M5 12h14\" />\n <path d=\"m12 5 7 7-7 7\" />\n </svg>\n </wr-btn>\n </div>\n\n @if (showSizeChanger()) {\n <!--\n `ariaLabel`, not `[attr.aria-label]`: `<wr-select>`'s host carries no role,\n so an attribute there names nothing and never reaches the `role=\"combobox\"`\n button inside — the size changer announced itself as the catalog's generic\n \"Select\".\n -->\n <wr-select\n class=\"wr-pagination__size\"\n [ariaLabel]=\"resolvedItemsPerPage()\"\n [disabled]=\"disabled()\"\n [value]=\"pageSize()\"\n (valueChange)=\"onSizeChange($event)\"\n >\n @for (option of pageSizeOptions(); track option) {\n <wr-option [value]=\"option\">{{ perPage({ size: option }) }}</wr-option>\n }\n </wr-select>\n }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-pagination.mjs","sources":["../../../projects/lib/pagination/pagination.ts","../../../projects/lib/pagination/pagination.html","../../../projects/lib/pagination/ngwr-pagination.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, LOCALE_ID, ViewEncapsulation, computed, effect, inject, input, model } from '@angular/core';\n\nimport { WrButton } from 'ngwr/button';\nimport { useI18nFormatter, useI18nText } from 'ngwr/i18n';\nimport { WrOption, WrSelect } from 'ngwr/select';\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrPaginationAlign, WrPaginationShape, WrPaginationSize } from './interfaces';\n\nconst ELLIPSIS = '…' as const;\ntype PageEntry = number | typeof ELLIPSIS;\n\n/**\n * Numbered page navigator with optional total / page-size controls.\n *\n * Two-way binds `page` and `pageSize` via signal `model()` inputs.\n *\n * @example\n * ```html\n * <wr-pagination\n * [total]=\"120\"\n * [(page)]=\"page\"\n * [(pageSize)]=\"size\"\n * showTotal\n * showSizeChanger\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/pagination\n */\n@Component({\n selector: 'wr-pagination',\n templateUrl: './pagination.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()', role: 'navigation', '[attr.aria-label]': 'navLabel()' },\n imports: [WrButton, WrSelect, WrOption],\n})\nexport class WrPagination {\n /**\n * Currently displayed page (1-based). Two-way bindable.\n *\n * `page` rather than `currentPage` since v14, so this and `<wr-table>` — which\n * renders one of these in its own footer — spell the two concepts they share\n * one way each. It also names the output: a `model()` called `X` forces\n * `(XChange)`, so the old name produced `(currentPageChange)` against the\n * table's `(pageChange)`.\n */\n readonly page = model<number>(1);\n\n /** Items per page. Two-way bindable. */\n readonly pageSize = model<number>(10);\n\n /** Total item count across all pages. */\n readonly total = input(0, { transform: numAttr(0) });\n\n /** Options shown in the page-size dropdown. */\n readonly pageSizeOptions = input<readonly number[]>([10, 20, 50, 100]);\n\n /** Render the page-size dropdown. @default false */\n readonly showSizeChanger = input(false, { transform: coerceBooleanProperty });\n\n /** Render the \"X–Y of Z\" total label. @default false */\n readonly showTotal = input(false, { transform: coerceBooleanProperty });\n\n /** Horizontal alignment. @default 'start' */\n readonly align = input<WrPaginationAlign>('start');\n\n /** Size variant — cascades to every internal button. @default 'sm' */\n readonly size = input<WrPaginationSize>('sm');\n\n /** Cell corner treatment. @default 'rounded' */\n readonly shape = input<WrPaginationShape>('rounded');\n\n /** Disable interaction. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Collapse to a compact `‹ page / total ›` pager when the control's own box\n * is too narrow for the full numbered strip (a container query on its own\n * width, not the viewport). @default false\n */\n readonly responsive = input(false, { transform: coerceBooleanProperty });\n\n /** Previous-page button aria-label. Falls back to `pagination.prev`. */\n readonly prevLabel = input<string | null>(null);\n\n /** Next-page button aria-label. Falls back to `pagination.next`. */\n readonly nextLabel = input<string | null>(null);\n\n /** \"Items per page\" label. Falls back to `pagination.itemsPerPage`. */\n readonly itemsPerPageLabel = input<string | null>(null);\n\n /** Accessible name for the `role=\"navigation\"` host. Falls back to `pagination.label`. */\n readonly label = input<string | null>(null);\n\n /** Resolved labels. */\n protected readonly resolvedPrev = useI18nText(this.prevLabel, 'pagination.prev', 'Previous page');\n protected readonly resolvedNext = useI18nText(this.nextLabel, 'pagination.next', 'Next page');\n protected readonly resolvedItemsPerPage = useI18nText(\n this.itemsPerPageLabel,\n 'pagination.itemsPerPage',\n 'Items per page'\n );\n\n /**\n * Page-size option text — interpolates `{{size}}`. Was the template literal\n * `{{ option }} / page`, which no catalog could reach, so a Russian UI read\n * \"25 / page\".\n */\n protected readonly perPage = useI18nFormatter('pagination.perPage', '{{size}} / page');\n\n /** Per-page-button aria-label — interpolates `{{page}}`. */\n protected readonly goToPage = useI18nFormatter('pagination.goToPage', 'Go to page {{page}}');\n\n /** The nav landmark's own accessible name — was a hardcoded English literal. */\n protected readonly navLabel = useI18nText(this.label, 'pagination.label', 'Pagination');\n\n /** Internal: total page count. */\n /**\n * A page size that is not a positive number yields ONE page, not `Infinity`.\n *\n * `pageSize` is a `model()`, so Angular offers no `transform` to coerce it and\n * whatever the host writes arrives raw. At 0 the division is `Infinity`,\n * `Math.max(1, …)` keeps it, and the item list below pushes `total` as the\n * last page — so the strip rendered a clickable cell reading \"Infinity\", and\n * clicking it locked the tab. `NaN` (a `pageSize` bound to an emptied number\n * field) came through untouched for the same reason.\n *\n * One page is the honest answer: a size of zero describes no paging at all.\n * Note this is about `pageSize` and does NOT touch the recorded contract\n * about `total` — pagination still refuses to pull the page DOWN while\n * `total` is at or below zero, because 0 is ambiguous with \"still loading\".\n */\n private readonly usablePageSize = computed(() => {\n const size = this.pageSize();\n if (Number.isFinite(size) && size >= 1) return size;\n // Everything on one page. Deliberately not `Infinity`, which gives the\n // right page COUNT by accident and then puts `Infinity` in the range label\n // the moment `page` is above 1.\n return Math.max(1, this.total());\n });\n\n protected readonly totalPages = computed(() => Math.max(1, Math.ceil(this.total() / this.usablePageSize())));\n\n /**\n * Internal: where the previous arrow goes. Clamped to the end of the valid\n * range rather than being a plain `page() - 1`, because the page can\n * legitimately sit past that end while `total` is 0 (see the guard effect).\n * From page 5 of one page, `goTo(4)` is refused as out of range, so the arrow\n * was enabled and did nothing — and under `responsive` on a narrow box the\n * numbered cells are hidden by a container query, which left no way back at\n * all. `Math.min` only ever moves the target INWARD, so `goTo`'s own\n * past-the-end refusal keeps working as the backstop it is.\n */\n protected readonly prevPage = computed(() => Math.min(this.page() - 1, this.totalPages()));\n\n /**\n * Internal: the \"1–10 of 235\" line, as ONE catalog template.\n *\n * It used to be `` `${start}-${end} ${of} ${total}` ``, which localised the\n * word in the middle and nothing else: the ASCII hyphen between the bounds and\n * the operand order were both frozen in TypeScript, so ja-JP and ar-SA got\n * English word order and no language could change the range separator. The\n * three numbers go through `Intl.NumberFormat` for the same reason the total\n * beside them does — a five-figure total needs the locale's grouping, and\n * ar-SA needs its own digits.\n *\n * The `ofLabel` input is gone with it: an input for the middle word is the\n * concatenation, re-offered as API.\n */\n protected readonly rangeLabel = computed(() => {\n // The same guarded size, or the range label reads \"1 – NaN of 100\".\n const size = this.usablePageSize();\n const start = this.total() === 0 ? 0 : (this.page() - 1) * size + 1;\n const end = Math.min(this.page() * size, this.total());\n return this.rangeText({\n from: this.number(start),\n to: this.number(end),\n total: this.number(this.total()),\n });\n });\n\n /** Internal: the compact pager's `1 / 24`, likewise a catalog template. */\n protected readonly compactLabel = computed(() =>\n this.compactText({ current: this.number(this.page()), total: this.number(this.totalPages()) })\n );\n\n private readonly rangeText = useI18nFormatter('pagination.range', '{{from}}–{{to}} of {{total}}');\n private readonly compactText = useI18nFormatter('pagination.compact', '{{current}} / {{total}}');\n\n private readonly locale = inject(LOCALE_ID);\n\n /**\n * Grouped per `LOCALE_ID`. Only the range and the compact pager go through\n * this — a page NUMBER on a button is an identifier, and `1,024` on a button\n * reads as two things.\n */\n private number(value: number): string {\n return new Intl.NumberFormat(this.locale).format(value);\n }\n\n /**\n * Internal: page list with ellipses. Window of 7 visible page slots.\n *\n * 1 … 5 6 [7] 8 9 … 20\n */\n protected readonly pages = computed<readonly PageEntry[]>(() => {\n const total = this.totalPages();\n const current = this.page();\n\n if (total <= 7) {\n return Array.from({ length: total }, (_, i) => i + 1);\n }\n\n const items: PageEntry[] = [1];\n if (current > 4) items.push(ELLIPSIS);\n\n let start: number;\n let end: number;\n if (current <= 4) {\n start = 2;\n end = 5;\n } else if (current >= total - 3) {\n start = total - 4;\n end = total - 1;\n } else {\n start = current - 2;\n end = current + 2;\n }\n\n for (let i = start; i <= end; i++) items.push(i);\n if (current < total - 3) items.push(ELLIPSIS);\n if (end !== total) items.push(total);\n\n return items;\n });\n\n protected readonly ellipsis = ELLIPSIS;\n\n protected readonly classes = computed(() => {\n const parts = [\n 'wr-pagination',\n `wr-pagination--${this.align()}`,\n `wr-pagination--${this.size()}`,\n `wr-pagination--${this.shape()}`,\n ];\n if (this.disabled()) parts.push('wr-pagination--disabled');\n if (this.responsive()) parts.push('wr-pagination--responsive');\n return parts.join(' ');\n });\n\n protected isCurrent(page: number): boolean {\n return this.page() === page;\n }\n\n protected goTo(page: number | typeof ELLIPSIS): void {\n if (typeof page !== 'number') return;\n if (this.disabled() || page < 1 || page > this.totalPages() || page === this.page()) return;\n this.page.set(page);\n }\n\n /**\n * `wr-select` carries an `unknown` value by design, so the size arrives\n * untyped and is narrowed here. It used to arrive as `any` through\n * `[ngModel]`, which hid the coercion rather than removing the need for it.\n */\n constructor() {\n // `page` is a plain `model` — a `model()` takes no transform — so a\n // shrinking `total` and a host `[(page)]` write both land in it\n // unchecked. The guard used to read and write the page inside `untracked()`\n // and pull DOWNWARDS only, so it fired when `total` / `pageSize` moved and\n // never when the host wrote: past the end no cell carries `aria-current`,\n // the arrow at that end stays enabled and inert (`goTo` refuses the step\n // rather than correcting the stored page), and below 1 `rangeLabel()` counts\n // backwards (\"-9-0 of 120\"). Two-sided and tracked, the same shape\n // `wr-stepper` and `wr-carousel` use for their own host-owned `model()`.\n //\n // Only the UPPER bound is conditional. A `total` at or below 0 is the one\n // value where pulling the page down is destructive rather than cosmetic:\n // there is no `loading` input, so 0 is genuinely ambiguous between \"the\n // list is empty\" and \"the page is still in flight\", and the second is the\n // ordinary state of a server-paged host — Angular's `resource` drops its\n // value whenever the params change, and a params function returning an\n // object literal is never reference-equal, so `total` reads 0 between the\n // click and the response. `totalPages()` is 1 there, so the guard pulled\n // every page above the first back to 1 and emitted a `pageChange`\n // the host could not tell from a click: the pager could not leave page 1,\n // and each click cost two events. A negative `total` is folded into the\n // same branch because it is as meaningless as a zero one.\n //\n // The LOWER bound is unconditional, and gating it on `total` was a mistake\n // in the first cut of this fix: no value of `total` makes page 0 correct,\n // and while it was skipped a host write of -3 survived — `rangeLabel()`\n // guards `start` for an empty total and not `end`, so it read \"0--30 of 0\",\n // and the compact pager read \"-3 / 1\". It is written as one expression\n // rather than an early return so the effect depends on `page()` and\n // `total()` on every run, whichever branch decides the result.\n //\n // What IS still given up while `total` is 0 and the held page is past the\n // first: no cell carries `aria-current`, because `pages` renders the single\n // page and that is not the page the host holds. `rangeLabel()` is not in\n // that list — it already special-cases an empty total and reads \"0-0 of 0\".\n // Nor are the arrows: they compare against the RANGE rather than its ends\n // (see the template), so past the end next renders disabled and prev stays\n // enabled and steps back INTO range via `prevPage`. That is a change from\n // the \"enabled and inert\" behaviour described above, which this component\n // used to accept at both ends.\n effect(() => {\n const page = this.page();\n const floored = Math.max(1, page);\n const clamped = this.total() <= 0 ? floored : Math.min(floored, this.totalPages());\n if (clamped !== page) this.page.set(clamped);\n });\n }\n\n protected onSizeChange(size: unknown): void {\n const next = Number(size);\n if (this.disabled() || !Number.isFinite(next) || next <= 0 || next === this.pageSize()) return;\n // The size and nothing else. This used to clamp the page against the new\n // size too, and it read a value that was already superseded: `pageSize.set`\n // emits `pageSizeChange` synchronously, so by the following statement the\n // host has run its own policy — most tables reset to the first page — and\n // that write has not reached the model yet, it arrives on the next binding\n // pass. So the clamp computed a cap from the PRE-change page and wrote the\n // result over the host's decision: from page 6 at size 10, choosing 25\n // emitted `pageChange(5)` on top of the host's reset to 1, which the\n // host cannot tell from a navigation, and a second request went out. The\n // guard effect in the constructor takes it over and runs once the bindings\n // have settled, so it clamps the page the host actually wants.\n //\n // For a SETTLED `total` that is the whole story. For the server-paged host\n // this fix exists for it is not: the size change invalidates the payload\n // too, so `total` reads 0 and the guard returns early. Measured, from page\n // 6 at size 10 choosing 25 with a host that does not reset — in flight:\n // `navigations []`, page 6, total 0; after settle: `navigations [5]`, page\n // 5. So a non-resetting host issues one doomed request for a page that no\n // longer exists and is corrected only when the answer lands. That is an\n // accepted cost of refusing to write back from transient state, not\n // something the guard covers.\n this.pageSize.set(next);\n }\n}\n","<div class=\"wr-pagination__inner\">\n @if (showTotal()) {\n <span class=\"wr-pagination__total\">{{ rangeLabel() }}</span>\n }\n\n <div class=\"wr-pagination__nav\">\n <!--\n `wr-pagination__nav-btn` exists so the stylesheet can mirror the arrow\n glyph under `dir=\"rtl\"`. A class rather than `:first-child` /\n `:last-child` on the row: those two positions do hold today — which is why\n the `--square` end caps use them — but only while the prev button, the\n compact label and the next button all stay unconditional and in this\n order. A mirror keyed to position would follow a reorder, or land on a\n page cell, without a word.\n\n The two `[disabled]` bindings compare against the RANGE (`<= 1`,\n `>= totalPages()`) rather than for equality with its ends. While `total`\n is 0 the guard effect stops enforcing the upper bound, so the page can\n legitimately sit past `totalPages()` — and `=== totalPages()` reported\n next as enabled there while `goTo` refused the step. Prev is still\n enabled in that state, correctly: `prevPage()` walks it back into range.\n -->\n <wr-btn\n class=\"wr-pagination__nav-btn\"\n [size]=\"size()\"\n outlined\n [attr.aria-label]=\"resolvedPrev()\"\n [disabled]=\"disabled() || page() <= 1\"\n (click)=\"goTo(prevPage())\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m12 19-7-7 7-7\" />\n <path d=\"M19 12H5\" />\n </svg>\n </wr-btn>\n\n @for (page of pages(); track $index) {\n @if (page === ellipsis) {\n <span class=\"wr-pagination__ellipsis\">{{ ellipsis }}</span>\n } @else {\n <wr-btn\n class=\"wr-pagination__page\"\n [size]=\"size()\"\n [outlined]=\"!isCurrent(page)\"\n [color]=\"isCurrent(page) ? 'primary' : null\"\n [attr.aria-current]=\"isCurrent(page) ? 'page' : null\"\n [attr.aria-label]=\"goToPage({ page })\"\n [disabled]=\"disabled()\"\n (click)=\"goTo(page)\"\n >\n {{ page }}\n </wr-btn>\n }\n }\n\n <!-- Compact pager — shown only in responsive mode on a narrow box (CSS). -->\n <span class=\"wr-pagination__current\" aria-hidden=\"true\">{{ compactLabel() }}</span>\n\n <wr-btn\n class=\"wr-pagination__nav-btn\"\n [size]=\"size()\"\n outlined\n [attr.aria-label]=\"resolvedNext()\"\n [disabled]=\"disabled() || page() >= totalPages()\"\n (click)=\"goTo(page() + 1)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M5 12h14\" />\n <path d=\"m12 5 7 7-7 7\" />\n </svg>\n </wr-btn>\n </div>\n\n @if (showSizeChanger()) {\n <!--\n `ariaLabel`, not `[attr.aria-label]`: `<wr-select>`'s host carries no role,\n so an attribute there names nothing and never reaches the `role=\"combobox\"`\n button inside — the size changer announced itself as the catalog's generic\n \"Select\".\n -->\n <wr-select\n class=\"wr-pagination__size\"\n [ariaLabel]=\"resolvedItemsPerPage()\"\n [disabled]=\"disabled()\"\n [value]=\"pageSize()\"\n (valueChange)=\"onSizeChange($event)\"\n >\n @for (option of pageSizeOptions(); track option) {\n <wr-option [value]=\"option\">{{ perPage({ size: option }) }}</wr-option>\n }\n </wr-select>\n }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -189,7 +189,7 @@ class WrSchemaForm {
|
|
|
189
189
|
}, /* @ts-ignore */
|
|
190
190
|
...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
|
|
191
191
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrSchemaForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
192
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: WrSchemaForm, isStandalone: true, selector: "wr-schema-form", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.grid-template-columns": "template()" }, classAttribute: "wr-schema-form" }, ngImport: i0, template: "@for (f of fields(); track f.key) {\n @let state = f.field();\n\n <!-- Angular's `hidden()` does not hide anything on its own; the docs are\n explicit that the template has to honour it. -->\n @if (!state.hidden()) {\n <div class=\"wr-schema-form__field\" [style.grid-column]=\"f.column\">\n <wr-form-field [label]=\"f.labelAbove\" [hint]=\"f.spec.hint ?? ''\" [required]=\"state.required()\">\n @switch (f.spec.kind) {\n @case ('textarea') {\n <wr-textarea [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('number') {\n <wr-input-number\n [formField]=\"f.field\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n [step]=\"f.spec.step ?? 1\"\n [ariaLabel]=\"f.label\"\n />\n }\n @case ('select') {\n <wr-select [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? null\" [ariaLabel]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-option [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-option>\n }\n </wr-select>\n }\n @case ('checkbox') {\n <wr-checkbox [formField]=\"f.field\">{{ f.label }}</wr-checkbox>\n }\n @case ('switch') {\n <wr-switch [formField]=\"f.field\">{{ f.label }}</wr-switch>\n }\n @case ('radio') {\n <!-- `wr-radio-group` has no `ariaLabel` input, and needs none: its\n host is a `role=\"radiogroup\"`, where `aria-label` is legal. -->\n <wr-radio-group [formField]=\"f.field\" [attr.aria-label]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-radio [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-radio>\n }\n </wr-radio-group>\n }\n @case ('date') {\n <wr-date-picker [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('slider') {\n <wr-slider [formField]=\"f.field\" [step]=\"f.spec.step ?? 1\" [ariaLabel]=\"f.label\" />\n }\n @default {\n <input\n wrInput\n [formField]=\"f.field\"\n [type]=\"f.spec.type ?? 'text'\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n />\n }\n }\n </wr-form-field>\n </div>\n }\n}\n", dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "component", type: WrCheckbox, selector: "wr-checkbox", inputs: ["id", "checkboxValue", "ariaLabel", "checked", "disabled", "readonly", "size", "icon", "indeterminate"], outputs: ["checkedChange", "touch"] }, { kind: "component", type: WrDatePicker, selector: "wr-date-picker", inputs: ["mode", "format", "placeholder", "min", "max", "dateFilter", "timeFormat", "showSeconds", "step", "disabled", "readonly", "value", "ariaLabel", "panelAriaLabel"], outputs: ["valueChange", "touch"] }, { kind: "component", type: WrFormField, selector: "wr-form-field", inputs: ["label", "hint", "required", "optional", "optionalLabel", "controlId", "autoErrors"] }, { kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["size", "rounded"] }, { kind: "component", type: WrInputNumber, selector: "wr-input-number", inputs: ["min", "max", "step", "decimals", "showSteppers", "size", "rounded", "prefix", "suffix", "placeholder", "ariaLabel", "incrementLabel", "decrementLabel", "disabled", "readonly", "value"], outputs: ["valueChange", "touch"] }, { kind: "component", type: WrOption, selector: "wr-option", inputs: ["value", "disabled"] }, { kind: "component", type: WrRadio, selector: "wr-radio", inputs: ["id", "value", "icon", "size", "disabled"] }, { kind: "component", type: WrRadioGroup, selector: "wr-radio-group", inputs: ["name", "disabled", "readonly", "value"], outputs: ["valueChange", "touch"] }, { kind: "component", type: WrSelect, selector: "wr-select", inputs: ["placeholder", "clearLabel", "ariaLabel", "disabled", "readonly", "rounded", "size", "responsive", "mode", "searchable", "searchQuery", "options", "displayWith", "loader", "serverSearch", "debounceMs", "minChars", "freeText", "virtualScroll", "rowHeight", "viewportHeight", "overscan", "noResultsText", "loadingText", "loading", "separators", "allowDuplicates", "validate", "clearable", "maxItems", "maxTagCount", "value"], outputs: ["searchQueryChange", "searchChange", "valueChange", "touch"] }, { kind: "component", type: WrSlider, selector: "wr-slider", inputs: ["min", "max", "step", "range", "disabled", "readonly", "showLabel", "ariaLabel", "upperLabel", "value"], outputs: ["valueChange", "touch"] }, { kind: "component", type: WrSwitch, selector: "wr-switch", inputs: ["ariaLabel", "id", "disabled", "readonly", "size", "checked"], outputs: ["checkedChange", "touch"] }, { kind: "component", type: WrTextarea, selector: "wr-textarea", inputs: ["placeholder", "ariaLabel", "size", "rows", "resizable", "resize", "readonly", "autosize", "maxRows", "disabled", "value"], outputs: ["valueChange", "touch"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
192
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: WrSchemaForm, isStandalone: true, selector: "wr-schema-form", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.grid-template-columns": "template()" }, classAttribute: "wr-schema-form" }, ngImport: i0, template: "@for (f of fields(); track f.key) {\n @let state = f.field();\n\n <!-- Angular's `hidden()` does not hide anything on its own; the docs are\n explicit that the template has to honour it. -->\n @if (!state.hidden()) {\n <div class=\"wr-schema-form__field\" [style.grid-column]=\"f.column\">\n <wr-form-field [label]=\"f.labelAbove\" [hint]=\"f.spec.hint ?? ''\" [required]=\"state.required()\">\n @switch (f.spec.kind) {\n @case ('textarea') {\n <wr-textarea [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('number') {\n <wr-input-number\n [formField]=\"f.field\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n [step]=\"f.spec.step ?? 1\"\n [ariaLabel]=\"f.label\"\n />\n }\n @case ('select') {\n <wr-select [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? null\" [ariaLabel]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-option [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-option>\n }\n </wr-select>\n }\n @case ('checkbox') {\n <wr-checkbox [formField]=\"f.field\">{{ f.label }}</wr-checkbox>\n }\n @case ('switch') {\n <wr-switch [formField]=\"f.field\">{{ f.label }}</wr-switch>\n }\n @case ('radio') {\n <!-- `wr-radio-group` has no `ariaLabel` input, and needs none: its\n host is a `role=\"radiogroup\"`, where `aria-label` is legal. -->\n <wr-radio-group [formField]=\"f.field\" [attr.aria-label]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-radio [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-radio>\n }\n </wr-radio-group>\n }\n @case ('date') {\n <wr-date-picker [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('slider') {\n <wr-slider [formField]=\"f.field\" [step]=\"f.spec.step ?? 1\" [ariaLabel]=\"f.label\" />\n }\n @default {\n <input\n wrInput\n [formField]=\"f.field\"\n [type]=\"f.spec.type ?? 'text'\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n />\n }\n }\n </wr-form-field>\n </div>\n }\n}\n", dependencies: [{ kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "component", type: WrCheckbox, selector: "wr-checkbox", inputs: ["id", "checkboxValue", "ariaLabel", "checked", "disabled", "readonly", "size", "icon", "indeterminate"], outputs: ["checkedChange", "touch"] }, { kind: "component", type: WrDatePicker, selector: "wr-date-picker", inputs: ["mode", "format", "placeholder", "min", "max", "dateFilter", "timeFormat", "showSeconds", "step", "disabled", "readonly", "value", "ariaLabel", "panelAriaLabel"], outputs: ["valueChange", "touch"] }, { kind: "component", type: WrFormField, selector: "wr-form-field", inputs: ["label", "hint", "required", "optional", "optionalLabel", "controlId", "autoErrors"] }, { kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["size", "rounded"] }, { kind: "component", type: WrInputNumber, selector: "wr-input-number", inputs: ["min", "max", "step", "decimals", "showSteppers", "size", "rounded", "prefix", "suffix", "placeholder", "ariaLabel", "incrementLabel", "decrementLabel", "disabled", "readonly", "value"], outputs: ["valueChange", "touch"] }, { kind: "component", type: WrOption, selector: "wr-option", inputs: ["value", "disabled", "label"] }, { kind: "component", type: WrRadio, selector: "wr-radio", inputs: ["id", "value", "icon", "size", "disabled"] }, { kind: "component", type: WrRadioGroup, selector: "wr-radio-group", inputs: ["name", "disabled", "readonly", "value"], outputs: ["valueChange", "touch"] }, { kind: "component", type: WrSelect, selector: "wr-select", inputs: ["placeholder", "clearLabel", "ariaLabel", "disabled", "readonly", "rounded", "size", "responsive", "mode", "searchable", "searchQuery", "options", "displayWith", "loader", "serverSearch", "debounceMs", "minChars", "freeText", "virtualScroll", "rowHeight", "viewportHeight", "overscan", "noResultsText", "loadingText", "loading", "separators", "allowDuplicates", "validate", "clearable", "maxItems", "maxTagCount", "value"], outputs: ["searchQueryChange", "searchChange", "valueChange", "touch"] }, { kind: "component", type: WrSlider, selector: "wr-slider", inputs: ["min", "max", "step", "range", "disabled", "readonly", "showLabel", "ariaLabel", "upperLabel", "value"], outputs: ["valueChange", "touch"] }, { kind: "component", type: WrSwitch, selector: "wr-switch", inputs: ["ariaLabel", "id", "disabled", "readonly", "size", "checked"], outputs: ["checkedChange", "touch"] }, { kind: "component", type: WrTextarea, selector: "wr-textarea", inputs: ["placeholder", "ariaLabel", "size", "rows", "resizable", "resize", "readonly", "autosize", "maxRows", "disabled", "value"], outputs: ["valueChange", "touch"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
193
193
|
}
|
|
194
194
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: WrSchemaForm, decorators: [{
|
|
195
195
|
type: Component,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-schema-form.mjs","sources":["../../../projects/lib/schema-form/schema-form.ts","../../../projects/lib/schema-form/schema-form.html","../../../projects/lib/schema-form/ngwr-schema-form.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Component, ViewEncapsulation, computed, input, isDevMode } from '@angular/core';\nimport { FormField, type Field, type FieldTree, isFieldTree } from '@angular/forms/signals';\n\nimport { WrCheckbox } from 'ngwr/checkbox';\nimport { WrDatePicker } from 'ngwr/date-picker';\nimport { WR_FIELD, WrFormField, wrFieldLabel, type WrFieldSpec } from 'ngwr/form';\nimport { WrInput } from 'ngwr/input';\nimport { WrInputNumber } from 'ngwr/input-number';\nimport { WrRadio, WrRadioGroup } from 'ngwr/radio';\nimport { WrOption, WrSelect } from 'ngwr/select';\nimport { WrSlider } from 'ngwr/slider';\nimport { WrSwitch } from 'ngwr/switch';\nimport { WrTextarea } from 'ngwr/textarea';\nimport { numAttr } from 'ngwr/utils';\n\n/** One field the template draws, resolved once per schema change. */\ninterface WrRenderedField {\n readonly key: string;\n readonly field: Field<never>;\n readonly spec: WrFieldSpec;\n /** `spec.label`, or the key de-camel-cased. */\n readonly label: string;\n /**\n * The same text, or `''` for the two kinds that carry their own label inside\n * the control. Resolved here rather than in the template: a method call in a\n * template is re-run on every pass, and this is decided once per schema.\n */\n readonly labelAbove: string;\n /** `grid-column`, already clamped to the form's own `columns`. */\n readonly column: string;\n}\n\n/**\n * The two kinds that carry their own label. Everything else gets one above the\n * control, from `<wr-form-field>`.\n */\nconst SELF_LABELLING = new Set<WrFieldSpec['kind']>(['checkbox', 'switch']);\n\n/** What each kind writes into the model, for the dev-mode mismatch warning. */\nconst EXPECTED_TYPE: Partial<Record<WrFieldSpec['kind'], string>> = {\n input: 'string',\n textarea: 'string',\n number: 'number',\n checkbox: 'boolean',\n switch: 'boolean',\n slider: 'number',\n};\n\n/**\n * Warn when a group's described fields are about to be drawn by nothing.\n *\n * A field tree is iterated one level deep, so an object-valued field with no\n * `WR_FIELD` of its own is skipped exactly like an undescribed leaf — and its\n * children go with it. That is the right default (a model nests for reasons\n * that have nothing to do with a screen), but it is indistinguishable from a\n * half-drawn form: nothing is missing from the DOM, so nothing looks wrong.\n *\n * Describing the children and getting none of them is never intentional, so\n * that exact shape is what this reports. A group whose children carry no spec\n * either is silent, because it is a model detail this screen does not render.\n */\nfunction warnOnUndrawnGroup(key: string, group: unknown): void {\n // A field tree over a primitive is not iterable, and every undescribed leaf\n // arrives here — so the guard is what keeps this from throwing on the common\n // case rather than a defence against an exotic one.\n const subfields = (group as Partial<Iterable<[string, unknown]>>)[Symbol.iterator];\n if (typeof subfields !== 'function') return;\n\n let described = 0;\n for (const [, child] of group as Iterable<[string, unknown]>) {\n if (isFieldTree(child) && (child as Field<never>)().metadata(WR_FIELD)?.()) described++;\n }\n if (described === 0) return;\n\n // eslint-disable-next-line no-console -- dev-mode validation\n console.warn(\n `[NGWR] <wr-schema-form>: \"${key}\" is a group holding ${described} described ` +\n `field(s), and none of them are drawn — a field tree is walked one level deep. ` +\n `Draw the group with a second <wr-schema-form [field]=\"form.${key}\" />, or give ` +\n `\"${key}\" a WR_FIELD of its own if this screen is meant to skip it.`\n );\n}\n\n/**\n * Warn when a `kind` contradicts what the field actually holds.\n *\n * This is the one failure mode of the whole component that nothing else\n * reports: `kind` is a string on a metadata value, so TypeScript cannot hold it\n * against the field's own type, and a `kind: 'number'` on a `string` field\n * renders a `<wr-input-number>` that writes numbers into it forever. The runtime\n * value is the only evidence available, so an empty or nullish one says nothing\n * and is left alone.\n */\nfunction warnOnKindMismatch(f: WrRenderedField): void {\n const expected = EXPECTED_TYPE[f.spec.kind];\n const value = f.field().value();\n if (!expected || value === null || value === undefined || value === '') return;\n if (typeof value === expected) return;\n\n // eslint-disable-next-line no-console -- dev-mode validation\n console.warn(\n `[NGWR] <wr-schema-form>: field \"${f.key}\" is described as kind '${f.spec.kind}', ` +\n `which writes a ${expected}, but the field holds a ${typeof value}. ` +\n `The control will overwrite the model with the wrong type.`\n );\n}\n\n/**\n * A form drawn from a Signal Forms schema — one `<wr-form-field>`, one control\n * and one set of error messages per described field, laid out on a grid.\n *\n * The presentation rides on the SAME schema as the validation, through the\n * `WR_FIELD` metadata key from `ngwr/form`:\n *\n * ```ts\n * const profile = form(model, path => {\n * required(path.email);\n * email(path.email);\n * metadata(path.email, WR_FIELD, () => ({ kind: 'input', type: 'email' }));\n * metadata(path.role, WR_FIELD, () => ({ kind: 'select', options: ROLES }));\n * });\n * ```\n * ```html\n * <wr-schema-form [field]=\"profile\" [columns]=\"2\" />\n * ```\n *\n * **A field with no `WR_FIELD` metadata is skipped**, silently and on purpose: a\n * schema may describe more than one screen renders, and guessing a control from\n * a field's type would draw a `wr-switch` for every boolean flag in the model.\n * Order is the model's own key order.\n *\n * **`required`, `disabled`, `readonly` and `hidden` are NOT in the spec.** They\n * are Angular's own schema functions, `[formField]` already applies the first\n * three to the control, and this component reads `hidden()` to decide whether to\n * draw the field at all — which Angular's docs say a template has to do itself.\n * Re-declaring any of them here would be a second source of truth for state the\n * form already owns.\n *\n * **Why its own entry point rather than `ngwr/form`.** Drawing nine kinds means\n * depending on nine controls, and `ngwr/form` is the layout primitive every\n * form imports — folding this in would put a date picker and a slider into the\n * bundle of an app that only wanted `<wr-form-field>`.\n *\n * @see https://ngwr.dev/reference/components/schema-form\n */\n@Component({\n selector: 'wr-schema-form',\n templateUrl: './schema-form.html',\n encapsulation: ViewEncapsulation.None,\n imports: [\n FormField,\n WrCheckbox,\n WrDatePicker,\n WrFormField,\n WrInput,\n WrInputNumber,\n WrOption,\n WrRadio,\n WrRadioGroup,\n WrSelect,\n WrSlider,\n WrSwitch,\n WrTextarea,\n ],\n host: {\n class: 'wr-schema-form',\n '[style.grid-template-columns]': 'template()',\n },\n})\nexport class WrSchemaForm<T extends object> {\n // Generic, rather than `FieldTree<Record<string, unknown>>`, because a model\n // declared as an `interface` has no index signature and is not assignable to\n // that: every consumer would have had to declare their model as a type alias\n // just to bind it. `T extends object` still refuses a field tree over a\n // primitive, which has no subfields and would draw nothing.\n /**\n * The form to draw — the value `form()` returned, or any object-valued field\n * inside one.\n */\n readonly field = input.required<FieldTree<T>>();\n\n /**\n * Grid columns. A field spans one of them unless its spec says otherwise.\n * @default 1\n */\n readonly columns = input(1, { transform: numAttr(1) });\n\n /**\n * The track list, written as a style rather than published as a\n * `--wr-schema-form-columns` hook: `columns` always has a value, so the\n * inline style would win over any declaration a consumer wrote and the hook\n * would be one nobody could use.\n */\n protected readonly template = computed(() => `repeat(${Math.max(1, this.columns())}, minmax(0, 1fr))`);\n\n /**\n * The described fields, in the model's key order.\n *\n * A field tree over an object is iterable as `[key, field]` pairs, which is\n * the whole reason this component needs no field list of its own. `isFieldTree`\n * guards the pair: a model carrying a method or a non-field property would\n * otherwise be read as a field and answer `undefined` to everything.\n */\n protected readonly fields = computed<readonly WrRenderedField[]>(() => {\n const columns = this.columns();\n const out: WrRenderedField[] = [];\n\n for (const [key, child] of this.field() as unknown as Iterable<[string, unknown]>) {\n if (!isFieldTree(child)) continue;\n\n const field = child as Field<never>;\n const spec = field().metadata(WR_FIELD)?.();\n if (!spec) {\n if (isDevMode()) warnOnUndrawnGroup(key, child);\n continue;\n }\n\n const label = spec.label ?? wrFieldLabel(key);\n const span = Math.min(Math.max(Math.round(spec.span ?? 1), 1), columns);\n out.push({\n key,\n field,\n spec,\n label,\n labelAbove: SELF_LABELLING.has(spec.kind) ? '' : label,\n column: `span ${span}`,\n });\n }\n\n if (isDevMode()) out.forEach(f => warnOnKindMismatch(f));\n return out;\n });\n}\n","@for (f of fields(); track f.key) {\n @let state = f.field();\n\n <!-- Angular's `hidden()` does not hide anything on its own; the docs are\n explicit that the template has to honour it. -->\n @if (!state.hidden()) {\n <div class=\"wr-schema-form__field\" [style.grid-column]=\"f.column\">\n <wr-form-field [label]=\"f.labelAbove\" [hint]=\"f.spec.hint ?? ''\" [required]=\"state.required()\">\n @switch (f.spec.kind) {\n @case ('textarea') {\n <wr-textarea [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('number') {\n <wr-input-number\n [formField]=\"f.field\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n [step]=\"f.spec.step ?? 1\"\n [ariaLabel]=\"f.label\"\n />\n }\n @case ('select') {\n <wr-select [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? null\" [ariaLabel]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-option [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-option>\n }\n </wr-select>\n }\n @case ('checkbox') {\n <wr-checkbox [formField]=\"f.field\">{{ f.label }}</wr-checkbox>\n }\n @case ('switch') {\n <wr-switch [formField]=\"f.field\">{{ f.label }}</wr-switch>\n }\n @case ('radio') {\n <!-- `wr-radio-group` has no `ariaLabel` input, and needs none: its\n host is a `role=\"radiogroup\"`, where `aria-label` is legal. -->\n <wr-radio-group [formField]=\"f.field\" [attr.aria-label]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-radio [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-radio>\n }\n </wr-radio-group>\n }\n @case ('date') {\n <wr-date-picker [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('slider') {\n <wr-slider [formField]=\"f.field\" [step]=\"f.spec.step ?? 1\" [ariaLabel]=\"f.label\" />\n }\n @default {\n <input\n wrInput\n [formField]=\"f.field\"\n [type]=\"f.spec.type ?? 'text'\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n />\n }\n }\n </wr-form-field>\n </div>\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-schema-form.mjs","sources":["../../../projects/lib/schema-form/schema-form.ts","../../../projects/lib/schema-form/schema-form.html","../../../projects/lib/schema-form/ngwr-schema-form.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Component, ViewEncapsulation, computed, input, isDevMode } from '@angular/core';\nimport { FormField, type Field, type FieldTree, isFieldTree } from '@angular/forms/signals';\n\nimport { WrCheckbox } from 'ngwr/checkbox';\nimport { WrDatePicker } from 'ngwr/date-picker';\nimport { WR_FIELD, WrFormField, wrFieldLabel, type WrFieldSpec } from 'ngwr/form';\nimport { WrInput } from 'ngwr/input';\nimport { WrInputNumber } from 'ngwr/input-number';\nimport { WrRadio, WrRadioGroup } from 'ngwr/radio';\nimport { WrOption, WrSelect } from 'ngwr/select';\nimport { WrSlider } from 'ngwr/slider';\nimport { WrSwitch } from 'ngwr/switch';\nimport { WrTextarea } from 'ngwr/textarea';\nimport { numAttr } from 'ngwr/utils';\n\n/** One field the template draws, resolved once per schema change. */\ninterface WrRenderedField {\n readonly key: string;\n readonly field: Field<never>;\n readonly spec: WrFieldSpec;\n /** `spec.label`, or the key de-camel-cased. */\n readonly label: string;\n /**\n * The same text, or `''` for the two kinds that carry their own label inside\n * the control. Resolved here rather than in the template: a method call in a\n * template is re-run on every pass, and this is decided once per schema.\n */\n readonly labelAbove: string;\n /** `grid-column`, already clamped to the form's own `columns`. */\n readonly column: string;\n}\n\n/**\n * The two kinds that carry their own label. Everything else gets one above the\n * control, from `<wr-form-field>`.\n */\nconst SELF_LABELLING = new Set<WrFieldSpec['kind']>(['checkbox', 'switch']);\n\n/** What each kind writes into the model, for the dev-mode mismatch warning. */\nconst EXPECTED_TYPE: Partial<Record<WrFieldSpec['kind'], string>> = {\n input: 'string',\n textarea: 'string',\n number: 'number',\n checkbox: 'boolean',\n switch: 'boolean',\n slider: 'number',\n};\n\n/**\n * Warn when a group's described fields are about to be drawn by nothing.\n *\n * A field tree is iterated one level deep, so an object-valued field with no\n * `WR_FIELD` of its own is skipped exactly like an undescribed leaf — and its\n * children go with it. That is the right default (a model nests for reasons\n * that have nothing to do with a screen), but it is indistinguishable from a\n * half-drawn form: nothing is missing from the DOM, so nothing looks wrong.\n *\n * Describing the children and getting none of them is never intentional, so\n * that exact shape is what this reports. A group whose children carry no spec\n * either is silent, because it is a model detail this screen does not render.\n */\nfunction warnOnUndrawnGroup(key: string, group: unknown): void {\n // A field tree over a primitive is not iterable, and every undescribed leaf\n // arrives here — so the guard is what keeps this from throwing on the common\n // case rather than a defence against an exotic one.\n const subfields = (group as Partial<Iterable<[string, unknown]>>)[Symbol.iterator];\n if (typeof subfields !== 'function') return;\n\n let described = 0;\n for (const [, child] of group as Iterable<[string, unknown]>) {\n if (isFieldTree(child) && (child as Field<never>)().metadata(WR_FIELD)?.()) described++;\n }\n if (described === 0) return;\n\n // eslint-disable-next-line no-console -- dev-mode validation\n console.warn(\n `[NGWR] <wr-schema-form>: \"${key}\" is a group holding ${described} described ` +\n `field(s), and none of them are drawn — a field tree is walked one level deep. ` +\n `Draw the group with a second <wr-schema-form [field]=\"form.${key}\" />, or give ` +\n `\"${key}\" a WR_FIELD of its own if this screen is meant to skip it.`\n );\n}\n\n/**\n * Warn when a `kind` contradicts what the field actually holds.\n *\n * This is the one failure mode of the whole component that nothing else\n * reports: `kind` is a string on a metadata value, so TypeScript cannot hold it\n * against the field's own type, and a `kind: 'number'` on a `string` field\n * renders a `<wr-input-number>` that writes numbers into it forever. The runtime\n * value is the only evidence available, so an empty or nullish one says nothing\n * and is left alone.\n */\nfunction warnOnKindMismatch(f: WrRenderedField): void {\n const expected = EXPECTED_TYPE[f.spec.kind];\n const value = f.field().value();\n if (!expected || value === null || value === undefined || value === '') return;\n if (typeof value === expected) return;\n\n // eslint-disable-next-line no-console -- dev-mode validation\n console.warn(\n `[NGWR] <wr-schema-form>: field \"${f.key}\" is described as kind '${f.spec.kind}', ` +\n `which writes a ${expected}, but the field holds a ${typeof value}. ` +\n `The control will overwrite the model with the wrong type.`\n );\n}\n\n/**\n * A form drawn from a Signal Forms schema — one `<wr-form-field>`, one control\n * and one set of error messages per described field, laid out on a grid.\n *\n * The presentation rides on the SAME schema as the validation, through the\n * `WR_FIELD` metadata key from `ngwr/form`:\n *\n * ```ts\n * const profile = form(model, path => {\n * required(path.email);\n * email(path.email);\n * metadata(path.email, WR_FIELD, () => ({ kind: 'input', type: 'email' }));\n * metadata(path.role, WR_FIELD, () => ({ kind: 'select', options: ROLES }));\n * });\n * ```\n * ```html\n * <wr-schema-form [field]=\"profile\" [columns]=\"2\" />\n * ```\n *\n * **A field with no `WR_FIELD` metadata is skipped**, silently and on purpose: a\n * schema may describe more than one screen renders, and guessing a control from\n * a field's type would draw a `wr-switch` for every boolean flag in the model.\n * Order is the model's own key order.\n *\n * **`required`, `disabled`, `readonly` and `hidden` are NOT in the spec.** They\n * are Angular's own schema functions, `[formField]` already applies the first\n * three to the control, and this component reads `hidden()` to decide whether to\n * draw the field at all — which Angular's docs say a template has to do itself.\n * Re-declaring any of them here would be a second source of truth for state the\n * form already owns.\n *\n * **Why its own entry point rather than `ngwr/form`.** Drawing nine kinds means\n * depending on nine controls, and `ngwr/form` is the layout primitive every\n * form imports — folding this in would put a date picker and a slider into the\n * bundle of an app that only wanted `<wr-form-field>`.\n *\n * @see https://ngwr.dev/reference/components/schema-form\n */\n@Component({\n selector: 'wr-schema-form',\n templateUrl: './schema-form.html',\n encapsulation: ViewEncapsulation.None,\n imports: [\n FormField,\n WrCheckbox,\n WrDatePicker,\n WrFormField,\n WrInput,\n WrInputNumber,\n WrOption,\n WrRadio,\n WrRadioGroup,\n WrSelect,\n WrSlider,\n WrSwitch,\n WrTextarea,\n ],\n host: {\n class: 'wr-schema-form',\n '[style.grid-template-columns]': 'template()',\n },\n})\nexport class WrSchemaForm<T extends object> {\n // Generic, rather than `FieldTree<Record<string, unknown>>`, because a model\n // declared as an `interface` has no index signature and is not assignable to\n // that: every consumer would have had to declare their model as a type alias\n // just to bind it. `T extends object` still refuses a field tree over a\n // primitive, which has no subfields and would draw nothing.\n /**\n * The form to draw — the value `form()` returned, or any object-valued field\n * inside one.\n */\n readonly field = input.required<FieldTree<T>>();\n\n /**\n * Grid columns. A field spans one of them unless its spec says otherwise.\n * @default 1\n */\n readonly columns = input(1, { transform: numAttr(1) });\n\n /**\n * The track list, written as a style rather than published as a\n * `--wr-schema-form-columns` hook: `columns` always has a value, so the\n * inline style would win over any declaration a consumer wrote and the hook\n * would be one nobody could use.\n */\n protected readonly template = computed(() => `repeat(${Math.max(1, this.columns())}, minmax(0, 1fr))`);\n\n /**\n * The described fields, in the model's key order.\n *\n * A field tree over an object is iterable as `[key, field]` pairs, which is\n * the whole reason this component needs no field list of its own. `isFieldTree`\n * guards the pair: a model carrying a method or a non-field property would\n * otherwise be read as a field and answer `undefined` to everything.\n */\n protected readonly fields = computed<readonly WrRenderedField[]>(() => {\n const columns = this.columns();\n const out: WrRenderedField[] = [];\n\n for (const [key, child] of this.field() as unknown as Iterable<[string, unknown]>) {\n if (!isFieldTree(child)) continue;\n\n const field = child as Field<never>;\n const spec = field().metadata(WR_FIELD)?.();\n if (!spec) {\n if (isDevMode()) warnOnUndrawnGroup(key, child);\n continue;\n }\n\n const label = spec.label ?? wrFieldLabel(key);\n const span = Math.min(Math.max(Math.round(spec.span ?? 1), 1), columns);\n out.push({\n key,\n field,\n spec,\n label,\n labelAbove: SELF_LABELLING.has(spec.kind) ? '' : label,\n column: `span ${span}`,\n });\n }\n\n if (isDevMode()) out.forEach(f => warnOnKindMismatch(f));\n return out;\n });\n}\n","@for (f of fields(); track f.key) {\n @let state = f.field();\n\n <!-- Angular's `hidden()` does not hide anything on its own; the docs are\n explicit that the template has to honour it. -->\n @if (!state.hidden()) {\n <div class=\"wr-schema-form__field\" [style.grid-column]=\"f.column\">\n <wr-form-field [label]=\"f.labelAbove\" [hint]=\"f.spec.hint ?? ''\" [required]=\"state.required()\">\n @switch (f.spec.kind) {\n @case ('textarea') {\n <wr-textarea [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('number') {\n <wr-input-number\n [formField]=\"f.field\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n [step]=\"f.spec.step ?? 1\"\n [ariaLabel]=\"f.label\"\n />\n }\n @case ('select') {\n <wr-select [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? null\" [ariaLabel]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-option [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-option>\n }\n </wr-select>\n }\n @case ('checkbox') {\n <wr-checkbox [formField]=\"f.field\">{{ f.label }}</wr-checkbox>\n }\n @case ('switch') {\n <wr-switch [formField]=\"f.field\">{{ f.label }}</wr-switch>\n }\n @case ('radio') {\n <!-- `wr-radio-group` has no `ariaLabel` input, and needs none: its\n host is a `role=\"radiogroup\"`, where `aria-label` is legal. -->\n <wr-radio-group [formField]=\"f.field\" [attr.aria-label]=\"f.label\">\n @for (option of f.spec.options ?? []; track option.value) {\n <wr-radio [value]=\"option.value\" [disabled]=\"option.disabled ?? false\">{{ option.label }}</wr-radio>\n }\n </wr-radio-group>\n }\n @case ('date') {\n <wr-date-picker [formField]=\"f.field\" [placeholder]=\"f.spec.placeholder ?? ''\" [ariaLabel]=\"f.label\" />\n }\n @case ('slider') {\n <wr-slider [formField]=\"f.field\" [step]=\"f.spec.step ?? 1\" [ariaLabel]=\"f.label\" />\n }\n @default {\n <input\n wrInput\n [formField]=\"f.field\"\n [type]=\"f.spec.type ?? 'text'\"\n [placeholder]=\"f.spec.placeholder ?? ''\"\n />\n }\n }\n </wr-form-field>\n </div>\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -25,9 +25,28 @@ class WrOptionHarness extends ComponentHarness {
|
|
|
25
25
|
.addOption('selected', options.selected, async (harness, selected) => (await harness.isSelected()) === selected)
|
|
26
26
|
.addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* The option's text as drawn, trimmed — without its leading visual.
|
|
30
|
+
*
|
|
31
|
+
* A `wrOptionLeading` template renders INSIDE the row, so reading the whole
|
|
32
|
+
* host would hand back an avatar's initials as the first word of the name. It
|
|
33
|
+
* is excluded here for the same reason the select excludes it from the label.
|
|
34
|
+
*
|
|
35
|
+
* This is the drawn text, not the `label` input: an option whose `label` is
|
|
36
|
+
* shorter than what it draws still answers with what it draws. What the select
|
|
37
|
+
* REPORTS for it is on the select harness, as `getValueText()` or
|
|
38
|
+
* `getChipLabels()`.
|
|
39
|
+
*/
|
|
29
40
|
async getText() {
|
|
30
|
-
return (await this.host()).text();
|
|
41
|
+
return (await this.host()).text({ exclude: '.wr-option__leading' });
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether the row is drawing a leading visual right now. Reads the slot the
|
|
45
|
+
* select renders the template into, so it answers `false` for an option that
|
|
46
|
+
* declares a template while the panel is closed — which is the contract.
|
|
47
|
+
*/
|
|
48
|
+
async hasLeading() {
|
|
49
|
+
return (await this.locatorForOptional('.wr-option__leading')()) !== null;
|
|
31
50
|
}
|
|
32
51
|
/** Whether the option is currently selected. */
|
|
33
52
|
async isSelected() {
|