@idds/js 1.0.84 → 1.0.86

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.
@@ -41,6 +41,7 @@ var InaUI = (() => {
41
41
  initTab: () => initTab,
42
42
  initTabHorizontal: () => initTabHorizontal,
43
43
  initTabVertical: () => initTabVertical,
44
+ initTable: () => initTable,
44
45
  initTimepicker: () => initTimepicker,
45
46
  initToggle: () => initToggle,
46
47
  setBrandTheme: () => setBrandTheme,
@@ -2902,6 +2903,575 @@ var InaUI = (() => {
2902
2903
  });
2903
2904
  }
2904
2905
 
2906
+ // src/js/components/stateful/table.js
2907
+ var Table = class {
2908
+ constructor(selectorOrElement, options = {}) {
2909
+ this.container = typeof selectorOrElement === "string" ? document.querySelector(selectorOrElement) : selectorOrElement;
2910
+ if (!this.container) {
2911
+ console.warn("[IDDS Table] Container not found:", selectorOrElement);
2912
+ return;
2913
+ }
2914
+ if (this.container.dataset.initialized === "true") {
2915
+ return;
2916
+ }
2917
+ this.container.dataset.initialized = "true";
2918
+ this.options = {
2919
+ columns: [],
2920
+ fetchData: null,
2921
+ data: [],
2922
+ initialPage: 1,
2923
+ initialPageSize: 10,
2924
+ pageSizeOptions: [10, 20, 50],
2925
+ initialSortField: null,
2926
+ initialSortOrder: null,
2927
+ searchPlaceholder: "Input pencarian",
2928
+ buttonSearchLabel: "Cari",
2929
+ selectable: false,
2930
+ onSelectionChange: null,
2931
+ rowKey: "id",
2932
+ showSearch: false,
2933
+ rowClickable: false,
2934
+ onRowClick: null,
2935
+ emptyState: "No data found",
2936
+ showPagination: true,
2937
+ searchContainer: null,
2938
+ searchButton: null,
2939
+ onSearch: null,
2940
+ ...options
2941
+ };
2942
+ this.state = {
2943
+ currentPage: this.options.initialPage,
2944
+ pageSize: this.options.initialPageSize,
2945
+ totalPages: 1,
2946
+ total: this.options.data.length,
2947
+ sortField: this.options.initialSortField,
2948
+ sortOrder: this.options.initialSortOrder,
2949
+ searchTerm: "",
2950
+ loading: false,
2951
+ currentData: [...this.options.data],
2952
+ selectedKeys: /* @__PURE__ */ new Set(),
2953
+ selectedRows: /* @__PURE__ */ new Map()
2954
+ };
2955
+ this.elements = {};
2956
+ this.initDOM();
2957
+ this.bindEvents();
2958
+ this.loadData();
2959
+ }
2960
+ initDOM() {
2961
+ this.container.innerHTML = "";
2962
+ this.container.classList.add(`${PREFIX}-table-wrapper`);
2963
+ if (this.options.showSearch && !this.options.searchContainer) {
2964
+ const searchWrap = document.createElement("div");
2965
+ searchWrap.className = "flex items-center gap-2 mb-4";
2966
+ searchWrap.style.display = "flex";
2967
+ searchWrap.style.alignItems = "center";
2968
+ searchWrap.style.gap = "8px";
2969
+ searchWrap.style.marginBottom = "16px";
2970
+ searchWrap.innerHTML = `
2971
+ <div style="flex: 1;">
2972
+ <div class="${PREFIX}-text-field">
2973
+ <div class="${PREFIX}-text-field__wrapper ${PREFIX}-text-field__wrapper--size-md">
2974
+ <input type="text" class="${PREFIX}-text-field__input" placeholder="${this.options.searchPlaceholder}">
2975
+ </div>
2976
+ </div>
2977
+ </div>
2978
+ <button type="button" class="${PREFIX}-button ${PREFIX}-button--primary ${PREFIX}-button--md">
2979
+ ${this.options.buttonSearchLabel}
2980
+ </button>
2981
+ `;
2982
+ this.container.appendChild(searchWrap);
2983
+ this.elements.searchInput = searchWrap.querySelector("input");
2984
+ this.elements.searchButton = searchWrap.querySelector("button");
2985
+ } else {
2986
+ this.elements.searchInput = typeof this.options.searchContainer === "string" ? document.querySelector(this.options.searchContainer) : this.options.searchContainer;
2987
+ this.elements.searchButton = typeof this.options.searchButton === "string" ? document.querySelector(this.options.searchButton) : this.options.searchButton;
2988
+ }
2989
+ const tableDiv = document.createElement("div");
2990
+ tableDiv.className = `${PREFIX}-table`;
2991
+ const progressDiv = document.createElement("div");
2992
+ progressDiv.className = `${PREFIX}-table__progress-bar`;
2993
+ progressDiv.style.display = "none";
2994
+ progressDiv.innerHTML = `
2995
+ <div class="${PREFIX}-progress-bar ${PREFIX}-progress-bar--variant-primary ${PREFIX}-progress-bar--height-sm">
2996
+ <div class="${PREFIX}-progress-bar__track">
2997
+ <div class="${PREFIX}-progress-bar__fill" style="width: 0%;"></div>
2998
+ </div>
2999
+ </div>
3000
+ `;
3001
+ tableDiv.appendChild(progressDiv);
3002
+ const tableEl = document.createElement("table");
3003
+ tableEl.className = `${PREFIX}-table__container`;
3004
+ const theadEl = document.createElement("thead");
3005
+ theadEl.className = `${PREFIX}-table__header`;
3006
+ const trHeader = document.createElement("tr");
3007
+ theadEl.appendChild(trHeader);
3008
+ tableEl.appendChild(theadEl);
3009
+ const tbodyEl = document.createElement("tbody");
3010
+ tbodyEl.className = `${PREFIX}-table__body`;
3011
+ tableEl.appendChild(tbodyEl);
3012
+ tableDiv.appendChild(tableEl);
3013
+ const paginationEl = document.createElement("div");
3014
+ paginationEl.className = `${PREFIX}-table__pagination`;
3015
+ if (!this.options.showPagination) {
3016
+ paginationEl.style.display = "none";
3017
+ }
3018
+ tableDiv.appendChild(paginationEl);
3019
+ this.container.appendChild(tableDiv);
3020
+ this.elements.progressEl = progressDiv;
3021
+ this.elements.progressFillEl = progressDiv.querySelector(
3022
+ `.${PREFIX}-progress-bar__fill`
3023
+ );
3024
+ this.elements.theadTr = trHeader;
3025
+ this.elements.tbody = tbodyEl;
3026
+ this.elements.pagination = paginationEl;
3027
+ this.buildHeader();
3028
+ }
3029
+ buildHeader() {
3030
+ this.elements.theadTr.innerHTML = "";
3031
+ if (this.options.selectable) {
3032
+ const th = document.createElement("th");
3033
+ th.className = `${PREFIX}-table__header-cell`;
3034
+ th.style.width = "48px";
3035
+ const checkboxWrap = document.createElement("div");
3036
+ checkboxWrap.className = `${PREFIX}-checkbox`;
3037
+ checkboxWrap.innerHTML = `
3038
+ <label class="${PREFIX}-checkbox__label-wrapper">
3039
+ <input type="checkbox" class="${PREFIX}-checkbox__input" id="selectAll">
3040
+ <div class="${PREFIX}-checkbox__box ${PREFIX}-checkbox__box--unchecked"></div>
3041
+ </label>
3042
+ `;
3043
+ th.appendChild(checkboxWrap);
3044
+ this.elements.theadTr.appendChild(th);
3045
+ this.elements.selectAllInput = checkboxWrap.querySelector("input");
3046
+ this.elements.selectAllBox = checkboxWrap.querySelector(
3047
+ `.${PREFIX}-checkbox__box`
3048
+ );
3049
+ this.elements.selectAllInput.addEventListener("change", (e) => {
3050
+ this.toggleAllSelection(e.target.checked);
3051
+ });
3052
+ }
3053
+ this.options.columns.forEach((col) => {
3054
+ const th = document.createElement("th");
3055
+ th.className = `${PREFIX}-table__header-cell`;
3056
+ if (col.sortable && col.accessor) {
3057
+ th.classList.add(`${PREFIX}-table__header-cell--sortable`);
3058
+ th.setAttribute("data-sort", col.accessor);
3059
+ let sortControlsHtml = `
3060
+ <div class="${PREFIX}-table__sort-controls">
3061
+ ${col.header}
3062
+ <div class="${PREFIX}-table__sort-icon">
3063
+ <div class="${PREFIX}-table__sort-button" data-order="asc">
3064
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9L12 15L18 9"></path></svg>
3065
+ </div>
3066
+ <div class="${PREFIX}-table__sort-button ${PREFIX}-table__sort-button-right" data-order="desc">
3067
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 15L12 9L6 15"></path></svg>
3068
+ </div>
3069
+ </div>
3070
+ </div>
3071
+ `;
3072
+ th.innerHTML = sortControlsHtml;
3073
+ th.addEventListener("click", () => {
3074
+ const field = col.accessor;
3075
+ if (this.state.sortField === field) {
3076
+ if (this.state.sortOrder === "asc") this.state.sortOrder = "desc";
3077
+ else {
3078
+ this.state.sortField = null;
3079
+ this.state.sortOrder = null;
3080
+ }
3081
+ } else {
3082
+ this.state.sortField = field;
3083
+ this.state.sortOrder = "asc";
3084
+ }
3085
+ this.state.currentPage = 1;
3086
+ this.loadData();
3087
+ });
3088
+ } else {
3089
+ th.textContent = col.header;
3090
+ }
3091
+ this.elements.theadTr.appendChild(th);
3092
+ });
3093
+ }
3094
+ bindEvents() {
3095
+ if (this.elements.searchInput && this.elements.searchButton) {
3096
+ const handleSearch = () => {
3097
+ this.state.searchTerm = this.elements.searchInput.value.trim();
3098
+ this.state.currentPage = 1;
3099
+ this.loadData();
3100
+ if (typeof this.options.onSearch === "function") {
3101
+ this.options.onSearch(this.state.searchTerm);
3102
+ }
3103
+ };
3104
+ this.elements.searchButton.addEventListener("click", handleSearch);
3105
+ this.elements.searchInput.addEventListener("keydown", (e) => {
3106
+ if (e.key === "Enter") handleSearch();
3107
+ });
3108
+ }
3109
+ }
3110
+ setLoading(isLoading) {
3111
+ this.state.loading = isLoading;
3112
+ if (!this.elements.progressEl || !this.elements.progressFillEl) return;
3113
+ if (isLoading) {
3114
+ this.elements.progressEl.style.display = "block";
3115
+ let progress = 0;
3116
+ clearInterval(this.loadingInterval);
3117
+ this.loadingInterval = setInterval(() => {
3118
+ if (progress >= 90 || !this.state.loading) {
3119
+ clearInterval(this.loadingInterval);
3120
+ if (!this.state.loading) {
3121
+ progress = 100;
3122
+ this.elements.progressFillEl.style.width = "100%";
3123
+ setTimeout(() => {
3124
+ this.elements.progressEl.style.display = "none";
3125
+ this.elements.progressFillEl.style.width = "0%";
3126
+ }, 300);
3127
+ }
3128
+ return;
3129
+ }
3130
+ progress += Math.random() * 15;
3131
+ this.elements.progressFillEl.style.width = progress + "%";
3132
+ }, 200);
3133
+ }
3134
+ }
3135
+ renderRows(rowData) {
3136
+ this.elements.tbody.innerHTML = "";
3137
+ if (rowData.length === 0) {
3138
+ const tr = document.createElement("tr");
3139
+ const colspan = this.options.columns.length + (this.options.selectable ? 1 : 0);
3140
+ tr.innerHTML = `<td colspan="${colspan}" class="${PREFIX}-table__empty-cell">${this.options.emptyState}</td>`;
3141
+ this.elements.tbody.appendChild(tr);
3142
+ this.updateSelectAllState();
3143
+ return;
3144
+ }
3145
+ rowData.forEach((row, index) => {
3146
+ const tr = document.createElement("tr");
3147
+ tr.className = `${PREFIX}-table__row`;
3148
+ if (this.options.rowClickable) {
3149
+ tr.style.cursor = "pointer";
3150
+ tr.classList.add(`${PREFIX}-table__row--clickable`);
3151
+ tr.addEventListener("click", (e) => {
3152
+ if (e.target.closest(`.${PREFIX}-checkbox`) && this.options.selectable) {
3153
+ return;
3154
+ }
3155
+ if (typeof this.options.onRowClick === "function") {
3156
+ this.options.onRowClick(row, index);
3157
+ }
3158
+ });
3159
+ }
3160
+ const rowKeyStr = String(row[this.options.rowKey] || index);
3161
+ if (this.options.selectable) {
3162
+ const td = document.createElement("td");
3163
+ td.className = `${PREFIX}-table__cell`;
3164
+ const isChecked = this.state.selectedKeys.has(rowKeyStr);
3165
+ const checkboxWrap = document.createElement("div");
3166
+ checkboxWrap.className = `${PREFIX}-checkbox`;
3167
+ checkboxWrap.innerHTML = `
3168
+ <label class="${PREFIX}-checkbox__label-wrapper">
3169
+ <input type="checkbox" class="${PREFIX}-checkbox__input" ${isChecked ? "checked" : ""}>
3170
+ <div class="${PREFIX}-checkbox__box ${isChecked ? `${PREFIX}-checkbox__box--checked` : `${PREFIX}-checkbox__box--unchecked`}">
3171
+ ${isChecked ? '<svg class="ina-checkbox__icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5l10 -10"></path></svg>' : ""}
3172
+ </div>
3173
+ </label>
3174
+ `;
3175
+ td.appendChild(checkboxWrap);
3176
+ tr.appendChild(td);
3177
+ const rowInput = checkboxWrap.querySelector("input");
3178
+ rowInput.addEventListener("change", (e) => {
3179
+ this.toggleRowSelection(rowKeyStr, row, e.target.checked);
3180
+ });
3181
+ }
3182
+ this.options.columns.forEach((col) => {
3183
+ const td = document.createElement("td");
3184
+ td.className = `${PREFIX}-table__cell`;
3185
+ if (typeof col.render === "function") {
3186
+ const content = col.render(row, index);
3187
+ if (typeof content === "string") {
3188
+ td.innerHTML = content;
3189
+ } else if (content instanceof HTMLElement) {
3190
+ td.appendChild(content);
3191
+ } else {
3192
+ td.textContent = content;
3193
+ }
3194
+ } else {
3195
+ td.textContent = row[col.accessor] !== void 0 && row[col.accessor] !== null ? row[col.accessor] : "";
3196
+ }
3197
+ tr.appendChild(td);
3198
+ });
3199
+ this.elements.tbody.appendChild(tr);
3200
+ });
3201
+ this.updateSelectAllState();
3202
+ }
3203
+ renderPagination() {
3204
+ if (!this.elements.pagination || !this.options.showPagination) return;
3205
+ this.state.totalPages = Math.ceil(this.state.total / this.state.pageSize);
3206
+ if (this.state.totalPages < 1) this.state.totalPages = 1;
3207
+ this.elements.pagination.innerHTML = `
3208
+ <div class="${PREFIX}-pagination">
3209
+ <div class="${PREFIX}-pagination__nav-container">
3210
+ <div class="${PREFIX}-pagination__page-info">Halaman ${this.state.currentPage} dari ${this.state.totalPages}</div>
3211
+ <div class="${PREFIX}-pagination__nav-buttons"></div>
3212
+ </div>
3213
+ <div class="${PREFIX}-pagination__page-size-container">
3214
+ <span class="${PREFIX}-pagination__page-size-label">Baris per halaman</span>
3215
+ <select class="${PREFIX}-pagination__page-size-select">
3216
+ ${this.options.pageSizeOptions.map(
3217
+ (opt) => `<option value="${opt}" ${this.state.pageSize === opt ? "selected" : ""}>${opt}</option>`
3218
+ ).join("")}
3219
+ </select>
3220
+ </div>
3221
+ </div>
3222
+ `;
3223
+ const navButtons = this.elements.pagination.querySelector(
3224
+ `.${PREFIX}-pagination__nav-buttons`
3225
+ );
3226
+ const pageSizeSelect = this.elements.pagination.querySelector(
3227
+ `.${PREFIX}-pagination__page-size-select`
3228
+ );
3229
+ pageSizeSelect.addEventListener("change", (e) => {
3230
+ this.state.pageSize = parseInt(e.target.value, 10);
3231
+ this.state.currentPage = 1;
3232
+ this.loadData();
3233
+ });
3234
+ const createBtn = (isNav, enabled, content, onClick) => {
3235
+ const btn = document.createElement("button");
3236
+ btn.type = "button";
3237
+ const baseCls = isNav ? `${PREFIX}-pagination__nav-button` : `${PREFIX}-pagination__page-button`;
3238
+ const stCls = isNav ? enabled ? `${baseCls}--enabled` : `${baseCls}--disabled` : enabled ? `${baseCls}--enabled` : `${baseCls}--active`;
3239
+ btn.className = `${baseCls} ${stCls}`;
3240
+ btn.disabled = !enabled;
3241
+ btn.innerHTML = content;
3242
+ btn.onclick = onClick;
3243
+ navButtons.appendChild(btn);
3244
+ };
3245
+ const SVG = {
3246
+ first: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ina-pagination__nav-icon"><path d="M11 7l-5 5l5 5"></path><path d="M17 7l-5 5l5 5"></path></svg>',
3247
+ prev: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ina-pagination__nav-icon"><path d="M15 6l-6 6l6 6"></path></svg>',
3248
+ next: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ina-pagination__nav-icon"><path d="M9 6l6 6l-6 6"></path></svg>',
3249
+ last: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ina-pagination__nav-icon"><path d="M7 7l5 5l-5 5"></path><path d="M13 7l5 5l-5 5"></path></svg>'
3250
+ };
3251
+ createBtn(true, this.state.currentPage > 1, SVG.first, () => {
3252
+ this.state.currentPage = 1;
3253
+ this.loadData();
3254
+ });
3255
+ createBtn(true, this.state.currentPage > 1, SVG.prev, () => {
3256
+ this.state.currentPage--;
3257
+ this.loadData();
3258
+ });
3259
+ const maxVisible = 5;
3260
+ let startPage = Math.max(
3261
+ 1,
3262
+ this.state.currentPage - Math.floor(maxVisible / 2)
3263
+ );
3264
+ let endPage = Math.min(this.state.totalPages, startPage + maxVisible - 1);
3265
+ if (endPage - startPage < maxVisible - 1) {
3266
+ startPage = Math.max(1, endPage - maxVisible + 1);
3267
+ }
3268
+ for (let i = startPage; i <= endPage; i++) {
3269
+ createBtn(false, i !== this.state.currentPage, String(i), () => {
3270
+ this.state.currentPage = i;
3271
+ this.loadData();
3272
+ });
3273
+ }
3274
+ createBtn(
3275
+ true,
3276
+ this.state.currentPage < this.state.totalPages,
3277
+ SVG.next,
3278
+ () => {
3279
+ this.state.currentPage++;
3280
+ this.loadData();
3281
+ }
3282
+ );
3283
+ createBtn(
3284
+ true,
3285
+ this.state.currentPage < this.state.totalPages,
3286
+ SVG.last,
3287
+ () => {
3288
+ this.state.currentPage = this.state.totalPages;
3289
+ this.loadData();
3290
+ }
3291
+ );
3292
+ }
3293
+ updateSortIndicators() {
3294
+ const btns = this.elements.theadTr.querySelectorAll(
3295
+ `.${PREFIX}-table__sort-button`
3296
+ );
3297
+ btns.forEach((btn) => {
3298
+ const th = btn.closest("th");
3299
+ if (!th) return;
3300
+ const field = th.getAttribute("data-sort");
3301
+ const order = btn.getAttribute("data-order");
3302
+ const active = this.state.sortField === field && this.state.sortOrder === order;
3303
+ btn.classList.toggle(`${PREFIX}-table__sort-button--active`, active);
3304
+ });
3305
+ }
3306
+ async loadData() {
3307
+ this.setLoading(true);
3308
+ try {
3309
+ if (typeof this.options.fetchData === "function") {
3310
+ const res = await this.options.fetchData({
3311
+ page: this.state.currentPage,
3312
+ pageSize: this.state.pageSize,
3313
+ sortField: this.state.sortField,
3314
+ sortOrder: this.state.sortOrder,
3315
+ searchTerm: this.state.searchTerm
3316
+ });
3317
+ this.state.total = res.total;
3318
+ this.state.currentData = res.data || [];
3319
+ } else {
3320
+ let filtered = [...this.options.data];
3321
+ if (this.state.searchTerm) {
3322
+ const lower = this.state.searchTerm.toLowerCase();
3323
+ filtered = filtered.filter(
3324
+ (row) => this.options.columns.some(
3325
+ (c) => String(row[c.accessor] || "").toLowerCase().includes(lower)
3326
+ )
3327
+ );
3328
+ }
3329
+ if (this.state.sortField && this.state.sortOrder) {
3330
+ filtered.sort((a, b) => {
3331
+ const aVal = a[this.state.sortField];
3332
+ const bVal = b[this.state.sortField];
3333
+ if (aVal < bVal) return this.state.sortOrder === "asc" ? -1 : 1;
3334
+ if (aVal > bVal) return this.state.sortOrder === "asc" ? 1 : -1;
3335
+ return 0;
3336
+ });
3337
+ }
3338
+ this.state.total = filtered.length;
3339
+ const skip = (this.state.currentPage - 1) * this.state.pageSize;
3340
+ this.state.currentData = filtered.slice(
3341
+ skip,
3342
+ skip + this.state.pageSize
3343
+ );
3344
+ }
3345
+ this.renderRows(this.state.currentData);
3346
+ this.renderPagination();
3347
+ this.updateSortIndicators();
3348
+ } catch (e) {
3349
+ console.error("Table API Error:", e);
3350
+ } finally {
3351
+ this.setLoading(false);
3352
+ }
3353
+ }
3354
+ toggleRowSelection(keyStr, row, checked) {
3355
+ if (checked) {
3356
+ this.state.selectedKeys.add(keyStr);
3357
+ this.state.selectedRows.set(keyStr, row);
3358
+ } else {
3359
+ this.state.selectedKeys.delete(keyStr);
3360
+ this.state.selectedRows.delete(keyStr);
3361
+ }
3362
+ const inputs = this.elements.tbody.querySelectorAll(
3363
+ `.${PREFIX}-checkbox__input`
3364
+ );
3365
+ inputs.forEach((input) => {
3366
+ const box = input.nextElementSibling;
3367
+ if (input.checked) {
3368
+ box.classList.remove(`${PREFIX}-checkbox__box--unchecked`);
3369
+ box.classList.add(`${PREFIX}-checkbox__box--checked`);
3370
+ box.innerHTML = '<svg class="ina-checkbox__icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5l10 -10"></path></svg>';
3371
+ } else {
3372
+ box.classList.remove(`${PREFIX}-checkbox__box--checked`);
3373
+ box.classList.add(`${PREFIX}-checkbox__box--unchecked`);
3374
+ box.innerHTML = "";
3375
+ }
3376
+ });
3377
+ this.updateSelectAllState();
3378
+ this.triggerSelectionChange();
3379
+ }
3380
+ toggleAllSelection(checked) {
3381
+ this.state.currentData.forEach((row, index) => {
3382
+ const keyStr = String(row[this.options.rowKey] || index);
3383
+ if (checked) {
3384
+ this.state.selectedKeys.add(keyStr);
3385
+ this.state.selectedRows.set(keyStr, row);
3386
+ } else {
3387
+ this.state.selectedKeys.delete(keyStr);
3388
+ this.state.selectedRows.delete(keyStr);
3389
+ }
3390
+ });
3391
+ this.renderRows(this.state.currentData);
3392
+ this.triggerSelectionChange();
3393
+ }
3394
+ updateSelectAllState() {
3395
+ if (!this.options.selectable || !this.elements.selectAllInput) return;
3396
+ const checkboxes = this.elements.tbody.querySelectorAll(
3397
+ 'input[type="checkbox"]'
3398
+ );
3399
+ const checkedCount = Array.from(checkboxes).filter(
3400
+ (cb) => cb.checked
3401
+ ).length;
3402
+ const totalCount = this.state.currentData.length;
3403
+ const box = this.elements.selectAllBox;
3404
+ box.innerHTML = "";
3405
+ if (totalCount === 0 || checkedCount === 0) {
3406
+ this.elements.selectAllInput.checked = false;
3407
+ this.elements.selectAllInput.indeterminate = false;
3408
+ box.classList.remove(
3409
+ `${PREFIX}-checkbox__box--checked`,
3410
+ `${PREFIX}-checkbox__box--indeterminate`
3411
+ );
3412
+ box.classList.add(`${PREFIX}-checkbox__box--unchecked`);
3413
+ } else if (checkedCount === totalCount) {
3414
+ this.elements.selectAllInput.checked = true;
3415
+ this.elements.selectAllInput.indeterminate = false;
3416
+ box.classList.remove(
3417
+ `${PREFIX}-checkbox__box--unchecked`,
3418
+ `${PREFIX}-checkbox__box--indeterminate`
3419
+ );
3420
+ box.classList.add(`${PREFIX}-checkbox__box--checked`);
3421
+ box.innerHTML = '<svg class="ina-checkbox__icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5l10 -10"></path></svg>';
3422
+ } else {
3423
+ this.elements.selectAllInput.checked = false;
3424
+ this.elements.selectAllInput.indeterminate = true;
3425
+ box.classList.remove(
3426
+ `${PREFIX}-checkbox__box--unchecked`,
3427
+ `${PREFIX}-checkbox__box--checked`
3428
+ );
3429
+ box.classList.add(`${PREFIX}-checkbox__box--indeterminate`);
3430
+ box.innerHTML = '<svg class="ina-checkbox__icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"></path></svg>';
3431
+ }
3432
+ }
3433
+ triggerSelectionChange() {
3434
+ if (typeof this.options.onSelectionChange === "function") {
3435
+ const keys = Array.from(this.state.selectedKeys);
3436
+ const rows = Array.from(this.state.selectedRows.values());
3437
+ this.options.onSelectionChange(keys, rows);
3438
+ }
3439
+ }
3440
+ // API Methods
3441
+ reload() {
3442
+ this.loadData();
3443
+ }
3444
+ setSearchTerm(term) {
3445
+ this.state.searchTerm = term;
3446
+ this.state.currentPage = 1;
3447
+ if (this.elements.searchInput) {
3448
+ this.elements.searchInput.value = term;
3449
+ }
3450
+ this.loadData();
3451
+ }
3452
+ setPage(page) {
3453
+ this.state.currentPage = page;
3454
+ this.loadData();
3455
+ }
3456
+ getSelectedRows() {
3457
+ return {
3458
+ keys: Array.from(this.state.selectedKeys),
3459
+ rows: Array.from(this.state.selectedRows.values())
3460
+ };
3461
+ }
3462
+ };
3463
+ function initTable(selectorOrElement, options = {}) {
3464
+ const elements = typeof selectorOrElement === "string" ? document.querySelectorAll(selectorOrElement) : selectorOrElement ? typeof selectorOrElement.length !== "undefined" ? selectorOrElement : [selectorOrElement] : document.querySelectorAll(`.${PREFIX}-table`);
3465
+ const instances = [];
3466
+ elements.forEach((container) => {
3467
+ const instance = new Table(container, options);
3468
+ container.__tableAPI = instance;
3469
+ instances.push(instance);
3470
+ });
3471
+ if (instances.length === 0) return null;
3472
+ return instances.length === 1 ? instances[0] : instances;
3473
+ }
3474
+
2905
3475
  // src/js/components/stateless/toast.js
2906
3476
  var ICONS3 = {
2907
3477
  default: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>`,
@@ -3231,6 +3801,7 @@ var InaUI = (() => {
3231
3801
  initChip();
3232
3802
  initTabVertical();
3233
3803
  initTabHorizontal();
3804
+ initTable();
3234
3805
  });
3235
3806
  }
3236
3807
  return __toCommonJS(bundle_exports);
package/dist/index.js CHANGED
@@ -2988,6 +2988,575 @@ function initTabHorizontal() {
2988
2988
  });
2989
2989
  }
2990
2990
 
2991
+ // src/js/components/stateful/table.js
2992
+ var Table = class {
2993
+ constructor(selectorOrElement, options = {}) {
2994
+ this.container = typeof selectorOrElement === "string" ? document.querySelector(selectorOrElement) : selectorOrElement;
2995
+ if (!this.container) {
2996
+ console.warn("[IDDS Table] Container not found:", selectorOrElement);
2997
+ return;
2998
+ }
2999
+ if (this.container.dataset.initialized === "true") {
3000
+ return;
3001
+ }
3002
+ this.container.dataset.initialized = "true";
3003
+ this.options = {
3004
+ columns: [],
3005
+ fetchData: null,
3006
+ data: [],
3007
+ initialPage: 1,
3008
+ initialPageSize: 10,
3009
+ pageSizeOptions: [10, 20, 50],
3010
+ initialSortField: null,
3011
+ initialSortOrder: null,
3012
+ searchPlaceholder: "Input pencarian",
3013
+ buttonSearchLabel: "Cari",
3014
+ selectable: false,
3015
+ onSelectionChange: null,
3016
+ rowKey: "id",
3017
+ showSearch: false,
3018
+ rowClickable: false,
3019
+ onRowClick: null,
3020
+ emptyState: "No data found",
3021
+ showPagination: true,
3022
+ searchContainer: null,
3023
+ searchButton: null,
3024
+ onSearch: null,
3025
+ ...options
3026
+ };
3027
+ this.state = {
3028
+ currentPage: this.options.initialPage,
3029
+ pageSize: this.options.initialPageSize,
3030
+ totalPages: 1,
3031
+ total: this.options.data.length,
3032
+ sortField: this.options.initialSortField,
3033
+ sortOrder: this.options.initialSortOrder,
3034
+ searchTerm: "",
3035
+ loading: false,
3036
+ currentData: [...this.options.data],
3037
+ selectedKeys: /* @__PURE__ */ new Set(),
3038
+ selectedRows: /* @__PURE__ */ new Map()
3039
+ };
3040
+ this.elements = {};
3041
+ this.initDOM();
3042
+ this.bindEvents();
3043
+ this.loadData();
3044
+ }
3045
+ initDOM() {
3046
+ this.container.innerHTML = "";
3047
+ this.container.classList.add(`${PREFIX}-table-wrapper`);
3048
+ if (this.options.showSearch && !this.options.searchContainer) {
3049
+ const searchWrap = document.createElement("div");
3050
+ searchWrap.className = "flex items-center gap-2 mb-4";
3051
+ searchWrap.style.display = "flex";
3052
+ searchWrap.style.alignItems = "center";
3053
+ searchWrap.style.gap = "8px";
3054
+ searchWrap.style.marginBottom = "16px";
3055
+ searchWrap.innerHTML = `
3056
+ <div style="flex: 1;">
3057
+ <div class="${PREFIX}-text-field">
3058
+ <div class="${PREFIX}-text-field__wrapper ${PREFIX}-text-field__wrapper--size-md">
3059
+ <input type="text" class="${PREFIX}-text-field__input" placeholder="${this.options.searchPlaceholder}">
3060
+ </div>
3061
+ </div>
3062
+ </div>
3063
+ <button type="button" class="${PREFIX}-button ${PREFIX}-button--primary ${PREFIX}-button--md">
3064
+ ${this.options.buttonSearchLabel}
3065
+ </button>
3066
+ `;
3067
+ this.container.appendChild(searchWrap);
3068
+ this.elements.searchInput = searchWrap.querySelector("input");
3069
+ this.elements.searchButton = searchWrap.querySelector("button");
3070
+ } else {
3071
+ this.elements.searchInput = typeof this.options.searchContainer === "string" ? document.querySelector(this.options.searchContainer) : this.options.searchContainer;
3072
+ this.elements.searchButton = typeof this.options.searchButton === "string" ? document.querySelector(this.options.searchButton) : this.options.searchButton;
3073
+ }
3074
+ const tableDiv = document.createElement("div");
3075
+ tableDiv.className = `${PREFIX}-table`;
3076
+ const progressDiv = document.createElement("div");
3077
+ progressDiv.className = `${PREFIX}-table__progress-bar`;
3078
+ progressDiv.style.display = "none";
3079
+ progressDiv.innerHTML = `
3080
+ <div class="${PREFIX}-progress-bar ${PREFIX}-progress-bar--variant-primary ${PREFIX}-progress-bar--height-sm">
3081
+ <div class="${PREFIX}-progress-bar__track">
3082
+ <div class="${PREFIX}-progress-bar__fill" style="width: 0%;"></div>
3083
+ </div>
3084
+ </div>
3085
+ `;
3086
+ tableDiv.appendChild(progressDiv);
3087
+ const tableEl = document.createElement("table");
3088
+ tableEl.className = `${PREFIX}-table__container`;
3089
+ const theadEl = document.createElement("thead");
3090
+ theadEl.className = `${PREFIX}-table__header`;
3091
+ const trHeader = document.createElement("tr");
3092
+ theadEl.appendChild(trHeader);
3093
+ tableEl.appendChild(theadEl);
3094
+ const tbodyEl = document.createElement("tbody");
3095
+ tbodyEl.className = `${PREFIX}-table__body`;
3096
+ tableEl.appendChild(tbodyEl);
3097
+ tableDiv.appendChild(tableEl);
3098
+ const paginationEl = document.createElement("div");
3099
+ paginationEl.className = `${PREFIX}-table__pagination`;
3100
+ if (!this.options.showPagination) {
3101
+ paginationEl.style.display = "none";
3102
+ }
3103
+ tableDiv.appendChild(paginationEl);
3104
+ this.container.appendChild(tableDiv);
3105
+ this.elements.progressEl = progressDiv;
3106
+ this.elements.progressFillEl = progressDiv.querySelector(
3107
+ `.${PREFIX}-progress-bar__fill`
3108
+ );
3109
+ this.elements.theadTr = trHeader;
3110
+ this.elements.tbody = tbodyEl;
3111
+ this.elements.pagination = paginationEl;
3112
+ this.buildHeader();
3113
+ }
3114
+ buildHeader() {
3115
+ this.elements.theadTr.innerHTML = "";
3116
+ if (this.options.selectable) {
3117
+ const th = document.createElement("th");
3118
+ th.className = `${PREFIX}-table__header-cell`;
3119
+ th.style.width = "48px";
3120
+ const checkboxWrap = document.createElement("div");
3121
+ checkboxWrap.className = `${PREFIX}-checkbox`;
3122
+ checkboxWrap.innerHTML = `
3123
+ <label class="${PREFIX}-checkbox__label-wrapper">
3124
+ <input type="checkbox" class="${PREFIX}-checkbox__input" id="selectAll">
3125
+ <div class="${PREFIX}-checkbox__box ${PREFIX}-checkbox__box--unchecked"></div>
3126
+ </label>
3127
+ `;
3128
+ th.appendChild(checkboxWrap);
3129
+ this.elements.theadTr.appendChild(th);
3130
+ this.elements.selectAllInput = checkboxWrap.querySelector("input");
3131
+ this.elements.selectAllBox = checkboxWrap.querySelector(
3132
+ `.${PREFIX}-checkbox__box`
3133
+ );
3134
+ this.elements.selectAllInput.addEventListener("change", (e) => {
3135
+ this.toggleAllSelection(e.target.checked);
3136
+ });
3137
+ }
3138
+ this.options.columns.forEach((col) => {
3139
+ const th = document.createElement("th");
3140
+ th.className = `${PREFIX}-table__header-cell`;
3141
+ if (col.sortable && col.accessor) {
3142
+ th.classList.add(`${PREFIX}-table__header-cell--sortable`);
3143
+ th.setAttribute("data-sort", col.accessor);
3144
+ let sortControlsHtml = `
3145
+ <div class="${PREFIX}-table__sort-controls">
3146
+ ${col.header}
3147
+ <div class="${PREFIX}-table__sort-icon">
3148
+ <div class="${PREFIX}-table__sort-button" data-order="asc">
3149
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9L12 15L18 9"></path></svg>
3150
+ </div>
3151
+ <div class="${PREFIX}-table__sort-button ${PREFIX}-table__sort-button-right" data-order="desc">
3152
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 15L12 9L6 15"></path></svg>
3153
+ </div>
3154
+ </div>
3155
+ </div>
3156
+ `;
3157
+ th.innerHTML = sortControlsHtml;
3158
+ th.addEventListener("click", () => {
3159
+ const field = col.accessor;
3160
+ if (this.state.sortField === field) {
3161
+ if (this.state.sortOrder === "asc") this.state.sortOrder = "desc";
3162
+ else {
3163
+ this.state.sortField = null;
3164
+ this.state.sortOrder = null;
3165
+ }
3166
+ } else {
3167
+ this.state.sortField = field;
3168
+ this.state.sortOrder = "asc";
3169
+ }
3170
+ this.state.currentPage = 1;
3171
+ this.loadData();
3172
+ });
3173
+ } else {
3174
+ th.textContent = col.header;
3175
+ }
3176
+ this.elements.theadTr.appendChild(th);
3177
+ });
3178
+ }
3179
+ bindEvents() {
3180
+ if (this.elements.searchInput && this.elements.searchButton) {
3181
+ const handleSearch = () => {
3182
+ this.state.searchTerm = this.elements.searchInput.value.trim();
3183
+ this.state.currentPage = 1;
3184
+ this.loadData();
3185
+ if (typeof this.options.onSearch === "function") {
3186
+ this.options.onSearch(this.state.searchTerm);
3187
+ }
3188
+ };
3189
+ this.elements.searchButton.addEventListener("click", handleSearch);
3190
+ this.elements.searchInput.addEventListener("keydown", (e) => {
3191
+ if (e.key === "Enter") handleSearch();
3192
+ });
3193
+ }
3194
+ }
3195
+ setLoading(isLoading) {
3196
+ this.state.loading = isLoading;
3197
+ if (!this.elements.progressEl || !this.elements.progressFillEl) return;
3198
+ if (isLoading) {
3199
+ this.elements.progressEl.style.display = "block";
3200
+ let progress = 0;
3201
+ clearInterval(this.loadingInterval);
3202
+ this.loadingInterval = setInterval(() => {
3203
+ if (progress >= 90 || !this.state.loading) {
3204
+ clearInterval(this.loadingInterval);
3205
+ if (!this.state.loading) {
3206
+ progress = 100;
3207
+ this.elements.progressFillEl.style.width = "100%";
3208
+ setTimeout(() => {
3209
+ this.elements.progressEl.style.display = "none";
3210
+ this.elements.progressFillEl.style.width = "0%";
3211
+ }, 300);
3212
+ }
3213
+ return;
3214
+ }
3215
+ progress += Math.random() * 15;
3216
+ this.elements.progressFillEl.style.width = progress + "%";
3217
+ }, 200);
3218
+ }
3219
+ }
3220
+ renderRows(rowData) {
3221
+ this.elements.tbody.innerHTML = "";
3222
+ if (rowData.length === 0) {
3223
+ const tr = document.createElement("tr");
3224
+ const colspan = this.options.columns.length + (this.options.selectable ? 1 : 0);
3225
+ tr.innerHTML = `<td colspan="${colspan}" class="${PREFIX}-table__empty-cell">${this.options.emptyState}</td>`;
3226
+ this.elements.tbody.appendChild(tr);
3227
+ this.updateSelectAllState();
3228
+ return;
3229
+ }
3230
+ rowData.forEach((row, index) => {
3231
+ const tr = document.createElement("tr");
3232
+ tr.className = `${PREFIX}-table__row`;
3233
+ if (this.options.rowClickable) {
3234
+ tr.style.cursor = "pointer";
3235
+ tr.classList.add(`${PREFIX}-table__row--clickable`);
3236
+ tr.addEventListener("click", (e) => {
3237
+ if (e.target.closest(`.${PREFIX}-checkbox`) && this.options.selectable) {
3238
+ return;
3239
+ }
3240
+ if (typeof this.options.onRowClick === "function") {
3241
+ this.options.onRowClick(row, index);
3242
+ }
3243
+ });
3244
+ }
3245
+ const rowKeyStr = String(row[this.options.rowKey] || index);
3246
+ if (this.options.selectable) {
3247
+ const td = document.createElement("td");
3248
+ td.className = `${PREFIX}-table__cell`;
3249
+ const isChecked = this.state.selectedKeys.has(rowKeyStr);
3250
+ const checkboxWrap = document.createElement("div");
3251
+ checkboxWrap.className = `${PREFIX}-checkbox`;
3252
+ checkboxWrap.innerHTML = `
3253
+ <label class="${PREFIX}-checkbox__label-wrapper">
3254
+ <input type="checkbox" class="${PREFIX}-checkbox__input" ${isChecked ? "checked" : ""}>
3255
+ <div class="${PREFIX}-checkbox__box ${isChecked ? `${PREFIX}-checkbox__box--checked` : `${PREFIX}-checkbox__box--unchecked`}">
3256
+ ${isChecked ? '<svg class="ina-checkbox__icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5l10 -10"></path></svg>' : ""}
3257
+ </div>
3258
+ </label>
3259
+ `;
3260
+ td.appendChild(checkboxWrap);
3261
+ tr.appendChild(td);
3262
+ const rowInput = checkboxWrap.querySelector("input");
3263
+ rowInput.addEventListener("change", (e) => {
3264
+ this.toggleRowSelection(rowKeyStr, row, e.target.checked);
3265
+ });
3266
+ }
3267
+ this.options.columns.forEach((col) => {
3268
+ const td = document.createElement("td");
3269
+ td.className = `${PREFIX}-table__cell`;
3270
+ if (typeof col.render === "function") {
3271
+ const content = col.render(row, index);
3272
+ if (typeof content === "string") {
3273
+ td.innerHTML = content;
3274
+ } else if (content instanceof HTMLElement) {
3275
+ td.appendChild(content);
3276
+ } else {
3277
+ td.textContent = content;
3278
+ }
3279
+ } else {
3280
+ td.textContent = row[col.accessor] !== void 0 && row[col.accessor] !== null ? row[col.accessor] : "";
3281
+ }
3282
+ tr.appendChild(td);
3283
+ });
3284
+ this.elements.tbody.appendChild(tr);
3285
+ });
3286
+ this.updateSelectAllState();
3287
+ }
3288
+ renderPagination() {
3289
+ if (!this.elements.pagination || !this.options.showPagination) return;
3290
+ this.state.totalPages = Math.ceil(this.state.total / this.state.pageSize);
3291
+ if (this.state.totalPages < 1) this.state.totalPages = 1;
3292
+ this.elements.pagination.innerHTML = `
3293
+ <div class="${PREFIX}-pagination">
3294
+ <div class="${PREFIX}-pagination__nav-container">
3295
+ <div class="${PREFIX}-pagination__page-info">Halaman ${this.state.currentPage} dari ${this.state.totalPages}</div>
3296
+ <div class="${PREFIX}-pagination__nav-buttons"></div>
3297
+ </div>
3298
+ <div class="${PREFIX}-pagination__page-size-container">
3299
+ <span class="${PREFIX}-pagination__page-size-label">Baris per halaman</span>
3300
+ <select class="${PREFIX}-pagination__page-size-select">
3301
+ ${this.options.pageSizeOptions.map(
3302
+ (opt) => `<option value="${opt}" ${this.state.pageSize === opt ? "selected" : ""}>${opt}</option>`
3303
+ ).join("")}
3304
+ </select>
3305
+ </div>
3306
+ </div>
3307
+ `;
3308
+ const navButtons = this.elements.pagination.querySelector(
3309
+ `.${PREFIX}-pagination__nav-buttons`
3310
+ );
3311
+ const pageSizeSelect = this.elements.pagination.querySelector(
3312
+ `.${PREFIX}-pagination__page-size-select`
3313
+ );
3314
+ pageSizeSelect.addEventListener("change", (e) => {
3315
+ this.state.pageSize = parseInt(e.target.value, 10);
3316
+ this.state.currentPage = 1;
3317
+ this.loadData();
3318
+ });
3319
+ const createBtn = (isNav, enabled, content, onClick) => {
3320
+ const btn = document.createElement("button");
3321
+ btn.type = "button";
3322
+ const baseCls = isNav ? `${PREFIX}-pagination__nav-button` : `${PREFIX}-pagination__page-button`;
3323
+ const stCls = isNav ? enabled ? `${baseCls}--enabled` : `${baseCls}--disabled` : enabled ? `${baseCls}--enabled` : `${baseCls}--active`;
3324
+ btn.className = `${baseCls} ${stCls}`;
3325
+ btn.disabled = !enabled;
3326
+ btn.innerHTML = content;
3327
+ btn.onclick = onClick;
3328
+ navButtons.appendChild(btn);
3329
+ };
3330
+ const SVG = {
3331
+ first: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ina-pagination__nav-icon"><path d="M11 7l-5 5l5 5"></path><path d="M17 7l-5 5l5 5"></path></svg>',
3332
+ prev: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ina-pagination__nav-icon"><path d="M15 6l-6 6l6 6"></path></svg>',
3333
+ next: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ina-pagination__nav-icon"><path d="M9 6l6 6l-6 6"></path></svg>',
3334
+ last: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="ina-pagination__nav-icon"><path d="M7 7l5 5l-5 5"></path><path d="M13 7l5 5l-5 5"></path></svg>'
3335
+ };
3336
+ createBtn(true, this.state.currentPage > 1, SVG.first, () => {
3337
+ this.state.currentPage = 1;
3338
+ this.loadData();
3339
+ });
3340
+ createBtn(true, this.state.currentPage > 1, SVG.prev, () => {
3341
+ this.state.currentPage--;
3342
+ this.loadData();
3343
+ });
3344
+ const maxVisible = 5;
3345
+ let startPage = Math.max(
3346
+ 1,
3347
+ this.state.currentPage - Math.floor(maxVisible / 2)
3348
+ );
3349
+ let endPage = Math.min(this.state.totalPages, startPage + maxVisible - 1);
3350
+ if (endPage - startPage < maxVisible - 1) {
3351
+ startPage = Math.max(1, endPage - maxVisible + 1);
3352
+ }
3353
+ for (let i = startPage; i <= endPage; i++) {
3354
+ createBtn(false, i !== this.state.currentPage, String(i), () => {
3355
+ this.state.currentPage = i;
3356
+ this.loadData();
3357
+ });
3358
+ }
3359
+ createBtn(
3360
+ true,
3361
+ this.state.currentPage < this.state.totalPages,
3362
+ SVG.next,
3363
+ () => {
3364
+ this.state.currentPage++;
3365
+ this.loadData();
3366
+ }
3367
+ );
3368
+ createBtn(
3369
+ true,
3370
+ this.state.currentPage < this.state.totalPages,
3371
+ SVG.last,
3372
+ () => {
3373
+ this.state.currentPage = this.state.totalPages;
3374
+ this.loadData();
3375
+ }
3376
+ );
3377
+ }
3378
+ updateSortIndicators() {
3379
+ const btns = this.elements.theadTr.querySelectorAll(
3380
+ `.${PREFIX}-table__sort-button`
3381
+ );
3382
+ btns.forEach((btn) => {
3383
+ const th = btn.closest("th");
3384
+ if (!th) return;
3385
+ const field = th.getAttribute("data-sort");
3386
+ const order = btn.getAttribute("data-order");
3387
+ const active = this.state.sortField === field && this.state.sortOrder === order;
3388
+ btn.classList.toggle(`${PREFIX}-table__sort-button--active`, active);
3389
+ });
3390
+ }
3391
+ async loadData() {
3392
+ this.setLoading(true);
3393
+ try {
3394
+ if (typeof this.options.fetchData === "function") {
3395
+ const res = await this.options.fetchData({
3396
+ page: this.state.currentPage,
3397
+ pageSize: this.state.pageSize,
3398
+ sortField: this.state.sortField,
3399
+ sortOrder: this.state.sortOrder,
3400
+ searchTerm: this.state.searchTerm
3401
+ });
3402
+ this.state.total = res.total;
3403
+ this.state.currentData = res.data || [];
3404
+ } else {
3405
+ let filtered = [...this.options.data];
3406
+ if (this.state.searchTerm) {
3407
+ const lower = this.state.searchTerm.toLowerCase();
3408
+ filtered = filtered.filter(
3409
+ (row) => this.options.columns.some(
3410
+ (c) => String(row[c.accessor] || "").toLowerCase().includes(lower)
3411
+ )
3412
+ );
3413
+ }
3414
+ if (this.state.sortField && this.state.sortOrder) {
3415
+ filtered.sort((a, b) => {
3416
+ const aVal = a[this.state.sortField];
3417
+ const bVal = b[this.state.sortField];
3418
+ if (aVal < bVal) return this.state.sortOrder === "asc" ? -1 : 1;
3419
+ if (aVal > bVal) return this.state.sortOrder === "asc" ? 1 : -1;
3420
+ return 0;
3421
+ });
3422
+ }
3423
+ this.state.total = filtered.length;
3424
+ const skip = (this.state.currentPage - 1) * this.state.pageSize;
3425
+ this.state.currentData = filtered.slice(
3426
+ skip,
3427
+ skip + this.state.pageSize
3428
+ );
3429
+ }
3430
+ this.renderRows(this.state.currentData);
3431
+ this.renderPagination();
3432
+ this.updateSortIndicators();
3433
+ } catch (e) {
3434
+ console.error("Table API Error:", e);
3435
+ } finally {
3436
+ this.setLoading(false);
3437
+ }
3438
+ }
3439
+ toggleRowSelection(keyStr, row, checked) {
3440
+ if (checked) {
3441
+ this.state.selectedKeys.add(keyStr);
3442
+ this.state.selectedRows.set(keyStr, row);
3443
+ } else {
3444
+ this.state.selectedKeys.delete(keyStr);
3445
+ this.state.selectedRows.delete(keyStr);
3446
+ }
3447
+ const inputs = this.elements.tbody.querySelectorAll(
3448
+ `.${PREFIX}-checkbox__input`
3449
+ );
3450
+ inputs.forEach((input) => {
3451
+ const box = input.nextElementSibling;
3452
+ if (input.checked) {
3453
+ box.classList.remove(`${PREFIX}-checkbox__box--unchecked`);
3454
+ box.classList.add(`${PREFIX}-checkbox__box--checked`);
3455
+ box.innerHTML = '<svg class="ina-checkbox__icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5l10 -10"></path></svg>';
3456
+ } else {
3457
+ box.classList.remove(`${PREFIX}-checkbox__box--checked`);
3458
+ box.classList.add(`${PREFIX}-checkbox__box--unchecked`);
3459
+ box.innerHTML = "";
3460
+ }
3461
+ });
3462
+ this.updateSelectAllState();
3463
+ this.triggerSelectionChange();
3464
+ }
3465
+ toggleAllSelection(checked) {
3466
+ this.state.currentData.forEach((row, index) => {
3467
+ const keyStr = String(row[this.options.rowKey] || index);
3468
+ if (checked) {
3469
+ this.state.selectedKeys.add(keyStr);
3470
+ this.state.selectedRows.set(keyStr, row);
3471
+ } else {
3472
+ this.state.selectedKeys.delete(keyStr);
3473
+ this.state.selectedRows.delete(keyStr);
3474
+ }
3475
+ });
3476
+ this.renderRows(this.state.currentData);
3477
+ this.triggerSelectionChange();
3478
+ }
3479
+ updateSelectAllState() {
3480
+ if (!this.options.selectable || !this.elements.selectAllInput) return;
3481
+ const checkboxes = this.elements.tbody.querySelectorAll(
3482
+ 'input[type="checkbox"]'
3483
+ );
3484
+ const checkedCount = Array.from(checkboxes).filter(
3485
+ (cb) => cb.checked
3486
+ ).length;
3487
+ const totalCount = this.state.currentData.length;
3488
+ const box = this.elements.selectAllBox;
3489
+ box.innerHTML = "";
3490
+ if (totalCount === 0 || checkedCount === 0) {
3491
+ this.elements.selectAllInput.checked = false;
3492
+ this.elements.selectAllInput.indeterminate = false;
3493
+ box.classList.remove(
3494
+ `${PREFIX}-checkbox__box--checked`,
3495
+ `${PREFIX}-checkbox__box--indeterminate`
3496
+ );
3497
+ box.classList.add(`${PREFIX}-checkbox__box--unchecked`);
3498
+ } else if (checkedCount === totalCount) {
3499
+ this.elements.selectAllInput.checked = true;
3500
+ this.elements.selectAllInput.indeterminate = false;
3501
+ box.classList.remove(
3502
+ `${PREFIX}-checkbox__box--unchecked`,
3503
+ `${PREFIX}-checkbox__box--indeterminate`
3504
+ );
3505
+ box.classList.add(`${PREFIX}-checkbox__box--checked`);
3506
+ box.innerHTML = '<svg class="ina-checkbox__icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12l5 5l10 -10"></path></svg>';
3507
+ } else {
3508
+ this.elements.selectAllInput.checked = false;
3509
+ this.elements.selectAllInput.indeterminate = true;
3510
+ box.classList.remove(
3511
+ `${PREFIX}-checkbox__box--unchecked`,
3512
+ `${PREFIX}-checkbox__box--checked`
3513
+ );
3514
+ box.classList.add(`${PREFIX}-checkbox__box--indeterminate`);
3515
+ box.innerHTML = '<svg class="ina-checkbox__icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"></path></svg>';
3516
+ }
3517
+ }
3518
+ triggerSelectionChange() {
3519
+ if (typeof this.options.onSelectionChange === "function") {
3520
+ const keys = Array.from(this.state.selectedKeys);
3521
+ const rows = Array.from(this.state.selectedRows.values());
3522
+ this.options.onSelectionChange(keys, rows);
3523
+ }
3524
+ }
3525
+ // API Methods
3526
+ reload() {
3527
+ this.loadData();
3528
+ }
3529
+ setSearchTerm(term) {
3530
+ this.state.searchTerm = term;
3531
+ this.state.currentPage = 1;
3532
+ if (this.elements.searchInput) {
3533
+ this.elements.searchInput.value = term;
3534
+ }
3535
+ this.loadData();
3536
+ }
3537
+ setPage(page) {
3538
+ this.state.currentPage = page;
3539
+ this.loadData();
3540
+ }
3541
+ getSelectedRows() {
3542
+ return {
3543
+ keys: Array.from(this.state.selectedKeys),
3544
+ rows: Array.from(this.state.selectedRows.values())
3545
+ };
3546
+ }
3547
+ };
3548
+ function initTable(selectorOrElement, options = {}) {
3549
+ const elements = typeof selectorOrElement === "string" ? document.querySelectorAll(selectorOrElement) : selectorOrElement ? typeof selectorOrElement.length !== "undefined" ? selectorOrElement : [selectorOrElement] : document.querySelectorAll(`.${PREFIX}-table`);
3550
+ const instances = [];
3551
+ elements.forEach((container) => {
3552
+ const instance = new Table(container, options);
3553
+ container.__tableAPI = instance;
3554
+ instances.push(instance);
3555
+ });
3556
+ if (instances.length === 0) return null;
3557
+ return instances.length === 1 ? instances[0] : instances;
3558
+ }
3559
+
2991
3560
  // src/js/components/stateless/toast.js
2992
3561
  var ICONS3 = {
2993
3562
  default: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>`,
@@ -3129,6 +3698,7 @@ function initAll() {
3129
3698
  initChip();
3130
3699
  initTabVertical();
3131
3700
  initTabHorizontal();
3701
+ initTable();
3132
3702
  }
3133
3703
  export {
3134
3704
  PREFIX,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idds/js",
3
- "version": "1.0.84",
3
+ "version": "1.0.86",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },