vue2-excel-grid 2.1.0 → 2.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2-excel-grid",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "Excel-style grid component for Vue 2.6.14 with virtual scrolling",
5
5
  "license": "MIT",
6
6
  "author": "Fozilbek Raimberdiyev",
@@ -57,6 +57,9 @@
57
57
  {{ opt.label }}
58
58
  </div>
59
59
  <div v-if="!filteredOptions.length" class="eg-select-empty">{{ t.noOptions }}</div>
60
+ <div v-if="clearable && selectedOpt" class="eg-select-footer">
61
+ <button class="eg-select-btn eg-select-btn--clear" tabindex="-1" @mousedown.prevent="pickClear">{{ t.clear }}</button>
62
+ </div>
60
63
  </template>
61
64
 
62
65
  <!-- Options panel in standalone mode (floating dropdown below trigger) -->
@@ -97,6 +100,9 @@
97
100
  {{ opt.label }}
98
101
  </div>
99
102
  <div v-if="!filteredOptions.length" class="eg-select-empty">{{ t.noOptions }}</div>
103
+ <div v-if="clearable && selectedOpt" class="eg-select-footer">
104
+ <button class="eg-select-btn eg-select-btn--clear" tabindex="-1" @mousedown.prevent="pickClear">{{ t.clear }}</button>
105
+ </div>
100
106
  </div>
101
107
 
102
108
  <div v-if="hoverTooltip" class="eg-option-tooltip" :style="hoverTooltipStyle">{{ hoverTooltip.text }}</div>
@@ -129,7 +135,8 @@ export default {
129
135
  dropdownOnly: { type: Boolean, default: false },
130
136
  triggerRect: { type: Object, default: null },
131
137
  size: { type: String, default: 'middle' },
132
- locale: { type: String, default: 'en' }
138
+ locale: { type: String, default: 'en' },
139
+ clearable: { type: Boolean, default: true }
133
140
  },
