pict-section-recordset 1.1.0 → 1.3.0
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 +5 -1
- package/source/views/RecordSet-Filters.js +196 -72
- package/source/views/filters/RecordSet-Filter-Base.js +86 -8
- package/source/views/read/RecordSet-Read.js +308 -2
- package/types/providers/Filter-Data-Provider.d.ts +1 -1
- package/types/providers/Filter-Data-Provider.d.ts.map +1 -1
- package/types/views/Filter-PersistenceView.d.ts +23 -2
- package/types/views/Filter-PersistenceView.d.ts.map +1 -1
- package/types/views/RecordSet-Filters.d.ts +54 -2
- package/types/views/RecordSet-Filters.d.ts.map +1 -1
- package/types/views/filters/RecordSet-Filter-Base.d.ts +14 -0
- package/types/views/filters/RecordSet-Filter-Base.d.ts.map +1 -1
- package/types/views/list/RecordSet-List.d.ts.map +1 -1
- package/types/views/read/RecordSet-Read.d.ts +51 -0
- package/types/views/read/RecordSet-Read.d.ts.map +1 -1
- package/.vscode/launch.json +0 -46
- package/CONTRIBUTING.md +0 -50
- package/debug/Harness.js +0 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +0 -76
- package/docs/_brand.json +0 -18
- package/docs/_cover.md +0 -11
- package/docs/_sidebar.md +0 -19
- package/docs/_version.json +0 -7
- package/docs/api-reference.md +0 -233
- package/docs/filters.md +0 -151
- package/docs/index.html +0 -38
- package/docs/record-providers.md +0 -155
- package/docs/retold-catalog.json +0 -87
- package/docs/retold-keyword-index.json +0 -5227
- package/docs/views/create/README.md +0 -181
- package/docs/views/dashboard/README.md +0 -308
- package/docs/views/list/README.md +0 -260
- package/docs/views/read/README.md +0 -216
- package/eslint.config.mjs +0 -10
- package/example_applications/README.md +0 -39
- package/example_applications/ServeExamples.js +0 -82
- package/example_applications/bookstore/.quackage.json +0 -9
- package/example_applications/bookstore/Bookstore-Application-Configuration.json +0 -4
- package/example_applications/bookstore/Bookstore-Application.js +0 -671
- package/example_applications/bookstore/css/bookstore.css +0 -729
- package/example_applications/bookstore/css/pure.min.css +0 -11
- package/example_applications/bookstore/html/index.html +0 -46
- package/example_applications/bookstore/package.json +0 -34
- package/example_applications/bookstore/providers/PictRouter-Bookstore.json +0 -32
- package/example_applications/bookstore/views/PictView-Bookstore-Content-About.json +0 -21
- package/example_applications/bookstore/views/PictView-Bookstore-Content-Legal.json +0 -21
- package/example_applications/bookstore/views/PictView-Bookstore-Dashboard.js +0 -147
- package/example_applications/bookstore/views/PictView-Bookstore-Layout.js +0 -85
- package/example_applications/bookstore/views/PictView-Bookstore-Login.js +0 -58
- package/example_applications/bookstore/views/PictView-Bookstore-Navigation.js +0 -228
- package/example_applications/index.html +0 -50
- package/example_applications/mocks/book-edit-view.html +0 -173
- package/example_applications/mocks/book-read-view.html +0 -166
- package/example_applications/mocks/list-view.html +0 -185
- package/example_applications/package.json +0 -16
- package/example_applications/simple_entity/.quackage.json +0 -9
- package/example_applications/simple_entity/README-Simple-RecordSet.md +0 -8
- package/example_applications/simple_entity/Simple-RecordSet-Application.js +0 -887
- package/example_applications/simple_entity/html/index.html +0 -207
- package/example_applications/simple_entity/package.json +0 -27
- package/test/PictSectionRecordSet-Basic_tests.js +0 -205
- package/test/PictSectionRecordSet-Filter-Data-Provider_tests.js +0 -263
- package/test/PictSectionRecordSet-Filter-InstanceViews-Render_tests.js +0 -328
- package/test/PictSectionRecordSet-RecordProvider-Meadow_tests.js +0 -216
- package/tsconfig.build.json +0 -16
- package/tsconfig.json +0 -16
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pict-section-recordset",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Pict dynamic record set management views",
|
|
5
5
|
"main": "source/Pict-Section-RecordSet.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"source",
|
|
8
|
+
"types"
|
|
9
|
+
],
|
|
6
10
|
"directories": {
|
|
7
11
|
"test": "test"
|
|
8
12
|
},
|
|
@@ -58,12 +58,36 @@ const _DEFAULT_CONFIGURATION_SUBSET_Filter =
|
|
|
58
58
|
.prsp-filters-drawer-inner { overflow: hidden; min-height: 0; }
|
|
59
59
|
.prsp-filters.drawer-open .prsp-filters-drawer-inner { margin-top: 0.6rem; padding: 0.95rem 1.1rem;
|
|
60
60
|
border: 1px solid var(--theme-color-border-light, #e8ebf0); border-radius: 10px; background: var(--theme-color-background-panel, #fff); }
|
|
61
|
-
.prsp-filters-add { margin: 0.4rem 0 0.2rem; }
|
|
61
|
+
.prsp-filters-add { position: relative; margin: 0.4rem 0 0.2rem; }
|
|
62
|
+
.prsp-addfilter-trigger { display: inline-flex; align-items: center; gap: 0.35rem; }
|
|
62
63
|
/* Drawer footer: filter experience on the left, Clear/Reset/Apply on the right. */
|
|
63
64
|
.prsp-filters-footer { display: flex; align-items: flex-end; justify-content: space-between; gap: 1.5rem; flex-wrap: wrap;
|
|
64
65
|
margin-top: 0.85rem; padding-top: 0.75rem; border-top: 1px solid var(--theme-color-border-light, #e8ebf0); }
|
|
65
66
|
.prsp-filters-experiences { flex: 0 1 auto; min-width: 0; }
|
|
66
67
|
.prsp-filters-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 0.5rem; }
|
|
68
|
+
|
|
69
|
+
/* Module-owned "Add filter" popover (replaces the old native <select> pickers). */
|
|
70
|
+
.prsp-addfilter-pop { position: absolute; z-index: 30; top: calc(100% + 0.35rem); left: 0; min-width: 250px; max-width: 340px; display: none; }
|
|
71
|
+
.prsp-addfilter-pop.open { display: block; }
|
|
72
|
+
.prsp-addfilter-panel { background: var(--theme-color-background-panel, #fff); border: 1px solid var(--theme-color-border-default, #d7dce3);
|
|
73
|
+
border-radius: 10px; box-shadow: 0 10px 28px rgba(17, 24, 39, 0.14); overflow: hidden; }
|
|
74
|
+
.prsp-addfilter-search { display: flex; align-items: center; gap: 0.4rem; padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--theme-color-border-light, #e8ebf0); }
|
|
75
|
+
.prsp-addfilter-search-ic { display: inline-flex; color: var(--theme-color-text-muted, #6b7686); font-size: 0.9rem; }
|
|
76
|
+
.prsp-addfilter-search input { flex: 1 1 auto; min-width: 0; font: inherit; font-size: 0.9rem; border: none; outline: none; background: transparent; color: var(--theme-color-text-primary, #1f2733); }
|
|
77
|
+
.prsp-addfilter-list { max-height: 280px; overflow-y: auto; }
|
|
78
|
+
.prsp-addfilter-empty { padding: 0.7rem 0.8rem; color: var(--theme-color-text-muted, #6b7686); font-size: 0.86rem; }
|
|
79
|
+
.prsp-addfilter-field { border-bottom: 1px solid var(--theme-color-border-light, #eef1f5); }
|
|
80
|
+
.prsp-addfilter-field:last-child { border-bottom: none; }
|
|
81
|
+
.prsp-addfilter-field-btn { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; width: 100%;
|
|
82
|
+
font: inherit; font-size: 0.9rem; text-align: left; cursor: pointer; padding: 0.5rem 0.75rem; border: none; background: transparent; color: var(--theme-color-text-primary, #1f2733); }
|
|
83
|
+
.prsp-addfilter-field-btn:hover { background: var(--theme-color-background-tertiary, #eceef2); }
|
|
84
|
+
.prsp-addfilter-chev { display: inline-flex; flex: 0 0 auto; color: var(--theme-color-text-muted, #6b7686); font-size: 0.85rem; transition: transform 0.15s ease; }
|
|
85
|
+
.prsp-addfilter-field.is-expanded .prsp-addfilter-chev { transform: rotate(90deg); }
|
|
86
|
+
.prsp-addfilter-clauses { display: flex; flex-direction: column; }
|
|
87
|
+
.prsp-addfilter-clause { display: flex; align-items: center; gap: 0.4rem; width: 100%; text-align: left; cursor: pointer;
|
|
88
|
+
font: inherit; font-size: 0.85rem; padding: 0.4rem 0.8rem 0.4rem 1.6rem; border: none; background: transparent; color: var(--theme-color-text-secondary, #45505f); }
|
|
89
|
+
.prsp-addfilter-clause:hover { background: color-mix(in srgb, var(--theme-color-brand-primary, #156dd1) 10%, transparent); color: var(--theme-color-brand-primary, #156dd1); }
|
|
90
|
+
.prsp-addfilter-clause-ic { display: inline-flex; font-size: 0.8rem; }
|
|
67
91
|
`,
|
|
68
92
|
CSSPriority: 500,
|
|
69
93
|
|
|
@@ -149,54 +173,62 @@ const _DEFAULT_CONFIGURATION_SUBSET_Filter =
|
|
|
149
173
|
Template: /*html*/`
|
|
150
174
|
<!-- DefaultPackage pict view template: [PRSP-SUBSET-Filters-Template-AddFilter-Fieldset] -->
|
|
151
175
|
<div class="prsp-filters-add">
|
|
152
|
-
<button type="button" class="prsp-filters-btn-text" id="PRSP_Filter_Button_Add" title="Add a new filter clause" onclick="_Pict.views['PRSP-Filters'].
|
|
153
|
-
<div
|
|
176
|
+
<button type="button" class="prsp-filters-btn-text prsp-addfilter-trigger" id="PRSP_Filter_Button_Add" title="Add a new filter clause" onclick="_Pict.views['PRSP-Filters'].toggleAddFilterPopover(event, '{~D:Record.RecordSet~}', '{~D:Record.ViewContext~}')">{~I:Plus~} Add filter</button>
|
|
177
|
+
<div class="prsp-addfilter-pop" id="PRSP_AddFilter_Popover"></div>
|
|
154
178
|
</div>
|
|
155
179
|
<!-- DefaultPackage end view template: [PRSP-SUBSET-Filters-Template-AddFilter-Fieldset] -->
|
|
156
180
|
`
|
|
157
181
|
},
|
|
158
182
|
{
|
|
159
|
-
Hash: 'PRSP-
|
|
183
|
+
Hash: 'PRSP-AddFilter-Popover',
|
|
160
184
|
Template: /*html*/`
|
|
161
|
-
<!-- DefaultPackage pict view template: [PRSP-
|
|
162
|
-
<div>
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
});">
|
|
170
|
-
{~TS:PRSP-SUBSET-Filters-Template-AddFilter-Dropdown-Entry:Scope.getFilterSchema()~}
|
|
171
|
-
</select>
|
|
172
|
-
<div id="PRSP-SUBSET-Filters-Template-AddFilter-Dropdown-AddFilterClauseDropdown">
|
|
185
|
+
<!-- DefaultPackage pict view template: [PRSP-AddFilter-Popover] -->
|
|
186
|
+
<div class="prsp-addfilter-panel">
|
|
187
|
+
<div class="prsp-addfilter-search">
|
|
188
|
+
<span class="prsp-addfilter-search-ic">{~I:Search~}</span>
|
|
189
|
+
<input type="text" id="PRSP_AddFilter_Search" placeholder="Search filters…" autocomplete="off" value="{~D:AppData.PRSPAddFilter.Search~}" oninput="_Pict.views['PRSP-Filters'].searchAddFilter(this.value)" onkeydown="if (event.key === 'Escape') { event.preventDefault(); _Pict.views['PRSP-Filters'].closeAddFilterPopover(); }">
|
|
190
|
+
</div>
|
|
191
|
+
<div class="prsp-addfilter-list" id="PRSP_AddFilter_List">
|
|
192
|
+
{~T:PRSP-AddFilter-List~}
|
|
173
193
|
</div>
|
|
174
194
|
</div>
|
|
175
|
-
<!-- DefaultPackage end view template:
|
|
195
|
+
<!-- DefaultPackage end view template: [PRSP-AddFilter-Popover] -->
|
|
176
196
|
`
|
|
177
197
|
},
|
|
178
198
|
{
|
|
179
|
-
Hash: 'PRSP-
|
|
199
|
+
Hash: 'PRSP-AddFilter-List',
|
|
180
200
|
Template: /*html*/`
|
|
181
|
-
<!-- DefaultPackage pict view template: [PRSP-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
<button type="button" id="PRSP_Filter_Button_ConfirmAdd" onclick="_Pict.views['PRSP-Filters'].addFilter(event, '{~D:Record.RecordSet~}', '{~D:Record.ViewContext~}',
|
|
186
|
-
document.getElementById('PRSP-SUBSET-Filters-Template-AddFilter-Dropdown-AddFilterClauseDropdown').querySelector('option:checked').getAttribute('data-i-filter-key'),
|
|
187
|
-
document.getElementById('PRSP-SUBSET-Filters-Template-AddFilter-Dropdown-AddFilterClauseDropdown').querySelector('option:checked').getAttribute('data-i-clause-key'),
|
|
188
|
-
)">Add Filter</button>
|
|
189
|
-
<!-- DefaultPackage end view template: [PRSP-SUBSET-Filters-Template-AddFilter-Dropdown-AddFilterClauseDropdown] -->
|
|
201
|
+
<!-- DefaultPackage pict view template: [PRSP-AddFilter-List] -->
|
|
202
|
+
{~TS:PRSP-AddFilter-Field:AppData.PRSPAddFilter.Fields~}
|
|
203
|
+
{~NE:AppData.PRSPAddFilter.IsEmpty^<div class="prsp-addfilter-empty">No filters found.</div>~}
|
|
204
|
+
<!-- DefaultPackage end view template: [PRSP-AddFilter-List] -->
|
|
190
205
|
`
|
|
191
206
|
},
|
|
192
207
|
{
|
|
193
|
-
Hash: 'PRSP-
|
|
208
|
+
Hash: 'PRSP-AddFilter-Field',
|
|
194
209
|
Template: /*html*/`
|
|
195
|
-
<!-- DefaultPackage pict view template: [PRSP-
|
|
196
|
-
<
|
|
197
|
-
{~D:Record.
|
|
198
|
-
|
|
199
|
-
|
|
210
|
+
<!-- DefaultPackage pict view template: [PRSP-AddFilter-Field] -->
|
|
211
|
+
<div class="prsp-addfilter-field {~D:Record.ExpandedClass~}">
|
|
212
|
+
<button type="button" class="prsp-addfilter-field-btn" onclick="_Pict.views['PRSP-Filters'].toggleAddFilterField('{~D:Record.FilterKey~}')">
|
|
213
|
+
<span class="prsp-addfilter-field-name">{~D:Record.DisplayName~}</span>
|
|
214
|
+
<span class="prsp-addfilter-chev">{~I:ChevronRight~}</span>
|
|
215
|
+
</button>
|
|
216
|
+
<div class="prsp-addfilter-clauses">
|
|
217
|
+
{~TS:PRSP-AddFilter-Clause:Record.ClausesToShow~}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
<!-- DefaultPackage end view template: [PRSP-AddFilter-Field] -->
|
|
221
|
+
`
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
Hash: 'PRSP-AddFilter-Clause',
|
|
225
|
+
Template: /*html*/`
|
|
226
|
+
<!-- DefaultPackage pict view template: [PRSP-AddFilter-Clause] -->
|
|
227
|
+
<button type="button" class="prsp-addfilter-clause" onclick="_Pict.views['PRSP-Filters'].addFilter(event, '{~D:AppData.PRSPAddFilter.RecordSet~}', '{~D:AppData.PRSPAddFilter.ViewContext~}', '{~D:Record.FilterKey~}', '{~D:Record.ClauseKey~}')">
|
|
228
|
+
<span class="prsp-addfilter-clause-ic">{~I:Plus~}</span>
|
|
229
|
+
<span>{~D:Record.DisplayName~}</span>
|
|
230
|
+
</button>
|
|
231
|
+
<!-- DefaultPackage end view template: [PRSP-AddFilter-Clause] -->
|
|
200
232
|
`
|
|
201
233
|
},
|
|
202
234
|
],
|
|
@@ -210,15 +242,15 @@ const _DEFAULT_CONFIGURATION_SUBSET_Filter =
|
|
|
210
242
|
RenderMethod: 'replace'
|
|
211
243
|
},
|
|
212
244
|
{
|
|
213
|
-
RenderableHash: '
|
|
214
|
-
TemplateHash: 'PRSP-
|
|
215
|
-
ContentDestinationAddress: '#
|
|
245
|
+
RenderableHash: 'PRSP_AddFilter_Popover',
|
|
246
|
+
TemplateHash: 'PRSP-AddFilter-Popover',
|
|
247
|
+
ContentDestinationAddress: '#PRSP_AddFilter_Popover',
|
|
216
248
|
RenderMethod: 'replace',
|
|
217
249
|
},
|
|
218
250
|
{
|
|
219
|
-
RenderableHash: '
|
|
220
|
-
TemplateHash: 'PRSP-
|
|
221
|
-
ContentDestinationAddress: '#
|
|
251
|
+
RenderableHash: 'PRSP_AddFilter_List',
|
|
252
|
+
TemplateHash: 'PRSP-AddFilter-List',
|
|
253
|
+
ContentDestinationAddress: '#PRSP_AddFilter_List',
|
|
222
254
|
RenderMethod: 'replace',
|
|
223
255
|
},
|
|
224
256
|
],
|
|
@@ -315,6 +347,15 @@ class ViewRecordSetSUBSETFilters extends libPictView
|
|
|
315
347
|
// stale callback doesn't clobber DOM that now belongs to a different
|
|
316
348
|
// filter experience.
|
|
317
349
|
this._renderEpoch = 0;
|
|
350
|
+
// Add-filter popover state (module-owned, replaces the old native <select> pickers).
|
|
351
|
+
this._addFilterOpen = false;
|
|
352
|
+
this._addFilterRecordSet = null;
|
|
353
|
+
this._addFilterViewContext = null;
|
|
354
|
+
this._addFilterSearch = '';
|
|
355
|
+
this._addFilterExpandedKey = null;
|
|
356
|
+
// FilterKeys to hide from the add-filter popover (e.g. internal/audit columns). Host apps
|
|
357
|
+
// set this; it is merged with a per-record-set config `FilterFieldBlacklist`.
|
|
358
|
+
this.filterFieldBlacklist = [];
|
|
318
359
|
}
|
|
319
360
|
|
|
320
361
|
/**
|
|
@@ -617,12 +658,96 @@ class ViewRecordSetSUBSETFilters extends libPictView
|
|
|
617
658
|
* @param {string} pRecordSet - The record set being filtered
|
|
618
659
|
* @param {string} pViewContext - The view context for the filter (ex. List, Dashboard)
|
|
619
660
|
*/
|
|
620
|
-
|
|
661
|
+
toggleAddFilterPopover(pEvent, pRecordSet, pViewContext)
|
|
621
662
|
{
|
|
622
|
-
if (pEvent) pEvent.preventDefault();
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
663
|
+
if (pEvent) { pEvent.preventDefault(); }
|
|
664
|
+
if (this._addFilterOpen)
|
|
665
|
+
{
|
|
666
|
+
return this.closeAddFilterPopover();
|
|
667
|
+
}
|
|
668
|
+
this._addFilterOpen = true;
|
|
669
|
+
this._addFilterRecordSet = pRecordSet;
|
|
670
|
+
this._addFilterViewContext = pViewContext;
|
|
671
|
+
this._addFilterSearch = '';
|
|
672
|
+
this._addFilterExpandedKey = null;
|
|
673
|
+
this._buildAddFilterFields(pRecordSet);
|
|
674
|
+
this.render('PRSP_AddFilter_Popover', undefined, { RecordSet: pRecordSet, ViewContext: pViewContext });
|
|
675
|
+
this._paintAddFilterOpenState();
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** Close the add-filter popover. */
|
|
679
|
+
closeAddFilterPopover()
|
|
680
|
+
{
|
|
681
|
+
this._addFilterOpen = false;
|
|
682
|
+
this._addFilterExpandedKey = null;
|
|
683
|
+
this._paintAddFilterOpenState();
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Filter the add-filter field list by a search term, re-rendering only the list so the
|
|
688
|
+
* search input keeps focus.
|
|
689
|
+
* @param {string} pValue - The search term.
|
|
690
|
+
*/
|
|
691
|
+
searchAddFilter(pValue)
|
|
692
|
+
{
|
|
693
|
+
this._addFilterSearch = pValue || '';
|
|
694
|
+
this._buildAddFilterFields(this._addFilterRecordSet);
|
|
695
|
+
this.render('PRSP_AddFilter_List', undefined, { RecordSet: this._addFilterRecordSet, ViewContext: this._addFilterViewContext });
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Expand or collapse a field's available clauses in the add-filter popover.
|
|
700
|
+
* @param {string} pFilterKey - The field whose clauses to toggle.
|
|
701
|
+
*/
|
|
702
|
+
toggleAddFilterField(pFilterKey)
|
|
703
|
+
{
|
|
704
|
+
this._addFilterExpandedKey = (this._addFilterExpandedKey === pFilterKey) ? null : pFilterKey;
|
|
705
|
+
this._buildAddFilterFields(this._addFilterRecordSet);
|
|
706
|
+
this.render('PRSP_AddFilter_List', undefined, { RecordSet: this._addFilterRecordSet, ViewContext: this._addFilterViewContext });
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* (Re)build the add-filter popover's field list into AppData from the record set's filter
|
|
711
|
+
* schema, honouring the current search term and the expanded field.
|
|
712
|
+
* @param {string} pRecordSet - The record set whose filter schema to read.
|
|
713
|
+
*/
|
|
714
|
+
_buildAddFilterFields(pRecordSet)
|
|
715
|
+
{
|
|
716
|
+
const tmpProvider = this.pict.providers[`RSP-Provider-${pRecordSet}`];
|
|
717
|
+
const tmpSchema = (tmpProvider && typeof tmpProvider.getFilterSchema === 'function') ? tmpProvider.getFilterSchema() : {};
|
|
718
|
+
const tmpRecordSetConfig = this.pict.PictSectionRecordSet?.recordSetProviderConfigurations?.[pRecordSet] || {};
|
|
719
|
+
const tmpBlacklist = [].concat(this.filterFieldBlacklist || [], Array.isArray(tmpRecordSetConfig.FilterFieldBlacklist) ? tmpRecordSetConfig.FilterFieldBlacklist : []);
|
|
720
|
+
const tmpSearch = (this._addFilterSearch || '').toLowerCase();
|
|
721
|
+
const tmpFields = Object.values(tmpSchema)
|
|
722
|
+
.filter((pField) => Array.isArray(pField.AvailableClauses) && pField.AvailableClauses.length > 0)
|
|
723
|
+
.filter((pField) => !tmpBlacklist.includes(pField.FilterKey))
|
|
724
|
+
.filter((pField) => !tmpSearch || String(pField.DisplayName || pField.FilterKey).toLowerCase().includes(tmpSearch))
|
|
725
|
+
.sort((pA, pB) => String(pA.DisplayName || pA.FilterKey).localeCompare(String(pB.DisplayName || pB.FilterKey)))
|
|
726
|
+
.map((pField) =>
|
|
727
|
+
{
|
|
728
|
+
const tmpExpanded = (pField.FilterKey === this._addFilterExpandedKey);
|
|
729
|
+
return {
|
|
730
|
+
FilterKey: pField.FilterKey,
|
|
731
|
+
DisplayName: pField.DisplayName || pField.FilterKey,
|
|
732
|
+
ExpandedClass: tmpExpanded ? 'is-expanded' : '',
|
|
733
|
+
ClausesToShow: tmpExpanded ? pField.AvailableClauses : [],
|
|
734
|
+
};
|
|
735
|
+
});
|
|
736
|
+
this.pict.AppData.PRSPAddFilter =
|
|
737
|
+
{
|
|
738
|
+
RecordSet: pRecordSet,
|
|
739
|
+
ViewContext: this._addFilterViewContext,
|
|
740
|
+
Search: this._addFilterSearch || '',
|
|
741
|
+
IsEmpty: tmpFields.length === 0,
|
|
742
|
+
Fields: tmpFields,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/** Reflect the add-filter popover's open/closed state on its container element. */
|
|
747
|
+
_paintAddFilterOpenState()
|
|
748
|
+
{
|
|
749
|
+
const tmpPopover = document.getElementById('PRSP_AddFilter_Popover');
|
|
750
|
+
if (tmpPopover) { tmpPopover.classList.toggle('open', !!this._addFilterOpen); }
|
|
626
751
|
}
|
|
627
752
|
|
|
628
753
|
/**
|
|
@@ -681,11 +806,29 @@ class ViewRecordSetSUBSETFilters extends libPictView
|
|
|
681
806
|
*/
|
|
682
807
|
onBeforeRender(pRenderable)
|
|
683
808
|
{
|
|
809
|
+
// Re-assert ONLY the consolidated-control chrome (the wrapper, search row, filter toggle +
|
|
810
|
+
// Search button, the Filter Experiences container, and Clear/Reset/Apply) so the module owns
|
|
811
|
+
// that markup. Deliberately NOT the add-filter dropdown or per-clause templates: host apps
|
|
812
|
+
// (e.g. Headlight) layer their own styled add-filter popover and clause UI on top, and
|
|
813
|
+
// re-asserting those clobbers them — re-exposing bare native <select> dropdowns and a dead
|
|
814
|
+
// remove button. Those host overrides register at init and must survive each re-render.
|
|
815
|
+
const tmpChromeTemplateHashes =
|
|
816
|
+
[
|
|
817
|
+
'PRSP-SUBSET-Filters-Template',
|
|
818
|
+
'PRSP-SUBSET-Filters-Template-Input-Fieldset',
|
|
819
|
+
'PRSP-SUBSET-Filters-Template-Button-Fieldset',
|
|
820
|
+
'PRSP-SUBSET-Filters-Template-ManageFilters-Fieldset',
|
|
821
|
+
'PRSP-SUBSET-Filters-Template-DrawerActions-Fieldset'
|
|
822
|
+
];
|
|
684
823
|
if (this.pict.TemplateProvider && Array.isArray(_DEFAULT_CONFIGURATION_SUBSET_Filter.Templates))
|
|
685
824
|
{
|
|
686
|
-
for (const
|
|
825
|
+
for (const tmpTemplateHash of tmpChromeTemplateHashes)
|
|
687
826
|
{
|
|
688
|
-
|
|
827
|
+
const tmpTemplate = _DEFAULT_CONFIGURATION_SUBSET_Filter.Templates.find((pTemplate) => pTemplate.Hash === tmpTemplateHash);
|
|
828
|
+
if (tmpTemplate)
|
|
829
|
+
{
|
|
830
|
+
this.pict.TemplateProvider.addTemplate(tmpTemplate.Hash, tmpTemplate.Template);
|
|
831
|
+
}
|
|
689
832
|
}
|
|
690
833
|
}
|
|
691
834
|
return super.onBeforeRender(pRenderable);
|
|
@@ -698,34 +841,15 @@ class ViewRecordSetSUBSETFilters extends libPictView
|
|
|
698
841
|
onAfterRender(pRenderable)
|
|
699
842
|
{
|
|
700
843
|
const res = super.onAfterRender(pRenderable);
|
|
701
|
-
|
|
844
|
+
// Add-filter popover sub-renders (the panel + its list) only repaint that widget — skip the
|
|
845
|
+
// heavy post-render pass below and just keep the open/closed class in sync.
|
|
846
|
+
if (pRenderable?.RenderableHash === 'PRSP_AddFilter_Popover' || pRenderable?.RenderableHash === 'PRSP_AddFilter_List')
|
|
702
847
|
{
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const tmpRecord = { };
|
|
706
|
-
const tmpSelect = document.getElementById('PRSP-SUBSET-Filters-Template-AddFilter-Dropdown-Select');
|
|
707
|
-
if (tmpSelect)
|
|
708
|
-
{
|
|
709
|
-
const tmpActiveOption = document.getElementById('PRSP-SUBSET-Filters-Template-AddFilter-Dropdown-Select')?.querySelector('option:checked');
|
|
710
|
-
const tmpRecordSet = tmpActiveOption?.getAttribute('data-i-recordset');
|
|
711
|
-
const tmpFilterKey = tmpActiveOption?.getAttribute('data-i-filter-key');
|
|
712
|
-
const tmpViewContext = tmpSelect?.getAttribute('data-i-view-context');
|
|
713
|
-
if (tmpRecordSet && tmpFilterKey)
|
|
714
|
-
{
|
|
715
|
-
const tmpProvider = this.pict.providers[`RSP-Provider-${tmpRecordSet}`];
|
|
716
|
-
if (tmpProvider)
|
|
717
|
-
{
|
|
718
|
-
tmpRecord.RecordSet = tmpRecordSet;
|
|
719
|
-
tmpRecord.FilterKey = tmpFilterKey;
|
|
720
|
-
tmpRecord.ViewContext = tmpViewContext;
|
|
721
|
-
tmpRecord.AvailableClauses = tmpProvider.getFilterClauseSchemaForKey(tmpFilterKey).AvailableClauses;
|
|
722
|
-
if (Array.isArray(tmpRecord.AvailableClauses))
|
|
723
|
-
{
|
|
724
|
-
this.render('PRSP-SUBSET-Filters-Template-AddFilter-Dropdown-AddFilterClauseDropdown', undefined, tmpRecord, pRenderable);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
}
|
|
848
|
+
this._paintAddFilterOpenState();
|
|
849
|
+
return res;
|
|
728
850
|
}
|
|
851
|
+
// Any full re-render rebuilds the add-filter slot, so the popover is closed — reset its state.
|
|
852
|
+
this._addFilterOpen = false;
|
|
729
853
|
this.onMarshalToView();
|
|
730
854
|
|
|
731
855
|
// NOTE: This is where we ensure the filter experience is applied after a render.
|
|
@@ -20,7 +20,49 @@ const _DEFAULT_CONFIGURATION_SUBSET_Filter =
|
|
|
20
20
|
AutoSolveWithApp: false,
|
|
21
21
|
AutoSolveOrdinal: 0,
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// Themeable filter-clause chrome. Every filter type renders through the shared
|
|
24
|
+
// PRSP-Filter-Base-Template wrapper below (each type only plugs in its own value
|
|
25
|
+
// input via getFilterFormTemplate()), so styling it here themes every clause for
|
|
26
|
+
// every host app. This view (PRSP-FilterType-Base) is always constructed, and
|
|
27
|
+
// pict-view registers a view's CSS into the global cascade at construction, so
|
|
28
|
+
// these rules apply to all clauses regardless of which type rendered them.
|
|
29
|
+
//
|
|
30
|
+
// Host apps brand by defining the --theme-color-* tokens; the hardcoded values are
|
|
31
|
+
// sensible fallbacks. CSS-first: native <select>s are de-nativized with
|
|
32
|
+
// appearance:none + a custom chevron rather than swapped for a combobox widget
|
|
33
|
+
// (that is a later upgrade with cross-browser quirks of its own).
|
|
34
|
+
CSS: /*css*/`
|
|
35
|
+
.prsp-filters-clauses { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
36
|
+
|
|
37
|
+
.prsp-filter { display: flex; align-items: flex-end; gap: 0.5rem; }
|
|
38
|
+
.prsp-filter *, .prsp-filter *::before, .prsp-filter *::after { box-sizing: border-box; }
|
|
39
|
+
.prsp-filter-body { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.4rem 0.75rem; }
|
|
40
|
+
|
|
41
|
+
/* Inputs rendered by pict-section-form inside a clause. Match the consolidated
|
|
42
|
+
control's search box so clauses stop looking like bare native controls. */
|
|
43
|
+
.prsp-filter label { font: inherit; font-size: 0.78rem; font-weight: 600; display: block;
|
|
44
|
+
margin: 0 0 0.2rem; color: var(--theme-color-text-secondary, #45505f); }
|
|
45
|
+
.prsp-filter input, .prsp-filter select, .prsp-filter textarea { font: inherit; font-size: 0.92rem; width: 100%;
|
|
46
|
+
padding: 0.45rem 0.7rem; border-radius: 8px; border: 1px solid var(--theme-color-border-default, #d7dce3);
|
|
47
|
+
background: var(--theme-color-background-primary, #fff); color: var(--theme-color-text-primary, #1f2733); }
|
|
48
|
+
.prsp-filter input:focus, .prsp-filter select:focus, .prsp-filter textarea:focus { outline: none;
|
|
49
|
+
border-color: var(--theme-color-brand-primary, #156dd1);
|
|
50
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--theme-color-brand-primary, #156dd1) 16%, transparent); }
|
|
51
|
+
/* De-nativize the <select>: drop the platform arrow, add a themed chevron + room for it. */
|
|
52
|
+
.prsp-filter select { appearance: none; -webkit-appearance: none; -moz-appearance: none; cursor: pointer;
|
|
53
|
+
padding-right: 1.9rem;
|
|
54
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7686' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
|
55
|
+
background-repeat: no-repeat; background-position: right 0.6rem center; background-size: 1.05em; }
|
|
56
|
+
|
|
57
|
+
/* Remove (trash) control — one per clause, inherited by every filter type. */
|
|
58
|
+
.prsp-filter-remove { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
|
|
59
|
+
width: 2.05rem; height: 2.05rem; padding: 0; cursor: pointer; line-height: 1;
|
|
60
|
+
border-radius: 8px; border: 1px solid var(--theme-color-border-default, #d7dce3);
|
|
61
|
+
background: var(--theme-color-background-primary, #fff); color: var(--theme-color-text-muted, #6b7686); }
|
|
62
|
+
.prsp-filter-remove:hover { border-color: var(--theme-color-status-error, #d64545); color: var(--theme-color-status-error, #d64545);
|
|
63
|
+
background: color-mix(in srgb, var(--theme-color-status-error, #d64545) 12%, transparent); }
|
|
64
|
+
.prsp-filter-remove .pict-icon { font-size: 1.05rem; }
|
|
65
|
+
`,
|
|
24
66
|
CSSPriority: 500,
|
|
25
67
|
|
|
26
68
|
Manifests: {},
|
|
@@ -38,16 +80,21 @@ const _DEFAULT_CONFIGURATION_SUBSET_Filter =
|
|
|
38
80
|
`
|
|
39
81
|
},
|
|
40
82
|
{
|
|
83
|
+
// The shared clause wrapper for every filter type. The type view plugs its own
|
|
84
|
+
// value input in via getFilterFormTemplate(); the themed remove control is
|
|
85
|
+
// inherited here (calls the base removeClause() method, reached through the
|
|
86
|
+
// always-registered PRSP-FilterType-<Type> view) so no host app re-implements one.
|
|
41
87
|
Hash: 'PRSP-Filter-Base-Template',
|
|
42
88
|
Template: /*html*/`
|
|
43
89
|
<!-- DefaultPackage pict view template: [PRSP-Filter-Base-Template] -->
|
|
44
|
-
<div id="PRSP_Filter_Container_{~D:Record.Hash~}">
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
90
|
+
<div class="prsp-filter" id="PRSP_Filter_Container_{~D:Record.Hash~}" data-i-filter-hash="{~D:Record.Hash~}">
|
|
91
|
+
<div class="prsp-filter-body">
|
|
92
|
+
{~TBR:Context[0].getFilterFormTemplate()~}
|
|
93
|
+
</div>
|
|
94
|
+
<button type="button" class="prsp-filter-remove" title="Remove this filter" aria-label="Remove this filter"
|
|
95
|
+
onclick="_Pict.views['PRSP-FilterType-{~D:Record.Type~}'].removeClause(event, '{~D:Record.RecordSet~}', '{~D:Record.ViewContext~}', '{~D:Record.Hash~}');">
|
|
96
|
+
{~I:Trash~}
|
|
49
97
|
</button>
|
|
50
|
-
{~TBR:Context[0].getFilterFormTemplate()~}
|
|
51
98
|
</div>
|
|
52
99
|
<!-- DefaultPackage end view template: [PRSP-Filter-Base-Template] -->
|
|
53
100
|
`
|
|
@@ -99,6 +146,38 @@ class ViewRecordSetSUBSETFilterBase extends libPictView
|
|
|
99
146
|
return 'PRSP-Filter-Base-Form';
|
|
100
147
|
}
|
|
101
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Remove this filter clause from its record set and re-render the filter control.
|
|
151
|
+
*
|
|
152
|
+
* Lives on the base view so every filter type inherits a single, themeable remove
|
|
153
|
+
* affordance and no host application has to re-implement one. Delegates to the
|
|
154
|
+
* consolidated control view (the canonical re-render path) and falls back to the
|
|
155
|
+
* record-set provider directly if that view is somehow unavailable.
|
|
156
|
+
*
|
|
157
|
+
* @param {Event} pEvent - The DOM event that triggered the removal.
|
|
158
|
+
* @param {string} pRecordSet - The record set being filtered.
|
|
159
|
+
* @param {string} pViewContext - The view context for the filter (e.g. List, Dashboard).
|
|
160
|
+
* @param {string} pHash - The hash of the specific filter clause to remove.
|
|
161
|
+
*/
|
|
162
|
+
removeClause(pEvent, pRecordSet, pViewContext, pHash)
|
|
163
|
+
{
|
|
164
|
+
if (pEvent)
|
|
165
|
+
{
|
|
166
|
+
pEvent.preventDefault();
|
|
167
|
+
}
|
|
168
|
+
const tmpFiltersView = this.pict.views['PRSP-Filters'];
|
|
169
|
+
if (tmpFiltersView && typeof tmpFiltersView.removeFilter === 'function')
|
|
170
|
+
{
|
|
171
|
+
return tmpFiltersView.removeFilter(pEvent, pRecordSet, pViewContext, pHash);
|
|
172
|
+
}
|
|
173
|
+
// Fallback: the consolidated control view isn't present, so remove directly.
|
|
174
|
+
const tmpProvider = this.pict.providers[`RSP-Provider-${pRecordSet}`];
|
|
175
|
+
if (tmpProvider && typeof tmpProvider.removeFilterClause === 'function')
|
|
176
|
+
{
|
|
177
|
+
tmpProvider.removeFilterClause(pHash);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
102
181
|
/**
|
|
103
182
|
* @return {string} - The prefix for the informary address.
|
|
104
183
|
*/
|
|
@@ -121,4 +200,3 @@ class ViewRecordSetSUBSETFilterBase extends libPictView
|
|
|
121
200
|
module.exports = ViewRecordSetSUBSETFilterBase;
|
|
122
201
|
|
|
123
202
|
module.exports.default_configuration = _DEFAULT_CONFIGURATION_SUBSET_Filter;
|
|
124
|
-
|