@glossarist/concept-browser 0.7.86 → 0.7.88
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 +2 -2
- package/scripts/build-edges.js +34 -27
- package/scripts/generate-data.mjs +45 -31
- package/src/__tests__/components/PartitiveRelationDiagram.test.ts +37 -31
- package/src/__tests__/composables/use-concept-edges-v2.test.ts +56 -49
- package/src/__tests__/partitive-relation.test.ts +62 -32
- package/src/__tests__/uri-router.test.ts +29 -0
- package/src/__tests__/utils/partitive-multiplicity.test.ts +105 -81
- package/src/adapters/GraphDataSource.ts +1 -1
- package/src/adapters/UriRouter.ts +7 -3
- package/src/adapters/concept-identity.ts +6 -2
- package/src/adapters/model-bridge.ts +46 -13
- package/src/adapters/non-verbal/glossarist-augment.d.ts +71 -65
- package/src/adapters/types.ts +2 -1
- package/src/components/PartitiveRelationDiagram.vue +7 -5
- package/src/components/PartitiveRelationList.vue +6 -5
- package/src/components/RelationSphere.vue +4 -4
- package/src/composables/use-concept-edges.ts +15 -10
- package/src/utils/partitive-multiplicity.ts +69 -76
- package/src/views/ConceptView.vue +4 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Local module augmentation for glossarist 0.4.
|
|
1
|
+
// Local module augmentation for glossarist 0.4.26.
|
|
2
2
|
//
|
|
3
3
|
// Upstream's published src/models/index.d.ts declares ZERO classes for the
|
|
4
4
|
// non-verbal hierarchy (Figure, Table, Formula, FigureImage, NonVerbalEntity,
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
//
|
|
10
10
|
// This file declares the runtime shape so consumer code can be type-checked.
|
|
11
11
|
// DELETE this file when upstream ships proper declarations — tracked by
|
|
12
|
-
// PR glossarist/glossarist-js#31
|
|
12
|
+
// PR glossarist/glossarist-js#31.
|
|
13
13
|
//
|
|
14
|
-
// v2 PartitiveRelation block (below) tracks glossarist 0.4.
|
|
15
|
-
// exports PartitiveRelation/PartitiveMember/
|
|
16
|
-
//
|
|
17
|
-
//
|
|
14
|
+
// v2 PartitiveRelation block (below) tracks glossarist 0.4.26 — the runtime
|
|
15
|
+
// exports PartitiveRelation/PartitiveMember from src/models/index.js, but
|
|
16
|
+
// the d.ts has not been updated. Remove the v2 block when upstream PRs the
|
|
17
|
+
// d.ts changes.
|
|
18
18
|
|
|
19
19
|
import type { ConceptSource, Citation, GlossaristModel } from 'glossarist';
|
|
20
|
-
import type {
|
|
20
|
+
import type { PartitivePresence, PartitiveCount } from '../../utils/partitive-multiplicity';
|
|
21
21
|
|
|
22
22
|
declare module 'glossarist' {
|
|
23
23
|
class RegistrableModel extends GlossaristModel {
|
|
@@ -137,8 +137,9 @@ declare module 'glossarist' {
|
|
|
137
137
|
static fromJSON(data: Record<string, unknown>): BibliographyData;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
// ──
|
|
140
|
+
// ── PartitiveRelation (glossarist 0.4.26 — MECE presence × count) ─
|
|
141
141
|
// Runtime exports these from src/models/index.js; d.ts is stale.
|
|
142
|
+
// (Tracked by PR glossarist/glossarist-js#31.)
|
|
142
143
|
|
|
143
144
|
type Completeness = 'complete' | 'partial';
|
|
144
145
|
const COMPLETENESS: { readonly COMPLETE: 'complete'; readonly PARTIAL: 'partial' };
|
|
@@ -146,51 +147,49 @@ declare module 'glossarist' {
|
|
|
146
147
|
const DEFAULT_COMPLETENESS: Completeness;
|
|
147
148
|
function isValidCompleteness(value: unknown): value is Completeness;
|
|
148
149
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
function
|
|
150
|
+
const PARTITIVE_PRESENCE: { readonly REQUIRED: 'required'; readonly OPTIONAL: 'optional' } & { readonly VALUES: readonly ['required', 'optional'] };
|
|
151
|
+
type PartitivePresence = (typeof PARTITIVE_PRESENCE.VALUES)[number];
|
|
152
|
+
const PARTITIVE_PRESENCE_VALUES: readonly PartitivePresence[];
|
|
153
|
+
const DEFAULT_PRESENCE: PartitivePresence;
|
|
154
|
+
function isValidPresence(value: unknown): value is PartitivePresence;
|
|
154
155
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
156
|
+
const PARTITIVE_COUNT: { readonly EXACTLY_ONE: 'exactly_one'; readonly AT_LEAST_ONE: 'at_least_one'; readonly MULTIPLE: 'multiple' } & { readonly VALUES: readonly ['exactly_one', 'at_least_one', 'multiple'] };
|
|
157
|
+
type PartitiveCount = (typeof PARTITIVE_COUNT.VALUES)[number];
|
|
158
|
+
const PARTITIVE_COUNT_VALUES: readonly PartitiveCount[];
|
|
159
|
+
const DEFAULT_COUNT: PartitiveCount;
|
|
160
|
+
function isValidCount(value: unknown): value is PartitiveCount;
|
|
161
|
+
|
|
162
|
+
type Multiplicity =
|
|
163
|
+
| 'compulsory' | 'optional' | 'compulsory_multiple'
|
|
164
|
+
| 'optional_multiple' | 'compulsory_at_least_one';
|
|
165
|
+
const MULTIPLICITY: Record<string, Multiplicity>;
|
|
166
|
+
const MULTIPLICITY_VALUES: readonly Multiplicity[];
|
|
167
|
+
const DEFAULT_MULTIPLICITY: Multiplicity;
|
|
168
|
+
function isValidMultiplicity(value: unknown): value is Multiplicity;
|
|
169
|
+
function multiplicityFromPair(presence: PartitivePresence, count: PartitiveCount): Multiplicity;
|
|
170
|
+
function pairFromMultiplicity(name: Multiplicity): { presence: PartitivePresence; count: PartitiveCount };
|
|
171
171
|
|
|
172
172
|
interface Concept {
|
|
173
173
|
readonly partitiveRelations: PartitiveRelation[];
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
// ── v3 PartitiveRelation (glossarist 0.4.24 — ISO 704:2022 multiplicity + delimiting) ─
|
|
177
176
|
// Re-exposed here so callers importing from the top-level `glossarist`
|
|
178
|
-
// entry get the
|
|
177
|
+
// entry get the MECE-augmented shape, not the stale d.ts type.
|
|
179
178
|
export class PartitiveMember extends GlossaristModel {
|
|
180
179
|
constructor(data?: {
|
|
181
180
|
ref?: ConceptRef;
|
|
182
|
-
|
|
181
|
+
presence?: PartitivePresence;
|
|
182
|
+
count?: PartitiveCount;
|
|
183
183
|
is_delimiting?: boolean;
|
|
184
184
|
});
|
|
185
185
|
readonly ref: ConceptRef;
|
|
186
|
-
readonly
|
|
186
|
+
readonly presence: PartitivePresence;
|
|
187
|
+
readonly count: PartitiveCount;
|
|
187
188
|
readonly is_delimiting: boolean;
|
|
188
|
-
readonly
|
|
189
|
-
readonly isPossible: boolean;
|
|
190
|
-
readonly isCompulsory: boolean;
|
|
189
|
+
readonly isRequired: boolean;
|
|
191
190
|
readonly isOptional: boolean;
|
|
192
191
|
get isDelimiting(): boolean;
|
|
193
|
-
toJSON(): { ref: ReturnType<ConceptRef['toJSON']>;
|
|
192
|
+
toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; presence?: PartitivePresence; count?: PartitiveCount; is_delimiting?: boolean };
|
|
194
193
|
static fromJSON(data: Record<string, unknown>): PartitiveMember;
|
|
195
194
|
static identityOf(value: unknown): string;
|
|
196
195
|
}
|
|
@@ -225,6 +224,12 @@ declare module 'glossarist' {
|
|
|
225
224
|
// Re-declare the same exports under the 'glossarist/models' subpath so
|
|
226
225
|
// deep-import callers can use them. Upstream's d.ts is stale on this
|
|
227
226
|
// subpath too.
|
|
227
|
+
//
|
|
228
|
+
// The duplication between the 'glossarist' block above and this block
|
|
229
|
+
// is forced by upstream's broken d.ts (PR glossarist/glossarist-js#31).
|
|
230
|
+
// We cannot `export import` from a local namespace because TypeScript
|
|
231
|
+
// requires `declare module` blocks to be self-contained. When upstream
|
|
232
|
+
// ships proper d.ts, DELETE this entire file.
|
|
228
233
|
declare module 'glossarist/models' {
|
|
229
234
|
export type Completeness = 'complete' | 'partial';
|
|
230
235
|
export const COMPLETENESS: { readonly COMPLETE: 'complete'; readonly PARTIAL: 'partial' };
|
|
@@ -232,46 +237,47 @@ declare module 'glossarist/models' {
|
|
|
232
237
|
export const DEFAULT_COMPLETENESS: Completeness;
|
|
233
238
|
export function isValidCompleteness(value: unknown): value is Completeness;
|
|
234
239
|
|
|
235
|
-
export
|
|
236
|
-
export
|
|
237
|
-
export const
|
|
238
|
-
export const
|
|
239
|
-
export function
|
|
240
|
+
export const PARTITIVE_PRESENCE: { readonly REQUIRED: 'required'; readonly OPTIONAL: 'optional' } & { readonly VALUES: readonly ['required', 'optional'] };
|
|
241
|
+
export type PartitivePresence = (typeof PARTITIVE_PRESENCE.VALUES)[number];
|
|
242
|
+
export const PARTITIVE_PRESENCE_VALUES: readonly PartitivePresence[];
|
|
243
|
+
export const DEFAULT_PRESENCE: PartitivePresence;
|
|
244
|
+
export function isValidPresence(value: unknown): value is PartitivePresence;
|
|
240
245
|
|
|
241
|
-
export
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
246
|
+
export const PARTITIVE_COUNT: { readonly EXACTLY_ONE: 'exactly_one'; readonly AT_LEAST_ONE: 'at_least_one'; readonly MULTIPLE: 'multiple' } & { readonly VALUES: readonly ['exactly_one', 'at_least_one', 'multiple'] };
|
|
247
|
+
export type PartitiveCount = (typeof PARTITIVE_COUNT.VALUES)[number];
|
|
248
|
+
export const PARTITIVE_COUNT_VALUES: readonly PartitiveCount[];
|
|
249
|
+
export const DEFAULT_COUNT: PartitiveCount;
|
|
250
|
+
export function isValidCount(value: unknown): value is PartitiveCount;
|
|
251
|
+
|
|
252
|
+
export type Multiplicity =
|
|
253
|
+
| 'compulsory' | 'optional' | 'compulsory_multiple'
|
|
254
|
+
| 'optional_multiple' | 'compulsory_at_least_one';
|
|
255
|
+
export const MULTIPLICITY: Record<string, Multiplicity>;
|
|
256
|
+
export const MULTIPLICITY_VALUES: readonly Multiplicity[];
|
|
257
|
+
export const DEFAULT_MULTIPLICITY: Multiplicity;
|
|
258
|
+
export function isValidMultiplicity(value: unknown): value is Multiplicity;
|
|
259
|
+
export function multiplicityFromPair(presence: PartitivePresence, count: PartitiveCount): Multiplicity;
|
|
260
|
+
export function pairFromMultiplicity(name: Multiplicity): { presence: PartitivePresence; count: PartitiveCount };
|
|
261
|
+
// (TypeSharedPlurality removed — superseded by presence/count on
|
|
262
|
+
// PartitiveMember per MECE refactor. Was dead code.)
|
|
257
263
|
|
|
258
|
-
// ──
|
|
264
|
+
// ── PartitiveRelation (glossarist 0.4.26 — MECE presence × count) ─
|
|
259
265
|
|
|
260
266
|
export class PartitiveMember extends GlossaristModel {
|
|
261
267
|
constructor(data?: {
|
|
262
268
|
ref?: ConceptRef;
|
|
263
|
-
|
|
269
|
+
presence?: PartitivePresence;
|
|
270
|
+
count?: PartitiveCount;
|
|
264
271
|
is_delimiting?: boolean;
|
|
265
272
|
});
|
|
266
273
|
readonly ref: ConceptRef;
|
|
267
|
-
readonly
|
|
274
|
+
readonly presence: PartitivePresence;
|
|
275
|
+
readonly count: PartitiveCount;
|
|
268
276
|
readonly is_delimiting: boolean;
|
|
269
|
-
readonly
|
|
270
|
-
readonly isPossible: boolean;
|
|
271
|
-
readonly isCompulsory: boolean;
|
|
277
|
+
readonly isRequired: boolean;
|
|
272
278
|
readonly isOptional: boolean;
|
|
273
279
|
get isDelimiting(): boolean;
|
|
274
|
-
toJSON(): { ref: ReturnType<ConceptRef['toJSON']>;
|
|
280
|
+
toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; presence?: PartitivePresence; count?: PartitiveCount; is_delimiting?: boolean };
|
|
275
281
|
static fromJSON(data: Record<string, unknown>): PartitiveMember;
|
|
276
282
|
static identityOf(value: unknown): string;
|
|
277
283
|
}
|
package/src/adapters/types.ts
CHANGED
|
@@ -174,7 +174,8 @@ export interface PartitiveRelationWire {
|
|
|
174
174
|
|
|
175
175
|
export interface PartitiveMemberWire {
|
|
176
176
|
uri: string;
|
|
177
|
-
|
|
177
|
+
presence: 'required' | 'optional';
|
|
178
|
+
count: 'exactly_one' | 'at_least_one' | 'multiple';
|
|
178
179
|
isDelimiting: boolean;
|
|
179
180
|
}
|
|
180
181
|
|
|
@@ -40,14 +40,16 @@
|
|
|
40
40
|
import { computed } from 'vue';
|
|
41
41
|
import {
|
|
42
42
|
rakeStrokeStyle,
|
|
43
|
-
type
|
|
43
|
+
type PartitivePresence,
|
|
44
|
+
type PartitiveCount,
|
|
44
45
|
type RakeStrokeStyle,
|
|
45
46
|
} from '../utils/partitive-multiplicity';
|
|
46
47
|
|
|
47
48
|
export interface PartitiveMemberLabeled {
|
|
48
49
|
uri: string;
|
|
49
50
|
label: string;
|
|
50
|
-
|
|
51
|
+
presence: PartitivePresence;
|
|
52
|
+
count: PartitiveCount;
|
|
51
53
|
isDelimiting: boolean;
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -57,7 +59,7 @@ const props = withDefaults(defineProps<{
|
|
|
57
59
|
completeness: 'complete' | 'partial';
|
|
58
60
|
criterion?: string | null;
|
|
59
61
|
/** Optional per-member tooltip string. */
|
|
60
|
-
memberTooltip?: (m: {
|
|
62
|
+
memberTooltip?: (m: { presence: PartitivePresence; count: PartitiveCount; isDelimiting: boolean }) => string;
|
|
61
63
|
}>(), {
|
|
62
64
|
criterion: null,
|
|
63
65
|
});
|
|
@@ -142,7 +144,7 @@ const layout = computed(() => {
|
|
|
142
144
|
member,
|
|
143
145
|
x,
|
|
144
146
|
labelLines,
|
|
145
|
-
style: rakeStrokeStyle(member.
|
|
147
|
+
style: rakeStrokeStyle(member.presence, member.count, member.isDelimiting),
|
|
146
148
|
};
|
|
147
149
|
});
|
|
148
150
|
|
|
@@ -198,7 +200,7 @@ function emitNav(uri: string) {
|
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
function tooltipFor(member: PartitiveMemberLabeled): string | undefined {
|
|
201
|
-
return props.memberTooltip?.({
|
|
203
|
+
return props.memberTooltip?.({ presence: member.presence, count: member.count, isDelimiting: member.isDelimiting });
|
|
202
204
|
}
|
|
203
205
|
</script>
|
|
204
206
|
|
|
@@ -19,7 +19,7 @@ import { useVocabularyStore } from '../stores/vocabulary';
|
|
|
19
19
|
import { getFactory } from '../adapters/factory';
|
|
20
20
|
import { useI18n, locale } from '../i18n';
|
|
21
21
|
import { completenessLabel } from '../utils/partitive-relation-styling';
|
|
22
|
-
import {
|
|
22
|
+
import { presenceLabel, countLabel } from '../utils/partitive-multiplicity';
|
|
23
23
|
import PartitiveRelationDiagram, {
|
|
24
24
|
type PartitiveMemberLabeled,
|
|
25
25
|
} from './PartitiveRelationDiagram.vue';
|
|
@@ -85,15 +85,16 @@ function labeledMembers(rel: PartitiveRelationWire): PartitiveMemberLabeled[] {
|
|
|
85
85
|
return rel.partitives.map(m => ({
|
|
86
86
|
uri: m.uri,
|
|
87
87
|
label: designationFor(m.uri),
|
|
88
|
-
|
|
88
|
+
presence: m.presence,
|
|
89
|
+
count: m.count,
|
|
89
90
|
isDelimiting: m.isDelimiting,
|
|
90
91
|
}));
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
/** Tooltip text for a member, shown on hover. */
|
|
94
|
-
function memberTooltip(m: {
|
|
95
|
-
const
|
|
96
|
-
return m.isDelimiting ? `Delimiting ${
|
|
95
|
+
function memberTooltip(m: { presence: 'required' | 'optional'; count: 'exactly_one' | 'at_least_one' | 'multiple'; isDelimiting: boolean }): string {
|
|
96
|
+
const label = `${presenceLabel(m.presence)} · ${countLabel(m.count).toLowerCase()}`;
|
|
97
|
+
return m.isDelimiting ? `Delimiting · ${label}` : label;
|
|
97
98
|
}
|
|
98
99
|
</script>
|
|
99
100
|
|
|
@@ -25,8 +25,8 @@ import { hashSeed, expandParams, portSide, portPoint, idToUriGet } from '../util
|
|
|
25
25
|
import { useI18n } from '../i18n';
|
|
26
26
|
|
|
27
27
|
const { t, locale } = useI18n();
|
|
28
|
-
import type { Concept, Manifest, GraphEdge, PartitiveRelationWire } from '../adapters/types';
|
|
29
|
-
import { rakeStrokeStyle
|
|
28
|
+
import type { Concept, Manifest, GraphEdge, PartitiveRelationWire, PartitiveMemberWire } from '../adapters/types';
|
|
29
|
+
import { rakeStrokeStyle } from '../utils/partitive-multiplicity';
|
|
30
30
|
import { conceptUri } from '../adapters/model-bridge';
|
|
31
31
|
|
|
32
32
|
const props = defineProps<{
|
|
@@ -818,7 +818,7 @@ function drawRakeBundles(
|
|
|
818
818
|
const p = pos.get(nid);
|
|
819
819
|
return p ? { member, pos: p } : null;
|
|
820
820
|
})
|
|
821
|
-
.filter((x): x is { member:
|
|
821
|
+
.filter((x): x is { member: PartitiveMemberWire; pos: {x: number; y: number} } => x !== null);
|
|
822
822
|
|
|
823
823
|
if (memberPositions.length === 0) continue;
|
|
824
824
|
|
|
@@ -850,7 +850,7 @@ function drawRakeBundles(
|
|
|
850
850
|
toothX,
|
|
851
851
|
toothY,
|
|
852
852
|
along,
|
|
853
|
-
style: rakeStrokeStyle(member.
|
|
853
|
+
style: rakeStrokeStyle(member.presence, member.count, member.isDelimiting),
|
|
854
854
|
};
|
|
855
855
|
});
|
|
856
856
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { computed, type ComputedRef } from 'vue';
|
|
2
2
|
import type { Router } from 'vue-router';
|
|
3
|
-
// glossarist
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// glossarist-js's top-level d.ts is incomplete for v3 model fields.
|
|
4
|
+
// Import the MECE-augmented types from glossarist/models (extended by
|
|
5
|
+
// src/adapters/non-verbal/glossarist-augment.d.ts).
|
|
6
6
|
import type { Concept, RelatedConcept, ConceptRef } from 'glossarist';
|
|
7
7
|
import type {
|
|
8
8
|
PartitiveRelation as GlsPartitiveRelation,
|
|
@@ -23,7 +23,7 @@ import { categorizeRelationship, relationshipLabel, INVERSE_RELATIONSHIPS } from
|
|
|
23
23
|
import { langLabel } from '../utils/lang';
|
|
24
24
|
import { escapeAttr } from '../utils/escape';
|
|
25
25
|
import { useI18n } from '../i18n';
|
|
26
|
-
import type
|
|
26
|
+
import { type PartitivePresence, type PartitiveCount } from '../utils/partitive-multiplicity';
|
|
27
27
|
|
|
28
28
|
export interface EdgeDisplay {
|
|
29
29
|
uri: string;
|
|
@@ -192,7 +192,8 @@ export function useConceptEdges(
|
|
|
192
192
|
if (!uri || uri === source) return null;
|
|
193
193
|
return {
|
|
194
194
|
uri,
|
|
195
|
-
|
|
195
|
+
presence: readPresence(m),
|
|
196
|
+
count: readCount(m),
|
|
196
197
|
isDelimiting: readIsDelimiting(m),
|
|
197
198
|
};
|
|
198
199
|
})
|
|
@@ -216,14 +217,18 @@ export function useConceptEdges(
|
|
|
216
217
|
return resolveRefUri({ source: ref.source ?? null, id: ref.id ?? null });
|
|
217
218
|
}
|
|
218
219
|
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
function
|
|
222
|
-
return m.
|
|
220
|
+
// Read presence + count directly from the glossarist-js model
|
|
221
|
+
// (MECE native, since glossarist 0.4.26).
|
|
222
|
+
function readPresence(m: GlsPartitiveMember): PartitivePresence {
|
|
223
|
+
return m.presence;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function readCount(m: GlsPartitiveMember): PartitiveCount {
|
|
227
|
+
return m.count;
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
function readIsDelimiting(m: GlsPartitiveMember): boolean {
|
|
226
|
-
return m.is_delimiting;
|
|
231
|
+
return m.is_delimiting === true;
|
|
227
232
|
}
|
|
228
233
|
|
|
229
234
|
function resolveRefUri(ref: { source: string | null; id: string | null } | null): string | null {
|
|
@@ -1,106 +1,99 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ISO 704:2022 §5.5.4.2 — partitive multiplicity
|
|
2
|
+
* ISO 704:2022 §5.5.4.2 — partitive multiplicity model (MECE).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - compulsory : exactly one, required
|
|
6
|
-
* - optional : zero or one
|
|
7
|
-
* - compulsory_multiple : two or more, required
|
|
8
|
-
* - optional_multiple : zero or more
|
|
9
|
-
* - compulsory_at_least_one : one or more (required, count unknown)
|
|
4
|
+
* Two independent axes:
|
|
10
5
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* comprehensive concept from coordinate concepts. Visual encoding:
|
|
14
|
-
* 3× stroke width (4.5 px vs 1.5 px).
|
|
6
|
+
* presence: 'required' | 'optional'
|
|
7
|
+
* Is this part always present?
|
|
15
8
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
9
|
+
* count: 'exactly_one' | 'at_least_one' | 'multiple'
|
|
10
|
+
* How many instances?
|
|
11
|
+
*
|
|
12
|
+
* 6 combinations, 5 visually distinct (optional + at_least_one
|
|
13
|
+
* collapses to 2-dashed = same as optional + multiple) and the
|
|
14
|
+
* invalid combo (optional, at_least_one) is rejected at construction
|
|
15
|
+
* by glossarist-js.
|
|
16
|
+
*
|
|
17
|
+
* Types are re-exported from glossarist-js — the model is the SSOT.
|
|
18
|
+
* The render-ready `rakeStrokeStyle(presence, count, isDelimiting)`
|
|
19
|
+
* helper is purely a browser-rendering concern; everything else
|
|
20
|
+
* (presence, count, is_delimiting fields, MULTIPLICITY name lookup via
|
|
21
|
+
* multiplicityFromPair, validity via isValidPresence / isValidCount)
|
|
22
|
+
* comes from `glossarist/models`.
|
|
19
23
|
*/
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export const PARTITIVE_MULTIPLICITY_VALUES: readonly PartitiveMultiplicity[] =
|
|
32
|
-
Object.keys(PARTITIVE_MULTIPLICITY) as PartitiveMultiplicity[];
|
|
25
|
+
import {
|
|
26
|
+
PARTITIVE_PRESENCE,
|
|
27
|
+
PARTITIVE_COUNT,
|
|
28
|
+
multiplicityFromPair,
|
|
29
|
+
isValidPresence,
|
|
30
|
+
isValidCount,
|
|
31
|
+
type PartitivePresence,
|
|
32
|
+
type PartitiveCount,
|
|
33
|
+
} from 'glossarist/models';
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
return typeof value === 'string' && value in PARTITIVE_MULTIPLICITY;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export type LinePattern = 'solid' | 'dashed' | 'mixed';
|
|
35
|
+
// ── Two independent axes (re-exported SSOT) ────────────────────────────
|
|
39
36
|
|
|
40
|
-
export
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
/** Dash pattern: solid, dashed, or mixed (one solid + one dashed). */
|
|
44
|
-
readonly pattern: LinePattern;
|
|
45
|
-
/** Human-readable label for legends and badges. */
|
|
46
|
-
readonly label: string;
|
|
47
|
-
}
|
|
37
|
+
export { PARTITIVE_PRESENCE, PARTITIVE_COUNT };
|
|
38
|
+
export type { PartitivePresence, PartitiveCount };
|
|
39
|
+
export { isValidPresence as isPartitivePresence, isValidCount as isPartitiveCount };
|
|
48
40
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
41
|
+
/** ISO 704 name for a (presence, count) pair. Throws on invalid combos. */
|
|
42
|
+
export const partitiveMultiplicityName = multiplicityFromPair;
|
|
52
43
|
|
|
53
|
-
// ──
|
|
44
|
+
// ── Render-ready stroke style (rendering-only concern) ─────────────────
|
|
54
45
|
|
|
55
46
|
export interface RakeStrokeStyle {
|
|
56
|
-
/** 1 or 2 parallel lines. */
|
|
57
47
|
readonly lineCount: 1 | 2;
|
|
58
|
-
/** Primary (always-present) line dash. */
|
|
59
48
|
readonly primaryDashed: boolean;
|
|
60
|
-
/** Secondary line dash (only meaningful when lineCount === 2). */
|
|
61
49
|
readonly secondaryDashed: boolean;
|
|
62
|
-
/** Stroke width in px. 1.5 normal, 4.5 (3×) when is_delimiting. */
|
|
63
50
|
readonly strokeWidth: number;
|
|
64
51
|
}
|
|
65
52
|
|
|
66
53
|
export const NORMAL_STROKE_WIDTH = 1.5;
|
|
67
|
-
export const DELIMITING_STROKE_WIDTH = 4.5;
|
|
54
|
+
export const DELIMITING_STROKE_WIDTH = 4.5;
|
|
68
55
|
|
|
69
56
|
/**
|
|
70
|
-
* Compute the render-ready stroke style
|
|
57
|
+
* Compute the render-ready stroke style.
|
|
71
58
|
*
|
|
72
|
-
* Pure function —
|
|
73
|
-
*
|
|
74
|
-
*
|
|
59
|
+
* Pure function — consumed by both the sidebar rake diagram
|
|
60
|
+
* (PartitiveRelationDiagram) and the sphere rake bundles
|
|
61
|
+
* (RelationSphere.drawRakeBundles).
|
|
62
|
+
*
|
|
63
|
+
* Derivation:
|
|
64
|
+
* presence=required → primary line is solid
|
|
65
|
+
* presence=optional → primary line is dashed
|
|
66
|
+
* count=exactly_one → 1 line
|
|
67
|
+
* count=multiple → 2 lines, both same style as primary
|
|
68
|
+
* count=at_least_one → 2 lines: primary + dashed ("possibly more")
|
|
75
69
|
*/
|
|
76
70
|
export function rakeStrokeStyle(
|
|
77
|
-
|
|
71
|
+
presence: PartitivePresence,
|
|
72
|
+
count: PartitiveCount,
|
|
78
73
|
isDelimiting: boolean,
|
|
79
74
|
): RakeStrokeStyle {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
75
|
+
const primaryDashed = presence === 'optional';
|
|
76
|
+
const width = isDelimiting ? DELIMITING_STROKE_WIDTH : NORMAL_STROKE_WIDTH;
|
|
77
|
+
|
|
78
|
+
if (count === 'exactly_one') {
|
|
79
|
+
return { lineCount: 1, primaryDashed, secondaryDashed: false, strokeWidth: width };
|
|
80
|
+
}
|
|
81
|
+
// multiple or at_least_one
|
|
82
|
+
const secondaryDashed = count === 'at_least_one' ? true : primaryDashed;
|
|
83
|
+
return { lineCount: 2, primaryDashed, secondaryDashed, strokeWidth: width };
|
|
87
84
|
}
|
|
88
85
|
|
|
89
|
-
// ──
|
|
86
|
+
// ── Human-readable labels (UI concern) ────────────────────────────────
|
|
90
87
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
* it directly and this helper can be deleted.
|
|
103
|
-
*/
|
|
104
|
-
export function multiplicityFromCertainty(certainty: 'confirmed' | 'possible' | null | undefined): PartitiveMultiplicity {
|
|
105
|
-
return certainty === 'possible' ? 'optional' : 'compulsory';
|
|
88
|
+
export function presenceLabel(p: PartitivePresence): string {
|
|
89
|
+
return p === 'optional' ? 'Optional' : 'Required';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function countLabel(c: PartitiveCount): string {
|
|
93
|
+
switch (c) {
|
|
94
|
+
case 'exactly_one': return 'Exactly one';
|
|
95
|
+
case 'at_least_one': return 'At least one';
|
|
96
|
+
case 'multiple': return 'Multiple';
|
|
97
|
+
default: return '';
|
|
98
|
+
}
|
|
106
99
|
}
|
|
@@ -6,7 +6,7 @@ import { conceptUri } from '../adapters/model-bridge';
|
|
|
6
6
|
import { UriRouter } from '../adapters/UriRouter';
|
|
7
7
|
import type { PartitiveRelationWire, PartitiveMemberWire } from '../adapters/types';
|
|
8
8
|
import type { PartitiveMember } from 'glossarist/models';
|
|
9
|
-
import type {
|
|
9
|
+
import type { PartitivePresence, PartitiveCount } from '../utils/partitive-multiplicity';
|
|
10
10
|
import ConceptDetail from '../components/ConceptDetail.vue';
|
|
11
11
|
import RelationSphere from '../components/RelationSphere.vue';
|
|
12
12
|
import ShortcutsModal from '../components/ShortcutsModal.vue';
|
|
@@ -87,9 +87,10 @@ const partitiveRelationsForSphere = computed<PartitiveRelationWire[]>(() => {
|
|
|
87
87
|
if (!ref?.id) return null;
|
|
88
88
|
const uri = UriRouter.buildConceptUri(m.uriBase, activeRegisterId.value, ref.id);
|
|
89
89
|
if (uri === sourceUri) return null;
|
|
90
|
-
const
|
|
90
|
+
const presence: PartitivePresence = member.presence;
|
|
91
|
+
const count: PartitiveCount = member.count;
|
|
91
92
|
const isDelimiting = member.isDelimiting;
|
|
92
|
-
return { uri,
|
|
93
|
+
return { uri, presence, count, isDelimiting };
|
|
93
94
|
})
|
|
94
95
|
.filter((x): x is PartitiveMemberWire => x !== null);
|
|
95
96
|
if (partitives.length === 0) continue;
|