@rt-tools/ui-kit-v2 0.23.2 → 0.23.4
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/fesm2022/rt-tools-ui-kit-v2-accordion.mjs +142 -71
- package/fesm2022/rt-tools-ui-kit-v2-accordion.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-action-bar.mjs +467 -231
- package/fesm2022/rt-tools-ui-kit-v2-action-bar.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-ai-run-status.mjs +260 -91
- package/fesm2022/rt-tools-ui-kit-v2-ai-run-status.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-aside-section.mjs +80 -46
- package/fesm2022/rt-tools-ui-kit-v2-aside-section.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-aside.mjs +984 -697
- package/fesm2022/rt-tools-ui-kit-v2-aside.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-autocomplete.mjs +452 -210
- package/fesm2022/rt-tools-ui-kit-v2-autocomplete.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-bar-list.mjs +143 -52
- package/fesm2022/rt-tools-ui-kit-v2-bar-list.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-bottom-sheet.mjs +158 -83
- package/fesm2022/rt-tools-ui-kit-v2-bottom-sheet.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-breadcrumbs.mjs +335 -0
- package/fesm2022/rt-tools-ui-kit-v2-breadcrumbs.mjs.map +1 -0
- package/fesm2022/rt-tools-ui-kit-v2-button.mjs +437 -363
- package/fesm2022/rt-tools-ui-kit-v2-button.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-calendar.mjs +430 -149
- package/fesm2022/rt-tools-ui-kit-v2-calendar.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-card.mjs +155 -71
- package/fesm2022/rt-tools-ui-kit-v2-card.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-carousel.mjs +155 -0
- package/fesm2022/rt-tools-ui-kit-v2-carousel.mjs.map +1 -0
- package/fesm2022/rt-tools-ui-kit-v2-checkbox.mjs +215 -110
- package/fesm2022/rt-tools-ui-kit-v2-checkbox.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-collapsible-text.mjs +158 -75
- package/fesm2022/rt-tools-ui-kit-v2-collapsible-text.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-color-palette.mjs +769 -0
- package/fesm2022/rt-tools-ui-kit-v2-color-palette.mjs.map +1 -0
- package/fesm2022/rt-tools-ui-kit-v2-confirm-popover.mjs +462 -253
- package/fesm2022/rt-tools-ui-kit-v2-confirm-popover.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-container.mjs +1276 -1316
- package/fesm2022/rt-tools-ui-kit-v2-container.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-copy-value.mjs +185 -80
- package/fesm2022/rt-tools-ui-kit-v2-copy-value.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-core.mjs +2309 -2619
- package/fesm2022/rt-tools-ui-kit-v2-core.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-counter-row.mjs +100 -44
- package/fesm2022/rt-tools-ui-kit-v2-counter-row.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-counter.mjs +290 -155
- package/fesm2022/rt-tools-ui-kit-v2-counter.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-data-list.mjs +2097 -858
- package/fesm2022/rt-tools-ui-kit-v2-data-list.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-data-table.mjs +2247 -1176
- package/fesm2022/rt-tools-ui-kit-v2-data-table.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-date-picker.mjs +1043 -705
- package/fesm2022/rt-tools-ui-kit-v2-date-picker.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-date-range.mjs +810 -525
- package/fesm2022/rt-tools-ui-kit-v2-date-range.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-delta-view.mjs +158 -138
- package/fesm2022/rt-tools-ui-kit-v2-delta-view.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-detail-list.mjs +177 -67
- package/fesm2022/rt-tools-ui-kit-v2-detail-list.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-dialog.mjs +550 -477
- package/fesm2022/rt-tools-ui-kit-v2-dialog.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-dot-field.mjs +185 -168
- package/fesm2022/rt-tools-ui-kit-v2-dot-field.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-download-link.mjs +116 -43
- package/fesm2022/rt-tools-ui-kit-v2-download-link.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-draggable-tree.mjs +585 -0
- package/fesm2022/rt-tools-ui-kit-v2-draggable-tree.mjs.map +1 -0
- package/fesm2022/rt-tools-ui-kit-v2-dynamic-list.mjs +755 -259
- package/fesm2022/rt-tools-ui-kit-v2-dynamic-list.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-dynamic-selector.mjs +3266 -1115
- package/fesm2022/rt-tools-ui-kit-v2-dynamic-selector.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-empty-state.mjs +190 -55
- package/fesm2022/rt-tools-ui-kit-v2-empty-state.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-expansion-panel.mjs +283 -103
- package/fesm2022/rt-tools-ui-kit-v2-expansion-panel.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-field.mjs +393 -177
- package/fesm2022/rt-tools-ui-kit-v2-field.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-file-card.mjs +399 -207
- package/fesm2022/rt-tools-ui-kit-v2-file-card.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-file-drop.mjs +288 -212
- package/fesm2022/rt-tools-ui-kit-v2-file-drop.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-file-input.mjs +262 -116
- package/fesm2022/rt-tools-ui-kit-v2-file-input.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-file-list.mjs +57 -31
- package/fesm2022/rt-tools-ui-kit-v2-file-list.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-filter-control.mjs +258 -90
- package/fesm2022/rt-tools-ui-kit-v2-filter-control.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-form-control.mjs +249 -215
- package/fesm2022/rt-tools-ui-kit-v2-form-control.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-header.mjs +199 -71
- package/fesm2022/rt-tools-ui-kit-v2-header.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree-selector.mjs +231 -0
- package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree-selector.mjs.map +1 -0
- package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree.mjs +336 -0
- package/fesm2022/rt-tools-ui-kit-v2-hybrid-tree.mjs.map +1 -0
- package/fesm2022/rt-tools-ui-kit-v2-icon-button.mjs +451 -162
- package/fesm2022/rt-tools-ui-kit-v2-icon-button.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-icon.mjs +931 -686
- package/fesm2022/rt-tools-ui-kit-v2-icon.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-image-cropper.mjs +664 -491
- package/fesm2022/rt-tools-ui-kit-v2-image-cropper.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-image-upload.mjs +574 -222
- package/fesm2022/rt-tools-ui-kit-v2-image-upload.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-info-item.mjs +150 -36
- package/fesm2022/rt-tools-ui-kit-v2-info-item.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-inline-edit.mjs +606 -0
- package/fesm2022/rt-tools-ui-kit-v2-inline-edit.mjs.map +1 -0
- package/fesm2022/rt-tools-ui-kit-v2-input-number.mjs +461 -355
- package/fesm2022/rt-tools-ui-kit-v2-input-number.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-input.mjs +338 -143
- package/fesm2022/rt-tools-ui-kit-v2-input.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-live-badge.mjs +135 -48
- package/fesm2022/rt-tools-ui-kit-v2-live-badge.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-logo.mjs +166 -92
- package/fesm2022/rt-tools-ui-kit-v2-logo.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-markdown-text.mjs +118 -56
- package/fesm2022/rt-tools-ui-kit-v2-markdown-text.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-menu.mjs +1054 -527
- package/fesm2022/rt-tools-ui-kit-v2-menu.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-message.mjs +230 -104
- package/fesm2022/rt-tools-ui-kit-v2-message.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-money-list.mjs +210 -70
- package/fesm2022/rt-tools-ui-kit-v2-money-list.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-multiselect.mjs +532 -311
- package/fesm2022/rt-tools-ui-kit-v2-multiselect.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-night-grid.mjs +125 -50
- package/fesm2022/rt-tools-ui-kit-v2-night-grid.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-note.mjs +73 -43
- package/fesm2022/rt-tools-ui-kit-v2-note.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-notifications-bell.mjs +161 -53
- package/fesm2022/rt-tools-ui-kit-v2-notifications-bell.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-page-header.mjs +491 -310
- package/fesm2022/rt-tools-ui-kit-v2-page-header.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-pagination.mjs +374 -174
- package/fesm2022/rt-tools-ui-kit-v2-pagination.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-photo-viewer.mjs +202 -159
- package/fesm2022/rt-tools-ui-kit-v2-photo-viewer.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-popover.mjs +438 -309
- package/fesm2022/rt-tools-ui-kit-v2-popover.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-prompt-suggestion.mjs +135 -45
- package/fesm2022/rt-tools-ui-kit-v2-prompt-suggestion.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-radio-button.mjs +293 -171
- package/fesm2022/rt-tools-ui-kit-v2-radio-button.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-radius.mjs +62 -55
- package/fesm2022/rt-tools-ui-kit-v2-radius.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-rich-editor.mjs +3379 -1466
- package/fesm2022/rt-tools-ui-kit-v2-rich-editor.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-ripple.mjs +114 -116
- package/fesm2022/rt-tools-ui-kit-v2-ripple.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-scroll-area.mjs +406 -299
- package/fesm2022/rt-tools-ui-kit-v2-scroll-area.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-section-nav.mjs +137 -36
- package/fesm2022/rt-tools-ui-kit-v2-section-nav.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-select.mjs +687 -468
- package/fesm2022/rt-tools-ui-kit-v2-select.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-side-menu.mjs +2195 -1569
- package/fesm2022/rt-tools-ui-kit-v2-side-menu.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-skeleton-wrapper.mjs +187 -48
- package/fesm2022/rt-tools-ui-kit-v2-skeleton-wrapper.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-skeleton.mjs +242 -122
- package/fesm2022/rt-tools-ui-kit-v2-skeleton.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-speed-menu.mjs +439 -0
- package/fesm2022/rt-tools-ui-kit-v2-speed-menu.mjs.map +1 -0
- package/fesm2022/rt-tools-ui-kit-v2-spinner.mjs +218 -69
- package/fesm2022/rt-tools-ui-kit-v2-spinner.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-split-button.mjs +292 -91
- package/fesm2022/rt-tools-ui-kit-v2-split-button.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-stat-tile.mjs +225 -138
- package/fesm2022/rt-tools-ui-kit-v2-stat-tile.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-stepper.mjs +150 -116
- package/fesm2022/rt-tools-ui-kit-v2-stepper.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-table.mjs +2462 -1583
- package/fesm2022/rt-tools-ui-kit-v2-table.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-tabs.mjs +647 -346
- package/fesm2022/rt-tools-ui-kit-v2-tabs.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-tag.mjs +335 -121
- package/fesm2022/rt-tools-ui-kit-v2-tag.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-textarea.mjs +217 -84
- package/fesm2022/rt-tools-ui-kit-v2-textarea.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-theme-toggle.mjs +151 -72
- package/fesm2022/rt-tools-ui-kit-v2-theme-toggle.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-thread-list.mjs +697 -226
- package/fesm2022/rt-tools-ui-kit-v2-thread-list.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-timeline.mjs +92 -38
- package/fesm2022/rt-tools-ui-kit-v2-timeline.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-toast.mjs +752 -338
- package/fesm2022/rt-tools-ui-kit-v2-toast.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-toggle-button-group.mjs +310 -129
- package/fesm2022/rt-tools-ui-kit-v2-toggle-button-group.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-toggle-switch.mjs +286 -158
- package/fesm2022/rt-tools-ui-kit-v2-toggle-switch.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-toolbar.mjs +270 -134
- package/fesm2022/rt-tools-ui-kit-v2-toolbar.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-tooltip.mjs +322 -202
- package/fesm2022/rt-tools-ui-kit-v2-tooltip.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-tree-selector.mjs +908 -0
- package/fesm2022/rt-tools-ui-kit-v2-tree-selector.mjs.map +1 -0
- package/fesm2022/rt-tools-ui-kit-v2-tree.mjs +785 -0
- package/fesm2022/rt-tools-ui-kit-v2-tree.mjs.map +1 -0
- package/fesm2022/rt-tools-ui-kit-v2-welcome-dialog.mjs +118 -63
- package/fesm2022/rt-tools-ui-kit-v2-welcome-dialog.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-workspace-details.mjs +783 -262
- package/fesm2022/rt-tools-ui-kit-v2-workspace-details.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2-workspace.mjs +633 -294
- package/fesm2022/rt-tools-ui-kit-v2-workspace.mjs.map +1 -1
- package/fesm2022/rt-tools-ui-kit-v2.mjs +100 -103
- package/package.json +41 -1
- package/src/assets/icons/collapse-all.svg +9 -0
- package/src/assets/icons/expand-all.svg +9 -0
- package/src/assets/icons-material/collapse-all.fill.svg +5 -0
- package/src/assets/icons-material/collapse-all.svg +5 -0
- package/src/assets/icons-material/expand-all.fill.svg +5 -0
- package/src/assets/icons-material/expand-all.svg +5 -0
- package/src/assets/icons-material/undo.fill.svg +5 -0
- package/src/assets/icons-material/undo.svg +5 -0
- package/src/lib/components/bottom-sheet/CONTEXT.md +20 -2
- package/src/lib/components/bottom-sheet/rt-bottom-sheet.component.scss +35 -1
- package/src/lib/components/breadcrumbs/CONTEXT.md +37 -0
- package/src/lib/components/breadcrumbs/rt-breadcrumbs.component.scss +205 -0
- package/src/lib/components/carousel/CONTEXT.md +42 -0
- package/src/lib/components/carousel/rt-carousel.component.scss +117 -0
- package/src/lib/components/checkbox/CONTEXT.md +3 -0
- package/src/lib/components/checkbox/rt-checkbox.component.scss +9 -3
- package/src/lib/components/color-palette/CONTEXT.md +49 -0
- package/src/lib/components/color-palette/picker/rt-color-picker.component.scss +161 -0
- package/src/lib/components/color-palette/rt-color-palette.component.scss +132 -0
- package/src/lib/components/draggable-tree/CONTEXT.md +54 -0
- package/src/lib/components/draggable-tree/rt-draggable-tree.component.scss +219 -0
- package/src/lib/components/hybrid-tree/CONTEXT.md +31 -0
- package/src/lib/components/hybrid-tree-selector/CONTEXT.md +25 -0
- package/src/lib/components/icon-button/CONTEXT.md +3 -0
- package/src/lib/components/inline-edit/CONTEXT.md +41 -0
- package/src/lib/components/inline-edit/rt-inline-edit.component.scss +274 -0
- package/src/lib/components/popover/CONTEXT.md +3 -0
- package/src/lib/components/speed-menu/CONTEXT.md +34 -0
- package/src/lib/components/speed-menu/rt-speed-menu.component.scss +118 -0
- package/src/lib/components/tag/rt-tag.component.scss +12 -0
- package/src/lib/components/thread-list/CONTEXT.md +1 -0
- package/src/lib/components/thread-list/rt-thread-list.component.scss +2 -1
- package/src/lib/components/toolbar/CONTEXT.md +10 -0
- package/src/lib/components/toolbar/rt-toolbar.component.scss +18 -0
- package/src/lib/components/tooltip/CONTEXT.md +8 -1
- package/src/lib/components/tree/CONTEXT.md +66 -0
- package/src/lib/components/tree/rt-tree.component.scss +313 -0
- package/src/lib/components/tree-selector/CONTEXT.md +47 -0
- package/src/lib/components/tree-selector/rt-tree-selector.component.scss +121 -0
- package/src/styles/_primitives.scss +4 -0
- package/styles/tokens.css +3 -0
- package/types/rt-tools-ui-kit-v2-accordion.d.ts +31 -24
- package/types/rt-tools-ui-kit-v2-action-bar.d.ts +112 -91
- package/types/rt-tools-ui-kit-v2-ai-run-status.d.ts +64 -40
- package/types/rt-tools-ui-kit-v2-aside-section.d.ts +13 -10
- package/types/rt-tools-ui-kit-v2-aside.d.ts +268 -206
- package/types/rt-tools-ui-kit-v2-autocomplete.d.ts +102 -61
- package/types/rt-tools-ui-kit-v2-bar-list.d.ts +39 -27
- package/types/rt-tools-ui-kit-v2-bottom-sheet.d.ts +31 -20
- package/types/rt-tools-ui-kit-v2-breadcrumbs.d.ts +89 -0
- package/types/rt-tools-ui-kit-v2-button.d.ts +119 -59
- package/types/rt-tools-ui-kit-v2-calendar.d.ts +151 -86
- package/types/rt-tools-ui-kit-v2-card.d.ts +42 -21
- package/types/rt-tools-ui-kit-v2-carousel.d.ts +76 -0
- package/types/rt-tools-ui-kit-v2-checkbox.d.ts +48 -24
- package/types/rt-tools-ui-kit-v2-collapsible-text.d.ts +28 -20
- package/types/rt-tools-ui-kit-v2-color-palette.d.ts +208 -0
- package/types/rt-tools-ui-kit-v2-confirm-popover.d.ts +104 -47
- package/types/rt-tools-ui-kit-v2-container.d.ts +278 -221
- package/types/rt-tools-ui-kit-v2-copy-value.d.ts +39 -24
- package/types/rt-tools-ui-kit-v2-core.d.ts +856 -865
- package/types/rt-tools-ui-kit-v2-counter-row.d.ts +20 -12
- package/types/rt-tools-ui-kit-v2-counter.d.ts +70 -38
- package/types/rt-tools-ui-kit-v2-data-list.d.ts +429 -264
- package/types/rt-tools-ui-kit-v2-data-table.d.ts +500 -351
- package/types/rt-tools-ui-kit-v2-date-picker.d.ts +164 -138
- package/types/rt-tools-ui-kit-v2-date-range.d.ts +102 -84
- package/types/rt-tools-ui-kit-v2-delta-view.d.ts +16 -13
- package/types/rt-tools-ui-kit-v2-detail-list.d.ts +23 -16
- package/types/rt-tools-ui-kit-v2-dialog.d.ts +130 -101
- package/types/rt-tools-ui-kit-v2-dot-field.d.ts +10 -13
- package/types/rt-tools-ui-kit-v2-download-link.d.ts +22 -17
- package/types/rt-tools-ui-kit-v2-draggable-tree.d.ts +132 -0
- package/types/rt-tools-ui-kit-v2-dynamic-list.d.ts +185 -96
- package/types/rt-tools-ui-kit-v2-dynamic-selector.d.ts +904 -404
- package/types/rt-tools-ui-kit-v2-empty-state.d.ts +48 -19
- package/types/rt-tools-ui-kit-v2-expansion-panel.d.ts +67 -39
- package/types/rt-tools-ui-kit-v2-field.d.ts +90 -49
- package/types/rt-tools-ui-kit-v2-file-card.d.ts +115 -75
- package/types/rt-tools-ui-kit-v2-file-drop.d.ts +75 -48
- package/types/rt-tools-ui-kit-v2-file-input.d.ts +53 -36
- package/types/rt-tools-ui-kit-v2-file-list.d.ts +5 -8
- package/types/rt-tools-ui-kit-v2-filter-control.d.ts +83 -49
- package/types/rt-tools-ui-kit-v2-form-control.d.ts +102 -74
- package/types/rt-tools-ui-kit-v2-header.d.ts +40 -22
- package/types/rt-tools-ui-kit-v2-hybrid-tree-selector.d.ts +14 -0
- package/types/rt-tools-ui-kit-v2-hybrid-tree.d.ts +72 -0
- package/types/rt-tools-ui-kit-v2-icon-button.d.ts +190 -90
- package/types/rt-tools-ui-kit-v2-icon.d.ts +160 -129
- package/types/rt-tools-ui-kit-v2-image-cropper.d.ts +151 -111
- package/types/rt-tools-ui-kit-v2-image-upload.d.ts +159 -85
- package/types/rt-tools-ui-kit-v2-info-item.d.ts +26 -13
- package/types/rt-tools-ui-kit-v2-inline-edit.d.ts +194 -0
- package/types/rt-tools-ui-kit-v2-input-number.d.ts +102 -53
- package/types/rt-tools-ui-kit-v2-input.d.ts +82 -43
- package/types/rt-tools-ui-kit-v2-live-badge.d.ts +30 -17
- package/types/rt-tools-ui-kit-v2-logo.d.ts +49 -32
- package/types/rt-tools-ui-kit-v2-markdown-text.d.ts +26 -17
- package/types/rt-tools-ui-kit-v2-menu.d.ts +272 -155
- package/types/rt-tools-ui-kit-v2-message.d.ts +59 -33
- package/types/rt-tools-ui-kit-v2-money-list.d.ts +36 -18
- package/types/rt-tools-ui-kit-v2-multiselect.d.ts +114 -80
- package/types/rt-tools-ui-kit-v2-night-grid.d.ts +34 -25
- package/types/rt-tools-ui-kit-v2-note.d.ts +12 -9
- package/types/rt-tools-ui-kit-v2-notifications-bell.d.ts +37 -16
- package/types/rt-tools-ui-kit-v2-page-header.d.ts +218 -150
- package/types/rt-tools-ui-kit-v2-pagination.d.ts +99 -63
- package/types/rt-tools-ui-kit-v2-photo-viewer.d.ts +61 -66
- package/types/rt-tools-ui-kit-v2-popover.d.ts +116 -55
- package/types/rt-tools-ui-kit-v2-prompt-suggestion.d.ts +26 -16
- package/types/rt-tools-ui-kit-v2-radio-button.d.ts +85 -43
- package/types/rt-tools-ui-kit-v2-radius.d.ts +18 -15
- package/types/rt-tools-ui-kit-v2-rich-editor.d.ts +931 -590
- package/types/rt-tools-ui-kit-v2-ripple.d.ts +28 -25
- package/types/rt-tools-ui-kit-v2-scroll-area.d.ts +60 -58
- package/types/rt-tools-ui-kit-v2-section-nav.d.ts +32 -22
- package/types/rt-tools-ui-kit-v2-select.d.ts +182 -139
- package/types/rt-tools-ui-kit-v2-side-menu.d.ts +452 -349
- package/types/rt-tools-ui-kit-v2-skeleton-wrapper.d.ts +53 -20
- package/types/rt-tools-ui-kit-v2-skeleton.d.ts +55 -26
- package/types/rt-tools-ui-kit-v2-speed-menu.d.ts +131 -0
- package/types/rt-tools-ui-kit-v2-spinner.d.ts +56 -28
- package/types/rt-tools-ui-kit-v2-split-button.d.ts +96 -55
- package/types/rt-tools-ui-kit-v2-stat-tile.d.ts +88 -60
- package/types/rt-tools-ui-kit-v2-stepper.d.ts +50 -37
- package/types/rt-tools-ui-kit-v2-table.d.ts +631 -480
- package/types/rt-tools-ui-kit-v2-tabs.d.ts +198 -113
- package/types/rt-tools-ui-kit-v2-tag.d.ts +106 -53
- package/types/rt-tools-ui-kit-v2-textarea.d.ts +48 -25
- package/types/rt-tools-ui-kit-v2-theme-toggle.d.ts +28 -26
- package/types/rt-tools-ui-kit-v2-thread-list.d.ts +154 -94
- package/types/rt-tools-ui-kit-v2-timeline.d.ts +30 -28
- package/types/rt-tools-ui-kit-v2-toast.d.ts +174 -98
- package/types/rt-tools-ui-kit-v2-toggle-button-group.d.ts +132 -86
- package/types/rt-tools-ui-kit-v2-toggle-switch.d.ts +88 -50
- package/types/rt-tools-ui-kit-v2-toolbar.d.ts +30 -27
- package/types/rt-tools-ui-kit-v2-tooltip.d.ts +65 -41
- package/types/rt-tools-ui-kit-v2-tree-selector.d.ts +257 -0
- package/types/rt-tools-ui-kit-v2-tree.d.ts +282 -0
- package/types/rt-tools-ui-kit-v2-welcome-dialog.d.ts +21 -24
- package/types/rt-tools-ui-kit-v2-workspace-details.d.ts +195 -124
- package/types/rt-tools-ui-kit-v2-workspace.d.ts +122 -79
- package/types/rt-tools-ui-kit-v2.d.ts +100 -90
- package/fesm2022/rt-tools-ui-kit-v2.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rt-tools-ui-kit-v2-pagination.mjs","sources":["../../../projects/ui-kit-v2/src/lib/components/pagination/rt-pagination.logic.ts","../../../projects/ui-kit-v2/src/lib/components/pagination/rt-pagination.component.ts","../../../projects/ui-kit-v2/src/lib/components/pagination/rt-pagination.component.html","../../../projects/ui-kit-v2/src/lib/components/pagination/rt-tools-ui-kit-v2-pagination.ts"],"sourcesContent":["import { IPageModel } from '@rt-tools/utils';\n\nimport { IRtPagination } from './rt-pagination.model';\n\n/** Сколько всего страниц. Пустой список — одна страница, листать нечего. */\nexport function lastPageOf(page: IPageModel): number {\n if (page.pageSize <= 0) {\n return 1;\n }\n\n return Math.max(1, Math.ceil(page.totalCount / page.pageSize));\n}\n\n/** Номер первой записи открытой страницы. */\nexport function rangeFromOf(page: IPageModel): number {\n return (page.pageNumber - 1) * page.pageSize + 1;\n}\n\n/** Номер последней записи открытой страницы — на последней странице она короче. */\nexport function rangeToOf(page: IPageModel): number {\n return Math.min(page.pageNumber * page.pageSize, page.totalCount);\n}\n\n/**\n * Полоса номеров страниц с разрывами «…»: первая, последняя и соседи открытой.\n * Пуста при единственной странице — показывать нечего.\n */\nexport function pageItemsOf(page: IPageModel, neighbours: number): ReadonlyArray<IRtPagination.PageItem> {\n const lastPage: number = lastPageOf(page);\n if (lastPage <= 1) {\n return [];\n }\n\n const items: IRtPagination.PageItem[] = [];\n let previous: number = 0;\n for (let candidate: number = 1; candidate <= lastPage; candidate++) {\n const isEdge: boolean = candidate === 1 || candidate === lastPage;\n const isNeighbour: boolean = candidate >= page.pageNumber - neighbours && candidate <= page.pageNumber + neighbours;\n if (!isEdge && !isNeighbour) {\n continue;\n }\n if (previous && candidate - previous > 1) {\n items.push('gap');\n }\n items.push(candidate);\n previous = candidate;\n }\n\n return items;\n}\n\n/** Номера с `from` по `to` включительно. */\nfunction pagesBetween(from: number, to: number): number[] {\n return Array.from({ length: to - from + 1 }, (_value: unknown, index: number): number => from + index);\n}\n\n/**\n * Полоса номеров первого кита — семь мест: до шести страниц видны все, дальше края по три\n * (`1 2 3 … 11 12 13`) или первая, соседи открытой и последняя (`1 … 6 7 8 … 13`); у края разрыв\n * съезжает на одно место за открытой страницей. Одна страница — один номер.\n */\nexport function pageSlotsOf(page: IPageModel): ReadonlyArray<IRtPagination.PageItem> {\n const lastPage: number = lastPageOf(page);\n const current: number = page.pageNumber;\n\n if (lastPage <= 6) {\n return pagesBetween(1, lastPage);\n }\n if (current < 3 || current > lastPage - 2) {\n return [...pagesBetween(1, 3), 'gap', ...pagesBetween(lastPage - 2, lastPage)];\n }\n if (current === 3) {\n return [...pagesBetween(1, 4), 'gap', ...pagesBetween(lastPage - 1, lastPage)];\n }\n if (current === 4) {\n return [...pagesBetween(1, 5), 'gap', lastPage];\n }\n if (current <= lastPage - 4) {\n return [1, 'gap', current - 1, current, current + 1, 'gap', lastPage];\n }\n if (current === lastPage - 3) {\n return [1, 'gap', ...pagesBetween(lastPage - 4, lastPage)];\n }\n\n return [1, 2, 'gap', ...pagesBetween(lastPage - 3, lastPage)];\n}\n","import {\n booleanAttribute,\n computed,\n inject,\n input,\n output,\n ChangeDetectionStrategy,\n Component,\n InputSignal,\n InputSignalWithTransform,\n OutputEmitterRef,\n Signal,\n ViewEncapsulation,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\nimport { BlockDirective, ElemDirective, ModDirective } from '@rt-tools/core';\nimport { IPageModel } from '@rt-tools/utils';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap, TRtKitLabelParams, rtKitLabel } from '@rt-tools/ui-kit-v2/core';\nimport { IRtIcon } from '@rt-tools/ui-kit-v2/core';\nimport { RtIconButtonComponent } from '@rt-tools/ui-kit-v2/icon-button';\nimport { RtRadiusDirective } from '@rt-tools/ui-kit-v2/radius';\nimport { RtSelectComponent } from '@rt-tools/ui-kit-v2/select';\nimport { IRtSelect } from '@rt-tools/ui-kit-v2/select';\nimport { lastPageOf, pageItemsOf, pageSlotsOf, rangeFromOf, rangeToOf } from './rt-pagination.logic';\nimport { IRtPagination } from './rt-pagination.model';\n\nconst BEM_BLOCK: string = 'rt-pagination';\nconst NEIGHBOURS: number = 1; // соседей вокруг открытой страницы\n\n@Component({\n selector: 'rt-pagination',\n templateUrl: './rt-pagination.component.html',\n styleUrl: './rt-pagination.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // Angular\n FormsModule,\n\n // standalone components / directives\n RtIconButtonComponent,\n RtSelectComponent,\n BlockDirective,\n ElemDirective,\n ModDirective,\n ],\n hostDirectives: [{ directive: RtRadiusDirective, inputs: ['radius'] }],\n host: {\n class: BEM_BLOCK,\n '[style.display]': \"isVisible() ? null : 'none'\",\n },\n})\nexport class RtPaginationComponent {\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly perPageSelectOptions: Signal<ReadonlyArray<IRtSelect.Option<number>>> = computed(\n (): ReadonlyArray<IRtSelect.Option<number>> =>\n this.perPageOptions().map((n: number): IRtSelect.Option<number> => ({ label: String(n), value: n }))\n );\n\n /**\n * Диапазон записей и номер страницы — с подстановками, поэтому карте подписей\n * они не годятся: числа приходят из модели страницы.\n */\n protected readonly rangeLabel: Signal<string> = rtKitLabel(\n 'uiRangeOf',\n computed((): TRtKitLabelParams => ({ from: this.rangeFrom(), to: this.rangeTo(), total: this.pageModel().totalCount }))\n );\n\n protected readonly pageLabel: Signal<string> = rtKitLabel(\n 'uiPageOf',\n computed((): TRtKitLabelParams => ({ page: this.pageModel().pageNumber, last: this.lastPage() }))\n );\n\n protected readonly isFirst: Signal<boolean> = computed((): boolean => this.pageModel().pageNumber <= 1);\n\n protected readonly isLast: Signal<boolean> = computed((): boolean => this.pageModel().pageNumber >= this.lastPage());\n\n public readonly pageModel: InputSignal<IPageModel> = input.required<IPageModel>();\n\n public readonly perPageOptions: InputSignal<ReadonlyArray<number>> = input<ReadonlyArray<number>>([20, 50, 100]);\n\n /** Значок стрелки «назад»: вид первого кита рисует стрелку, а не шеврон. */\n public readonly prevIcon: InputSignal<IRtIcon.Name> = input<IRtIcon.Name>('chevron-left');\n\n /** Значок стрелки «вперёд». */\n public readonly nextIcon: InputSignal<IRtIcon.Name> = input<IRtIcon.Name>('chevron-right');\n\n /** Счёт номеров: список под материальным набором просит семь мест первого кита. */\n public readonly numbering: InputSignal<IRtPagination.Numbering> = input<IRtPagination.Numbering>('neighbours');\n\n public readonly loading: InputSignalWithTransform<boolean, unknown> = input(false, {\n transform: booleanAttribute,\n });\n\n public readonly pageChange: OutputEmitterRef<number> = output<number>();\n\n public readonly perPageChange: OutputEmitterRef<number> = output<number>();\n\n /**\n * Пагинацию показываем, только когда есть что листать/менять: всё не влезает\n * на одну страницу даже при минимальной опции размера (`total > наименьший\n * perPageOption`). Иначе и навигация, и селектор размера бесполезны — прячем\n * весь бар (включая пустой и пустой-по-фильтру список).\n */\n public readonly isVisible: Signal<boolean> = computed((): boolean => {\n const options: ReadonlyArray<number> = this.perPageOptions();\n const smallest: number = options.length > 0 ? Math.min(...options) : 0;\n return this.pageModel().totalCount > smallest;\n });\n\n /** Сколько всего страниц при нынешнем размере страницы. */\n public readonly lastPage: Signal<number> = computed((): number => lastPageOf(this.pageModel()));\n\n /**\n * Границы диапазона записей открытой страницы. Собираются числами, а сама\n * подпись — в шаблоне: только там подстановка проходит через словарь и\n * переживает смену языка.\n */\n public readonly rangeFrom: Signal<number> = computed((): number => rangeFromOf(this.pageModel()));\n\n public readonly rangeTo: Signal<number> = computed((): number => rangeToOf(this.pageModel()));\n\n /** Полоса номеров страниц с разрывами «…». По соседям пуста при единственной странице. */\n public readonly pageItems: Signal<ReadonlyArray<IRtPagination.PageItem>> = computed((): ReadonlyArray<IRtPagination.PageItem> =>\n this.numbering() === 'seven' ? pageSlotsOf(this.pageModel()) : pageItemsOf(this.pageModel(), NEIGHBOURS)\n );\n\n public goTo(pageNumber: number): void {\n if (this.loading() || pageNumber === this.pageModel().pageNumber || pageNumber < 1 || pageNumber > this.lastPage()) {\n return;\n }\n this.pageChange.emit(pageNumber);\n }\n\n protected onPerPage(value: number | null): void {\n if (value === null || this.loading() || value === this.pageModel().pageSize) {\n return;\n }\n this.perPageChange.emit(value);\n }\n}\n","<ng-container rtBlock=\"rt-pagination\">\n <!--\n Ветки «ничего не найдено» здесь нет намеренно: пустой набор прячет весь\n бар целиком (`isVisible`), так что до этой строки дело не доходит. Пока\n такая ветка тут висела, она читалась как рабочее состояние, а показать\n её было нельзя ни при каких входах.\n -->\n <span rtElem=\"range\" qa-dataid=\"pagination-range\">\n {{ rangeLabel() }}\n </span>\n\n @if (pageItems().length) {\n <nav rtElem=\"compact\" qa-dataid=\"pagination-compact\" [attr.aria-label]=\"t().uiPagination\">\n <rt-icon-button\n qa-dataid=\"pagination-compact-prev\"\n variant=\"ghost\"\n size=\"sm\"\n [icon]=\"prevIcon()\"\n [ariaLabel]=\"t().uiPrevPage\"\n [tooltip]=\"t().uiBack\"\n [disabled]=\"isFirst() || loading()\"\n (clicked)=\"goTo(pageModel().pageNumber - 1)\" />\n\n <span rtElem=\"compact-label\" qa-dataid=\"pagination-compact-label\">\n {{ pageLabel() }}\n </span>\n\n <rt-icon-button\n qa-dataid=\"pagination-compact-next\"\n variant=\"ghost\"\n size=\"sm\"\n [icon]=\"nextIcon()\"\n [ariaLabel]=\"t().uiNextPage\"\n [tooltip]=\"t().uiForward\"\n [disabled]=\"isLast() || loading()\"\n (clicked)=\"goTo(pageModel().pageNumber + 1)\" />\n </nav>\n\n <nav rtElem=\"nav\" qa-dataid=\"pagination-nav\" [attr.aria-label]=\"t().uiPagination\">\n <rt-icon-button\n qa-dataid=\"pagination-prev\"\n variant=\"ghost\"\n size=\"sm\"\n [icon]=\"prevIcon()\"\n [ariaLabel]=\"t().uiPrevPage\"\n [tooltip]=\"t().uiBack\"\n [disabled]=\"isFirst() || loading()\"\n (clicked)=\"goTo(pageModel().pageNumber - 1)\" />\n\n @for (item of pageItems(); track $index) {\n @if (item === 'gap') {\n <span rtElem=\"gap\" aria-hidden=\"true\">…</span>\n } @else {\n <button\n rtElem=\"page\"\n qa-dataid=\"pagination-page\"\n type=\"button\"\n [attr.data-page]=\"item\"\n [rtMod]=\"{ current: item === pageModel().pageNumber }\"\n [attr.aria-current]=\"item === pageModel().pageNumber ? 'page' : null\"\n [disabled]=\"loading()\"\n (click)=\"goTo(item)\">\n {{ item }}\n </button>\n }\n }\n\n <rt-icon-button\n qa-dataid=\"pagination-next\"\n variant=\"ghost\"\n size=\"sm\"\n [icon]=\"nextIcon()\"\n [ariaLabel]=\"t().uiNextPage\"\n [tooltip]=\"t().uiForward\"\n [disabled]=\"isLast() || loading()\"\n (clicked)=\"goTo(pageModel().pageNumber + 1)\" />\n </nav>\n }\n\n <div rtElem=\"per-page\">\n <span rtElem=\"per-page-label\">{{ t().uiPerPage }}</span>\n <rt-select\n qa-dataid=\"pagination-per-page\"\n [ariaLabel]=\"t().uiItemsPerPage\"\n [options]=\"perPageSelectOptions()\"\n [clearable]=\"false\"\n [ngModel]=\"pageModel().pageSize\"\n [ngModelOptions]=\"{ standalone: true }\"\n (selectionChange)=\"onPerPage($event)\" />\n </div>\n</ng-container>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rt-tools-ui-kit-v2-pagination.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/pagination/rt-pagination.logic.ts","../../../projects/ui-kit-v2/src/lib/components/pagination/rt-pagination.component.ts","../../../projects/ui-kit-v2/src/lib/components/pagination/rt-pagination.component.html"],"sourcesContent":["import { IPageModel } from '@rt-tools/utils';\n\nimport { IRtPagination } from './rt-pagination.model';\n\n/** Сколько всего страниц. Пустой список — одна страница, листать нечего. */\nexport function lastPageOf(page: IPageModel): number {\n if (page.pageSize <= 0) {\n return 1;\n }\n\n return Math.max(1, Math.ceil(page.totalCount / page.pageSize));\n}\n\n/** Номер первой записи открытой страницы. */\nexport function rangeFromOf(page: IPageModel): number {\n return (page.pageNumber - 1) * page.pageSize + 1;\n}\n\n/** Номер последней записи открытой страницы — на последней странице она короче. */\nexport function rangeToOf(page: IPageModel): number {\n return Math.min(page.pageNumber * page.pageSize, page.totalCount);\n}\n\n/**\n * Полоса номеров страниц с разрывами «…»: первая, последняя и соседи открытой.\n * Пуста при единственной странице — показывать нечего.\n */\nexport function pageItemsOf(page: IPageModel, neighbours: number): ReadonlyArray<IRtPagination.PageItem> {\n const lastPage: number = lastPageOf(page);\n if (lastPage <= 1) {\n return [];\n }\n\n const items: IRtPagination.PageItem[] = [];\n let previous: number = 0;\n for (let candidate: number = 1; candidate <= lastPage; candidate++) {\n const isEdge: boolean = candidate === 1 || candidate === lastPage;\n const isNeighbour: boolean = candidate >= page.pageNumber - neighbours && candidate <= page.pageNumber + neighbours;\n if (!isEdge && !isNeighbour) {\n continue;\n }\n if (previous && candidate - previous > 1) {\n items.push('gap');\n }\n items.push(candidate);\n previous = candidate;\n }\n\n return items;\n}\n\n/** Номера с `from` по `to` включительно. */\nfunction pagesBetween(from: number, to: number): number[] {\n return Array.from({ length: to - from + 1 }, (_value: unknown, index: number): number => from + index);\n}\n\n/**\n * Полоса номеров первого кита — семь мест: до шести страниц видны все, дальше края по три\n * (`1 2 3 … 11 12 13`) или первая, соседи открытой и последняя (`1 … 6 7 8 … 13`); у края разрыв\n * съезжает на одно место за открытой страницей. Одна страница — один номер.\n */\nexport function pageSlotsOf(page: IPageModel): ReadonlyArray<IRtPagination.PageItem> {\n const lastPage: number = lastPageOf(page);\n const current: number = page.pageNumber;\n\n if (lastPage <= 6) {\n return pagesBetween(1, lastPage);\n }\n if (current < 3 || current > lastPage - 2) {\n return [...pagesBetween(1, 3), 'gap', ...pagesBetween(lastPage - 2, lastPage)];\n }\n if (current === 3) {\n return [...pagesBetween(1, 4), 'gap', ...pagesBetween(lastPage - 1, lastPage)];\n }\n if (current === 4) {\n return [...pagesBetween(1, 5), 'gap', lastPage];\n }\n if (current <= lastPage - 4) {\n return [1, 'gap', current - 1, current, current + 1, 'gap', lastPage];\n }\n if (current === lastPage - 3) {\n return [1, 'gap', ...pagesBetween(lastPage - 4, lastPage)];\n }\n\n return [1, 2, 'gap', ...pagesBetween(lastPage - 3, lastPage)];\n}\n","import {\n booleanAttribute,\n computed,\n inject,\n input,\n output,\n ChangeDetectionStrategy,\n Component,\n InputSignal,\n InputSignalWithTransform,\n OutputEmitterRef,\n Signal,\n ViewEncapsulation,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\nimport { BlockDirective, ElemDirective, ModDirective } from '@rt-tools/core';\nimport { IPageModel } from '@rt-tools/utils';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap, TRtKitLabelParams, rtKitLabel } from '@rt-tools/ui-kit-v2/core';\nimport { IRtIcon } from '@rt-tools/ui-kit-v2/core';\nimport { RtIconButtonComponent } from '@rt-tools/ui-kit-v2/icon-button';\nimport { RtRadiusDirective } from '@rt-tools/ui-kit-v2/radius';\nimport { RtSelectComponent } from '@rt-tools/ui-kit-v2/select';\nimport { IRtSelect } from '@rt-tools/ui-kit-v2/select';\nimport { lastPageOf, pageItemsOf, pageSlotsOf, rangeFromOf, rangeToOf } from './rt-pagination.logic';\nimport { IRtPagination } from './rt-pagination.model';\n\nconst BEM_BLOCK: string = 'rt-pagination';\nconst NEIGHBOURS: number = 1; // соседей вокруг открытой страницы\n\n@Component({\n selector: 'rt-pagination',\n templateUrl: './rt-pagination.component.html',\n styleUrl: './rt-pagination.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // Angular\n FormsModule,\n\n // standalone components / directives\n RtIconButtonComponent,\n RtSelectComponent,\n BlockDirective,\n ElemDirective,\n ModDirective,\n ],\n hostDirectives: [{ directive: RtRadiusDirective, inputs: ['radius'] }],\n host: {\n class: BEM_BLOCK,\n '[style.display]': \"isVisible() ? null : 'none'\",\n },\n})\nexport class RtPaginationComponent {\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly perPageSelectOptions: Signal<ReadonlyArray<IRtSelect.Option<number>>> = computed(\n (): ReadonlyArray<IRtSelect.Option<number>> =>\n this.perPageOptions().map((n: number): IRtSelect.Option<number> => ({ label: String(n), value: n }))\n );\n\n /**\n * Диапазон записей и номер страницы — с подстановками, поэтому карте подписей\n * они не годятся: числа приходят из модели страницы.\n */\n protected readonly rangeLabel: Signal<string> = rtKitLabel(\n 'uiRangeOf',\n computed((): TRtKitLabelParams => ({ from: this.rangeFrom(), to: this.rangeTo(), total: this.pageModel().totalCount }))\n );\n\n protected readonly pageLabel: Signal<string> = rtKitLabel(\n 'uiPageOf',\n computed((): TRtKitLabelParams => ({ page: this.pageModel().pageNumber, last: this.lastPage() }))\n );\n\n protected readonly isFirst: Signal<boolean> = computed((): boolean => this.pageModel().pageNumber <= 1);\n\n protected readonly isLast: Signal<boolean> = computed((): boolean => this.pageModel().pageNumber >= this.lastPage());\n\n public readonly pageModel: InputSignal<IPageModel> = input.required<IPageModel>();\n\n public readonly perPageOptions: InputSignal<ReadonlyArray<number>> = input<ReadonlyArray<number>>([20, 50, 100]);\n\n /** Значок стрелки «назад»: вид первого кита рисует стрелку, а не шеврон. */\n public readonly prevIcon: InputSignal<IRtIcon.Name> = input<IRtIcon.Name>('chevron-left');\n\n /** Значок стрелки «вперёд». */\n public readonly nextIcon: InputSignal<IRtIcon.Name> = input<IRtIcon.Name>('chevron-right');\n\n /** Счёт номеров: список под материальным набором просит семь мест первого кита. */\n public readonly numbering: InputSignal<IRtPagination.Numbering> = input<IRtPagination.Numbering>('neighbours');\n\n public readonly loading: InputSignalWithTransform<boolean, unknown> = input(false, {\n transform: booleanAttribute,\n });\n\n public readonly pageChange: OutputEmitterRef<number> = output<number>();\n\n public readonly perPageChange: OutputEmitterRef<number> = output<number>();\n\n /**\n * Пагинацию показываем, только когда есть что листать/менять: всё не влезает\n * на одну страницу даже при минимальной опции размера (`total > наименьший\n * perPageOption`). Иначе и навигация, и селектор размера бесполезны — прячем\n * весь бар (включая пустой и пустой-по-фильтру список).\n */\n public readonly isVisible: Signal<boolean> = computed((): boolean => {\n const options: ReadonlyArray<number> = this.perPageOptions();\n const smallest: number = options.length > 0 ? Math.min(...options) : 0;\n return this.pageModel().totalCount > smallest;\n });\n\n /** Сколько всего страниц при нынешнем размере страницы. */\n public readonly lastPage: Signal<number> = computed((): number => lastPageOf(this.pageModel()));\n\n /**\n * Границы диапазона записей открытой страницы. Собираются числами, а сама\n * подпись — в шаблоне: только там подстановка проходит через словарь и\n * переживает смену языка.\n */\n public readonly rangeFrom: Signal<number> = computed((): number => rangeFromOf(this.pageModel()));\n\n public readonly rangeTo: Signal<number> = computed((): number => rangeToOf(this.pageModel()));\n\n /** Полоса номеров страниц с разрывами «…». По соседям пуста при единственной странице. */\n public readonly pageItems: Signal<ReadonlyArray<IRtPagination.PageItem>> = computed((): ReadonlyArray<IRtPagination.PageItem> =>\n this.numbering() === 'seven' ? pageSlotsOf(this.pageModel()) : pageItemsOf(this.pageModel(), NEIGHBOURS)\n );\n\n public goTo(pageNumber: number): void {\n if (this.loading() || pageNumber === this.pageModel().pageNumber || pageNumber < 1 || pageNumber > this.lastPage()) {\n return;\n }\n this.pageChange.emit(pageNumber);\n }\n\n protected onPerPage(value: number | null): void {\n if (value === null || this.loading() || value === this.pageModel().pageSize) {\n return;\n }\n this.perPageChange.emit(value);\n }\n}\n","<ng-container rtBlock=\"rt-pagination\">\n <!--\n Ветки «ничего не найдено» здесь нет намеренно: пустой набор прячет весь\n бар целиком (`isVisible`), так что до этой строки дело не доходит. Пока\n такая ветка тут висела, она читалась как рабочее состояние, а показать\n её было нельзя ни при каких входах.\n -->\n <span rtElem=\"range\" qa-dataid=\"pagination-range\">\n {{ rangeLabel() }}\n </span>\n\n @if (pageItems().length) {\n <nav rtElem=\"compact\" qa-dataid=\"pagination-compact\" [attr.aria-label]=\"t().uiPagination\">\n <rt-icon-button\n qa-dataid=\"pagination-compact-prev\"\n variant=\"ghost\"\n size=\"sm\"\n [icon]=\"prevIcon()\"\n [ariaLabel]=\"t().uiPrevPage\"\n [tooltip]=\"t().uiBack\"\n [disabled]=\"isFirst() || loading()\"\n (clicked)=\"goTo(pageModel().pageNumber - 1)\" />\n\n <span rtElem=\"compact-label\" qa-dataid=\"pagination-compact-label\">\n {{ pageLabel() }}\n </span>\n\n <rt-icon-button\n qa-dataid=\"pagination-compact-next\"\n variant=\"ghost\"\n size=\"sm\"\n [icon]=\"nextIcon()\"\n [ariaLabel]=\"t().uiNextPage\"\n [tooltip]=\"t().uiForward\"\n [disabled]=\"isLast() || loading()\"\n (clicked)=\"goTo(pageModel().pageNumber + 1)\" />\n </nav>\n\n <nav rtElem=\"nav\" qa-dataid=\"pagination-nav\" [attr.aria-label]=\"t().uiPagination\">\n <rt-icon-button\n qa-dataid=\"pagination-prev\"\n variant=\"ghost\"\n size=\"sm\"\n [icon]=\"prevIcon()\"\n [ariaLabel]=\"t().uiPrevPage\"\n [tooltip]=\"t().uiBack\"\n [disabled]=\"isFirst() || loading()\"\n (clicked)=\"goTo(pageModel().pageNumber - 1)\" />\n\n @for (item of pageItems(); track $index) {\n @if (item === 'gap') {\n <span rtElem=\"gap\" aria-hidden=\"true\">…</span>\n } @else {\n <button\n rtElem=\"page\"\n qa-dataid=\"pagination-page\"\n type=\"button\"\n [attr.data-page]=\"item\"\n [rtMod]=\"{ current: item === pageModel().pageNumber }\"\n [attr.aria-current]=\"item === pageModel().pageNumber ? 'page' : null\"\n [disabled]=\"loading()\"\n (click)=\"goTo(item)\">\n {{ item }}\n </button>\n }\n }\n\n <rt-icon-button\n qa-dataid=\"pagination-next\"\n variant=\"ghost\"\n size=\"sm\"\n [icon]=\"nextIcon()\"\n [ariaLabel]=\"t().uiNextPage\"\n [tooltip]=\"t().uiForward\"\n [disabled]=\"isLast() || loading()\"\n (clicked)=\"goTo(pageModel().pageNumber + 1)\" />\n </nav>\n }\n\n <div rtElem=\"per-page\">\n <span rtElem=\"per-page-label\">{{ t().uiPerPage }}</span>\n <rt-select\n qa-dataid=\"pagination-per-page\"\n [ariaLabel]=\"t().uiItemsPerPage\"\n [options]=\"perPageSelectOptions()\"\n [clearable]=\"false\"\n [ngModel]=\"pageModel().pageSize\"\n [ngModelOptions]=\"{ standalone: true }\"\n (selectionChange)=\"onPerPage($event)\" />\n </div>\n</ng-container>\n"],"mappings":";;;;;;;;;;AAKA,SAAgB,WAAW,MAAgB;CACvC,IAAI,KAAK,YAAY,GACjB,OAAO;CAGX,OAAO,KAAK,IAAI,GAAG,KAAK,KAAK,KAAK,aAAa,KAAK,QAAQ,CAAC;AACjE;AAGA,SAAgB,YAAY,MAAgB;CACxC,QAAQ,KAAK,aAAa,KAAK,KAAK,WAAW;AACnD;AAGA,SAAgB,UAAU,MAAgB;CACtC,OAAO,KAAK,IAAI,KAAK,aAAa,KAAK,UAAU,KAAK,UAAU;AACpE;AAMA,SAAgB,YAAY,MAAkB,YAAkB;CAC5D,MAAM,WAAmB,WAAW,IAAI;CACxC,IAAI,YAAY,GACZ,OAAO,CAAA;CAGX,MAAM,QAAkC,CAAA;CACxC,IAAI,WAAmB;CACvB,KAAK,IAAI,YAAoB,GAAG,aAAa,UAAU,aAAa;EAChE,MAAM,SAAkB,cAAc,KAAK,cAAc;EACzD,MAAM,cAAuB,aAAa,KAAK,aAAa,cAAc,aAAa,KAAK,aAAa;EACzG,IAAI,CAAC,UAAU,CAAC,aACZ;EAEJ,IAAI,YAAY,YAAY,WAAW,GACnC,MAAM,KAAK,KAAK;EAEpB,MAAM,KAAK,SAAS;EACpB,WAAW;CACf;CAEA,OAAO;AACX;AAGA,SAAS,aAAa,MAAc,IAAU;CAC1C,OAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,OAAO,EAAC,IAAK,QAAiB,UAA0B,OAAO,KAAK;AACzG;AAOA,SAAgB,YAAY,MAAgB;CACxC,MAAM,WAAmB,WAAW,IAAI;CACxC,MAAM,UAAkB,KAAK;CAE7B,IAAI,YAAY,GACZ,OAAO,aAAa,GAAG,QAAQ;CAEnC,IAAI,UAAU,KAAK,UAAU,WAAW,GACpC,OAAO;EAAC,GAAG,aAAa,GAAG,CAAC;EAAG;EAAO,GAAG,aAAa,WAAW,GAAG,QAAQ;CAAC;CAEjF,IAAI,YAAY,GACZ,OAAO;EAAC,GAAG,aAAa,GAAG,CAAC;EAAG;EAAO,GAAG,aAAa,WAAW,GAAG,QAAQ;CAAC;CAEjF,IAAI,YAAY,GACZ,OAAO;EAAC,GAAG,aAAa,GAAG,CAAC;EAAG;EAAO;CAAQ;CAElD,IAAI,WAAW,WAAW,GACtB,OAAO;EAAC;EAAG;EAAO,UAAU;EAAG;EAAS,UAAU;EAAG;EAAO;CAAQ;CAExE,IAAI,YAAY,WAAW,GACvB,OAAO;EAAC;EAAG;EAAO,GAAG,aAAa,WAAW,GAAG,QAAQ;CAAC;CAG7D,OAAO;EAAC;EAAG;EAAG;EAAO,GAAG,aAAa,WAAW,GAAG,QAAQ;CAAC;AAChE;ACzDA,MAAM,YAAoB;AAC1B,MAAM,aAAqB;AAyB3B,IAAa,wBAAb,MAAa,sBAAqB;CAvBlC,cAAA;EAwBuB,KAAA,IAA4B,OAAO,aAAa;EAEhD,KAAA,uBAAwE,eAEnF,KAAK,eAAc,CAAE,CAAC,KAAK,OAAyC;GAAE,OAAO,OAAO,CAAC;GAAG,OAAO;EAAC,EAAG,GAC1G,GAAA,YAAA,CAAA,EAAA,WAAA,uBAAA,CAAA,+BAAA,CAAA,CAAA;EAMkB,KAAA,aAA6B,WAC5C,aACA,gBAAmC;GAAE,MAAM,KAAK,UAAS;GAAI,IAAI,KAAK,QAAO;GAAI,OAAO,KAAK,UAAS,CAAE,CAAC;EAAU,EAAG,CAAC;EAGxG,KAAA,YAA4B,WAC3C,YACA,gBAAmC;GAAE,MAAM,KAAK,UAAS,CAAE,CAAC;GAAY,MAAM,KAAK,SAAQ;EAAE,EAAG,CAAC;EAGlF,KAAA,UAA2B,eAAwB,KAAK,UAAS,CAAE,CAAC,cAAc,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;EAEpF,KAAA,SAA0B,eAAwB,KAAK,UAAS,CAAE,CAAC,cAAc,KAAK,SAAQ,GAAG,GAAA,YAAA,CAAA,EAAA,WAAA,SAAA,CAAA,+BAAA,CAAA,CAAA;EAEpG,KAAA,YAAqC,MAAM,SAAsB,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;EAEjE,KAAA,iBAAqD,MAA6B;GAAC;GAAI;GAAI;EAAG,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;EAGhG,KAAA,WAAsC,MAAoB,gBAAe,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;EAGzE,KAAA,WAAsC,MAAoB,iBAAgB,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;EAG1E,KAAA,YAAkD,MAA+B,cAAa,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;EAE9F,KAAA,UAAsD,MAAM,OAAK;GAAA,GAAA,YAAA,EAAA,WAAA,UAAA,+BAAA,CAAA;GAC7E,WAAW;EAAgB,CAAA;EAGf,KAAA,aAAuC,OAAM;EAE7C,KAAA,gBAA0C,OAAM;EAQhD,KAAA,YAA6B,eAAuB;GAChE,MAAM,UAAiC,KAAK,eAAc;GAC1D,MAAM,WAAmB,QAAQ,SAAS,IAAI,KAAK,IAAI,GAAG,OAAO,IAAI;GACrE,OAAO,KAAK,UAAS,CAAE,CAAC,aAAa;EACzC,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;EAGc,KAAA,WAA2B,eAAuB,WAAW,KAAK,UAAS,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,WAAA,CAAA,+BAAA,CAAA,CAAA;EAO/E,KAAA,YAA4B,eAAuB,YAAY,KAAK,UAAS,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;EAEjF,KAAA,UAA0B,eAAuB,UAAU,KAAK,UAAS,CAAE,GAAE,GAAA,YAAA,CAAA,EAAA,WAAA,UAAA,CAAA,+BAAA,CAAA,CAAA;EAG7E,KAAA,YAA2D,eACvE,KAAK,UAAS,MAAO,UAAU,YAAY,KAAK,UAAS,CAAE,IAAI,YAAY,KAAK,UAAS,GAAI,UAAU,GAC1G,GAAA,YAAA,CAAA,EAAA,WAAA,YAAA,CAAA,+BAAA,CAAA,CAAA;CAeJ;CAbU,KAAK,YAAkB;EAC1B,IAAI,KAAK,QAAO,KAAM,eAAe,KAAK,UAAS,CAAE,CAAC,cAAc,aAAa,KAAK,aAAa,KAAK,SAAQ,GAC5G;EAEJ,KAAK,WAAW,KAAK,UAAU;CACnC;CAEU,UAAU,OAAoB;EACpC,IAAI,UAAU,QAAQ,KAAK,QAAO,KAAM,UAAU,KAAK,UAAS,CAAE,CAAC,UAC/D;EAEJ,KAAK,cAAc,KAAK,KAAK;CACjC;CAxFS;EAAA,KAAA,OAAA,GAAA,mBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,UAAA;GAAA,MAAA;GAAqB,MAAA,CAAA;GAAA,QAAA,GAAA,gBAAA;EAAA,CAAA;CAAA;CAArB;EAAA,KAAA,OAAA,GAAA,qBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,MAAA;GAAqB,cAAA;GAAA,UAAA;GAAA,QAAA;IAAA,WAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,gBAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,UAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,UAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,WAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;IAAA,SAAA;KAAA,mBAAA;KAAA,YAAA;KAAA,UAAA;KAAA,YAAA;KAAA,mBAAA;IAAA;GAAA;GAAA,SAAA;IAAA,YAAA;IAAA,eAAA;GAAA;GAAA,MAAA;IAAA,YAAA,EAAA,iBAAA,8BAAA;IAAA,gBAAA;GAAA;GAAA,gBAAA,CAAA;IAAA,WAAA,GAAA;IAAA,QAAA,CAAA,UAAA,QAAA;GAAA,CAAA;GAAA,UAAA;GAAA,UCtDlC;GA2FA,QAAA,CAAA,g3HAAA;GAAA,cAAA;IAAA;KAAA,MAAA;KAAA,MDpDQ;IAAW;IAAA;KAAA,MAAA;KAAA,MAAA,GAAA;KAAA,UAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MAAA,GAAA;KAAA,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,SAAA,CAAA,eAAA;KAAA,UAAA,CAAA,SAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MAGX;KAAqB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,SAAA,CAAA,SAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACrB;KAAiB,UAAA;KAAA,QAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;KAAA;KAAA,SAAA,CAAA,iBAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACjB;KAAc,UAAA;KAAA,QAAA,CAAA,OAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACd;KAAa,UAAA;KAAA,QAAA,CAAA,OAAA;IAAA;IAAA;KAAA,MAAA;KAAA,MACb;KAAY,UAAA;IAAA;GAAA;GAAA,iBAAA,GAAA,wBAAA;GAAA,eAAA,GAAA,kBAAA;EAAA,CAAA;CAAA;;AAQP,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAqB,YAAA,CAAA;EAvBjC,MAAA;EACa,MAAA,CAAA;GAAA,UAAA;GAAe,iBAGR,wBAAwB;GAAM,eAChC,kBAAkB;GAAI,SAC5B;IAEL;IAGA;IACA;IACA;IACA;IACA;GACH;GAAA,gBACe,CAAC;IAAE,WAAW;IAAmB,QAAQ,CAAC,QAAQ;GAAC,CAAE;GAAC,MAChE;IACF,OAAO;IACP,mBAAmB;GACtB;GAAA,UAAA;GAAA,QAAA,CAAA,g3HAAA;EAAA,CAAA"}
|
|
@@ -1,170 +1,213 @@
|
|
|
1
|
-
import { CdkTrapFocus } from
|
|
2
|
-
import { NgTemplateOutlet } from
|
|
3
|
-
import * as i0 from
|
|
4
|
-
import {
|
|
5
|
-
import { BlockDirective, ElemDirective } from
|
|
6
|
-
import { RT_KIT_LABELS } from
|
|
7
|
-
import { RT_DIALOG_DATA, RtDialogRef } from
|
|
8
|
-
import { RtIconButtonComponent } from
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Чистая логика просмотрщика: переходы между кадрами и то, какие из них стоит
|
|
12
|
-
* держать загруженными.
|
|
13
|
-
*/
|
|
14
|
-
/** Листание по кругу: с последнего кадра стрелка вправо ведёт на первый */
|
|
1
|
+
import { CdkTrapFocus } from "@angular/cdk/a11y";
|
|
2
|
+
import { NgTemplateOutlet } from "@angular/common";
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
import { ChangeDetectionStrategy, Component, ElementRef, ViewEncapsulation, afterNextRender, computed, inject, signal, viewChild } from "@angular/core";
|
|
5
|
+
import { BlockDirective, ElemDirective } from "@rt-tools/core";
|
|
6
|
+
import { RT_KIT_LABELS } from "@rt-tools/ui-kit-v2/core";
|
|
7
|
+
import { RT_DIALOG_DATA, RtDialogRef } from "@rt-tools/ui-kit-v2/dialog";
|
|
8
|
+
import { RtIconButtonComponent } from "@rt-tools/ui-kit-v2/icon-button";
|
|
15
9
|
function nextPhotoIndex(current, total, step) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
return (((current + step) % total) + total) % total;
|
|
10
|
+
if (total <= 0) return 0;
|
|
11
|
+
return ((current + step) % total + total) % total;
|
|
20
12
|
}
|
|
21
|
-
/** Индекс из адреса: в URL кадры нумеруются с единицы, внутри — с нуля */
|
|
22
13
|
function indexFromParam(param, total) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
return parsed - 1;
|
|
14
|
+
const parsed = Number(param);
|
|
15
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > total) return -1;
|
|
16
|
+
return parsed - 1;
|
|
28
17
|
}
|
|
29
18
|
function paramFromIndex(index) {
|
|
30
|
-
|
|
19
|
+
return String(index + 1);
|
|
31
20
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Индекс кадра по позиции прокрутки. Нужен, потому что листать можно свайпом:
|
|
34
|
-
* без этого счётчик показывал бы один кадр, а на экране был бы другой.
|
|
35
|
-
*/
|
|
36
21
|
function indexFromScroll(scrollLeft, slideWidth, total) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const index = Math.round(scrollLeft / slideWidth);
|
|
41
|
-
return Math.min(Math.max(index, 0), total - 1);
|
|
22
|
+
if (slideWidth <= 0 || total <= 0) return 0;
|
|
23
|
+
const index = Math.round(scrollLeft / slideWidth);
|
|
24
|
+
return Math.min(Math.max(index, 0), total - 1);
|
|
42
25
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Кадры, которые держим в разметке: текущий и соседние. Полноразмерная
|
|
45
|
-
* фотография весит сотни килобайт, и загружать все пятьдесят разом — значит
|
|
46
|
-
* выесть трафик пользователя ради кадров, до которых он не долистает.
|
|
47
|
-
*/
|
|
48
26
|
function shouldRenderPhoto(index, current, total) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
const distance = Math.min(Math.abs(index - current), total - Math.abs(index - current));
|
|
53
|
-
return distance <= 1;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const BEM_BLOCK = 'rt-photo-viewer';
|
|
57
|
-
/**
|
|
58
|
-
* Полноэкранный просмотрщик фотографий поверх `RtDialogService`.
|
|
59
|
-
*
|
|
60
|
-
* Листание — нативный `scroll-snap`: свайп, инерцию и прилипание даёт сам
|
|
61
|
-
* браузер, и на телефоне это ощущается правильнее любой реализации на
|
|
62
|
-
* обработчиках. Готовую библиотеку не берём — весь кит здесь собственный, а
|
|
63
|
-
* токены, BEM и `qa-dataid` ложатся естественно.
|
|
64
|
-
*
|
|
65
|
-
* Руками сделаны: стрелки, клавиши `←` `→`, счётчик и предзагрузка соседних
|
|
66
|
-
* кадров. `Esc`, блокировку прокрутки фона и затемнение даёт сам диалог.
|
|
67
|
-
*/
|
|
68
|
-
class RtPhotoViewerComponent {
|
|
69
|
-
#data;
|
|
70
|
-
#dialogRef;
|
|
71
|
-
#host;
|
|
72
|
-
constructor() {
|
|
73
|
-
this.#data = inject(RT_DIALOG_DATA);
|
|
74
|
-
this.#dialogRef = inject((RtDialogRef));
|
|
75
|
-
this.#host = inject((ElementRef));
|
|
76
|
-
this.t = inject(RT_KIT_LABELS);
|
|
77
|
-
this.track = viewChild('track', /* @ts-ignore */
|
|
78
|
-
...(ngDevMode ? [{ debugName: "track" }] : /* istanbul ignore next */ []));
|
|
79
|
-
this.photos = this.#data.photos;
|
|
80
|
-
/** Панель правки под дорожкой; там, где кадр только смотрят, её нет */
|
|
81
|
-
this.panel = this.#data.panel ?? null;
|
|
82
|
-
this.currentIndex = signal(this.#data.startIndex, /* @ts-ignore */
|
|
83
|
-
...(ngDevMode ? [{ debugName: "currentIndex" }] : /* istanbul ignore next */ []));
|
|
84
|
-
this.counter = computed(() => `${this.currentIndex() + 1} / ${this.photos.length}`, /* @ts-ignore */
|
|
85
|
-
...(ngDevMode ? [{ debugName: "counter" }] : /* istanbul ignore next */ []));
|
|
86
|
-
/** Соседние кадры готовятся заранее, дальние в разметку не попадают */
|
|
87
|
-
this.rendered = computed(() => this.photos.map((_photo, index) => shouldRenderPhoto(index, this.currentIndex(), this.photos.length)), /* @ts-ignore */
|
|
88
|
-
...(ngDevMode ? [{ debugName: "rendered" }] : /* istanbul ignore next */ []));
|
|
89
|
-
// Открытие может быть не с первого кадра: без явной прокрутки счётчик
|
|
90
|
-
// показывал бы второй кадр, а на экране остался бы первый
|
|
91
|
-
afterNextRender(() => {
|
|
92
|
-
this.#host.nativeElement.focus();
|
|
93
|
-
this.#scrollToCurrent();
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
/** Наружу отдаётся кадр, на котором закрыли: по нему вызывающая сторона чистит адрес */
|
|
97
|
-
close() {
|
|
98
|
-
this.#dialogRef.close(this.currentIndex());
|
|
99
|
-
}
|
|
100
|
-
move(step) {
|
|
101
|
-
this.select(nextPhotoIndex(this.currentIndex(), this.photos.length, step));
|
|
102
|
-
}
|
|
103
|
-
select(index) {
|
|
104
|
-
this.currentIndex.set(index);
|
|
105
|
-
this.#scrollToCurrent();
|
|
106
|
-
}
|
|
107
|
-
/** Свайп листает мимо стрелок — счётчик догоняет позицию дорожки */
|
|
108
|
-
onScroll() {
|
|
109
|
-
const track = this.track()?.nativeElement;
|
|
110
|
-
if (track) {
|
|
111
|
-
this.currentIndex.set(indexFromScroll(track.scrollLeft, track.clientWidth, this.photos.length));
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Прокрутка ленты вслед за выбором. Позиция считается от ширины дорожки, а
|
|
116
|
-
* не от `scrollWidth`: кадры за пределами окна ещё не отрисованы, и общая
|
|
117
|
-
* ширина до их появления меньше реальной.
|
|
118
|
-
*
|
|
119
|
-
* Переход мгновенный: `behavior: 'smooth'` внутри оверлея молча не двигает
|
|
120
|
-
* дорожку — счётчик менялся, а кадр оставался прежним. Свайп при этом
|
|
121
|
-
* по-прежнему анимирует сам браузер через scroll-snap.
|
|
122
|
-
*/
|
|
123
|
-
#scrollToCurrent() {
|
|
124
|
-
const track = this.track()?.nativeElement;
|
|
125
|
-
if (!track) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
track.scrollTo({ left: track.clientWidth * this.currentIndex(), behavior: 'instant' });
|
|
129
|
-
}
|
|
130
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: RtPhotoViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
131
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: RtPhotoViewerComponent, isStandalone: true, selector: "rt-photo-viewer", host: { attributes: { "role": "dialog", "aria-modal": "true", "tabindex": "-1" }, listeners: { "keydown.escape": "close()", "keydown.arrowLeft": "move(-1)", "keydown.arrowRight": "move(1)" }, classAttribute: "rt-photo-viewer" }, viewQueries: [{ propertyName: "track", first: true, predicate: ["track"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container rtBlock=\"rt-photo-viewer\">\n <!-- \u041B\u043E\u0432\u0443\u0448\u043A\u0430 \u0434\u0435\u0440\u0436\u0438\u0442 Tab \u0432\u043D\u0443\u0442\u0440\u0438; \u0444\u043E\u043A\u0443\u0441 \u0441\u0442\u0430\u0432\u0438\u0442 \u0441\u0430\u043C \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442, \u043F\u043E\u044D\u0442\u043E\u043C\u0443\n \u0430\u0432\u0442\u043E-\u0437\u0430\u0445\u0432\u0430\u0442\u0430 \u0437\u0434\u0435\u0441\u044C \u043D\u0435\u0442: \u043E\u043D \u043F\u0435\u0440\u0435\u0431\u0438\u0432\u0430\u043B \u0444\u043E\u043A\u0443\u0441 \u043D\u0430 \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0449\u0438\u043A\u0435, \u0438 \u043A\u043B\u0430\u0432\u0438\u0448\u0438\n \u043F\u0435\u0440\u0435\u0441\u0442\u0430\u0432\u0430\u043B\u0438 \u043B\u0438\u0441\u0442\u0430\u0442\u044C -->\n <div rtElem=\"surface\" cdkTrapFocus>\n <header rtElem=\"bar\">\n <span rtElem=\"counter\" qa-dataid=\"photo-viewer-counter\">{{ counter() }}</span>\n <rt-icon-button\n qa-dataid=\"photo-viewer-close\"\n icon=\"close\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerClose\"\n (clicked)=\"close()\" />\n </header>\n\n <div rtElem=\"track\" qa-dataid=\"photo-viewer-track\" #track (scroll)=\"onScroll()\">\n @for (photo of photos; track photo.id; let index = $index) {\n <figure rtElem=\"slide\" [attr.data-photo-index]=\"index\">\n @if (rendered()[index]) {\n <picture>\n @for (source of photo.picture.sources; track source.type) {\n <source sizes=\"100vw\" [type]=\"source.type\" [srcset]=\"source.srcset\" />\n }\n <img\n rtElem=\"image\"\n qa-dataid=\"photo-viewer-image\"\n decoding=\"async\"\n sizes=\"100vw\"\n [srcset]=\"photo.picture.fallbackSrcset\"\n [src]=\"photo.picture.fallbackSrc\"\n [alt]=\"photo.alt\" />\n </picture>\n }\n </figure>\n }\n </div>\n\n @if (photos.length > 1) {\n <rt-icon-button\n rtElem=\"arrow-prev\"\n qa-dataid=\"photo-viewer-prev\"\n icon=\"angle-left\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerPrev\"\n (clicked)=\"move(-1)\" />\n\n <rt-icon-button\n rtElem=\"arrow-next\"\n qa-dataid=\"photo-viewer-next\"\n icon=\"angle-right\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerNext\"\n (clicked)=\"move(1)\" />\n }\n\n <!-- \u041F\u0430\u043D\u0435\u043B\u044C \u043F\u0440\u0438\u0445\u043E\u0434\u0438\u0442 \u0441\u043D\u0430\u0440\u0443\u0436\u0438: \u0433\u0434\u0435-\u0442\u043E \u043A\u0430\u0434\u0440 \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u043C\u043E\u0442\u0440\u044F\u0442, \u0433\u0434\u0435-\u0442\u043E \u0438\u043C \u0435\u0449\u0451 \u0438\n \u0443\u043F\u0440\u0430\u0432\u043B\u044F\u044E\u0442, \u0438 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u0442\u0430\u043C \u0436\u0435, \u0433\u0434\u0435 \u043A\u0430\u0434\u0440 \u0432\u0438\u0434\u0435\u043D \u043A\u0440\u0443\u043F\u043D\u043E -->\n @if (panel) {\n <footer rtElem=\"panel\" qa-dataid=\"photo-viewer-panel\">\n <ng-container\n [ngTemplateOutlet]=\"panel\"\n [ngTemplateOutletContext]=\"{ $implicit: photos[currentIndex()], index: currentIndex() }\" />\n </footer>\n }\n </div>\n</ng-container>\n", styles: ["@charset \"UTF-8\";@layer rt-kit.components{.rt-photo-viewer{--rt-photo-viewer-control-radius: var(--rt-radius-md);--rt-photo-viewer-control-color: var(--rt-color-text-inverse);--rt-photo-viewer-control-bg: color-mix(in srgb, var(--rt-color-bg-inverse) 55%, transparent);--rt-photo-viewer-control-bg-hover: color-mix(in srgb, var(--rt-color-text-inverse) 24%, transparent);display:block;width:100vw;height:100vh;height:100dvh;background:var(--rt-color-bg-inverse)}.rt-photo-viewer__surface{position:absolute;inset:0}.rt-photo-viewer__bar{position:absolute;z-index:1;top:0;right:0;left:0;display:flex;align-items:center;justify-content:space-between;padding:var(--rt-space-4) var(--rt-space-5);color:var(--rt-color-text-inverse);gap:var(--rt-space-4)}.rt-photo-viewer__counter{font-size:var(--rt-text-sm);font-variant-numeric:tabular-nums}.rt-photo-viewer__track{display:flex;overflow:auto hidden;width:100%;height:100%;scroll-snap-type:x mandatory;scrollbar-width:none}.rt-photo-viewer__slide{display:flex;flex:0 0 100%;align-items:center;justify-content:center;padding:var(--rt-space-10) var(--rt-space-4);margin:0;scroll-snap-align:center}.rt-photo-viewer__slide picture{display:flex;width:100%;height:100%;align-items:center;justify-content:center}.rt-photo-viewer__image{display:block;max-width:100%;max-height:100%;object-fit:contain}.rt-photo-viewer__panel{position:absolute;z-index:1;right:0;bottom:0;left:0;padding:var(--rt-space-4) var(--rt-space-5);background:var(--rt-color-bg-surface);color:var(--rt-color-text-primary)}.rt-photo-viewer__bar rt-icon-button,.rt-photo-viewer__arrow-prev,.rt-photo-viewer__arrow-next{--rt-icon-button-color: var(--rt-photo-viewer-control-color);--rt-icon-button-color-bg-hover: var(--rt-photo-viewer-control-bg-hover);border-radius:var(--rt-photo-viewer-control-radius);background:var(--rt-photo-viewer-control-bg)}.rt-photo-viewer__arrow-prev,.rt-photo-viewer__arrow-next{position:absolute;top:50%;transform:translateY(-50%)}.rt-photo-viewer__arrow-prev{left:var(--rt-space-4)}.rt-photo-viewer__arrow-next{right:var(--rt-space-4)}.rt-photo-viewer:has(.rt-photo-viewer__panel) .rt-photo-viewer__slide{padding-bottom:var(--rt-space-20)}@media(width<=700px){.rt-photo-viewer__arrow-prev,.rt-photo-viewer__arrow-next{display:none}.rt-photo-viewer__slide{padding:var(--rt-space-12) 0}}}\n"], dependencies: [{ kind: "directive", type:
|
|
132
|
-
// angular
|
|
133
|
-
CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type:
|
|
134
|
-
// rt-tools
|
|
135
|
-
BlockDirective, selector: "[rtBlock]", inputs: ["rtMod"] }, { kind: "directive", type: ElemDirective, selector: "[rtElem]", inputs: ["rtMod"] }, { kind: "component", type:
|
|
136
|
-
// components
|
|
137
|
-
RtIconButtonComponent, selector: "rt-icon-button", inputs: ["icon", "glyph", "ariaLabel", "variant", "iconColor", "iconFill", "size", "iconSize", "type", "tooltip", "tabIndex", "loading", "disabled", "active", "indicator"], outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
27
|
+
if (total <= 0) return false;
|
|
28
|
+
return Math.min(Math.abs(index - current), total - Math.abs(index - current)) <= 1;
|
|
138
29
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
30
|
+
const BEM_BLOCK = "rt-photo-viewer";
|
|
31
|
+
var RtPhotoViewerComponent = class RtPhotoViewerComponent {
|
|
32
|
+
#data;
|
|
33
|
+
#dialogRef;
|
|
34
|
+
#host;
|
|
35
|
+
constructor() {
|
|
36
|
+
this.#data = inject(RT_DIALOG_DATA);
|
|
37
|
+
this.#dialogRef = inject(RtDialogRef);
|
|
38
|
+
this.#host = inject(ElementRef);
|
|
39
|
+
this.t = inject(RT_KIT_LABELS);
|
|
40
|
+
this.track = viewChild("track", ...ngDevMode ? [{ debugName: "track" }] : /* istanbul ignore next */ []);
|
|
41
|
+
this.photos = this.#data.photos;
|
|
42
|
+
this.panel = this.#data.panel ?? null;
|
|
43
|
+
this.currentIndex = signal(this.#data.startIndex, ...ngDevMode ? [{ debugName: "currentIndex" }] : /* istanbul ignore next */ []);
|
|
44
|
+
this.counter = computed(() => `${this.currentIndex() + 1} / ${this.photos.length}`, ...ngDevMode ? [{ debugName: "counter" }] : /* istanbul ignore next */ []);
|
|
45
|
+
this.rendered = computed(() => this.photos.map((_photo, index) => shouldRenderPhoto(index, this.currentIndex(), this.photos.length)), ...ngDevMode ? [{ debugName: "rendered" }] : /* istanbul ignore next */ []);
|
|
46
|
+
afterNextRender(() => {
|
|
47
|
+
this.#host.nativeElement.focus();
|
|
48
|
+
this.#scrollToCurrent();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
close() {
|
|
52
|
+
this.#dialogRef.close(this.currentIndex());
|
|
53
|
+
}
|
|
54
|
+
move(step) {
|
|
55
|
+
this.select(nextPhotoIndex(this.currentIndex(), this.photos.length, step));
|
|
56
|
+
}
|
|
57
|
+
select(index) {
|
|
58
|
+
this.currentIndex.set(index);
|
|
59
|
+
this.#scrollToCurrent();
|
|
60
|
+
}
|
|
61
|
+
onScroll() {
|
|
62
|
+
const track = this.track()?.nativeElement;
|
|
63
|
+
if (track) this.currentIndex.set(indexFromScroll(track.scrollLeft, track.clientWidth, this.photos.length));
|
|
64
|
+
}
|
|
65
|
+
#scrollToCurrent() {
|
|
66
|
+
const track = this.track()?.nativeElement;
|
|
67
|
+
if (!track) return;
|
|
68
|
+
track.scrollTo({
|
|
69
|
+
left: track.clientWidth * this.currentIndex(),
|
|
70
|
+
behavior: "instant"
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
static {
|
|
74
|
+
this.ɵfac = i0.ɵɵngDeclareFactory({
|
|
75
|
+
minVersion: "12.0.0",
|
|
76
|
+
version: "22.2.2",
|
|
77
|
+
ngImport: i0,
|
|
78
|
+
type: RtPhotoViewerComponent,
|
|
79
|
+
deps: [],
|
|
80
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
static {
|
|
84
|
+
this.ɵcmp = i0.ɵɵngDeclareComponent({
|
|
85
|
+
minVersion: "17.0.0",
|
|
86
|
+
version: "22.2.2",
|
|
87
|
+
type: RtPhotoViewerComponent,
|
|
88
|
+
isStandalone: true,
|
|
89
|
+
selector: "rt-photo-viewer",
|
|
90
|
+
host: {
|
|
91
|
+
attributes: {
|
|
92
|
+
"role": "dialog",
|
|
93
|
+
"aria-modal": "true",
|
|
94
|
+
"tabindex": "-1"
|
|
95
|
+
},
|
|
96
|
+
listeners: {
|
|
97
|
+
"keydown.escape": "close()",
|
|
98
|
+
"keydown.arrowLeft": "move(-1)",
|
|
99
|
+
"keydown.arrowRight": "move(1)"
|
|
100
|
+
},
|
|
101
|
+
classAttribute: "rt-photo-viewer"
|
|
102
|
+
},
|
|
103
|
+
viewQueries: [{
|
|
104
|
+
propertyName: "track",
|
|
105
|
+
first: true,
|
|
106
|
+
predicate: ["track"],
|
|
107
|
+
descendants: true,
|
|
108
|
+
isSignal: true
|
|
109
|
+
}],
|
|
110
|
+
ngImport: i0,
|
|
111
|
+
template: "<ng-container rtBlock=\"rt-photo-viewer\">\n <!-- Ловушка держит Tab внутри; фокус ставит сам компонент, поэтому\n авто-захвата здесь нет: он перебивал фокус на просмотрщике, и клавиши\n переставали листать -->\n <div rtElem=\"surface\" cdkTrapFocus>\n <header rtElem=\"bar\">\n <span rtElem=\"counter\" qa-dataid=\"photo-viewer-counter\">{{ counter() }}</span>\n <rt-icon-button\n qa-dataid=\"photo-viewer-close\"\n icon=\"close\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerClose\"\n (clicked)=\"close()\" />\n </header>\n\n <div rtElem=\"track\" qa-dataid=\"photo-viewer-track\" #track (scroll)=\"onScroll()\">\n @for (photo of photos; track photo.id; let index = $index) {\n <figure rtElem=\"slide\" [attr.data-photo-index]=\"index\">\n @if (rendered()[index]) {\n <picture>\n @for (source of photo.picture.sources; track source.type) {\n <source sizes=\"100vw\" [type]=\"source.type\" [srcset]=\"source.srcset\" />\n }\n <img\n rtElem=\"image\"\n qa-dataid=\"photo-viewer-image\"\n decoding=\"async\"\n sizes=\"100vw\"\n [srcset]=\"photo.picture.fallbackSrcset\"\n [src]=\"photo.picture.fallbackSrc\"\n [alt]=\"photo.alt\" />\n </picture>\n }\n </figure>\n }\n </div>\n\n @if (photos.length > 1) {\n <rt-icon-button\n rtElem=\"arrow-prev\"\n qa-dataid=\"photo-viewer-prev\"\n icon=\"angle-left\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerPrev\"\n (clicked)=\"move(-1)\" />\n\n <rt-icon-button\n rtElem=\"arrow-next\"\n qa-dataid=\"photo-viewer-next\"\n icon=\"angle-right\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerNext\"\n (clicked)=\"move(1)\" />\n }\n\n <!-- Панель приходит снаружи: где-то кадр только смотрят, где-то им ещё и\n управляют, и решение принимается там же, где кадр виден крупно -->\n @if (panel) {\n <footer rtElem=\"panel\" qa-dataid=\"photo-viewer-panel\">\n <ng-container\n [ngTemplateOutlet]=\"panel\"\n [ngTemplateOutletContext]=\"{ $implicit: photos[currentIndex()], index: currentIndex() }\" />\n </footer>\n }\n </div>\n</ng-container>\n",
|
|
112
|
+
styles: ["@charset \"UTF-8\";@layer rt-kit.components{.rt-photo-viewer{--rt-photo-viewer-control-radius: var(--rt-radius-md);--rt-photo-viewer-control-color: var(--rt-color-text-inverse);--rt-photo-viewer-control-bg: color-mix(in srgb, var(--rt-color-bg-inverse) 55%, transparent);--rt-photo-viewer-control-bg-hover: color-mix(in srgb, var(--rt-color-text-inverse) 24%, transparent);display:block;width:100vw;height:100vh;height:100dvh;background:var(--rt-color-bg-inverse)}.rt-photo-viewer__surface{position:absolute;inset:0}.rt-photo-viewer__bar{position:absolute;z-index:1;top:0;right:0;left:0;display:flex;align-items:center;justify-content:space-between;padding:var(--rt-space-4) var(--rt-space-5);color:var(--rt-color-text-inverse);gap:var(--rt-space-4)}.rt-photo-viewer__counter{font-size:var(--rt-text-sm);font-variant-numeric:tabular-nums}.rt-photo-viewer__track{display:flex;overflow:auto hidden;width:100%;height:100%;scroll-snap-type:x mandatory;scrollbar-width:none}.rt-photo-viewer__slide{display:flex;flex:0 0 100%;align-items:center;justify-content:center;padding:var(--rt-space-10) var(--rt-space-4);margin:0;scroll-snap-align:center}.rt-photo-viewer__slide picture{display:flex;width:100%;height:100%;align-items:center;justify-content:center}.rt-photo-viewer__image{display:block;max-width:100%;max-height:100%;object-fit:contain}.rt-photo-viewer__panel{position:absolute;z-index:1;right:0;bottom:0;left:0;padding:var(--rt-space-4) var(--rt-space-5);background:var(--rt-color-bg-surface);color:var(--rt-color-text-primary)}.rt-photo-viewer__bar rt-icon-button,.rt-photo-viewer__arrow-prev,.rt-photo-viewer__arrow-next{--rt-icon-button-color: var(--rt-photo-viewer-control-color);--rt-icon-button-color-bg-hover: var(--rt-photo-viewer-control-bg-hover);border-radius:var(--rt-photo-viewer-control-radius);background:var(--rt-photo-viewer-control-bg)}.rt-photo-viewer__arrow-prev,.rt-photo-viewer__arrow-next{position:absolute;top:50%;transform:translateY(-50%)}.rt-photo-viewer__arrow-prev{left:var(--rt-space-4)}.rt-photo-viewer__arrow-next{right:var(--rt-space-4)}.rt-photo-viewer:has(.rt-photo-viewer__panel) .rt-photo-viewer__slide{padding-bottom:var(--rt-space-20)}@media(width<=700px){.rt-photo-viewer__arrow-prev,.rt-photo-viewer__arrow-next{display:none}.rt-photo-viewer__slide{padding:var(--rt-space-12) 0}}}\n"],
|
|
113
|
+
dependencies: [
|
|
114
|
+
{
|
|
115
|
+
kind: "directive",
|
|
116
|
+
type: CdkTrapFocus,
|
|
117
|
+
selector: "[cdkTrapFocus]",
|
|
118
|
+
inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"],
|
|
119
|
+
exportAs: ["cdkTrapFocus"]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
kind: "directive",
|
|
123
|
+
type: NgTemplateOutlet,
|
|
124
|
+
selector: "[ngTemplateOutlet]",
|
|
125
|
+
inputs: [
|
|
126
|
+
"ngTemplateOutletContext",
|
|
127
|
+
"ngTemplateOutlet",
|
|
128
|
+
"ngTemplateOutletInjector"
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
kind: "directive",
|
|
133
|
+
type: BlockDirective,
|
|
134
|
+
selector: "[rtBlock]",
|
|
135
|
+
inputs: ["rtMod"]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
kind: "directive",
|
|
139
|
+
type: ElemDirective,
|
|
140
|
+
selector: "[rtElem]",
|
|
141
|
+
inputs: ["rtMod"]
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
kind: "component",
|
|
145
|
+
type: RtIconButtonComponent,
|
|
146
|
+
selector: "rt-icon-button",
|
|
147
|
+
inputs: [
|
|
148
|
+
"icon",
|
|
149
|
+
"glyph",
|
|
150
|
+
"ariaLabel",
|
|
151
|
+
"variant",
|
|
152
|
+
"iconColor",
|
|
153
|
+
"iconFill",
|
|
154
|
+
"size",
|
|
155
|
+
"iconSize",
|
|
156
|
+
"type",
|
|
157
|
+
"tooltip",
|
|
158
|
+
"tooltipHideOnClick",
|
|
159
|
+
"tabIndex",
|
|
160
|
+
"loading",
|
|
161
|
+
"disabled",
|
|
162
|
+
"active",
|
|
163
|
+
"expanded",
|
|
164
|
+
"indicator"
|
|
165
|
+
],
|
|
166
|
+
outputs: ["clicked"]
|
|
167
|
+
}
|
|
168
|
+
],
|
|
169
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush,
|
|
170
|
+
encapsulation: i0.ViewEncapsulation.None
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
175
|
+
minVersion: "12.0.0",
|
|
176
|
+
version: "22.2.2",
|
|
177
|
+
ngImport: i0,
|
|
178
|
+
type: RtPhotoViewerComponent,
|
|
179
|
+
decorators: [{
|
|
180
|
+
type: Component,
|
|
181
|
+
args: [{
|
|
182
|
+
selector: "rt-photo-viewer",
|
|
183
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
184
|
+
encapsulation: ViewEncapsulation.None,
|
|
185
|
+
imports: [
|
|
186
|
+
CdkTrapFocus,
|
|
187
|
+
NgTemplateOutlet,
|
|
188
|
+
BlockDirective,
|
|
189
|
+
ElemDirective,
|
|
190
|
+
RtIconButtonComponent
|
|
191
|
+
],
|
|
192
|
+
host: {
|
|
193
|
+
class: BEM_BLOCK,
|
|
194
|
+
role: "dialog",
|
|
195
|
+
"aria-modal": "true",
|
|
196
|
+
tabindex: "-1",
|
|
197
|
+
"(keydown.escape)": "close()",
|
|
198
|
+
"(keydown.arrowLeft)": "move(-1)",
|
|
199
|
+
"(keydown.arrowRight)": "move(1)"
|
|
200
|
+
},
|
|
201
|
+
template: "<ng-container rtBlock=\"rt-photo-viewer\">\n <!-- Ловушка держит Tab внутри; фокус ставит сам компонент, поэтому\n авто-захвата здесь нет: он перебивал фокус на просмотрщике, и клавиши\n переставали листать -->\n <div rtElem=\"surface\" cdkTrapFocus>\n <header rtElem=\"bar\">\n <span rtElem=\"counter\" qa-dataid=\"photo-viewer-counter\">{{ counter() }}</span>\n <rt-icon-button\n qa-dataid=\"photo-viewer-close\"\n icon=\"close\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerClose\"\n (clicked)=\"close()\" />\n </header>\n\n <div rtElem=\"track\" qa-dataid=\"photo-viewer-track\" #track (scroll)=\"onScroll()\">\n @for (photo of photos; track photo.id; let index = $index) {\n <figure rtElem=\"slide\" [attr.data-photo-index]=\"index\">\n @if (rendered()[index]) {\n <picture>\n @for (source of photo.picture.sources; track source.type) {\n <source sizes=\"100vw\" [type]=\"source.type\" [srcset]=\"source.srcset\" />\n }\n <img\n rtElem=\"image\"\n qa-dataid=\"photo-viewer-image\"\n decoding=\"async\"\n sizes=\"100vw\"\n [srcset]=\"photo.picture.fallbackSrcset\"\n [src]=\"photo.picture.fallbackSrc\"\n [alt]=\"photo.alt\" />\n </picture>\n }\n </figure>\n }\n </div>\n\n @if (photos.length > 1) {\n <rt-icon-button\n rtElem=\"arrow-prev\"\n qa-dataid=\"photo-viewer-prev\"\n icon=\"angle-left\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerPrev\"\n (clicked)=\"move(-1)\" />\n\n <rt-icon-button\n rtElem=\"arrow-next\"\n qa-dataid=\"photo-viewer-next\"\n icon=\"angle-right\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerNext\"\n (clicked)=\"move(1)\" />\n }\n\n <!-- Панель приходит снаружи: где-то кадр только смотрят, где-то им ещё и\n управляют, и решение принимается там же, где кадр виден крупно -->\n @if (panel) {\n <footer rtElem=\"panel\" qa-dataid=\"photo-viewer-panel\">\n <ng-container\n [ngTemplateOutlet]=\"panel\"\n [ngTemplateOutletContext]=\"{ $implicit: photos[currentIndex()], index: currentIndex() }\" />\n </footer>\n }\n </div>\n</ng-container>\n",
|
|
202
|
+
styles: ["@charset \"UTF-8\";@layer rt-kit.components{.rt-photo-viewer{--rt-photo-viewer-control-radius: var(--rt-radius-md);--rt-photo-viewer-control-color: var(--rt-color-text-inverse);--rt-photo-viewer-control-bg: color-mix(in srgb, var(--rt-color-bg-inverse) 55%, transparent);--rt-photo-viewer-control-bg-hover: color-mix(in srgb, var(--rt-color-text-inverse) 24%, transparent);display:block;width:100vw;height:100vh;height:100dvh;background:var(--rt-color-bg-inverse)}.rt-photo-viewer__surface{position:absolute;inset:0}.rt-photo-viewer__bar{position:absolute;z-index:1;top:0;right:0;left:0;display:flex;align-items:center;justify-content:space-between;padding:var(--rt-space-4) var(--rt-space-5);color:var(--rt-color-text-inverse);gap:var(--rt-space-4)}.rt-photo-viewer__counter{font-size:var(--rt-text-sm);font-variant-numeric:tabular-nums}.rt-photo-viewer__track{display:flex;overflow:auto hidden;width:100%;height:100%;scroll-snap-type:x mandatory;scrollbar-width:none}.rt-photo-viewer__slide{display:flex;flex:0 0 100%;align-items:center;justify-content:center;padding:var(--rt-space-10) var(--rt-space-4);margin:0;scroll-snap-align:center}.rt-photo-viewer__slide picture{display:flex;width:100%;height:100%;align-items:center;justify-content:center}.rt-photo-viewer__image{display:block;max-width:100%;max-height:100%;object-fit:contain}.rt-photo-viewer__panel{position:absolute;z-index:1;right:0;bottom:0;left:0;padding:var(--rt-space-4) var(--rt-space-5);background:var(--rt-color-bg-surface);color:var(--rt-color-text-primary)}.rt-photo-viewer__bar rt-icon-button,.rt-photo-viewer__arrow-prev,.rt-photo-viewer__arrow-next{--rt-icon-button-color: var(--rt-photo-viewer-control-color);--rt-icon-button-color-bg-hover: var(--rt-photo-viewer-control-bg-hover);border-radius:var(--rt-photo-viewer-control-radius);background:var(--rt-photo-viewer-control-bg)}.rt-photo-viewer__arrow-prev,.rt-photo-viewer__arrow-next{position:absolute;top:50%;transform:translateY(-50%)}.rt-photo-viewer__arrow-prev{left:var(--rt-space-4)}.rt-photo-viewer__arrow-next{right:var(--rt-space-4)}.rt-photo-viewer:has(.rt-photo-viewer__panel) .rt-photo-viewer__slide{padding-bottom:var(--rt-space-20)}@media(width<=700px){.rt-photo-viewer__arrow-prev,.rt-photo-viewer__arrow-next{display:none}.rt-photo-viewer__slide{padding:var(--rt-space-12) 0}}}\n"]
|
|
203
|
+
}]
|
|
204
|
+
}],
|
|
205
|
+
ctorParameters: () => [],
|
|
206
|
+
propDecorators: { track: [{
|
|
207
|
+
type: i0.ViewChild,
|
|
208
|
+
args: ["track", { isSignal: true }]
|
|
209
|
+
}] }
|
|
210
|
+
});
|
|
169
211
|
export { RtPhotoViewerComponent, indexFromParam, indexFromScroll, nextPhotoIndex, paramFromIndex, shouldRenderPhoto };
|
|
170
|
-
|
|
212
|
+
|
|
213
|
+
//# sourceMappingURL=rt-tools-ui-kit-v2-photo-viewer.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rt-tools-ui-kit-v2-photo-viewer.mjs","sources":["../../../projects/ui-kit-v2/src/lib/components/photo-viewer/rt-photo-viewer.logic.ts","../../../projects/ui-kit-v2/src/lib/components/photo-viewer/rt-photo-viewer.component.ts","../../../projects/ui-kit-v2/src/lib/components/photo-viewer/rt-photo-viewer.component.html","../../../projects/ui-kit-v2/src/lib/components/photo-viewer/rt-tools-ui-kit-v2-photo-viewer.ts"],"sourcesContent":["/**\n * Чистая логика просмотрщика: переходы между кадрами и то, какие из них стоит\n * держать загруженными.\n */\n\n/** Листание по кругу: с последнего кадра стрелка вправо ведёт на первый */\nexport function nextPhotoIndex(current: number, total: number, step: number): number {\n if (total <= 0) {\n return 0;\n }\n return (((current + step) % total) + total) % total;\n}\n\n/** Индекс из адреса: в URL кадры нумеруются с единицы, внутри — с нуля */\nexport function indexFromParam(param: string | null, total: number): number {\n const parsed: number = Number(param);\n if (!Number.isInteger(parsed) || parsed < 1 || parsed > total) {\n return -1;\n }\n return parsed - 1;\n}\n\nexport function paramFromIndex(index: number): string {\n return String(index + 1);\n}\n\n/**\n * Индекс кадра по позиции прокрутки. Нужен, потому что листать можно свайпом:\n * без этого счётчик показывал бы один кадр, а на экране был бы другой.\n */\nexport function indexFromScroll(scrollLeft: number, slideWidth: number, total: number): number {\n if (slideWidth <= 0 || total <= 0) {\n return 0;\n }\n const index: number = Math.round(scrollLeft / slideWidth);\n return Math.min(Math.max(index, 0), total - 1);\n}\n\n/**\n * Кадры, которые держим в разметке: текущий и соседние. Полноразмерная\n * фотография весит сотни килобайт, и загружать все пятьдесят разом — значит\n * выесть трафик пользователя ради кадров, до которых он не долистает.\n */\nexport function shouldRenderPhoto(index: number, current: number, total: number): boolean {\n if (total <= 0) {\n return false;\n }\n const distance: number = Math.min(Math.abs(index - current), total - Math.abs(index - current));\n return distance <= 1;\n}\n","import { CdkTrapFocus } from '@angular/cdk/a11y';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n afterNextRender,\n computed,\n inject,\n signal,\n viewChild,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n Signal,\n TemplateRef,\n ViewEncapsulation,\n WritableSignal,\n} from '@angular/core';\n\n// rt-tools\nimport { BlockDirective, ElemDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap } from '@rt-tools/ui-kit-v2/core';\nimport { RtDialogRef } from '@rt-tools/ui-kit-v2/dialog';\nimport { RT_DIALOG_DATA } from '@rt-tools/ui-kit-v2/dialog';\nimport { RtIconButtonComponent } from '@rt-tools/ui-kit-v2/icon-button';\nimport { indexFromScroll, nextPhotoIndex, shouldRenderPhoto } from './rt-photo-viewer.logic';\nimport { IRtPhotoViewer } from './rt-photo-viewer.model';\n\nconst BEM_BLOCK: string = 'rt-photo-viewer';\n\n/**\n * Полноэкранный просмотрщик фотографий поверх `RtDialogService`.\n *\n * Листание — нативный `scroll-snap`: свайп, инерцию и прилипание даёт сам\n * браузер, и на телефоне это ощущается правильнее любой реализации на\n * обработчиках. Готовую библиотеку не берём — весь кит здесь собственный, а\n * токены, BEM и `qa-dataid` ложатся естественно.\n *\n * Руками сделаны: стрелки, клавиши `←` `→`, счётчик и предзагрузка соседних\n * кадров. `Esc`, блокировку прокрутки фона и затемнение даёт сам диалог.\n */\n@Component({\n selector: 'rt-photo-viewer',\n templateUrl: './rt-photo-viewer.component.html',\n styleUrl: './rt-photo-viewer.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // angular\n CdkTrapFocus,\n NgTemplateOutlet,\n\n // rt-tools\n BlockDirective,\n ElemDirective,\n\n // components\n RtIconButtonComponent,\n\n // transloco\n ],\n host: {\n class: BEM_BLOCK,\n role: 'dialog',\n 'aria-modal': 'true',\n // Клавиши слушает сам просмотрщик, поэтому фокус переводится на него\n // сразу после открытия — иначе стрелки и Esc не работали бы, пока\n // пользователь куда-нибудь не кликнет\n tabindex: '-1',\n '(keydown.escape)': 'close()',\n '(keydown.arrowLeft)': 'move(-1)',\n '(keydown.arrowRight)': 'move(1)',\n },\n})\nexport class RtPhotoViewerComponent {\n readonly #data: IRtPhotoViewer.Data = inject(RT_DIALOG_DATA) as IRtPhotoViewer.Data;\n readonly #dialogRef: RtDialogRef<number> = inject(RtDialogRef<number>);\n readonly #host: ElementRef<HTMLElement> = inject(ElementRef<HTMLElement>);\n\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly track: Signal<ElementRef<HTMLElement> | undefined> = viewChild<ElementRef<HTMLElement>>('track');\n\n protected readonly photos: readonly IRtPhotoViewer.Photo[] = this.#data.photos;\n\n /** Панель правки под дорожкой; там, где кадр только смотрят, её нет */\n protected readonly panel: TemplateRef<IRtPhotoViewer.PanelContext> | null = this.#data.panel ?? null;\n\n protected readonly currentIndex: WritableSignal<number> = signal<number>(this.#data.startIndex);\n\n protected readonly counter: Signal<string> = computed((): string => `${this.currentIndex() + 1} / ${this.photos.length}`);\n\n /** Соседние кадры готовятся заранее, дальние в разметку не попадают */\n protected readonly rendered: Signal<readonly boolean[]> = computed((): readonly boolean[] =>\n this.photos.map((_photo: IRtPhotoViewer.Photo, index: number): boolean =>\n shouldRenderPhoto(index, this.currentIndex(), this.photos.length)\n )\n );\n\n constructor() {\n // Открытие может быть не с первого кадра: без явной прокрутки счётчик\n // показывал бы второй кадр, а на экране остался бы первый\n afterNextRender((): void => {\n this.#host.nativeElement.focus();\n this.#scrollToCurrent();\n });\n }\n\n /** Наружу отдаётся кадр, на котором закрыли: по нему вызывающая сторона чистит адрес */\n public close(): void {\n this.#dialogRef.close(this.currentIndex());\n }\n\n protected move(step: number): void {\n this.select(nextPhotoIndex(this.currentIndex(), this.photos.length, step));\n }\n\n protected select(index: number): void {\n this.currentIndex.set(index);\n this.#scrollToCurrent();\n }\n\n /** Свайп листает мимо стрелок — счётчик догоняет позицию дорожки */\n protected onScroll(): void {\n const track: HTMLElement | undefined = this.track()?.nativeElement;\n if (track) {\n this.currentIndex.set(indexFromScroll(track.scrollLeft, track.clientWidth, this.photos.length));\n }\n }\n\n /**\n * Прокрутка ленты вслед за выбором. Позиция считается от ширины дорожки, а\n * не от `scrollWidth`: кадры за пределами окна ещё не отрисованы, и общая\n * ширина до их появления меньше реальной.\n *\n * Переход мгновенный: `behavior: 'smooth'` внутри оверлея молча не двигает\n * дорожку — счётчик менялся, а кадр оставался прежним. Свайп при этом\n * по-прежнему анимирует сам браузер через scroll-snap.\n */\n #scrollToCurrent(): void {\n const track: HTMLElement | undefined = this.track()?.nativeElement;\n if (!track) {\n return;\n }\n track.scrollTo({ left: track.clientWidth * this.currentIndex(), behavior: 'instant' });\n }\n}\n","<ng-container rtBlock=\"rt-photo-viewer\">\n <!-- Ловушка держит Tab внутри; фокус ставит сам компонент, поэтому\n авто-захвата здесь нет: он перебивал фокус на просмотрщике, и клавиши\n переставали листать -->\n <div rtElem=\"surface\" cdkTrapFocus>\n <header rtElem=\"bar\">\n <span rtElem=\"counter\" qa-dataid=\"photo-viewer-counter\">{{ counter() }}</span>\n <rt-icon-button\n qa-dataid=\"photo-viewer-close\"\n icon=\"close\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerClose\"\n (clicked)=\"close()\" />\n </header>\n\n <div rtElem=\"track\" qa-dataid=\"photo-viewer-track\" #track (scroll)=\"onScroll()\">\n @for (photo of photos; track photo.id; let index = $index) {\n <figure rtElem=\"slide\" [attr.data-photo-index]=\"index\">\n @if (rendered()[index]) {\n <picture>\n @for (source of photo.picture.sources; track source.type) {\n <source sizes=\"100vw\" [type]=\"source.type\" [srcset]=\"source.srcset\" />\n }\n <img\n rtElem=\"image\"\n qa-dataid=\"photo-viewer-image\"\n decoding=\"async\"\n sizes=\"100vw\"\n [srcset]=\"photo.picture.fallbackSrcset\"\n [src]=\"photo.picture.fallbackSrc\"\n [alt]=\"photo.alt\" />\n </picture>\n }\n </figure>\n }\n </div>\n\n @if (photos.length > 1) {\n <rt-icon-button\n rtElem=\"arrow-prev\"\n qa-dataid=\"photo-viewer-prev\"\n icon=\"angle-left\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerPrev\"\n (clicked)=\"move(-1)\" />\n\n <rt-icon-button\n rtElem=\"arrow-next\"\n qa-dataid=\"photo-viewer-next\"\n icon=\"angle-right\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerNext\"\n (clicked)=\"move(1)\" />\n }\n\n <!-- Панель приходит снаружи: где-то кадр только смотрят, где-то им ещё и\n управляют, и решение принимается там же, где кадр виден крупно -->\n @if (panel) {\n <footer rtElem=\"panel\" qa-dataid=\"photo-viewer-panel\">\n <ng-container\n [ngTemplateOutlet]=\"panel\"\n [ngTemplateOutletContext]=\"{ $implicit: photos[currentIndex()], index: currentIndex() }\" />\n </footer>\n }\n </div>\n</ng-container>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rt-tools-ui-kit-v2-photo-viewer.mjs","names":[],"sources":["../../../projects/ui-kit-v2/src/lib/components/photo-viewer/rt-photo-viewer.logic.ts","../../../projects/ui-kit-v2/src/lib/components/photo-viewer/rt-photo-viewer.component.ts","../../../projects/ui-kit-v2/src/lib/components/photo-viewer/rt-photo-viewer.component.html"],"sourcesContent":["/**\n * Чистая логика просмотрщика: переходы между кадрами и то, какие из них стоит\n * держать загруженными.\n */\n\n/** Листание по кругу: с последнего кадра стрелка вправо ведёт на первый */\nexport function nextPhotoIndex(current: number, total: number, step: number): number {\n if (total <= 0) {\n return 0;\n }\n return (((current + step) % total) + total) % total;\n}\n\n/** Индекс из адреса: в URL кадры нумеруются с единицы, внутри — с нуля */\nexport function indexFromParam(param: string | null, total: number): number {\n const parsed: number = Number(param);\n if (!Number.isInteger(parsed) || parsed < 1 || parsed > total) {\n return -1;\n }\n return parsed - 1;\n}\n\nexport function paramFromIndex(index: number): string {\n return String(index + 1);\n}\n\n/**\n * Индекс кадра по позиции прокрутки. Нужен, потому что листать можно свайпом:\n * без этого счётчик показывал бы один кадр, а на экране был бы другой.\n */\nexport function indexFromScroll(scrollLeft: number, slideWidth: number, total: number): number {\n if (slideWidth <= 0 || total <= 0) {\n return 0;\n }\n const index: number = Math.round(scrollLeft / slideWidth);\n return Math.min(Math.max(index, 0), total - 1);\n}\n\n/**\n * Кадры, которые держим в разметке: текущий и соседние. Полноразмерная\n * фотография весит сотни килобайт, и загружать все пятьдесят разом — значит\n * выесть трафик пользователя ради кадров, до которых он не долистает.\n */\nexport function shouldRenderPhoto(index: number, current: number, total: number): boolean {\n if (total <= 0) {\n return false;\n }\n const distance: number = Math.min(Math.abs(index - current), total - Math.abs(index - current));\n return distance <= 1;\n}\n","import { CdkTrapFocus } from '@angular/cdk/a11y';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n afterNextRender,\n computed,\n inject,\n signal,\n viewChild,\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n Signal,\n TemplateRef,\n ViewEncapsulation,\n WritableSignal,\n} from '@angular/core';\n\n// rt-tools\nimport { BlockDirective, ElemDirective } from '@rt-tools/core';\n\nimport { RT_KIT_LABELS, TRtKitLabelMap } from '@rt-tools/ui-kit-v2/core';\nimport { RtDialogRef } from '@rt-tools/ui-kit-v2/dialog';\nimport { RT_DIALOG_DATA } from '@rt-tools/ui-kit-v2/dialog';\nimport { RtIconButtonComponent } from '@rt-tools/ui-kit-v2/icon-button';\nimport { indexFromScroll, nextPhotoIndex, shouldRenderPhoto } from './rt-photo-viewer.logic';\nimport { IRtPhotoViewer } from './rt-photo-viewer.model';\n\nconst BEM_BLOCK: string = 'rt-photo-viewer';\n\n/**\n * Полноэкранный просмотрщик фотографий поверх `RtDialogService`.\n *\n * Листание — нативный `scroll-snap`: свайп, инерцию и прилипание даёт сам\n * браузер, и на телефоне это ощущается правильнее любой реализации на\n * обработчиках. Готовую библиотеку не берём — весь кит здесь собственный, а\n * токены, BEM и `qa-dataid` ложатся естественно.\n *\n * Руками сделаны: стрелки, клавиши `←` `→`, счётчик и предзагрузка соседних\n * кадров. `Esc`, блокировку прокрутки фона и затемнение даёт сам диалог.\n */\n@Component({\n selector: 'rt-photo-viewer',\n templateUrl: './rt-photo-viewer.component.html',\n styleUrl: './rt-photo-viewer.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n imports: [\n // angular\n CdkTrapFocus,\n NgTemplateOutlet,\n\n // rt-tools\n BlockDirective,\n ElemDirective,\n\n // components\n RtIconButtonComponent,\n\n // transloco\n ],\n host: {\n class: BEM_BLOCK,\n role: 'dialog',\n 'aria-modal': 'true',\n // Клавиши слушает сам просмотрщик, поэтому фокус переводится на него\n // сразу после открытия — иначе стрелки и Esc не работали бы, пока\n // пользователь куда-нибудь не кликнет\n tabindex: '-1',\n '(keydown.escape)': 'close()',\n '(keydown.arrowLeft)': 'move(-1)',\n '(keydown.arrowRight)': 'move(1)',\n },\n})\nexport class RtPhotoViewerComponent {\n readonly #data: IRtPhotoViewer.Data = inject(RT_DIALOG_DATA) as IRtPhotoViewer.Data;\n readonly #dialogRef: RtDialogRef<number> = inject(RtDialogRef<number>);\n readonly #host: ElementRef<HTMLElement> = inject(ElementRef<HTMLElement>);\n\n protected readonly t: Signal<TRtKitLabelMap> = inject(RT_KIT_LABELS);\n\n protected readonly track: Signal<ElementRef<HTMLElement> | undefined> = viewChild<ElementRef<HTMLElement>>('track');\n\n protected readonly photos: readonly IRtPhotoViewer.Photo[] = this.#data.photos;\n\n /** Панель правки под дорожкой; там, где кадр только смотрят, её нет */\n protected readonly panel: TemplateRef<IRtPhotoViewer.PanelContext> | null = this.#data.panel ?? null;\n\n protected readonly currentIndex: WritableSignal<number> = signal<number>(this.#data.startIndex);\n\n protected readonly counter: Signal<string> = computed((): string => `${this.currentIndex() + 1} / ${this.photos.length}`);\n\n /** Соседние кадры готовятся заранее, дальние в разметку не попадают */\n protected readonly rendered: Signal<readonly boolean[]> = computed((): readonly boolean[] =>\n this.photos.map((_photo: IRtPhotoViewer.Photo, index: number): boolean =>\n shouldRenderPhoto(index, this.currentIndex(), this.photos.length)\n )\n );\n\n constructor() {\n // Открытие может быть не с первого кадра: без явной прокрутки счётчик\n // показывал бы второй кадр, а на экране остался бы первый\n afterNextRender((): void => {\n this.#host.nativeElement.focus();\n this.#scrollToCurrent();\n });\n }\n\n /** Наружу отдаётся кадр, на котором закрыли: по нему вызывающая сторона чистит адрес */\n public close(): void {\n this.#dialogRef.close(this.currentIndex());\n }\n\n protected move(step: number): void {\n this.select(nextPhotoIndex(this.currentIndex(), this.photos.length, step));\n }\n\n protected select(index: number): void {\n this.currentIndex.set(index);\n this.#scrollToCurrent();\n }\n\n /** Свайп листает мимо стрелок — счётчик догоняет позицию дорожки */\n protected onScroll(): void {\n const track: HTMLElement | undefined = this.track()?.nativeElement;\n if (track) {\n this.currentIndex.set(indexFromScroll(track.scrollLeft, track.clientWidth, this.photos.length));\n }\n }\n\n /**\n * Прокрутка ленты вслед за выбором. Позиция считается от ширины дорожки, а\n * не от `scrollWidth`: кадры за пределами окна ещё не отрисованы, и общая\n * ширина до их появления меньше реальной.\n *\n * Переход мгновенный: `behavior: 'smooth'` внутри оверлея молча не двигает\n * дорожку — счётчик менялся, а кадр оставался прежним. Свайп при этом\n * по-прежнему анимирует сам браузер через scroll-snap.\n */\n #scrollToCurrent(): void {\n const track: HTMLElement | undefined = this.track()?.nativeElement;\n if (!track) {\n return;\n }\n track.scrollTo({ left: track.clientWidth * this.currentIndex(), behavior: 'instant' });\n }\n}\n","<ng-container rtBlock=\"rt-photo-viewer\">\n <!-- Ловушка держит Tab внутри; фокус ставит сам компонент, поэтому\n авто-захвата здесь нет: он перебивал фокус на просмотрщике, и клавиши\n переставали листать -->\n <div rtElem=\"surface\" cdkTrapFocus>\n <header rtElem=\"bar\">\n <span rtElem=\"counter\" qa-dataid=\"photo-viewer-counter\">{{ counter() }}</span>\n <rt-icon-button\n qa-dataid=\"photo-viewer-close\"\n icon=\"close\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerClose\"\n (clicked)=\"close()\" />\n </header>\n\n <div rtElem=\"track\" qa-dataid=\"photo-viewer-track\" #track (scroll)=\"onScroll()\">\n @for (photo of photos; track photo.id; let index = $index) {\n <figure rtElem=\"slide\" [attr.data-photo-index]=\"index\">\n @if (rendered()[index]) {\n <picture>\n @for (source of photo.picture.sources; track source.type) {\n <source sizes=\"100vw\" [type]=\"source.type\" [srcset]=\"source.srcset\" />\n }\n <img\n rtElem=\"image\"\n qa-dataid=\"photo-viewer-image\"\n decoding=\"async\"\n sizes=\"100vw\"\n [srcset]=\"photo.picture.fallbackSrcset\"\n [src]=\"photo.picture.fallbackSrc\"\n [alt]=\"photo.alt\" />\n </picture>\n }\n </figure>\n }\n </div>\n\n @if (photos.length > 1) {\n <rt-icon-button\n rtElem=\"arrow-prev\"\n qa-dataid=\"photo-viewer-prev\"\n icon=\"angle-left\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerPrev\"\n (clicked)=\"move(-1)\" />\n\n <rt-icon-button\n rtElem=\"arrow-next\"\n qa-dataid=\"photo-viewer-next\"\n icon=\"angle-right\"\n variant=\"ghost\"\n [ariaLabel]=\"t().photoViewerNext\"\n (clicked)=\"move(1)\" />\n }\n\n <!-- Панель приходит снаружи: где-то кадр только смотрят, где-то им ещё и\n управляют, и решение принимается там же, где кадр виден крупно -->\n @if (panel) {\n <footer rtElem=\"panel\" qa-dataid=\"photo-viewer-panel\">\n <ng-container\n [ngTemplateOutlet]=\"panel\"\n [ngTemplateOutletContext]=\"{ $implicit: photos[currentIndex()], index: currentIndex() }\" />\n </footer>\n }\n </div>\n</ng-container>\n"],"mappings":"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"}
|