pict-section-recordset 1.20.0 → 1.21.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
|
@@ -395,6 +395,32 @@ class PictRecordSetAssociationManager extends libPictProvider
|
|
|
395
395
|
* @param {string|number} pThisID
|
|
396
396
|
* @return {Promise<Array<any>>}
|
|
397
397
|
*/
|
|
398
|
+
/**
|
|
399
|
+
* Does this join row reference a real other-side record? An unset key (undefined / null / '') is
|
|
400
|
+
* not an association; nor is 0 / '0' on an id-keyed side, where 0 is never a real auto-increment id
|
|
401
|
+
* (Meadow ids start at 1). Some hosts park sentinel rows in the join table — e.g. a "default set"
|
|
402
|
+
* keyed by IDProject = 0 that a SynthesizeDefaults hook reads — and those must not surface as broken
|
|
403
|
+
* "#0" rows in the editor. The 0-guard is gated to id-keyed sides (JoinField === IDField) so a
|
|
404
|
+
* name-keyed side, where '0' could pathologically be a real key, is never affected.
|
|
405
|
+
*
|
|
406
|
+
* @param {Record<string, any>} pJoin - a join row.
|
|
407
|
+
* @param {Record<string, any>} pOtherSide - the resolved other side.
|
|
408
|
+
* @return {Boolean}
|
|
409
|
+
*/
|
|
410
|
+
_joinReferencesRealRecord(pJoin, pOtherSide)
|
|
411
|
+
{
|
|
412
|
+
const tmpValue = pJoin[pOtherSide.JoinField];
|
|
413
|
+
if ((tmpValue === undefined) || (tmpValue === null) || (tmpValue === ''))
|
|
414
|
+
{
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
if ((pOtherSide.JoinField === pOtherSide.IDField) && ((tmpValue === 0) || (tmpValue === '0')))
|
|
418
|
+
{
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
|
|
398
424
|
async listAssociatedIDs(pAssociationHash, pThisRecordSetName, pThisID)
|
|
399
425
|
{
|
|
400
426
|
const tmpSides = this.resolveSides(pAssociationHash, pThisRecordSetName);
|
|
@@ -404,8 +430,8 @@ class PictRecordSetAssociationManager extends libPictProvider
|
|
|
404
430
|
}
|
|
405
431
|
const tmpJoins = await this.listJoinRecords(pAssociationHash, pThisRecordSetName, pThisID);
|
|
406
432
|
return tmpJoins
|
|
407
|
-
.
|
|
408
|
-
.
|
|
433
|
+
.filter((pJoin) => this._joinReferencesRealRecord(pJoin, tmpSides.otherSide))
|
|
434
|
+
.map((pJoin) => pJoin[tmpSides.otherSide.JoinField]);
|
|
409
435
|
}
|
|
410
436
|
|
|
411
437
|
/**
|
|
@@ -428,9 +454,10 @@ class PictRecordSetAssociationManager extends libPictProvider
|
|
|
428
454
|
}
|
|
429
455
|
const tmpJoinIDField = this.getJoinIDField(tmpSides.association);
|
|
430
456
|
const tmpJoins = await this.listJoinRecords(pAssociationHash, pThisRecordSetName, pThisID);
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
457
|
+
// Drop sentinel / unset rows (e.g. a "default set" keyed by other-side id 0) so they don't
|
|
458
|
+
// surface as broken "#0" rows; everything downstream works off the real-association set.
|
|
459
|
+
const tmpRealJoins = tmpJoins.filter((pJoin) => this._joinReferencesRealRecord(pJoin, tmpSides.otherSide));
|
|
460
|
+
const tmpOtherIDs = tmpRealJoins.map((pJoin) => pJoin[tmpSides.otherSide.JoinField]);
|
|
434
461
|
|
|
435
462
|
let tmpByID = {};
|
|
436
463
|
if (tmpOtherIDs.length > 0)
|
|
@@ -455,8 +482,7 @@ class PictRecordSetAssociationManager extends libPictProvider
|
|
|
455
482
|
}
|
|
456
483
|
}
|
|
457
484
|
|
|
458
|
-
return
|
|
459
|
-
.filter((pJoin) => (pJoin[tmpSides.otherSide.JoinField] !== undefined && pJoin[tmpSides.otherSide.JoinField] !== null && pJoin[tmpSides.otherSide.JoinField] !== ''))
|
|
485
|
+
return tmpRealJoins
|
|
460
486
|
.map((pJoin) =>
|
|
461
487
|
{
|
|
462
488
|
const tmpOtherID = pJoin[tmpSides.otherSide.JoinField];
|
|
@@ -31,6 +31,12 @@ const _DEFAULT_CONFIGURATION_AssociationEditor = (
|
|
|
31
31
|
DefaultDestinationAddress: '#PRSP_AssociationEditor_Container',
|
|
32
32
|
DefaultTemplateRecordAddress: false,
|
|
33
33
|
|
|
34
|
+
// Show the "Add defaults" affordance when the association has a defaults synthesizer. Set false
|
|
35
|
+
// on an instance where synthesis is directionally meaningless (e.g. anchored on the type side of
|
|
36
|
+
// a "seed a project with default types" synthesizer) — the synthesizer stays registered for the
|
|
37
|
+
// side it applies to; this just hides the button on the side it does not.
|
|
38
|
+
AllowSynthesize: true,
|
|
39
|
+
|
|
34
40
|
AutoInitialize: false,
|
|
35
41
|
AutoInitializeOrdinal: 0,
|
|
36
42
|
AutoRender: false,
|
|
@@ -265,8 +271,10 @@ class viewRecordSetAssociationEditor extends libPictView
|
|
|
265
271
|
// One-or-zero-element slot drives the empty-state line (TS parses inner tags; NE would not).
|
|
266
272
|
EmptySlot: (tmpItems.length === 0) ? [ { EmptyText: `No ${tmpOtherLabel} associated yet — use the search above to add some.` } ] : [],
|
|
267
273
|
PickerMissing: !tmpPickerPresent,
|
|
268
|
-
// The "Add defaults" button shows only when the host registered a defaults synthesizer
|
|
269
|
-
|
|
274
|
+
// The "Add defaults" button shows only when the host registered a defaults synthesizer AND this
|
|
275
|
+
// instance opts in (AllowSynthesize, default true) — an embedding anchored on the side the
|
|
276
|
+
// synthesizer does not apply to sets AllowSynthesize:false to hide it.
|
|
277
|
+
SynthesizeSlot: ((this.options.AllowSynthesize !== false) && this.manager.hasDefaultSynthesizer(this.options.AssociationHash)) ? [ { ViewHash: this.Hash } ] : [],
|
|
270
278
|
};
|
|
271
279
|
|
|
272
280
|
return new Promise((resolve) =>
|
|
@@ -59,6 +59,18 @@ const _DEFAULT_CONFIGURATION__Read = (
|
|
|
59
59
|
.prsp-record-read input.input[readonly], .prsp-record-read input.input[disabled], .prsp-record-read textarea[readonly], .prsp-record-read textarea[disabled], .prsp-record-read select[disabled] { background: transparent !important; border-color: transparent !important; box-shadow: none !important; padding-left: 0 !important; padding-right: 0 !important; height: auto !important; min-height: 0 !important; opacity: 1 !important; cursor: default !important; color: var(--theme-color-text-primary, #1f2733) !important; -webkit-text-fill-color: var(--theme-color-text-primary, #1f2733) !important; font-weight: 600 !important; font-size: 1.1rem !important; }
|
|
60
60
|
.prsp-record-read .label { font-size: 0.68rem !important; font-weight: 600 !important; text-transform: uppercase !important; letter-spacing: 0.05em !important; color: var(--theme-color-text-muted, #6b7686) !important; margin-bottom: 0.05rem !important; }
|
|
61
61
|
.prsp-record-read .section-header { background: var(--theme-color-background-selected, #e3edfb) !important; color: var(--theme-color-brand-primary, #156dd1) !important; font-size: 0.74rem !important; font-weight: 700 !important; text-transform: uppercase !important; letter-spacing: 0.06em !important; padding: 0.4rem 0.8rem !important; border-radius: 6px !important; border-bottom: 0 !important; margin: 1.1rem 0 0.7rem !important; }
|
|
62
|
+
/* Record header — a readable title (the record's own name, via a heuristic) with the entity
|
|
63
|
+
type as an eyebrow, instead of a raw "RecordSet GUIDField [guid]" string. */
|
|
64
|
+
.prsp-read-head { margin: 0 0 1.1rem; }
|
|
65
|
+
.prsp-read-eyebrow { font-size: 0.72rem; font-weight: 650; text-transform: uppercase; letter-spacing: 0.06em; color: var(--theme-color-text-muted, #6b7686); }
|
|
66
|
+
.prsp-read-title { font-size: 1.5rem; font-weight: 700; line-height: 1.2; margin: 0.1rem 0 0; color: var(--theme-color-text-primary, #1f2733); }
|
|
67
|
+
/* Read-view association tabs — pill nav, shared by the Tab and Split layouts (single source;
|
|
68
|
+
layouts style only their own nav container). */
|
|
69
|
+
.psrs-tab { padding: 0.4rem 0.85rem; border: 1px solid var(--theme-color-border-default, #d7dce3); border-radius: 8px; cursor: pointer; font-size: 0.88rem; color: var(--theme-color-text-secondary, #45505f); background: var(--theme-color-background-panel, #fff); user-select: none; transition: all 0.15s ease; }
|
|
70
|
+
.psrs-tab:hover { background: var(--theme-color-background-tertiary, #eceef2); color: var(--theme-color-text-primary, #1f2733); }
|
|
71
|
+
.psrs-tab.is-active { border-color: var(--theme-color-brand-primary, #156dd1); background: var(--theme-color-background-selected, #e3edfb); color: var(--theme-color-brand-primary, #156dd1); font-weight: 600; }
|
|
72
|
+
.psrs-tab-body { display: none; }
|
|
73
|
+
.psrs-tab-body.is-active { display: block; }
|
|
62
74
|
`,
|
|
63
75
|
CSSPriority: 500,
|
|
64
76
|
|
|
@@ -119,11 +131,22 @@ const _DEFAULT_CONFIGURATION__Read = (
|
|
|
119
131
|
Hash: 'PRSP-Read-RecordTabNav-Template',
|
|
120
132
|
Template: /*html*/`<!-- Placeholder for tabs, something has gone wrong if this comment is rendered. -->`
|
|
121
133
|
},
|
|
134
|
+
{
|
|
135
|
+
// Readable record header, shared by all three layouts: the record's display name (heuristic)
|
|
136
|
+
// over a small entity-type eyebrow, falling back to the GUID field + value when the record
|
|
137
|
+
// has no obvious name. DisplayTitle / TitleEyebrow are computed in renderRead().
|
|
138
|
+
Hash: 'PRSP-Read-Header-Template',
|
|
139
|
+
Template: /*html*/`
|
|
140
|
+
<div class="prsp-read-head">
|
|
141
|
+
<div class="prsp-read-eyebrow">{~D:Record.TitleEyebrow~}</div>
|
|
142
|
+
<h1 class="prsp-read-title">{~D:Record.DisplayTitle~}</h1>
|
|
143
|
+
</div>`
|
|
144
|
+
},
|
|
122
145
|
{
|
|
123
146
|
Hash: 'PRSP-Read-Basic-Template',
|
|
124
147
|
Template: /*html*/`
|
|
125
148
|
<!-- DefaultPackage pict view template: [PRSP-Read-Basic-Template] -->
|
|
126
|
-
|
|
149
|
+
{~T:PRSP-Read-Header-Template~}
|
|
127
150
|
<!--
|
|
128
151
|
{~DJ:Record~}
|
|
129
152
|
-->
|
|
@@ -156,7 +179,7 @@ const _DEFAULT_CONFIGURATION__Read = (
|
|
|
156
179
|
.psrs-split-view.psrs-collapsed #psrs-resize { display: none; }
|
|
157
180
|
.psrs-split-view.psrs-collapsed .psrs-left-panel { min-width: 100% !important; width: 100%; }
|
|
158
181
|
</style>
|
|
159
|
-
|
|
182
|
+
{~T:PRSP-Read-Header-Template~}
|
|
160
183
|
<div class="psrs-split-tabnav">{~T:PRSP-Read-RecordTabNav-Template~}</div>
|
|
161
184
|
<div class="psrs-split-view psrs-collapsed">
|
|
162
185
|
<div class="psrs-left-panel" style="min-width: {~D:Record.SplitLeftWidth~};">
|
|
@@ -174,36 +197,14 @@ const _DEFAULT_CONFIGURATION__Read = (
|
|
|
174
197
|
Hash: 'PRSP-Read-Tab-Template',
|
|
175
198
|
Template: /*html*/`
|
|
176
199
|
<!-- DefaultPackage pict view template: [PRSP-Read-Tab-Template] -->
|
|
177
|
-
|
|
200
|
+
{~T:PRSP-Read-Header-Template~}
|
|
178
201
|
<!--
|
|
179
202
|
{~DJ:Record~}
|
|
180
203
|
-->
|
|
181
204
|
<style>
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
border-bottom: 1px solid rgba(0,0,0,0.5);
|
|
186
|
-
margin-bottom: 20px;
|
|
187
|
-
width: 100%;
|
|
188
|
-
}
|
|
189
|
-
.psrs-tab.is-active
|
|
190
|
-
{
|
|
191
|
-
border: 1px solid rgba(0,0,0,0.5);
|
|
192
|
-
}
|
|
193
|
-
.psrs-tab
|
|
194
|
-
{
|
|
195
|
-
padding: 10px;
|
|
196
|
-
border-right: 1px solid rgba(0,0,0,0.5);
|
|
197
|
-
border-left: 1px solid rgba(0,0,0,0.5);
|
|
198
|
-
}
|
|
199
|
-
.psrs-tab-body
|
|
200
|
-
{
|
|
201
|
-
display: none;
|
|
202
|
-
}
|
|
203
|
-
.psrs-tab-body.is-active
|
|
204
|
-
{
|
|
205
|
-
display: inherit;
|
|
206
|
-
}
|
|
205
|
+
/* Tab layout owns only its nav container; the pill styling for .psrs-tab /
|
|
206
|
+
.psrs-tab-body is shared from the view's persistent CSS block. */
|
|
207
|
+
#PRSP-Read-Tab-Nav { display: flex; flex-wrap: wrap; gap: 0.35rem; width: 100%; margin: 0 0 1.25rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--theme-color-border-light, #e8ebf0); }
|
|
207
208
|
</style>
|
|
208
209
|
<div class="psrs-tab-view">
|
|
209
210
|
<div id="PRSP-Read-Tabs-Container">
|
|
@@ -821,6 +822,14 @@ class viewRecordSetRead extends libPictRecordSetRecordView
|
|
|
821
822
|
`);
|
|
822
823
|
}
|
|
823
824
|
|
|
825
|
+
// Readable record header: the record's own name (heuristic — Name / Title / DisplayName / …),
|
|
826
|
+
// with the entity type (camelCase split) as an eyebrow. Falls back to the GUID field + value
|
|
827
|
+
// when the record has no obvious display field. Computed before onBeforeRenderRead so apps can
|
|
828
|
+
// still override DisplayTitle / TitleEyebrow there.
|
|
829
|
+
const tmpReadableName = this._computeDisplayName(tmpRecordReadData.Record);
|
|
830
|
+
tmpRecordReadData.DisplayTitle = tmpReadableName || `${ tmpRecordReadData.GUIDAddress } [${ pRecordConfiguration.GUIDRecord }]`;
|
|
831
|
+
tmpRecordReadData.TitleEyebrow = String(pRecordConfiguration.RecordSet || '').replace(/([a-z0-9])([A-Z])/g, '$1 $2');
|
|
832
|
+
|
|
824
833
|
tmpRecordReadData = this.onBeforeRenderRead(tmpRecordReadData);
|
|
825
834
|
|
|
826
835
|
this.renderAsync(`PRSP_Renderable_Read_${ this.layoutType }`, tmpRecordReadData.RenderDestination, tmpRecordReadData,
|