@helsevestikt/hviktor-angular 0.0.15 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Input, Component, booleanAttribute, Directive, CUSTOM_ELEMENTS_SCHEMA, EventEmitter, inject, ElementRef, HostListener, Output, ViewChild, DestroyRef, signal, input, computed, HostBinding, ChangeDetectorRef, ViewEncapsulation, ContentChildren, forwardRef, Renderer2 } from '@angular/core';
2
+ import { Input, Component, booleanAttribute, Directive, CUSTOM_ELEMENTS_SCHEMA, EventEmitter, inject, ElementRef, HostListener, Output, ViewChild, DestroyRef, signal, input, computed, HostBinding, ChangeDetectorRef, ViewEncapsulation, numberAttribute, ContentChildren, forwardRef, Renderer2 } from '@angular/core';
3
3
  import '@u-elements/u-details';
4
4
  import { FormGroupDirective, NgControl, ControlContainer, Validators, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
5
5
  import { HttpClient } from '@angular/common/http';
@@ -2021,6 +2021,273 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
2021
2021
  }]
2022
2022
  }] });
2023
2023
 
2024
+ /**
2025
+ * @summary
2026
+ * Pagination er en liste med knapper som brukes for å navigere mellom
2027
+ * ulike sider med innhold, for eksempel søkeresultater eller tabeller.
2028
+ *
2029
+ * @example
2030
+ * ```html
2031
+ * <hvi-pagination
2032
+ * [totalItems]="100"
2033
+ * [pageSize]="10"
2034
+ * [(currentPage)]="currentPage"
2035
+ * />
2036
+ * ```
2037
+ *
2038
+ * @see {@link https://designsystemet.no/no/components/docs/pagination/code}
2039
+ */
2040
+ class HviPagination {
2041
+ /** Totalt antall elementer som pagineres */
2042
+ set totalItems(value) {
2043
+ this._totalItems.set(value);
2044
+ }
2045
+ /** Antall elementer per side */
2046
+ set pageSize(value) {
2047
+ this._pageSize.set(value);
2048
+ }
2049
+ /** Nåværende side (1-indeksert) */
2050
+ set currentPage(value) {
2051
+ this._currentPage.set(value);
2052
+ }
2053
+ /** Antall sider som skal vises rundt nåværende side */
2054
+ siblingCount = 1;
2055
+ /** Vis alltid første og siste side */
2056
+ showEdges = true;
2057
+ /** Vis "Forrige" og "Neste" tekst på knappene */
2058
+ showPreviousNextLabels = true;
2059
+ /** Aria-label for hele navigasjonen */
2060
+ ariaLabel = 'Sidenavigering';
2061
+ /** Tekst for forrige-knappen */
2062
+ previousLabel = 'Forrige';
2063
+ /** Tekst for neste-knappen */
2064
+ nextLabel = 'Neste';
2065
+ /** Event som emitteres når siden endres */
2066
+ currentPageChange = new EventEmitter();
2067
+ /** Event som emitteres med mer detaljer om sideendring */
2068
+ pageChange = new EventEmitter();
2069
+ // Internal signals
2070
+ _totalItems = signal(0, ...(ngDevMode ? [{ debugName: "_totalItems" }] : []));
2071
+ _pageSize = signal(10, ...(ngDevMode ? [{ debugName: "_pageSize" }] : []));
2072
+ _currentPage = signal(1, ...(ngDevMode ? [{ debugName: "_currentPage" }] : []));
2073
+ /** Beregnet totalt antall sider */
2074
+ totalPages = computed(() => {
2075
+ return Math.max(1, Math.ceil(this._totalItems() / this._pageSize()));
2076
+ }, ...(ngDevMode ? [{ debugName: "totalPages" }] : []));
2077
+ /** Er vi på første side? */
2078
+ isFirstPage = computed(() => this._currentPage() <= 1, ...(ngDevMode ? [{ debugName: "isFirstPage" }] : []));
2079
+ /** Er vi på siste side? */
2080
+ isLastPage = computed(() => this._currentPage() >= this.totalPages(), ...(ngDevMode ? [{ debugName: "isLastPage" }] : []));
2081
+ /** Beregner hvilke elementer som skal vises i pagineringen */
2082
+ paginationItems = computed(() => {
2083
+ const current = this._currentPage();
2084
+ const total = this.totalPages();
2085
+ const siblings = this.siblingCount;
2086
+ if (total <= 1)
2087
+ return [];
2088
+ const items = [];
2089
+ // Beregn range rundt nåværende side
2090
+ const leftSibling = Math.max(current - siblings, 1);
2091
+ const rightSibling = Math.min(current + siblings, total);
2092
+ const showLeftEllipsis = this.showEdges && leftSibling > 2;
2093
+ const showRightEllipsis = this.showEdges && rightSibling < total - 1;
2094
+ // Første side (hvis showEdges)
2095
+ if (this.showEdges && leftSibling > 1) {
2096
+ items.push({ type: 'page', page: 1 });
2097
+ if (showLeftEllipsis) {
2098
+ items.push({ type: 'ellipsis' });
2099
+ }
2100
+ }
2101
+ // Sider rundt nåværende
2102
+ for (let page = leftSibling; page <= rightSibling; page++) {
2103
+ items.push({ type: 'page', page });
2104
+ }
2105
+ // Siste side (hvis showEdges)
2106
+ if (this.showEdges && rightSibling < total) {
2107
+ if (showRightEllipsis) {
2108
+ items.push({ type: 'ellipsis' });
2109
+ }
2110
+ items.push({ type: 'page', page: total });
2111
+ }
2112
+ return items;
2113
+ }, ...(ngDevMode ? [{ debugName: "paginationItems" }] : []));
2114
+ /** Gå til en spesifikk side */
2115
+ goToPage(page) {
2116
+ const previousPage = this._currentPage();
2117
+ const newPage = Math.max(1, Math.min(page, this.totalPages()));
2118
+ if (newPage !== previousPage) {
2119
+ this._currentPage.set(newPage);
2120
+ this.currentPageChange.emit(newPage);
2121
+ this.pageChange.emit({ page: newPage, previousPage });
2122
+ }
2123
+ }
2124
+ /** Gå til forrige side */
2125
+ goToPrevious() {
2126
+ if (!this.isFirstPage()) {
2127
+ this.goToPage(this._currentPage() - 1);
2128
+ }
2129
+ }
2130
+ /** Gå til neste side */
2131
+ goToNext() {
2132
+ if (!this.isLastPage()) {
2133
+ this.goToPage(this._currentPage() + 1);
2134
+ }
2135
+ }
2136
+ /** Gå til første side */
2137
+ goToFirst() {
2138
+ this.goToPage(1);
2139
+ }
2140
+ /** Gå til siste side */
2141
+ goToLast() {
2142
+ this.goToPage(this.totalPages());
2143
+ }
2144
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: HviPagination, deps: [], target: i0.ɵɵFactoryTarget.Component });
2145
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: HviPagination, isStandalone: true, selector: "hvi-pagination", inputs: { totalItems: ["totalItems", "totalItems", numberAttribute], pageSize: ["pageSize", "pageSize", numberAttribute], currentPage: ["currentPage", "currentPage", numberAttribute], siblingCount: ["siblingCount", "siblingCount", numberAttribute], showEdges: ["showEdges", "showEdges", booleanAttribute], showPreviousNextLabels: ["showPreviousNextLabels", "showPreviousNextLabels", booleanAttribute], ariaLabel: "ariaLabel", previousLabel: "previousLabel", nextLabel: "nextLabel" }, outputs: { currentPageChange: "currentPageChange", pageChange: "pageChange" }, host: { classAttribute: "hvi-pagination" }, ngImport: i0, template: `
2146
+ <nav [attr.aria-label]="ariaLabel" class="ds-pagination">
2147
+ <ul>
2148
+ <!-- Forrige -->
2149
+ <li>
2150
+ <button
2151
+ class="ds-button"
2152
+ data-variant="tertiary"
2153
+ type="button"
2154
+ [attr.aria-label]="previousLabel"
2155
+ [disabled]="isFirstPage()"
2156
+ (click)="goToPrevious()"
2157
+ >
2158
+ {{ showPreviousNextLabels ? previousLabel : '' }}
2159
+ </button>
2160
+ </li>
2161
+
2162
+ <!-- Sidetall -->
2163
+ @for (item of paginationItems(); track $index) {
2164
+ @if (item.type === 'page') {
2165
+ <li>
2166
+ <button
2167
+ class="ds-button"
2168
+ [attr.data-variant]="item.page === _currentPage() ? 'primary' : 'tertiary'"
2169
+ type="button"
2170
+ [attr.aria-label]="'Side ' + item.page"
2171
+ [attr.aria-current]="item.page === _currentPage() ? 'page' : null"
2172
+ (click)="goToPage(item.page)"
2173
+ >
2174
+ {{ item.page }}
2175
+ </button>
2176
+ </li>
2177
+ } @else if (item.type === 'ellipsis') {
2178
+ <li></li>
2179
+ }
2180
+ }
2181
+
2182
+ <!-- Neste -->
2183
+ <li>
2184
+ <button
2185
+ class="ds-button"
2186
+ data-variant="tertiary"
2187
+ type="button"
2188
+ [attr.aria-label]="nextLabel"
2189
+ [disabled]="isLastPage()"
2190
+ (click)="goToNext()"
2191
+ >
2192
+ {{ showPreviousNextLabels ? nextLabel : '' }}
2193
+ </button>
2194
+ </li>
2195
+ </ul>
2196
+ </nav>
2197
+ `, isInline: true });
2198
+ }
2199
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: HviPagination, decorators: [{
2200
+ type: Component,
2201
+ args: [{
2202
+ selector: 'hvi-pagination',
2203
+ standalone: true,
2204
+ template: `
2205
+ <nav [attr.aria-label]="ariaLabel" class="ds-pagination">
2206
+ <ul>
2207
+ <!-- Forrige -->
2208
+ <li>
2209
+ <button
2210
+ class="ds-button"
2211
+ data-variant="tertiary"
2212
+ type="button"
2213
+ [attr.aria-label]="previousLabel"
2214
+ [disabled]="isFirstPage()"
2215
+ (click)="goToPrevious()"
2216
+ >
2217
+ {{ showPreviousNextLabels ? previousLabel : '' }}
2218
+ </button>
2219
+ </li>
2220
+
2221
+ <!-- Sidetall -->
2222
+ @for (item of paginationItems(); track $index) {
2223
+ @if (item.type === 'page') {
2224
+ <li>
2225
+ <button
2226
+ class="ds-button"
2227
+ [attr.data-variant]="item.page === _currentPage() ? 'primary' : 'tertiary'"
2228
+ type="button"
2229
+ [attr.aria-label]="'Side ' + item.page"
2230
+ [attr.aria-current]="item.page === _currentPage() ? 'page' : null"
2231
+ (click)="goToPage(item.page)"
2232
+ >
2233
+ {{ item.page }}
2234
+ </button>
2235
+ </li>
2236
+ } @else if (item.type === 'ellipsis') {
2237
+ <li></li>
2238
+ }
2239
+ }
2240
+
2241
+ <!-- Neste -->
2242
+ <li>
2243
+ <button
2244
+ class="ds-button"
2245
+ data-variant="tertiary"
2246
+ type="button"
2247
+ [attr.aria-label]="nextLabel"
2248
+ [disabled]="isLastPage()"
2249
+ (click)="goToNext()"
2250
+ >
2251
+ {{ showPreviousNextLabels ? nextLabel : '' }}
2252
+ </button>
2253
+ </li>
2254
+ </ul>
2255
+ </nav>
2256
+ `,
2257
+ host: {
2258
+ class: 'hvi-pagination',
2259
+ },
2260
+ }]
2261
+ }], propDecorators: { totalItems: [{
2262
+ type: Input,
2263
+ args: [{ transform: numberAttribute, required: true }]
2264
+ }], pageSize: [{
2265
+ type: Input,
2266
+ args: [{ transform: numberAttribute }]
2267
+ }], currentPage: [{
2268
+ type: Input,
2269
+ args: [{ transform: numberAttribute }]
2270
+ }], siblingCount: [{
2271
+ type: Input,
2272
+ args: [{ transform: numberAttribute }]
2273
+ }], showEdges: [{
2274
+ type: Input,
2275
+ args: [{ transform: booleanAttribute }]
2276
+ }], showPreviousNextLabels: [{
2277
+ type: Input,
2278
+ args: [{ transform: booleanAttribute }]
2279
+ }], ariaLabel: [{
2280
+ type: Input
2281
+ }], previousLabel: [{
2282
+ type: Input
2283
+ }], nextLabel: [{
2284
+ type: Input
2285
+ }], currentPageChange: [{
2286
+ type: Output
2287
+ }], pageChange: [{
2288
+ type: Output
2289
+ }] } });
2290
+
2024
2291
  /**
2025
2292
  * Paragraph is used for continuous text and is typically applied in articles, components, help text, and similar content.
2026
2293
  *
@@ -2608,6 +2875,442 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
2608
2875
  type: Input
2609
2876
  }] } });
2610
2877
 
2878
+ /**
2879
+ * @summary
2880
+ * Table brukes for å vise strukturert informasjon på en ryddig og oversiktlig måte.
2881
+ * Tabeller kan gjøre det enklere for brukerne å skanne og sammenligne informasjon.
2882
+ *
2883
+ * @example
2884
+ * ```html
2885
+ * <!-- Enkel bruk med innebygd sortering og søk -->
2886
+ * <table hviTable [value]="persons" [globalFilterFields]="['navn', 'epost']" #table="hviTable">
2887
+ * <caption>
2888
+ * <input type="search" (input)="table.filterGlobal($event.target.value)" />
2889
+ * </caption>
2890
+ * <thead>
2891
+ * <tr>
2892
+ * <th hviSortableColumn="navn">
2893
+ * <button>Navn</button>
2894
+ * </th>
2895
+ * <th>Epost</th>
2896
+ * </tr>
2897
+ * </thead>
2898
+ * <tbody>
2899
+ * @for (person of table.filteredValue(); track person.id) {
2900
+ * <tr>
2901
+ * <td>{{ person.navn }}</td>
2902
+ * <td>{{ person.epost }}</td>
2903
+ * </tr>
2904
+ * }
2905
+ * </tbody>
2906
+ * </table>
2907
+ * ```
2908
+ *
2909
+ * @see {@link https://designsystemet.no/no/components/docs/table/code}
2910
+ */
2911
+ class HviTable {
2912
+ /** Gir tabellen zebrastriper (annenhver rad har alternativ bakgrunn) */
2913
+ zebra = false;
2914
+ /** Gir tabellen en avrundet kant rundt */
2915
+ border = false;
2916
+ /** Gir tabellen hover-effekt på rader */
2917
+ hover = false;
2918
+ /** Gjør tabellens header sticky (fester seg til toppen ved scrolling) */
2919
+ stickyHeader = false;
2920
+ /** Data som skal vises i tabellen */
2921
+ set value(data) {
2922
+ this._value.set(data ?? []);
2923
+ }
2924
+ /** Felt som data skal sorteres etter (valgfri initial verdi) */
2925
+ set sortField(field) {
2926
+ if (field) {
2927
+ this._sortField.set(field);
2928
+ }
2929
+ }
2930
+ /** Sorteringsretning: 1 = ascending, -1 = descending, 0 = none */
2931
+ set sortOrder(order) {
2932
+ this._sortOrder.set(order);
2933
+ }
2934
+ /** Felt som global søk skal søke i */
2935
+ globalFilterFields = [];
2936
+ /** Aktiver paginering */
2937
+ paginator = false;
2938
+ /** Antall rader per side (når paginator er aktivert) */
2939
+ set rows(value) {
2940
+ this._rows.set(value);
2941
+ }
2942
+ /** Indeks for første rad som vises (0-basert) */
2943
+ set first(value) {
2944
+ this._first.set(value);
2945
+ }
2946
+ /** Event som emitteres når sortering endres */
2947
+ sortChange = new EventEmitter();
2948
+ /** Event som emitteres når side endres */
2949
+ pageChange = new EventEmitter();
2950
+ /** Event som emitteres med nåværende side (1-basert, for two-way binding) */
2951
+ currentPageChange = new EventEmitter();
2952
+ // Internal signals
2953
+ _value = signal([], ...(ngDevMode ? [{ debugName: "_value" }] : []));
2954
+ _sortField = signal(null, ...(ngDevMode ? [{ debugName: "_sortField" }] : []));
2955
+ _sortOrder = signal(0, ...(ngDevMode ? [{ debugName: "_sortOrder" }] : [])); // 0 = none, 1 = asc, -1 = desc
2956
+ _globalFilter = signal(null, ...(ngDevMode ? [{ debugName: "_globalFilter" }] : []));
2957
+ _rows = signal(10, ...(ngDevMode ? [{ debugName: "_rows" }] : []));
2958
+ _first = signal(0, ...(ngDevMode ? [{ debugName: "_first" }] : []));
2959
+ /** Kun sortert data (uten søk) - for bakoverkompatibilitet */
2960
+ sortedValue = computed(() => {
2961
+ return this.applySorting(this._value());
2962
+ }, ...(ngDevMode ? [{ debugName: "sortedValue" }] : []));
2963
+ /** Filtrert og sortert data - bruk denne i template */
2964
+ filteredValue = computed(() => {
2965
+ const data = this._value();
2966
+ const filtered = this.applyGlobalFilter(data);
2967
+ return this.applySorting(filtered);
2968
+ }, ...(ngDevMode ? [{ debugName: "filteredValue" }] : []));
2969
+ /** Paginert, filtrert og sortert data - bruk denne når paginator er aktivert */
2970
+ paginatedValue = computed(() => {
2971
+ const data = this.filteredValue();
2972
+ if (!this.paginator)
2973
+ return data;
2974
+ const first = this._first();
2975
+ const rows = this._rows();
2976
+ return data.slice(first, first + rows);
2977
+ }, ...(ngDevMode ? [{ debugName: "paginatedValue" }] : []));
2978
+ /** Antall rader etter søk */
2979
+ totalFilteredRecords = computed(() => this.filteredValue().length, ...(ngDevMode ? [{ debugName: "totalFilteredRecords" }] : []));
2980
+ /** Antall rader totalt (før søk) */
2981
+ totalRecords = computed(() => this._value().length, ...(ngDevMode ? [{ debugName: "totalRecords" }] : []));
2982
+ /** Totalt antall sider */
2983
+ pageCount = computed(() => {
2984
+ if (!this.paginator)
2985
+ return 1;
2986
+ return Math.max(1, Math.ceil(this.totalFilteredRecords() / this._rows()));
2987
+ }, ...(ngDevMode ? [{ debugName: "pageCount" }] : []));
2988
+ /** Nåværende side (1-basert) */
2989
+ currentPage = computed(() => {
2990
+ return Math.floor(this._first() / this._rows()) + 1;
2991
+ }, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
2992
+ /** Er vi på første side? */
2993
+ isFirstPage = computed(() => this._first() === 0, ...(ngDevMode ? [{ debugName: "isFirstPage" }] : []));
2994
+ /** Er vi på siste side? */
2995
+ isLastPage = computed(() => {
2996
+ return this._first() + this._rows() >= this.totalFilteredRecords();
2997
+ }, ...(ngDevMode ? [{ debugName: "isLastPage" }] : []));
2998
+ /** Nåværende sorteringsfelt */
2999
+ currentSortField = computed(() => this._sortField(), ...(ngDevMode ? [{ debugName: "currentSortField" }] : []));
3000
+ /** Nåværende sorteringsretning som SortDirection */
3001
+ currentSortDirection = computed(() => {
3002
+ const order = this._sortOrder();
3003
+ if (order === 1)
3004
+ return 'ascending';
3005
+ if (order === -1)
3006
+ return 'descending';
3007
+ return 'none';
3008
+ }, ...(ngDevMode ? [{ debugName: "currentSortDirection" }] : []));
3009
+ /** Nåværende søkeverdi */
3010
+ currentGlobalFilter = computed(() => this._globalFilter(), ...(ngDevMode ? [{ debugName: "currentGlobalFilter" }] : []));
3011
+ /**
3012
+ * Global søk - søker på tvers av alle felt i globalFilterFields.
3013
+ * @param value Søkeverdi
3014
+ */
3015
+ filterGlobal(value) {
3016
+ this._globalFilter.set(value?.trim() || null);
3017
+ }
3018
+ /**
3019
+ * Nullstiller søk.
3020
+ */
3021
+ clearFilter() {
3022
+ this._globalFilter.set(null);
3023
+ }
3024
+ /**
3025
+ * Nullstiller hele tabellen (sortering og søk).
3026
+ */
3027
+ clear() {
3028
+ this._sortField.set(null);
3029
+ this._sortOrder.set(0);
3030
+ this._globalFilter.set(null);
3031
+ this._first.set(0);
3032
+ }
3033
+ // ========== Paginering ==========
3034
+ /**
3035
+ * Gå til en spesifikk side (1-basert).
3036
+ */
3037
+ goToPage(page) {
3038
+ const totalPages = this.pageCount();
3039
+ const validPage = Math.max(1, Math.min(page, totalPages));
3040
+ const newFirst = (validPage - 1) * this._rows();
3041
+ if (newFirst !== this._first()) {
3042
+ this._first.set(newFirst);
3043
+ this.emitPageEvent();
3044
+ }
3045
+ }
3046
+ /**
3047
+ * Gå til første side.
3048
+ */
3049
+ goToFirstPage() {
3050
+ this.goToPage(1);
3051
+ }
3052
+ /**
3053
+ * Gå til siste side.
3054
+ */
3055
+ goToLastPage() {
3056
+ this.goToPage(this.pageCount());
3057
+ }
3058
+ /**
3059
+ * Gå til forrige side.
3060
+ */
3061
+ goToPreviousPage() {
3062
+ if (!this.isFirstPage()) {
3063
+ this.goToPage(this.currentPage() - 1);
3064
+ }
3065
+ }
3066
+ /**
3067
+ * Gå til neste side.
3068
+ */
3069
+ goToNextPage() {
3070
+ if (!this.isLastPage()) {
3071
+ this.goToPage(this.currentPage() + 1);
3072
+ }
3073
+ }
3074
+ /**
3075
+ * Endre antall rader per side.
3076
+ */
3077
+ setRows(rows) {
3078
+ this._rows.set(rows);
3079
+ this._first.set(0); // Reset til første side
3080
+ this.emitPageEvent();
3081
+ }
3082
+ emitPageEvent() {
3083
+ this.pageChange.emit({
3084
+ first: this._first(),
3085
+ rows: this._rows(),
3086
+ page: this.currentPage(),
3087
+ pageCount: this.pageCount(),
3088
+ });
3089
+ this.currentPageChange.emit(this.currentPage());
3090
+ }
3091
+ /**
3092
+ * Sorterer tabellen etter et felt.
3093
+ * Kalles av hviSortableColumn directive.
3094
+ */
3095
+ sort(field) {
3096
+ const currentField = this._sortField();
3097
+ const currentOrder = this._sortOrder();
3098
+ let newOrder;
3099
+ if (currentField === field) {
3100
+ // Samme felt - sykl gjennom: asc → desc → none
3101
+ if (currentOrder === 1) {
3102
+ newOrder = -1;
3103
+ }
3104
+ else if (currentOrder === -1) {
3105
+ newOrder = 0;
3106
+ }
3107
+ else {
3108
+ newOrder = 1;
3109
+ }
3110
+ }
3111
+ else {
3112
+ // Nytt felt - start med ascending
3113
+ newOrder = 1;
3114
+ }
3115
+ this._sortField.set(newOrder === 0 ? null : field);
3116
+ this._sortOrder.set(newOrder);
3117
+ const direction = newOrder === 1 ? 'ascending' : newOrder === -1 ? 'descending' : 'none';
3118
+ this.sortChange.emit({ field, direction });
3119
+ }
3120
+ /**
3121
+ * Henter sorteringsretning for et spesifikt felt.
3122
+ * Brukes av hviSortableColumn for å vise riktig aria-sort.
3123
+ */
3124
+ getSortDirection(field) {
3125
+ if (this._sortField() !== field) {
3126
+ return 'none';
3127
+ }
3128
+ return this.currentSortDirection();
3129
+ }
3130
+ // ========== Private methods ==========
3131
+ applyGlobalFilter(data) {
3132
+ const globalFilter = this._globalFilter();
3133
+ const globalFields = this.globalFilterFields;
3134
+ if (!globalFilter || globalFields.length === 0 || data.length === 0) {
3135
+ return data;
3136
+ }
3137
+ const searchTerm = globalFilter.toLowerCase();
3138
+ return data.filter((item) => {
3139
+ return globalFields.some((field) => {
3140
+ const value = this.getFieldValue(item, field);
3141
+ return String(value ?? '')
3142
+ .toLowerCase()
3143
+ .includes(searchTerm);
3144
+ });
3145
+ });
3146
+ }
3147
+ applySorting(data) {
3148
+ const field = this._sortField();
3149
+ const order = this._sortOrder();
3150
+ if (!field || order === 0 || data.length === 0) {
3151
+ return data;
3152
+ }
3153
+ return [...data].sort((a, b) => {
3154
+ const valueA = this.getFieldValue(a, field);
3155
+ const valueB = this.getFieldValue(b, field);
3156
+ let comparison = 0;
3157
+ if (valueA == null && valueB == null) {
3158
+ comparison = 0;
3159
+ }
3160
+ else if (valueA == null) {
3161
+ comparison = -1;
3162
+ }
3163
+ else if (valueB == null) {
3164
+ comparison = 1;
3165
+ }
3166
+ else if (typeof valueA === 'string' && typeof valueB === 'string') {
3167
+ comparison = valueA.localeCompare(valueB, 'nb');
3168
+ }
3169
+ else if (typeof valueA === 'number' && typeof valueB === 'number') {
3170
+ comparison = valueA - valueB;
3171
+ }
3172
+ else {
3173
+ comparison = String(valueA).localeCompare(String(valueB), 'nb');
3174
+ }
3175
+ return order * comparison;
3176
+ });
3177
+ }
3178
+ /**
3179
+ * Henter verdi fra et objekt basert på felt-path (støtter nested: "user.name")
3180
+ */
3181
+ getFieldValue(obj, field) {
3182
+ const keys = field.split('.');
3183
+ let value = obj;
3184
+ for (const key of keys) {
3185
+ if (value == null)
3186
+ return null;
3187
+ value = value[key];
3188
+ }
3189
+ return value;
3190
+ }
3191
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: HviTable, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3192
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "21.1.2", type: HviTable, isStandalone: true, selector: "[hviTable]", inputs: { zebra: ["zebra", "zebra", booleanAttribute], border: ["border", "border", booleanAttribute], hover: ["hover", "hover", booleanAttribute], stickyHeader: ["stickyHeader", "stickyHeader", booleanAttribute], value: "value", sortField: "sortField", sortOrder: "sortOrder", globalFilterFields: "globalFilterFields", paginator: ["paginator", "paginator", booleanAttribute], rows: ["rows", "rows", numberAttribute], first: ["first", "first", numberAttribute] }, outputs: { sortChange: "sortChange", pageChange: "pageChange", currentPageChange: "currentPageChange" }, host: { properties: { "attr.data-zebra": "zebra || null", "attr.data-border": "border || null", "attr.data-hover": "hover || null", "attr.data-sticky-header": "stickyHeader || null" }, classAttribute: "ds-table" }, exportAs: ["hviTable"], ngImport: i0 });
3193
+ }
3194
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: HviTable, decorators: [{
3195
+ type: Directive,
3196
+ args: [{
3197
+ selector: '[hviTable]',
3198
+ standalone: true,
3199
+ exportAs: 'hviTable',
3200
+ host: {
3201
+ class: 'ds-table',
3202
+ '[attr.data-zebra]': 'zebra || null',
3203
+ '[attr.data-border]': 'border || null',
3204
+ '[attr.data-hover]': 'hover || null',
3205
+ '[attr.data-sticky-header]': 'stickyHeader || null',
3206
+ },
3207
+ }]
3208
+ }], propDecorators: { zebra: [{
3209
+ type: Input,
3210
+ args: [{ transform: booleanAttribute }]
3211
+ }], border: [{
3212
+ type: Input,
3213
+ args: [{ transform: booleanAttribute }]
3214
+ }], hover: [{
3215
+ type: Input,
3216
+ args: [{ transform: booleanAttribute }]
3217
+ }], stickyHeader: [{
3218
+ type: Input,
3219
+ args: [{ transform: booleanAttribute }]
3220
+ }], value: [{
3221
+ type: Input
3222
+ }], sortField: [{
3223
+ type: Input
3224
+ }], sortOrder: [{
3225
+ type: Input
3226
+ }], globalFilterFields: [{
3227
+ type: Input
3228
+ }], paginator: [{
3229
+ type: Input,
3230
+ args: [{ transform: booleanAttribute }]
3231
+ }], rows: [{
3232
+ type: Input,
3233
+ args: [{ transform: numberAttribute }]
3234
+ }], first: [{
3235
+ type: Input,
3236
+ args: [{ transform: numberAttribute }]
3237
+ }], sortChange: [{
3238
+ type: Output
3239
+ }], pageChange: [{
3240
+ type: Output
3241
+ }], currentPageChange: [{
3242
+ type: Output
3243
+ }] } });
3244
+
3245
+ /**
3246
+ * @summary
3247
+ * Directive for sorterbare tabell-header celler.
3248
+ * Kommuniserer automatisk med parent HviTable for å håndtere sortering.
3249
+ *
3250
+ * @example
3251
+ * ```html
3252
+ * <table hviTable [value]="persons" #table>
3253
+ * <thead>
3254
+ * <tr>
3255
+ * <th hviSortableColumn="navn">
3256
+ * <button>Navn</button>
3257
+ * </th>
3258
+ * <th>Epost</th>
3259
+ * </tr>
3260
+ * </thead>
3261
+ * <tbody>
3262
+ * @for (person of table.sortedValue(); track person.id) {
3263
+ * <tr>
3264
+ * <td>{{ person.navn }}</td>
3265
+ * <td>{{ person.epost }}</td>
3266
+ * </tr>
3267
+ * }
3268
+ * </tbody>
3269
+ * </table>
3270
+ * ```
3271
+ *
3272
+ * @see {@link https://designsystemet.no/no/components/docs/table/code}
3273
+ */
3274
+ class HviSortableColumn {
3275
+ table = inject(HviTable, { optional: true });
3276
+ /**
3277
+ * Feltet som denne kolonnen sorterer etter.
3278
+ * Må matche property-navn i data-objektene.
3279
+ */
3280
+ field;
3281
+ /**
3282
+ * Henter sorteringsretning fra parent table.
3283
+ */
3284
+ get sortDirection() {
3285
+ return this.table?.getSortDirection(this.field) ?? 'none';
3286
+ }
3287
+ onClick(event) {
3288
+ const target = event.target;
3289
+ // Bare håndter klikk på button inne i th
3290
+ if (target.tagName === 'BUTTON' || target.closest('button')) {
3291
+ this.table?.sort(this.field);
3292
+ }
3293
+ }
3294
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: HviSortableColumn, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3295
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.2", type: HviSortableColumn, isStandalone: true, selector: "th[hviSortableColumn]", inputs: { field: ["hviSortableColumn", "field"] }, host: { listeners: { "click": "onClick($event)" }, properties: { "attr.aria-sort": "sortDirection" } }, ngImport: i0 });
3296
+ }
3297
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: HviSortableColumn, decorators: [{
3298
+ type: Directive,
3299
+ args: [{
3300
+ selector: 'th[hviSortableColumn]',
3301
+ standalone: true,
3302
+ host: {
3303
+ '[attr.aria-sort]': 'sortDirection',
3304
+ },
3305
+ }]
3306
+ }], propDecorators: { field: [{
3307
+ type: Input,
3308
+ args: [{ required: true, alias: 'hviSortableColumn' }]
3309
+ }], onClick: [{
3310
+ type: HostListener,
3311
+ args: ['click', ['$event']]
3312
+ }] } });
3313
+
2611
3314
  /**
2612
3315
  * TabPanel inneholder innholdet som vises når en tilhørende Tab er aktiv.
2613
3316
  *
@@ -3386,5 +4089,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
3386
4089
  * Generated bundle index. Do not edit.
3387
4090
  */
