@svgrid/grid 2.6.22 → 2.6.23
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/dist/GridMenus.svelte +184 -6
- package/dist/SvAutoComplete.svelte +4 -0
- package/dist/SvComboBox.svelte +5 -1
- package/dist/SvDateRangeInput.svelte +25 -3
- package/dist/SvDropDownList.svelte +4 -0
- package/dist/SvForm.svelte +8 -8
- package/dist/SvGrid.controller.svelte.d.ts +48 -1
- package/dist/SvGrid.controller.svelte.js +214 -52
- package/dist/SvGrid.css +221 -0
- package/dist/SvGrid.helpers.d.ts +55 -0
- package/dist/SvGrid.helpers.js +83 -0
- package/dist/SvGrid.svelte +145 -0
- package/dist/SvGrid.types.d.ts +105 -0
- package/dist/SvGridCellEditor.svelte +99 -17
- package/dist/SvGridSelect.svelte +6 -2
- package/dist/SvGridSelect.svelte.d.ts +1 -1
- package/dist/SvListBox.svelte +8 -0
- package/dist/SvMultiSelect.svelte +6 -2
- package/dist/SvMultiSelect.svelte.d.ts +1 -1
- package/dist/SvNumberInput.svelte +4 -0
- package/dist/SvRichCell.svelte +125 -0
- package/dist/SvRichCell.svelte.d.ts +15 -0
- package/dist/SvTextArea.svelte +5 -1
- package/dist/SvTreeSelect.svelte +6 -2
- package/dist/SvTreeSelect.svelte.d.ts +1 -1
- package/dist/builtin-editors.d.ts +2 -2
- package/dist/builtin-editors.js +19 -3
- package/dist/cdn/GridMenus-BzWXQjv3.js +600 -0
- package/dist/cdn/GridMenus-xr_rHx8F.js +601 -0
- package/dist/cdn/SvDateRangeInput-CaOuMs8O.js +198 -0
- package/dist/cdn/SvDateRangeInput-DMLKmGEc.js +199 -0
- package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +816 -0
- package/dist/cdn/SvDateTimePicker-sonaH0oh.js +812 -0
- package/dist/cdn/SvGridCellEditor-BAPSC7EL.js +550 -0
- package/dist/cdn/SvGridCellEditor-BwzmUWtL.js +549 -0
- package/dist/cdn/date-format-BNii4zeD.js +1393 -0
- package/dist/cdn/date-format-BnnHlqGw.js +1395 -0
- package/dist/cdn/{editor-registry-CiI0xlKg.js → editor-registry-BhgE3S84.js} +51 -25
- package/dist/cdn/{src-D1lXwq1l.js → src-DGo7IHug.js} +5730 -5198
- package/dist/cdn/{src-C9Hihx1W.js → src-skQN5eqh.js} +7216 -6684
- package/dist/cdn/svgrid.js +14 -12
- package/dist/cdn/svgrid.svelte-external.js +14 -12
- package/dist/cell-values.d.ts +16 -0
- package/dist/cell-values.js +28 -0
- package/dist/clipboard.d.ts +22 -0
- package/dist/clipboard.js +405 -27
- package/dist/editing.js +12 -7
- package/dist/editors/cell-editors.d.ts +11 -0
- package/dist/editors/cell-editors.js +18 -1
- package/dist/grid-messages.d.ts +15 -0
- package/dist/grid-messages.js +15 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/list-option.d.ts +6 -0
- package/dist/menus.js +40 -7
- package/dist/sanitize-html.d.ts +37 -0
- package/dist/sanitize-html.js +234 -0
- package/dist/selection-bar-view.svelte.d.ts +25 -0
- package/dist/selection-bar-view.svelte.js +13 -0
- package/dist/selection.d.ts +2 -1
- package/dist/selection.js +129 -13
- package/package.json +1 -1
- package/src/GridMenus.svelte +184 -6
- package/src/SvAutoComplete.svelte +4 -0
- package/src/SvComboBox.svelte +5 -1
- package/src/SvDateRangeInput.svelte +25 -3
- package/src/SvDropDownList.svelte +4 -0
- package/src/SvForm.svelte +8 -8
- package/src/SvGrid.controller.svelte.ts +251 -61
- package/src/SvGrid.css +221 -0
- package/src/SvGrid.helpers.ts +87 -0
- package/src/SvGrid.svelte +145 -0
- package/src/SvGrid.types.ts +112 -0
- package/src/SvGridCellEditor.svelte +99 -17
- package/src/SvGridSelect.svelte +6 -2
- package/src/SvListBox.svelte +8 -0
- package/src/SvMultiSelect.svelte +6 -2
- package/src/SvNumberInput.svelte +4 -0
- package/src/SvRichCell.svelte +125 -0
- package/src/SvTextArea.svelte +5 -1
- package/src/SvTreeSelect.svelte +6 -2
- package/src/builtin-editors.test.ts +18 -1
- package/src/builtin-editors.ts +19 -3
- package/src/cell-values.ts +30 -0
- package/src/clipboard.test.ts +93 -20
- package/src/clipboard.ts +433 -33
- package/src/date-string-column.test.ts +87 -0
- package/src/editing.test.ts +25 -0
- package/src/editing.ts +12 -9
- package/src/editor-block-prop.test.ts +132 -0
- package/src/editors/cell-editors.ts +27 -1
- package/src/grid-messages.ts +34 -0
- package/src/index.ts +13 -0
- package/src/list-option.ts +6 -0
- package/src/menus.test.ts +43 -0
- package/src/menus.ts +37 -7
- package/src/move-cells.test.ts +850 -0
- package/src/number-editor-literal.test.ts +84 -0
- package/src/number-editor.grid.test.ts +160 -0
- package/src/sanitize-html.test.ts +182 -0
- package/src/sanitize-html.ts +235 -0
- package/src/selection-bar-view.svelte.ts +36 -0
- package/src/selection.test.ts +157 -0
- package/src/selection.ts +131 -13
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +56 -10
- package/src/svgrid.in-suggest-snapshot.svelte.test.ts +91 -0
- package/src/svgrid.menu-scroll-close.test.ts +204 -0
- package/src/svgrid.selection-bar-seam.test.ts +185 -0
- package/src/svgrid.upsell-license.test.ts +117 -0
- package/dist/cdn/GridMenus-BuoBPqxx.js +0 -493
- package/dist/cdn/GridMenus-n4llxoOI.js +0 -494
- package/dist/cdn/SvDateTimePicker-CHnUkWcH.js +0 -2206
- package/dist/cdn/SvDateTimePicker-CRQH_U7E.js +0 -2201
- package/dist/cdn/SvGridCellEditor-BGUCzuPB.js +0 -523
- package/dist/cdn/SvGridCellEditor-G8IMWIws.js +0 -522
package/dist/SvGrid.css
CHANGED
|
@@ -14,6 +14,34 @@
|
|
|
14
14
|
position: relative;
|
|
15
15
|
font-family: var(--sg-font, inherit);
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* Room for the selection bar, INSIDE the scroll area.
|
|
20
|
+
*
|
|
21
|
+
* The bar floats - it must not resize the grid, or every layout holding one
|
|
22
|
+
* jumps the moment a checkbox is ticked. So the grid keeps its exact size and
|
|
23
|
+
* the SCROLLER gains bottom padding instead: scrollable space under the last
|
|
24
|
+
* row, the height of the bar. The bar then floats over padding rather than
|
|
25
|
+
* over data, and every row can still be scrolled into clear view.
|
|
26
|
+
*
|
|
27
|
+
* Shrinking the grid instead (padding on the root) was the first attempt. It
|
|
28
|
+
* kept rows visible but made the table resize under the user, which is worse.
|
|
29
|
+
*
|
|
30
|
+
* Only while the bar is up, so an unselected grid scrolls exactly as before.
|
|
31
|
+
*/
|
|
32
|
+
.sv-grid-root[data-selbar] {
|
|
33
|
+
--sg-selbar-reserve: 68px;
|
|
34
|
+
}
|
|
35
|
+
/* The upsell note is prose, so it is taller than the bar it stands in for. */
|
|
36
|
+
.sv-grid-root[data-selbar^='upsell'] {
|
|
37
|
+
--sg-selbar-reserve: 116px;
|
|
38
|
+
}
|
|
39
|
+
.sv-grid-root[data-selbar$='bottom'] .sv-grid-container {
|
|
40
|
+
padding-bottom: var(--sg-selbar-reserve);
|
|
41
|
+
}
|
|
42
|
+
.sv-grid-root[data-selbar$='top'] .sv-grid-container {
|
|
43
|
+
padding-top: var(--sg-selbar-reserve);
|
|
44
|
+
}
|
|
17
45
|
.sv-grid-sr-only {
|
|
18
46
|
position: absolute;
|
|
19
47
|
width: 1px;
|
|
@@ -928,6 +956,18 @@
|
|
|
928
956
|
padding: 5px var(--sg-filter-cell-px, var(--sg-cell-px, 7px));
|
|
929
957
|
}
|
|
930
958
|
|
|
959
|
+
/* The rich date picker in the filter row. It enters `.sv-grid-filter-row-control`
|
|
960
|
+
as a `.sv-field`, which carries none of the flex sizing every other filter
|
|
961
|
+
control has, so it laid itself out at the full cell width NEXT TO the
|
|
962
|
+
operator button: the box was cut off at the column line and the calendar
|
|
963
|
+
toggle ended up outside the cell, underneath the next column's filter, where
|
|
964
|
+
no click could reach it. Size it like `.sv-grid-filter-value` so it shares
|
|
965
|
+
the row instead of overflowing it. */
|
|
966
|
+
.sv-grid-filter-row-control > .sv-field {
|
|
967
|
+
flex: 3 1 0;
|
|
968
|
+
min-width: 0;
|
|
969
|
+
}
|
|
970
|
+
|
|
931
971
|
.sv-grid-filter-operator {
|
|
932
972
|
flex: 2 1 0;
|
|
933
973
|
min-width: 0;
|
|
@@ -1196,6 +1236,57 @@ select.sv-grid-fr-editor {
|
|
|
1196
1236
|
overflow: visible;
|
|
1197
1237
|
}
|
|
1198
1238
|
|
|
1239
|
+
/* ---- The editing cell's border -------------------------------------------
|
|
1240
|
+
One ring for every editor, drawn by the CELL rather than by each editor.
|
|
1241
|
+
They used to draw their own and they disagreed: the text/number/date inputs
|
|
1242
|
+
put a 2px accent-at-30% halo OUTSIDE the cell, the dropdown trigger put a
|
|
1243
|
+
solid 2px outline INSIDE it (outline-offset: -2px), and the checkbox drew
|
|
1244
|
+
nothing - so the border was single, double or missing depending on which
|
|
1245
|
+
column you happened to be editing.
|
|
1246
|
+
|
|
1247
|
+
Excel's shape: a 2px band centred on the cell's own grid line, so half of it
|
|
1248
|
+
covers the line and half sits inside the cell. `inset: -1px` with a 2px
|
|
1249
|
+
border straddles exactly that way. It is a pseudo-element, not the cell's
|
|
1250
|
+
`box-shadow`, because every editor fills the cell with an opaque background
|
|
1251
|
+
and would paint over an inset shadow; ::after is the cell's last child, so
|
|
1252
|
+
it lands on top of the editor. `pointer-events: none` keeps it out of the
|
|
1253
|
+
way of clicks on the editor underneath. */
|
|
1254
|
+
.sv-grid-cell.sv-grid-cell-editing::after {
|
|
1255
|
+
content: "";
|
|
1256
|
+
position: absolute;
|
|
1257
|
+
inset: -1px;
|
|
1258
|
+
border: 2px solid var(--sg-accent, #0b63f3);
|
|
1259
|
+
pointer-events: none;
|
|
1260
|
+
}
|
|
1261
|
+
/* The shared active/editing rule paints an inset ring. For an editing cell the
|
|
1262
|
+
::after above replaces it - keeping both would show two lines through any
|
|
1263
|
+
editor that does not fill the cell (the checkbox). */
|
|
1264
|
+
.sv-grid-cell.sv-grid-cell-editing {
|
|
1265
|
+
box-shadow: none;
|
|
1266
|
+
}
|
|
1267
|
+
/* Each editor's own focus ring, off while it is the in-cell editor. Elsewhere
|
|
1268
|
+
(filter row, standalone use) they keep it. Three class selectors so these win
|
|
1269
|
+
over the dropdown's and the picker's component-scoped `:focus` rules. */
|
|
1270
|
+
.sv-grid-cell.sv-grid-cell-editing .sv-grid-cell-editor:focus,
|
|
1271
|
+
.sv-grid-cell.sv-grid-cell-editing .sv-grid-dropdown-trigger:focus,
|
|
1272
|
+
.sv-grid-cell.sv-grid-cell-editing .sv-dtp__field:focus-within {
|
|
1273
|
+
box-shadow: none;
|
|
1274
|
+
outline: none;
|
|
1275
|
+
}
|
|
1276
|
+
/* The checkbox editor's own focus outline, plus any ring a host page puts on
|
|
1277
|
+
focused buttons (both demo shells do, via a global `button:focus-visible`).
|
|
1278
|
+
Either one is a second border around the only control in the cell, which the
|
|
1279
|
+
cell already frames. Outside an editing cell - the row-selection checkbox,
|
|
1280
|
+
read-only cells - the outline stays.
|
|
1281
|
+
|
|
1282
|
+
The rating stars deliberately keep their focus ring: with five buttons in a
|
|
1283
|
+
radiogroup it is what shows which star the arrow keys are on. */
|
|
1284
|
+
.sv-grid-cell.sv-grid-cell-editing .sv-grid-checkbox:focus-visible {
|
|
1285
|
+
box-shadow: none;
|
|
1286
|
+
outline: none;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
|
|
1199
1290
|
/* Excel-style fill handle: a 7×7 blue square anchored to the bottom-
|
|
1200
1291
|
right corner of the active cell or selection range. The parent
|
|
1201
1292
|
`.sv-grid-cell` is `position: relative` (sticky cells override
|
|
@@ -1246,6 +1337,44 @@ select.sv-grid-fr-editor {
|
|
|
1246
1337
|
transform-origin: bottom right;
|
|
1247
1338
|
}
|
|
1248
1339
|
|
|
1340
|
+
/* Placement for the selection-bar upsell note. The BAR itself is an Enterprise
|
|
1341
|
+
component and carries its own scoped styles - this is only the stand-in the
|
|
1342
|
+
free grid shows when no renderer is registered, positioned on the same edge
|
|
1343
|
+
the real bar would use so the layout does not jump once it is installed. */
|
|
1344
|
+
.sv-grid-selection-bar-upsell {
|
|
1345
|
+
position: absolute;
|
|
1346
|
+
left: 50%;
|
|
1347
|
+
transform: translateX(-50%);
|
|
1348
|
+
/* Same slot as the real bar: above the custom scrollbars (40/41/42), below
|
|
1349
|
+
find-in-grid (60) and the menus (900+). Sharing the value matters because
|
|
1350
|
+
installing enterprise must not change where this sits in the stack. */
|
|
1351
|
+
z-index: 45;
|
|
1352
|
+
max-width: calc(100% - 24px);
|
|
1353
|
+
}
|
|
1354
|
+
.sv-grid-selection-bar-upsell[data-position="bottom"] { bottom: 16px; }
|
|
1355
|
+
.sv-grid-selection-bar-upsell[data-position="top"] { top: 16px; }
|
|
1356
|
+
|
|
1357
|
+
/* Excel-style range drag-and-drop (`moveCells`). The grab target is the outer
|
|
1358
|
+
4px of the selection border, which is PAINTED (an inset box-shadow) rather
|
|
1359
|
+
than an element, so there is nothing to hang `cursor` off per cell. Instead
|
|
1360
|
+
the controller flips one attribute on the root while the pointer is over the
|
|
1361
|
+
strip, and this turns the whole grid's cursor for as long as that lasts -
|
|
1362
|
+
the cursor is on the border anyway, so the effect is identical to a hit
|
|
1363
|
+
target and costs nothing per cell. `!important` because several cell states
|
|
1364
|
+
(editing, checkbox, link) set their own cursor at higher specificity, and
|
|
1365
|
+
during a grab the move cursor has to win over all of them. */
|
|
1366
|
+
.sv-grid-root[data-move-grab="true"],
|
|
1367
|
+
.sv-grid-root[data-move-grab="true"] .sv-grid-cell {
|
|
1368
|
+
cursor: move !important;
|
|
1369
|
+
}
|
|
1370
|
+
/* The fill handle keeps its own crosshair: it sits in the bottom-right corner,
|
|
1371
|
+
inside the grab strip's territory, and it is a real element that captures
|
|
1372
|
+
the pointerdown first. Letting it read as "move" would promise the wrong
|
|
1373
|
+
gesture. */
|
|
1374
|
+
.sv-grid-root[data-move-grab="true"] .sv-grid-fill-handle {
|
|
1375
|
+
cursor: crosshair !important;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1249
1378
|
/* Excel-style fill-handle drag marquee: ONE thin dashed rectangle around the
|
|
1250
1379
|
whole target range (source + fill-into area), not a thick per-cell border or
|
|
1251
1380
|
fill tint. Drawn as four background-image layers on the boundary cells - no
|
|
@@ -1435,6 +1564,76 @@ select.sv-grid-fr-editor {
|
|
|
1435
1564
|
height: 100%;
|
|
1436
1565
|
}
|
|
1437
1566
|
|
|
1567
|
+
/* Rich date / datetime / time editor (SvDateTimePicker) inside a cell.
|
|
1568
|
+
The picker is a standalone UI-kit control: a fixed 34px pill with its own
|
|
1569
|
+
1px border, radius and outer focus ring. Dropped into a cell it stayed in
|
|
1570
|
+
flow, so that pill drew a second, smaller box inside the cell's editing
|
|
1571
|
+
ring, its radius pulled the corners away from the grid lines, and its ring
|
|
1572
|
+
bled past the cell - the borders never lined up with the column. In-cell it
|
|
1573
|
+
gets the same treatment as `.sv-grid-cell-editor`: taken out of flow, pinned
|
|
1574
|
+
to the cell box and stripped of its own chrome - the cell draws the single
|
|
1575
|
+
editing ring for every editor. Scoped to the editing cell, so the filter row
|
|
1576
|
+
and standalone pickers keep their box.
|
|
1577
|
+
Three class selectors (not two) so these win over the component's own
|
|
1578
|
+
scoped rules whatever order the two stylesheets land in. */
|
|
1579
|
+
.sv-grid-cell.sv-grid-cell-editing .sv-dtp {
|
|
1580
|
+
position: absolute;
|
|
1581
|
+
inset: 0;
|
|
1582
|
+
display: block;
|
|
1583
|
+
width: auto;
|
|
1584
|
+
}
|
|
1585
|
+
.sv-grid-cell.sv-grid-cell-editing .sv-dtp__field {
|
|
1586
|
+
height: 100%;
|
|
1587
|
+
border: 0;
|
|
1588
|
+
border-radius: 0;
|
|
1589
|
+
box-shadow: none;
|
|
1590
|
+
/* The compact buttons below carry their own breathing room. */
|
|
1591
|
+
padding-inline: 0;
|
|
1592
|
+
}
|
|
1593
|
+
/* Match the cell's own type instead of the control's fixed 13px, so the value
|
|
1594
|
+
doesn't change size the moment the cell goes into edit. */
|
|
1595
|
+
.sv-grid-cell.sv-grid-cell-editing .sv-dtp__input {
|
|
1596
|
+
font: inherit;
|
|
1597
|
+
}
|
|
1598
|
+
/* Date columns are narrow and the two 26px buttons ate enough of them to clip
|
|
1599
|
+
the value. Trimmed wherever the picker sits inside a column's width - the
|
|
1600
|
+
cell editor and the filter row - but not for standalone pickers. */
|
|
1601
|
+
.sv-grid-cell.sv-grid-cell-editing .sv-dtp__clear,
|
|
1602
|
+
.sv-grid-cell.sv-grid-cell-editing .sv-dtp__toggle,
|
|
1603
|
+
.sv-grid-filter-row-control > .sv-field .sv-dtp__toggle {
|
|
1604
|
+
width: 20px;
|
|
1605
|
+
height: 20px;
|
|
1606
|
+
}
|
|
1607
|
+
/* Safety valve for a very narrow date column: the toggle keeps its 20px while
|
|
1608
|
+
there is room and gives some back rather than spilling over the column line
|
|
1609
|
+
onto the next filter, where no click could reach it. */
|
|
1610
|
+
.sv-grid-filter-row-control > .sv-field .sv-dtp__toggle {
|
|
1611
|
+
flex: 0 1 auto;
|
|
1612
|
+
min-width: 0;
|
|
1613
|
+
}
|
|
1614
|
+
/* The filter row drops the picker's clear button. It costs ~20px of a filter
|
|
1615
|
+
cell that has none to spare, next to the operator button and the calendar
|
|
1616
|
+
toggle, and a date column is usually narrow. Nothing is lost: emptying the
|
|
1617
|
+
text clears the filter, as does Clear filter in the column menu. */
|
|
1618
|
+
.sv-grid-filter-row-control > .sv-field .sv-dtp__clear {
|
|
1619
|
+
display: none;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
/* The range field `between` uses (SvDateRangeInput). Its stock chrome - a 10px
|
|
1623
|
+
inset, a 15px icon, two 6px gaps and a 24px clear - adds up to more than a
|
|
1624
|
+
narrow date column has, and the clear button hung out past the field's own
|
|
1625
|
+
border. Trimmed to fit; the clear can shrink further before anything spills. */
|
|
1626
|
+
.sv-grid-filter-row-control > .sv-field .sv-dri__field {
|
|
1627
|
+
padding-inline: 4px 2px;
|
|
1628
|
+
gap: 3px;
|
|
1629
|
+
}
|
|
1630
|
+
.sv-grid-filter-row-control > .sv-field .sv-dri__clear {
|
|
1631
|
+
width: 20px;
|
|
1632
|
+
height: 20px;
|
|
1633
|
+
flex: 0 1 auto;
|
|
1634
|
+
min-width: 0;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1438
1637
|
/* List editor: native <select>. For multi-select it grows into a
|
|
1439
1638
|
small popup-style listbox that hangs over the cell. */
|
|
1440
1639
|
.sv-grid-cell-editor-list {
|
|
@@ -1981,6 +2180,16 @@ select.sv-grid-fr-editor {
|
|
|
1981
2180
|
font-weight: 600;
|
|
1982
2181
|
color: var(--sg-muted, #475569);
|
|
1983
2182
|
}
|
|
2183
|
+
/* Trailing status on a section heading: how much of the list is selected, or
|
|
2184
|
+
that a condition is ANDed with it. Pushed to the far edge and de-emphasised
|
|
2185
|
+
so it reads as state, not as part of the heading. */
|
|
2186
|
+
.sv-grid-menu-head-note {
|
|
2187
|
+
margin-inline-start: auto;
|
|
2188
|
+
font-weight: 500;
|
|
2189
|
+
font-size: 0.85em;
|
|
2190
|
+
color: var(--sg-muted, #64748b);
|
|
2191
|
+
opacity: 0.85;
|
|
2192
|
+
}
|
|
1984
2193
|
|
|
1985
2194
|
.sv-grid-menu-search {
|
|
1986
2195
|
width: 100%;
|
|
@@ -2745,6 +2954,18 @@ select.sv-grid-fr-editor {
|
|
|
2745
2954
|
}
|
|
2746
2955
|
.sv-grid-scheduler-upsell strong { font-size: 0.95rem; }
|
|
2747
2956
|
.sv-grid-scheduler-upsell p { margin: 0; font-size: 0.85rem; max-width: 42ch; }
|
|
2957
|
+
/* The licensing line sits under the "how to install it" line, quieter than it:
|
|
2958
|
+
the first sentence is what unblocks the developer looking at this, the second
|
|
2959
|
+
is what they need before shipping. */
|
|
2960
|
+
.sv-grid-upsell-license {
|
|
2961
|
+
font-size: 0.8rem;
|
|
2962
|
+
opacity: 0.85;
|
|
2963
|
+
}
|
|
2964
|
+
.sv-grid-upsell-license a {
|
|
2965
|
+
color: inherit;
|
|
2966
|
+
text-decoration: underline;
|
|
2967
|
+
text-underline-offset: 2px;
|
|
2968
|
+
}
|
|
2748
2969
|
.sv-grid-scheduler-upsell code {
|
|
2749
2970
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
2750
2971
|
font-size: 0.82em;
|
package/dist/SvGrid.helpers.d.ts
CHANGED
|
@@ -18,6 +18,61 @@ export declare function timeStringToDate(value: unknown): Date | null;
|
|
|
18
18
|
export declare function dateToTimeString(d: Date | null): string | null;
|
|
19
19
|
export declare function getEditableInputValue(editorType: CellEditorType, value: unknown): string;
|
|
20
20
|
export declare function getEditorInputType(editorType: CellEditorType): "number" | "date" | "text" | "time" | "password" | "color" | "datetime-local";
|
|
21
|
+
/**
|
|
22
|
+
* Input type for the CELL EDITOR specifically, which differs from
|
|
23
|
+
* {@link getEditorInputType} in one place: `number` becomes a text input.
|
|
24
|
+
*
|
|
25
|
+
* `<input type="number">` runs the HTML value-sanitization algorithm, so it
|
|
26
|
+
* only ever reports a value that parses as a valid float. Every intermediate
|
|
27
|
+
* state on the way to `12.5` - `12.` - is not one, and the element reports the
|
|
28
|
+
* empty string instead. The editor reads `event.currentTarget.value` into its
|
|
29
|
+
* draft, so the decimal point the user just typed disappeared. Same for a lone
|
|
30
|
+
* `-` on the way to a negative and `1e` on the way to `1e3`.
|
|
31
|
+
*
|
|
32
|
+
* A text input keeps the raw keystrokes, and the draft is coerced to a number
|
|
33
|
+
* at commit by `parseEditorValue` - which the editor already did. Pair it with
|
|
34
|
+
* `inputmode="decimal"` for the mobile keypad and
|
|
35
|
+
* {@link isNumericEditorInput} for the character filtering that
|
|
36
|
+
* `type="number"` used to provide.
|
|
37
|
+
*
|
|
38
|
+
* The filter-row and filter-menu inputs deliberately still use
|
|
39
|
+
* {@link getEditorInputType}: they feed a different pipeline, and changing
|
|
40
|
+
* them is a separate decision.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Should this column filter with the grid own date picker rather than the
|
|
44
|
+
* browser one?
|
|
45
|
+
*
|
|
46
|
+
* The grid convention is rich-by-default with an explicit `-native` opt-out,
|
|
47
|
+
* which the cell editor follows. The filter row did not: a `date` column
|
|
48
|
+
* edited through SvDateTimePicker was FILTERED through a native
|
|
49
|
+
* `<input type="date">`, so the same column offered two different date UIs.
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* A Date to the 'YYYY-MM-DD' a date filter compares against, using LOCAL
|
|
53
|
+
* parts. `toISOString()` would convert through UTC, so picking the 1st in a
|
|
54
|
+
* negative-offset zone would filter for the 31st of the month before.
|
|
55
|
+
*/
|
|
56
|
+
export declare function toIsoDateLocal(value: Date | null | undefined): string;
|
|
57
|
+
/**
|
|
58
|
+
* The inverse of {@link toIsoDateLocal}: read a 'YYYY-MM-DD' filter value back
|
|
59
|
+
* as that LOCAL calendar day. `new Date('2026-09-10')` is parsed as UTC
|
|
60
|
+
* midnight, which is the 9th anywhere west of Greenwich - so handing the raw
|
|
61
|
+
* string to a date control and writing it back through `toIsoDateLocal` walks
|
|
62
|
+
* the value backwards a day per round trip.
|
|
63
|
+
*/
|
|
64
|
+
export declare function fromIsoDateLocal(value: string | null | undefined): Date | null;
|
|
65
|
+
export declare function usesRichDateFilter(editorType: CellEditorType | undefined): boolean;
|
|
66
|
+
export declare function getCellEditorInputType(editorType: CellEditorType): "number" | "date" | "text" | "time" | "password" | "color" | "datetime-local";
|
|
67
|
+
/**
|
|
68
|
+
* Is `value` something the user could still be typing towards a number?
|
|
69
|
+
*
|
|
70
|
+
* Deliberately permissive about INTERMEDIATE states - `''`, `'-'`, `'12.'` and
|
|
71
|
+
* `'1e'` all pass, because rejecting them is the exact bug this replaces. It
|
|
72
|
+
* only rejects text that can never become a number, which is the guarantee
|
|
73
|
+
* `type="number"` used to give.
|
|
74
|
+
*/
|
|
75
|
+
export declare function isNumericEditorInput(value: string): boolean;
|
|
21
76
|
/** Resolve a stored value to an array form (for list/chips multi-select). */
|
|
22
77
|
export declare function toValueArray(value: unknown): Array<string | number>;
|
|
23
78
|
/** Look up the display label for a given option value. */
|
package/dist/SvGrid.helpers.js
CHANGED
|
@@ -106,6 +106,89 @@ export function getEditorInputType(editorType) {
|
|
|
106
106
|
return "color";
|
|
107
107
|
return "text";
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Input type for the CELL EDITOR specifically, which differs from
|
|
111
|
+
* {@link getEditorInputType} in one place: `number` becomes a text input.
|
|
112
|
+
*
|
|
113
|
+
* `<input type="number">` runs the HTML value-sanitization algorithm, so it
|
|
114
|
+
* only ever reports a value that parses as a valid float. Every intermediate
|
|
115
|
+
* state on the way to `12.5` - `12.` - is not one, and the element reports the
|
|
116
|
+
* empty string instead. The editor reads `event.currentTarget.value` into its
|
|
117
|
+
* draft, so the decimal point the user just typed disappeared. Same for a lone
|
|
118
|
+
* `-` on the way to a negative and `1e` on the way to `1e3`.
|
|
119
|
+
*
|
|
120
|
+
* A text input keeps the raw keystrokes, and the draft is coerced to a number
|
|
121
|
+
* at commit by `parseEditorValue` - which the editor already did. Pair it with
|
|
122
|
+
* `inputmode="decimal"` for the mobile keypad and
|
|
123
|
+
* {@link isNumericEditorInput} for the character filtering that
|
|
124
|
+
* `type="number"` used to provide.
|
|
125
|
+
*
|
|
126
|
+
* The filter-row and filter-menu inputs deliberately still use
|
|
127
|
+
* {@link getEditorInputType}: they feed a different pipeline, and changing
|
|
128
|
+
* them is a separate decision.
|
|
129
|
+
*/
|
|
130
|
+
/**
|
|
131
|
+
* Should this column filter with the grid own date picker rather than the
|
|
132
|
+
* browser one?
|
|
133
|
+
*
|
|
134
|
+
* The grid convention is rich-by-default with an explicit `-native` opt-out,
|
|
135
|
+
* which the cell editor follows. The filter row did not: a `date` column
|
|
136
|
+
* edited through SvDateTimePicker was FILTERED through a native
|
|
137
|
+
* `<input type="date">`, so the same column offered two different date UIs.
|
|
138
|
+
*/
|
|
139
|
+
/**
|
|
140
|
+
* A Date to the 'YYYY-MM-DD' a date filter compares against, using LOCAL
|
|
141
|
+
* parts. `toISOString()` would convert through UTC, so picking the 1st in a
|
|
142
|
+
* negative-offset zone would filter for the 31st of the month before.
|
|
143
|
+
*/
|
|
144
|
+
export function toIsoDateLocal(value) {
|
|
145
|
+
if (!value || Number.isNaN(value.getTime()))
|
|
146
|
+
return "";
|
|
147
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
148
|
+
return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The inverse of {@link toIsoDateLocal}: read a 'YYYY-MM-DD' filter value back
|
|
152
|
+
* as that LOCAL calendar day. `new Date('2026-09-10')` is parsed as UTC
|
|
153
|
+
* midnight, which is the 9th anywhere west of Greenwich - so handing the raw
|
|
154
|
+
* string to a date control and writing it back through `toIsoDateLocal` walks
|
|
155
|
+
* the value backwards a day per round trip.
|
|
156
|
+
*/
|
|
157
|
+
export function fromIsoDateLocal(value) {
|
|
158
|
+
if (!value)
|
|
159
|
+
return null;
|
|
160
|
+
const parts = /^(\d{4})-(\d{2})-(\d{2})/.exec(value);
|
|
161
|
+
if (!parts) {
|
|
162
|
+
const loose = new Date(value);
|
|
163
|
+
return Number.isNaN(loose.getTime()) ? null : loose;
|
|
164
|
+
}
|
|
165
|
+
const local = new Date(Number(parts[1]), Number(parts[2]) - 1, Number(parts[3]));
|
|
166
|
+
return Number.isNaN(local.getTime()) ? null : local;
|
|
167
|
+
}
|
|
168
|
+
export function usesRichDateFilter(editorType) {
|
|
169
|
+
if (!editorType)
|
|
170
|
+
return false;
|
|
171
|
+
if (String(editorType).endsWith("-native"))
|
|
172
|
+
return false;
|
|
173
|
+
const base = baseEditor(editorType);
|
|
174
|
+
return base === "date" || base === "datetime" || base === "time";
|
|
175
|
+
}
|
|
176
|
+
export function getCellEditorInputType(editorType) {
|
|
177
|
+
if (baseEditor(editorType) === "number")
|
|
178
|
+
return "text";
|
|
179
|
+
return getEditorInputType(editorType);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Is `value` something the user could still be typing towards a number?
|
|
183
|
+
*
|
|
184
|
+
* Deliberately permissive about INTERMEDIATE states - `''`, `'-'`, `'12.'` and
|
|
185
|
+
* `'1e'` all pass, because rejecting them is the exact bug this replaces. It
|
|
186
|
+
* only rejects text that can never become a number, which is the guarantee
|
|
187
|
+
* `type="number"` used to give.
|
|
188
|
+
*/
|
|
189
|
+
export function isNumericEditorInput(value) {
|
|
190
|
+
return /^-?(\d*\.?\d*)(e-?\d*)?$/i.test(value);
|
|
191
|
+
}
|
|
109
192
|
/** Resolve a stored value to an array form (for list/chips multi-select). */
|
|
110
193
|
export function toValueArray(value) {
|
|
111
194
|
if (value == null || value === "")
|
package/dist/SvGrid.svelte
CHANGED
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
import {
|
|
34
34
|
cfTextStyle,
|
|
35
35
|
getEditorInputType,
|
|
36
|
+
usesRichDateFilter,
|
|
37
|
+
toIsoDateLocal,
|
|
38
|
+
fromIsoDateLocal,
|
|
36
39
|
getOptionLabel,
|
|
37
40
|
getOptionColor,
|
|
38
41
|
colorfulChipStyle,
|
|
@@ -48,6 +51,7 @@
|
|
|
48
51
|
// SvGridChartPanel (charting) and SvGridBoard (Kanban) are heavy, prop-gated
|
|
49
52
|
// views - lazy-loaded below so they stay out of the base <SvGrid> bundle.
|
|
50
53
|
import { getSchedulerView } from "./scheduler-view.svelte";
|
|
54
|
+
import { getSelectionBarView } from "./selection-bar-view.svelte";
|
|
51
55
|
import { getBoardView } from "./board-view.svelte";
|
|
52
56
|
import { getChartView } from "./chart-view.svelte";
|
|
53
57
|
let props: Props<TFeatures, TData> = $props();
|
|
@@ -99,6 +103,37 @@
|
|
|
99
103
|
import("./SvGridChartView.svelte").then((m) => (ChartView = m.default));
|
|
100
104
|
});
|
|
101
105
|
let MenusOverlay = $state<typeof import("./GridMenus.svelte").default | null>(null);
|
|
106
|
+
// The grid's own date picker, for date FILTER inputs. Lazy for the same
|
|
107
|
+
// reason the cell editor's copy is: most grids never show a filter row, and
|
|
108
|
+
// most that do have no date column.
|
|
109
|
+
let FilterDatePicker = $state<typeof import("./SvDateTimePicker.svelte").default | null>(null);
|
|
110
|
+
$effect(() => {
|
|
111
|
+
if (!showFilterRowEffective && !ctrl.filterMenuFor) return;
|
|
112
|
+
if (FilterDatePicker) return;
|
|
113
|
+
const wantsPicker = ctrl.allColumns.some((c: any) =>
|
|
114
|
+
usesRichDateFilter(c.columnDef?.editorType),
|
|
115
|
+
);
|
|
116
|
+
if (wantsPicker)
|
|
117
|
+
import("./SvDateTimePicker.svelte").then((m) => (FilterDatePicker = m.default));
|
|
118
|
+
});
|
|
119
|
+
// `between` on a date column filters with a RANGE control rather than two
|
|
120
|
+
// single-date fields: one field, one popover, both ends picked in a single
|
|
121
|
+
// gesture - and it fits a narrow date column, which two pickers plus their
|
|
122
|
+
// buttons do not. Loaded only once a date column is actually set to Between,
|
|
123
|
+
// so a grid that never uses the operator never pays for the chunk.
|
|
124
|
+
let FilterRangePicker = $state<typeof import("./SvDateRangeInput.svelte").default | null>(null);
|
|
125
|
+
$effect(() => {
|
|
126
|
+
if (!showFilterRowEffective) return;
|
|
127
|
+
if (FilterRangePicker) return;
|
|
128
|
+
const wantsRange = ctrl.allColumns.some(
|
|
129
|
+
(c: any) =>
|
|
130
|
+
usesRichDateFilter(c.columnDef?.editorType) &&
|
|
131
|
+
(ctrl.filterMenuValues[c.id]?.operator ??
|
|
132
|
+
ctrl.defaultOperatorFor(c)) === "between",
|
|
133
|
+
);
|
|
134
|
+
if (wantsRange)
|
|
135
|
+
import("./SvDateRangeInput.svelte").then((m) => (FilterRangePicker = m.default));
|
|
136
|
+
});
|
|
102
137
|
$effect(() => {
|
|
103
138
|
if (ctrl.chartingEnabled && ctrl.chartPanelOpen && !ChartPanelView)
|
|
104
139
|
import("./SvGridChartPanel.svelte").then((m) => (ChartPanelView = m.default));
|
|
@@ -681,6 +716,24 @@
|
|
|
681
716
|
onpointermove={onWindowPointerMove}
|
|
682
717
|
/>
|
|
683
718
|
|
|
719
|
+
<!--
|
|
720
|
+
The licensing line every Enterprise upsell note carries. One snippet rather
|
|
721
|
+
than four copies: four gates worded four ways is how a product ends up
|
|
722
|
+
explaining its own licensing inconsistently.
|
|
723
|
+
|
|
724
|
+
Enterprise is SOFT-gated - the feature runs without a key and the grid shows
|
|
725
|
+
an unlicensed watermark - so this says exactly that rather than implying the
|
|
726
|
+
feature is inert without one.
|
|
727
|
+
-->
|
|
728
|
+
{#snippet enterpriseLicenseNote()}
|
|
729
|
+
<p class="sv-grid-upsell-license">
|
|
730
|
+
{messages.upsellLicense}
|
|
731
|
+
<a href="https://svgrid.com/pricing/" target="_blank" rel="noopener noreferrer">
|
|
732
|
+
{messages.upsellLicenseLink}
|
|
733
|
+
</a>
|
|
734
|
+
</p>
|
|
735
|
+
{/snippet}
|
|
736
|
+
|
|
684
737
|
{#if opt.loading && !opt.loadingOverlay && !hasMeasured}
|
|
685
738
|
<!-- Full-screen loading state only on the *initial* load, before the grid
|
|
686
739
|
has ever rendered. Once measured, a `loading` flip (from a server-mode
|
|
@@ -737,6 +790,7 @@
|
|
|
737
790
|
<code>@svgrid/enterprise</code> and call <code>enableBoardView()</code>
|
|
738
791
|
to render it.
|
|
739
792
|
</p>
|
|
793
|
+
{@render enterpriseLicenseNote()}
|
|
740
794
|
</div>
|
|
741
795
|
{/if}
|
|
742
796
|
</div>
|
|
@@ -784,6 +838,7 @@
|
|
|
784
838
|
<code>@svgrid/enterprise</code> and call <code>enableSchedulerView()</code>
|
|
785
839
|
to render it.
|
|
786
840
|
</p>
|
|
841
|
+
{@render enterpriseLicenseNote()}
|
|
787
842
|
</div>
|
|
788
843
|
{/if}
|
|
789
844
|
</div>
|
|
@@ -866,6 +921,7 @@
|
|
|
866
921
|
<div class="sv-grid-scheduler-upsell sv-grid-pivot-upsell" role="note">
|
|
867
922
|
<strong>{messages.pivotUpsellTitle}</strong>
|
|
868
923
|
<p>{messages.pivotUpsellBody}</p>
|
|
924
|
+
{@render enterpriseLicenseNote()}
|
|
869
925
|
</div>
|
|
870
926
|
{/if}
|
|
871
927
|
</div>
|
|
@@ -923,6 +979,13 @@
|
|
|
923
979
|
<path d="M8 5l9 7-9 7" />
|
|
924
980
|
{:else if name === "op-lessThan"}
|
|
925
981
|
<path d="M16 5l-9 7 9 7" />
|
|
982
|
+
{:else if name === "op-between"}
|
|
983
|
+
<!-- Two bounds with the span between them. The catalogue has always
|
|
984
|
+
named this icon; the snippet had no case for it, so the Between
|
|
985
|
+
row in the operator menu drew an empty <svg>. -->
|
|
986
|
+
<path d="M5 5v14" />
|
|
987
|
+
<path d="M19 5v14" />
|
|
988
|
+
<path d="M9 12h6" />
|
|
926
989
|
{:else if name === "op-isBlank"}
|
|
927
990
|
<circle cx="12" cy="12" r="8" />
|
|
928
991
|
<path d="M6.5 6.5l11 11" />
|
|
@@ -1351,6 +1414,12 @@
|
|
|
1351
1414
|
class="sv-grid-root"
|
|
1352
1415
|
class:sv-grid-root-fill={opt.containerHeight === "100%"}
|
|
1353
1416
|
style={chartDockReserveStyle}
|
|
1417
|
+
data-move-grab={ctrl.moveGrabHover || ctrl.moveDrag ? "true" : undefined}
|
|
1418
|
+
data-selbar={
|
|
1419
|
+
ctrl.selectionBarOn && ctrl.selectionBarTarget.ids.length > 0
|
|
1420
|
+
? (getSelectionBarView() ? ctrl.selectionBarPosition : `upsell-${ctrl.selectionBarPosition}`)
|
|
1421
|
+
: undefined
|
|
1422
|
+
}
|
|
1354
1423
|
use:lazyRowResize={{
|
|
1355
1424
|
disabled: !ctrl.rowResizeOn,
|
|
1356
1425
|
// No gutter column on most grids, so fall back to the first body cell -
|
|
@@ -1951,6 +2020,57 @@
|
|
|
1951
2020
|
}}
|
|
1952
2021
|
/>
|
|
1953
2022
|
</div>
|
|
2023
|
+
{:else if activeOperator !== "isBlank" && activeOperator !== "isNotBlank" && activeOperator !== "regex" && FilterDatePicker && usesRichDateFilter(rendered.column.columnDef.editorType)}
|
|
2024
|
+
<!-- Same picker the cell editor uses, so a date
|
|
2025
|
+
column looks the same wherever you touch it.
|
|
2026
|
+
`regex` keeps the text box - a pattern is not
|
|
2027
|
+
a date. -->
|
|
2028
|
+
{#if activeOperator === "between"}
|
|
2029
|
+
<!-- `between` needs BOTH bounds - `condActive`
|
|
2030
|
+
ignores the filter until `valueTo` is set too -
|
|
2031
|
+
so this is one range field rather than two
|
|
2032
|
+
single-date pickers: both ends come from a
|
|
2033
|
+
single gesture in one popover, and it still
|
|
2034
|
+
fits a narrow date column. It writes the same
|
|
2035
|
+
pair of stores the plain text inputs use. -->
|
|
2036
|
+
{#if FilterRangePicker}
|
|
2037
|
+
{@const RangePicker = FilterRangePicker}
|
|
2038
|
+
{@const from = fromIsoDateLocal(
|
|
2039
|
+
filterRowValues[rendered.column.id],
|
|
2040
|
+
)}
|
|
2041
|
+
{@const to = fromIsoDateLocal(
|
|
2042
|
+
filterMenuValues[rendered.column.id]?.valueTo,
|
|
2043
|
+
)}
|
|
2044
|
+
<RangePicker
|
|
2045
|
+
value={from && to ? [from, to] : null}
|
|
2046
|
+
formatString="yyyy-MM-dd"
|
|
2047
|
+
block
|
|
2048
|
+
placeholder="Range…"
|
|
2049
|
+
onChange={(range) => {
|
|
2050
|
+
updateFilterRow(
|
|
2051
|
+
rendered.column.id,
|
|
2052
|
+
range ? toIsoDateLocal(range[0]) : "",
|
|
2053
|
+
);
|
|
2054
|
+
updateFilterMenuValueTo(
|
|
2055
|
+
rendered.column.id,
|
|
2056
|
+
range ? toIsoDateLocal(range[1]) : "",
|
|
2057
|
+
);
|
|
2058
|
+
}}
|
|
2059
|
+
/>
|
|
2060
|
+
{/if}
|
|
2061
|
+
{:else}
|
|
2062
|
+
{@const FilterPicker = FilterDatePicker}
|
|
2063
|
+
<FilterPicker
|
|
2064
|
+
value={(filterRowValues[rendered.column.id] ?? "") || null}
|
|
2065
|
+
formatString="yyyy-MM-dd"
|
|
2066
|
+
dropDownDisplayMode="calendar"
|
|
2067
|
+
block
|
|
2068
|
+
nullable
|
|
2069
|
+
placeholder="Filter…"
|
|
2070
|
+
onChange={(d) =>
|
|
2071
|
+
updateFilterRow(rendered.column.id, toIsoDateLocal(d))}
|
|
2072
|
+
/>
|
|
2073
|
+
{/if}
|
|
1954
2074
|
{:else if activeOperator !== "isBlank" && activeOperator !== "isNotBlank"}
|
|
1955
2075
|
{@const frType = getEditorInputType(
|
|
1956
2076
|
rendered.column.columnDef.editorType ?? "text",
|
|
@@ -3002,6 +3122,31 @@
|
|
|
3002
3122
|
{#if ctrl.chartingEnabled && ctrl.chartPanelOpen && ChartPanelView}
|
|
3003
3123
|
<ChartPanelView {ctrl} />
|
|
3004
3124
|
{/if}
|
|
3125
|
+
|
|
3126
|
+
<!-- Bulk-action bar. INSIDE the root, unlike GridMenus: it is positioned
|
|
3127
|
+
`absolute` against it, so moving it outside would anchor it to the
|
|
3128
|
+
page. The renderer is Enterprise and arrives through the
|
|
3129
|
+
selection-bar-view seam; without it, a short note in its place. -->
|
|
3130
|
+
{#if ctrl.selectionBarOn && ctrl.selectionBarTarget.ids.length > 0}
|
|
3131
|
+
{#if getSelectionBarView()}
|
|
3132
|
+
{@const SelectionBarView = getSelectionBarView()}
|
|
3133
|
+
<SelectionBarView {ctrl} />
|
|
3134
|
+
{:else}
|
|
3135
|
+
<div
|
|
3136
|
+
class="sv-grid-scheduler-upsell sv-grid-selection-bar-upsell"
|
|
3137
|
+
data-position={ctrl.selectionBarPosition}
|
|
3138
|
+
role="note"
|
|
3139
|
+
>
|
|
3140
|
+
<strong>Bulk-action bar</strong>
|
|
3141
|
+
<p>
|
|
3142
|
+
The selection bar is an Enterprise feature. Install
|
|
3143
|
+
<code>@svgrid/enterprise</code> and call <code>enableSelectionBar()</code>
|
|
3144
|
+
to render it.
|
|
3145
|
+
</p>
|
|
3146
|
+
{@render enterpriseLicenseNote()}
|
|
3147
|
+
</div>
|
|
3148
|
+
{/if}
|
|
3149
|
+
{/if}
|
|
3005
3150
|
</div>
|
|
3006
3151
|
<!-- /.sv-grid-root -->
|
|
3007
3152
|
|