134
141
  data() {
135
142
  return {
@@ -239,6 +246,11 @@ export default {
239
246
  this.highlight = -1
240
247
  }
241
248
  },
249
+ // Reuses pickOption's write path (same input/select emit + dropdown-close
250
+ // handling) — a clear is just "pick the empty option", not a separate flow.
251
+ pickClear() {
252
+ this.pickOption({ value: '', label: '' })
253
+ },
242
254
  onSearch(e) {
243
255
  this._hideTooltip()
244
256
  this.searchQuery = e.target.value
@@ -535,6 +547,29 @@ export default {
535
547
  margin-top: 3px;
536
548
  }
537
549
 
550
+ /* "Clear" footer — same recipe as EgDatePicker.vue's .eg-dp-footer/.eg-dp-btn,
551
+ just a single right-aligned button (no "Today" equivalent here). Also
552
+ duplicated in ExcelGrid.vue's own <style> — see the comment above
553
+ .eg-select-wrapper there for why, and keep both copies token-identical. */
554
+ .eg-select-footer {
555
+ display: flex;
556
+ justify-content: flex-end;
557
+ padding: 4px 6px 6px;
558
+ border-top: 1px solid var(--eg-color-border, var(--eg-color-border-default));
559
+ }
560
+ .eg-select-btn {
561
+ background: none;
562
+ border: 1px solid var(--eg-color-border, var(--eg-color-border-default));
563
+ border-radius: var(--eg-radius, var(--eg-radius-default));
564
+ padding: 4px 10px;
565
+ cursor: pointer;
566
+ font-size: 12px;
567
+ color: var(--eg-color-text, var(--eg-color-text-default));
568
+ }
569
+ .eg-select-btn:hover { background: var(--eg-color-option-hover-bg, var(--eg-color-option-hover-bg-default)); }
570
+ .eg-select-btn--clear { color: var(--eg-color-danger, #c62828); border-color: var(--eg-color-danger, #f1b0b0); }
571
+ .eg-select-btn--clear:hover { background: #fff0f0; }
572
+
538
573
  /* Themed option/trigger tooltip — same recipe as .eg-select-dropdown
539
574
  (cell-bg/border/radius) plus .eg-validation-tooltip's shadow, so it reads
540
575
  as part of the grid's own chrome in every built-in theme instead of a
@@ -63,6 +63,9 @@
63
63
  {{ opt.label }}
64
64
  </div>
65
65
  <div v-if="!filteredOptions.length" class="eg-select-empty">{{ t.noOptions }}</div>
66
+ <div v-if="clearable && selectedLabel != null" class="eg-select-footer">
67
+ <button class="eg-select-btn eg-select-btn--clear" tabindex="-1" @mousedown.prevent="pickClear">{{ t.clear }}</button>
68
+ </div>
66
69
  </template>
67
70
 
68
71
  <!-- Options panel in standalone mode -->
@@ -110,6 +113,9 @@
110
113
  {{ opt.label }}
111
114
  </div>
112
115
  <div v-if="!filteredOptions.length" class="eg-select-empty">{{ t.noOptions }}</div>
116
+ <div v-if="clearable && selectedLabel != null" class="eg-select-footer">
117
+ <button class="eg-select-btn eg-select-btn--clear" tabindex="-1" @mousedown.prevent="pickClear">{{ t.clear }}</button>
118
+ </div>
113
119
  </div>
114
120
 
115
121
  <div v-if="hoverTooltip" class="eg-option-tooltip" :style="hoverTooltipStyle">{{ hoverTooltip.text }}</div>
@@ -140,7 +146,8 @@ export default {
140
146
  dropdownOnly: { type: Boolean, default: false },
141
147
  triggerRect: { type: Object, default: null },
142
148
  size: { type: String, default: 'middle' },
143
- locale: { type: String, default: 'en' }
149
+ locale: { type: String, default: 'en' },
150
+ clearable: { type: Boolean, default: true }
144
151
  },
145
152
  data() {
146
153
  return {
@@ -269,6 +276,11 @@ export default {
269
276
  this.highlight = -1
270
277
  }
271
278
  },
279
+ // Reuses pickOption's write path (same input/select emit + dropdown-close
280
+ // handling) — a clear is just "pick the empty option", not a separate flow.
281
+ pickClear() {
282
+ this.pickOption({ value: '', label: '' })
283
+ },
272
284
  toggleNode(idx) {
273
285
  const opt = this.flatOptions[idx]
274
286
  if (!opt || !opt.hasChildren) return
@@ -854,6 +854,7 @@
854
854
  :dropdown-only="true"
855
855
  :size="internalSize"
856
856
  :locale="locale"
857
+ :clearable="selectDropdown.clearable"
857
858
  @select="onComponentSelect"
858
859
  @close="closeSelectDropdown"
859
860
  />
@@ -871,6 +872,7 @@
871
872
  :dropdown-only="true"
872
873
  :size="internalSize"
873
874
  :locale="locale"
875
+ :clearable="selectDropdown.clearable"
874
876
  @select="onComponentSelect"
875
877
  @close="closeSelectDropdown"
876
878
  />
@@ -1290,6 +1292,19 @@ const ExcelGridComponent = {
1290
1292
  /** Extra custom toolbar buttons: [{ key, icon, title, onClick(event, gridApi) }] */
1291
1293
  toolbarButtons: { type: Array, default: () => [] },
1292
1294
 
1295
+ /**
1296
+ * Print (`printGrid()`/toolbar "Print" tugmasi) chiqishida jadval tepasida,
1297
+ * markazda ko'rinadigan sarlavha. `null` (standart) — sarlavha umuman
1298
+ * render qilinmaydi.
1299
+ * @property {String} title - sarlavha matni (bo'sh/undefined bo'lsa render qilinmaydi)
1300
+ * @property {Object} [style] - Vue :style uslubidagi obyekt (camelCase),
1301
+ * standart markazlashtirilgan/qalin uslub USTIGA qo'shiladi
1302
+ * @property {String} [class] - qo'shimcha CSS klass nomi(lari)
1303
+ * @example
1304
+ * <excel-grid :print-title="{ title: 'Talabalar ro\'yxati', style: { color: '#1a73e8' } }" />
1305
+ */
1306
+ printTitle: { type: Object, default: null },
1307
+
1293
1308
  /**
1294
1309
  * Locks the entire grid: blocks all edits (typing, paste, fill, delete,
1295
1310
  * checkbox, select/treeSelect) and structural changes (resize, column
@@ -4298,7 +4313,8 @@ const ExcelGridComponent = {
4298
4313
  rawOptions: this.resolveOptionsArray(col, ri, ci),
4299
4314
  currentValue,
4300
4315
  rect: rectObj,
4301
- enableSearch: !!col.enableSearch
4316
+ enableSearch: !!col.enableSearch,
4317
+ clearable: col.clearable !== false
4302
4318
  }
4303
4319
  },
4304
4320
 
@@ -4330,7 +4346,10 @@ const ExcelGridComponent = {
4330
4346
  const { row, col } = this.selectDropdown
4331
4347
  const colDef = this.leafCols[col]
4332
4348
  if (colDef) {
4333
- if (!this.isCellValueValid(row, col, value)) {
4349
+ // Clearing (value === '') always goes through, same as onDatePick()'s
4350
+ // empty-string bypass — "no selection" is a valid state regardless of
4351
+ // what cellValidator requires of a real picked value.
4352
+ if (value !== '' && !this.isCellValueValid(row, col, value)) {
4334
4353
  this._showInvalidCell(row, col, value)
4335
4354
  this.closeSelectDropdown()
4336
4355
  return
@@ -6092,7 +6111,8 @@ const ExcelGridComponent = {
6092
6111
  return String(this.getCellDisplayValue(ci, this.getCellValue(ri, ci), ri))
6093
6112
  },
6094
6113
  showFooter: this.hasFooter,
6095
- getFooterText: ci => this.getColumnTotalDisplay(ci)
6114
+ getFooterText: ci => this.getColumnTotalDisplay(ci),
6115
+ printTitle: this.printTitle
6096
6116
  })
6097
6117
  this._openPrintFrame(html)
6098
6118
  },
@@ -8268,6 +8288,24 @@ export default ExcelGridComponent
8268
8288
  word-break: break-word;
8269
8289
  z-index: 10000;
8270
8290
  }
8291
+ .eg-select-footer {
8292
+ display: flex;
8293
+ justify-content: flex-end;
8294
+ padding: 4px 6px 6px;
8295
+ border-top: 1px solid var(--eg-color-border, var(--eg-color-border-default));
8296
+ }
8297
+ .eg-select-btn {
8298
+ background: none;
8299
+ border: 1px solid var(--eg-color-border, var(--eg-color-border-default));
8300
+ border-radius: var(--eg-radius, var(--eg-radius-default));
8301
+ padding: 4px 10px;
8302
+ cursor: pointer;
8303
+ font-size: 12px;
8304
+ color: var(--eg-color-text, var(--eg-color-text-default));
8305
+ }
8306
+ .eg-select-btn:hover { background: var(--eg-color-option-hover-bg, var(--eg-color-option-hover-bg-default)); }
8307
+ .eg-select-btn--clear { color: var(--eg-color-danger, #c62828); border-color: var(--eg-color-danger, #f1b0b0); }
8308
+ .eg-select-btn--clear:hover { background: #fff0f0; }
8271
8309
  /* Grid `size` overrides — must live in this *unscoped* style block, not the
8272
8310
  scoped one above: .eg-select-option is rendered by the separate EgSelect /
8273
8311
  EgTreeSelect components, so Vue's scoped-CSS attribute (added only to this
@@ -14,6 +14,15 @@ function escapeHtml(value) {
14
14
  .replace(/"/g, '&quot;')
15
15
  }
16
16
 
17
+ // Serializes a Vue :style-shaped object (camelCase keys) into a CSS string —
18
+ // this file builds a raw HTML document, so there's no Vue runtime here to do
19
+ // the :style -> style="" conversion automatically.
20
+ function styleObjToCss(styleObj) {
21
+ return Object.entries(styleObj)
22
+ .map(([k, v]) => `${k.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}:${v}`)
23
+ .join(';')
24
+ }
25
+
17
26
  // Above this many visible columns, a portrait page can't fit them without
18
27
  // crushing every column to near-zero width — switch to landscape instead.
19
28
  const LANDSCAPE_COL_THRESHOLD = 6
@@ -38,6 +47,8 @@ function sizeTierFor(colCount) {
38
47
  * @param {(rowIndex:number, colIndex:number) => string} opts.getCellText
39
48
  * @param {boolean} opts.showFooter
40
49
  * @param {(colIndex:number) => string} opts.getFooterText
50
+ * @param {Object} [opts.printTitle] - { title, style, class } — rendered centered above the
51
+ * table when `title` is non-empty; omitted (or empty title) renders nothing
41
52
  * @returns {string} full HTML document
42
53
  */
43
54
  export function buildPrintTableHtml({
@@ -49,7 +60,8 @@ export function buildPrintTableHtml({
49
60
  colWidths,
50
61
  getCellText,
51
62
  showFooter = false,
52
- getFooterText
63
+ getFooterText,
64
+ printTitle = null
53
65
  }) {
54
66
  const exportCols = leafCols.filter((col, idx) => !hiddenColSet.has(idx))
55
67
  const exportKeySet = new Set(exportCols.map(c => c.key))
@@ -161,6 +173,15 @@ export function buildPrintTableHtml({
161
173
  const totalWidth = adjustedWidths.reduce((sum, w) => sum + w, 0) || 1
162
174
  const colgroupHtml = '<colgroup>' + adjustedWidths.map(w => `<col style="width:${(w / totalWidth * 100).toFixed(3)}%">`).join('') + '</colgroup>'
163
175
 
176
+ // ── Optional host-supplied title, centered above the table. The default
177
+ // .eg-print-title rule (below) sets the centered/bold look; a host style
178
+ // object is serialized as this element's OWN inline style, which wins over
179
+ // the stylesheet rule per normal CSS cascade — so it only overrides what
180
+ // the host actually specifies, the rest of the default look stays. ────────
181
+ const titleHtml = (printTitle && printTitle.title)
182
+ ? `<div class="eg-print-title${printTitle.class ? ' ' + escapeHtml(printTitle.class) : ''}"${printTitle.style ? ` style="${escapeHtml(styleObjToCss(printTitle.style))}"` : ''}>${escapeHtml(printTitle.title)}</div>`
183
+ : ''
184
+
164
185
  return `<!DOCTYPE html>
165
186
  <html>
166
187
  <head>
@@ -188,10 +209,12 @@ th { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
188
209
  td { word-break: normal; overflow-wrap: break-word; }
189
210
  thead th { background: #f0f0f0; font-weight: bold; text-align: center; }
190
211
  tfoot td { font-weight: bold; background: #f7f7f7; }
212
+ .eg-print-title { text-align: center; font-weight: bold; font-size: 20px; margin-bottom: 12px; }
191
213
  @page { size: ${orientation}; margin: 12mm; }
192
214
  </style>
193
215
  </head>
194
216
  <body>
217
+ ${titleHtml}
195
218
  <table>
196
219
  ${colgroupHtml}
197
220
  <thead>${theadRowsHtml}</thead>