3388
4091
 
3389
- export { HviAlert, HviAvatar, HviBadge, HviBadgePosition, HviBreadcrumbs, HviButton, HviCard, HviCardBlock, HviChipButton, HviChipLabel, HviControlInvalid, HviDetails, HviDetailsContent, HviDetailsSummary, HviDialog, HviDialogBlock, HviDivider, HviErrorSummary, HviField, HviFieldAffix, HviFieldAffixes, HviFieldCounter, HviFieldDescription, HviFieldKit, HviFieldOptional, HviFieldValidation, HviFieldset, HviForm, HviForms, HviHeading, HviIcon, HviInput, HviLabel, HviLink, HviList, HviParagraph, HviPopover, HviSearch, HviSearchClear, HviSelect, HviSkeleton, HviSkipLink, HviSpinner, HviTab, HviTabPanel, HviTabs, HviTag, HviToggleGroup, HviToggleGroupItem, HviToggleGroupItemToken, HviTooltip, HviValidationKit, HviValidationMessage, fieldObserver, hviCustom, hviEmail, hviExtractMessages, hviExtractValidators, hviMax, hviMaxLength, hviMin, hviMinLength, hviNullValidator, hviPattern, hviRequired, hviRequiredTrue, hviValidators, isElement, isInputLike, isLabel };
4092
+ export { HviAlert, HviAvatar, HviBadge, HviBadgePosition, HviBreadcrumbs, HviButton, HviCard, HviCardBlock, HviChipButton, HviChipLabel, HviControlInvalid, HviDetails, HviDetailsContent, HviDetailsSummary, HviDialog, HviDialogBlock, HviDivider, HviErrorSummary, HviField, HviFieldAffix, HviFieldAffixes, HviFieldCounter, HviFieldDescription, HviFieldKit, HviFieldOptional, HviFieldValidation, HviFieldset, HviForm, HviForms, HviHeading, HviIcon, HviInput, HviLabel, HviLink, HviList, HviPagination, HviParagraph, HviPopover, HviSearch, HviSearchClear, HviSelect, HviSkeleton, HviSkipLink, HviSortableColumn, HviSpinner, HviTab, HviTabPanel, HviTable, HviTabs, HviTag, HviToggleGroup, HviToggleGroupItem, HviToggleGroupItemToken, HviTooltip, HviValidationKit, HviValidationMessage, fieldObserver, hviCustom, hviEmail, hviExtractMessages, hviExtractValidators, hviMax, hviMaxLength, hviMin, hviMinLength, hviNullValidator, hviPattern, hviRequired, hviRequiredTrue, hviValidators, isElement, isInputLike, isLabel };
3390
4093
  //# sourceMappingURL=helsevestikt-hviktor-angular.mjs.map