@glossarist/concept-browser 0.7.84 → 0.7.86
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 +42 -26
- package/scripts/generate-data.mjs +45 -5
- package/scripts/migrate-v1-to-v3.d.mts +6 -0
- package/scripts/migrate-v1-to-v3.mjs +176 -0
- package/src/__tests__/components/PartitiveRelationDiagram.test.ts +92 -136
- package/src/__tests__/composables/use-concept-edges-v2.test.ts +115 -87
- package/src/__tests__/partitive-relation-styling.test.ts +12 -54
- package/src/__tests__/partitive-relation.test.ts +42 -90
- package/src/__tests__/scripts/migrate-v1-to-v3.d.mts +8 -0
- package/src/__tests__/scripts/migrate-v1-to-v3.test.ts +115 -0
- package/src/__tests__/utils/partitive-multiplicity.test.ts +113 -0
- package/src/adapters/model-bridge.ts +15 -19
- package/src/adapters/non-verbal/glossarist-augment.d.ts +34 -18
- package/src/adapters/types.ts +2 -8
- package/src/components/PartitiveRelationDiagram.vue +88 -76
- package/src/components/PartitiveRelationList.vue +26 -34
- package/src/components/RelationSphere.vue +173 -106
- package/src/composables/use-concept-edges.ts +21 -21
- package/src/utils/partitive-multiplicity.ts +106 -0
- package/src/utils/partitive-relation-styling.ts +29 -38
- package/src/views/ConceptView.vue +7 -13
- package/src/__tests__/model-bridge-partitive.test.ts +0 -146
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// block when upstream PRs the d.ts changes.
|
|
18
18
|
|
|
19
19
|
import type { ConceptSource, Citation, GlossaristModel } from 'glossarist';
|
|
20
|
+
import type { PartitiveMultiplicity } from '../../utils/partitive-multiplicity';
|
|
20
21
|
|
|
21
22
|
declare module 'glossarist' {
|
|
22
23
|
class RegistrableModel extends GlossaristModel {
|
|
@@ -168,29 +169,42 @@ declare module 'glossarist' {
|
|
|
168
169
|
static fromJSON(data: Record<string, unknown>): TypeSharedPlurality;
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
interface Concept {
|
|
173
|
+
readonly partitiveRelations: PartitiveRelation[];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── v3 PartitiveRelation (glossarist 0.4.24 — ISO 704:2022 multiplicity + delimiting) ─
|
|
177
|
+
// Re-exposed here so callers importing from the top-level `glossarist`
|
|
178
|
+
// entry get the v3-augmented shape, not the stale d.ts type.
|
|
179
|
+
export class PartitiveMember extends GlossaristModel {
|
|
180
|
+
constructor(data?: {
|
|
181
|
+
ref?: ConceptRef;
|
|
182
|
+
multiplicity?: PartitiveMultiplicity;
|
|
183
|
+
is_delimiting?: boolean;
|
|
184
|
+
});
|
|
173
185
|
readonly ref: ConceptRef;
|
|
174
|
-
readonly
|
|
186
|
+
readonly multiplicity: PartitiveMultiplicity;
|
|
187
|
+
readonly is_delimiting: boolean;
|
|
175
188
|
readonly isConfirmed: boolean;
|
|
176
189
|
readonly isPossible: boolean;
|
|
177
|
-
|
|
190
|
+
readonly isCompulsory: boolean;
|
|
191
|
+
readonly isOptional: boolean;
|
|
192
|
+
get isDelimiting(): boolean;
|
|
193
|
+
toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; multiplicity?: PartitiveMultiplicity; is_delimiting?: boolean };
|
|
178
194
|
static fromJSON(data: Record<string, unknown>): PartitiveMember;
|
|
179
195
|
static identityOf(value: unknown): string;
|
|
180
196
|
}
|
|
181
197
|
|
|
182
|
-
class PartitiveRelation extends GlossaristModel {
|
|
198
|
+
export class PartitiveRelation extends GlossaristModel {
|
|
183
199
|
constructor(data?: {
|
|
184
200
|
comprehensive?: ConceptRef;
|
|
185
201
|
partitives?: PartitiveMember[];
|
|
186
202
|
completeness?: Completeness;
|
|
187
|
-
plurality?: TypeSharedPlurality;
|
|
188
203
|
criterion?: Record<string, string> | string;
|
|
189
204
|
});
|
|
190
205
|
readonly comprehensive: ConceptRef;
|
|
191
206
|
readonly partitives: PartitiveMember[];
|
|
192
207
|
readonly completeness: Completeness;
|
|
193
|
-
readonly plurality: TypeSharedPlurality | null;
|
|
194
208
|
readonly criterion: Record<string, string> | null;
|
|
195
209
|
readonly isComplete: boolean;
|
|
196
210
|
readonly isPartial: boolean;
|
|
@@ -201,16 +215,11 @@ declare module 'glossarist' {
|
|
|
201
215
|
comprehensive: ReturnType<ConceptRef['toJSON']>;
|
|
202
216
|
partitives: ReturnType<PartitiveMember['toJSON']>[];
|
|
203
217
|
completeness: Completeness;
|
|
204
|
-
plurality?: ReturnType<TypeSharedPlurality['toJSON']>;
|
|
205
218
|
criterion?: Record<string, string>;
|
|
206
219
|
};
|
|
207
220
|
static fromJSON(data: Record<string, unknown>): PartitiveRelation;
|
|
208
221
|
static identityOf(value: unknown): string;
|
|
209
222
|
}
|
|
210
|
-
|
|
211
|
-
interface Concept {
|
|
212
|
-
readonly partitiveRelations: PartitiveRelation[];
|
|
213
|
-
}
|
|
214
223
|
}
|
|
215
224
|
|
|
216
225
|
// Re-declare the same exports under the 'glossarist/models' subpath so
|
|
@@ -246,13 +255,23 @@ declare module 'glossarist/models' {
|
|
|
246
255
|
static fromJSON(data: Record<string, unknown>): TypeSharedPlurality;
|
|
247
256
|
}
|
|
248
257
|
|
|
258
|
+
// ── v2 PartitiveRelation (glossarist 0.4.24 — ISO 704:2022 multiplicity + delimiting) ─
|
|
259
|
+
|
|
249
260
|
export class PartitiveMember extends GlossaristModel {
|
|
250
|
-
constructor(data?: {
|
|
261
|
+
constructor(data?: {
|
|
262
|
+
ref?: ConceptRef;
|
|
263
|
+
multiplicity?: PartitiveMultiplicity;
|
|
264
|
+
is_delimiting?: boolean;
|
|
265
|
+
});
|
|
251
266
|
readonly ref: ConceptRef;
|
|
252
|
-
readonly
|
|
267
|
+
readonly multiplicity: PartitiveMultiplicity;
|
|
268
|
+
readonly is_delimiting: boolean;
|
|
253
269
|
readonly isConfirmed: boolean;
|
|
254
270
|
readonly isPossible: boolean;
|
|
255
|
-
|
|
271
|
+
readonly isCompulsory: boolean;
|
|
272
|
+
readonly isOptional: boolean;
|
|
273
|
+
get isDelimiting(): boolean;
|
|
274
|
+
toJSON(): { ref: ReturnType<ConceptRef['toJSON']>; multiplicity?: PartitiveMultiplicity; is_delimiting?: boolean };
|
|
256
275
|
static fromJSON(data: Record<string, unknown>): PartitiveMember;
|
|
257
276
|
static identityOf(value: unknown): string;
|
|
258
277
|
}
|
|
@@ -262,13 +281,11 @@ declare module 'glossarist/models' {
|
|
|
262
281
|
comprehensive?: ConceptRef;
|
|
263
282
|
partitives?: PartitiveMember[];
|
|
264
283
|
completeness?: Completeness;
|
|
265
|
-
plurality?: TypeSharedPlurality;
|
|
266
284
|
criterion?: Record<string, string> | string;
|
|
267
285
|
});
|
|
268
286
|
readonly comprehensive: ConceptRef;
|
|
269
287
|
readonly partitives: PartitiveMember[];
|
|
270
288
|
readonly completeness: Completeness;
|
|
271
|
-
readonly plurality: TypeSharedPlurality | null;
|
|
272
289
|
readonly criterion: Record<string, string> | null;
|
|
273
290
|
readonly isComplete: boolean;
|
|
274
291
|
readonly isPartial: boolean;
|
|
@@ -279,7 +296,6 @@ declare module 'glossarist/models' {
|
|
|
279
296
|
comprehensive: ReturnType<ConceptRef['toJSON']>;
|
|
280
297
|
partitives: ReturnType<PartitiveMember['toJSON']>[];
|
|
281
298
|
completeness: Completeness;
|
|
282
|
-
plurality?: ReturnType<TypeSharedPlurality['toJSON']>;
|
|
283
299
|
criterion?: Record<string, string>;
|
|
284
300
|
};
|
|
285
301
|
static fromJSON(data: Record<string, unknown>): PartitiveRelation;
|
package/src/adapters/types.ts
CHANGED
|
@@ -168,20 +168,14 @@ export interface PartitiveRelationWire {
|
|
|
168
168
|
comprehensive: string;
|
|
169
169
|
partitives: PartitiveMemberWire[];
|
|
170
170
|
completeness: 'complete' | 'partial';
|
|
171
|
-
plurality: TypeSharedPluralityWire | null;
|
|
172
171
|
criterion?: Record<string, string>;
|
|
173
172
|
register: string;
|
|
174
173
|
}
|
|
175
174
|
|
|
176
175
|
export interface PartitiveMemberWire {
|
|
177
176
|
uri: string;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
export interface TypeSharedPluralityWire {
|
|
182
|
-
isShared: boolean;
|
|
183
|
-
isUncertain: boolean;
|
|
184
|
-
sharedType?: string | null;
|
|
177
|
+
multiplicity: 'compulsory' | 'optional' | 'compulsory_multiple' | 'optional_multiple' | 'compulsory_at_least_one';
|
|
178
|
+
isDelimiting: boolean;
|
|
185
179
|
}
|
|
186
180
|
|
|
187
181
|
export interface GraphNode {
|
|
@@ -1,46 +1,63 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* PartitiveRelationDiagram — renders a single PartitiveRelation as an
|
|
4
|
-
* ISO 704 rake/bracket diagram in SVG.
|
|
4
|
+
* ISO 704:2022 rake/bracket diagram in SVG.
|
|
5
5
|
*
|
|
6
|
-
* Layout
|
|
6
|
+
* Layout:
|
|
7
7
|
*
|
|
8
8
|
* [comprehensive]
|
|
9
9
|
* │
|
|
10
|
-
* │ ←
|
|
11
|
-
* ┌───────────────┴───────────────┐ ←
|
|
10
|
+
* │ ← stem (always single solid — relation frame)
|
|
11
|
+
* ┌───────────────┴───────────────┐ ← spine (always single solid — relation frame)
|
|
12
12
|
* │ │ │
|
|
13
|
-
* │ │ │ ←
|
|
13
|
+
* │ │ │ ← drops (per-member multiplicity + delimiting)
|
|
14
14
|
* [part1] [part2] [part3]
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
16
|
+
* Per ISO 704:2022, each drop's line style encodes its multiplicity +
|
|
17
|
+
* is_delimiting (see utils/partitive-multiplicity.ts for the registry):
|
|
18
|
+
*
|
|
19
|
+
* multiplicity is_delimiting visual
|
|
20
|
+
* ------------------- -------------- --------------------------------
|
|
21
|
+
* compulsory false 1 solid @ 1.5px
|
|
22
|
+
* compulsory true 1 solid @ 4.5px (bold, 3× width)
|
|
23
|
+
* optional false 1 dashed @ 1.5px
|
|
24
|
+
* optional true 1 dashed @ 4.5px
|
|
25
|
+
* compulsory_multiple false 2 solid @ 1.5px
|
|
26
|
+
* compulsory_multiple true 2 solid @ 4.5px
|
|
27
|
+
* optional_multiple false 2 dashed @ 1.5px
|
|
28
|
+
* optional_multiple true 2 dashed @ 4.5px
|
|
29
|
+
* compulsory_at_least_one false 1 solid + 1 dashed @ 1.5px
|
|
30
|
+
* compulsory_at_least_one true 1 solid + 1 dashed @ 4.5px
|
|
31
|
+
*
|
|
32
|
+
* completeness === 'partial' : spine extends past the last tooth
|
|
33
|
+
* (continued backline — "more exist but
|
|
34
|
+
* not shown")
|
|
23
35
|
*
|
|
24
36
|
* Node sizing is designation-aware: the longest label drives the node
|
|
25
37
|
* width (clamped to [MIN_NODE_W, MAX_NODE_W]); labels that don't fit
|
|
26
|
-
* wrap to a second line via tspan.
|
|
27
|
-
* designations ('system of quantities') instead of bare IDs ('1.3').
|
|
38
|
+
* wrap to a second line via tspan.
|
|
28
39
|
*/
|
|
29
40
|
import { computed } from 'vue';
|
|
30
|
-
import
|
|
41
|
+
import {
|
|
42
|
+
rakeStrokeStyle,
|
|
43
|
+
type PartitiveMultiplicity,
|
|
44
|
+
type RakeStrokeStyle,
|
|
45
|
+
} from '../utils/partitive-multiplicity';
|
|
31
46
|
|
|
32
47
|
export interface PartitiveMemberLabeled {
|
|
33
48
|
uri: string;
|
|
34
49
|
label: string;
|
|
35
|
-
|
|
50
|
+
multiplicity: PartitiveMultiplicity;
|
|
51
|
+
isDelimiting: boolean;
|
|
36
52
|
}
|
|
37
53
|
|
|
38
54
|
const props = withDefaults(defineProps<{
|
|
39
55
|
comprehensiveLabel: string;
|
|
40
56
|
partitives: PartitiveMemberLabeled[];
|
|
41
57
|
completeness: 'complete' | 'partial';
|
|
42
|
-
plurality: TypeSharedPluralityWire | null;
|
|
43
58
|
criterion?: string | null;
|
|
59
|
+
/** Optional per-member tooltip string. */
|
|
60
|
+
memberTooltip?: (m: { multiplicity: PartitiveMultiplicity; isDelimiting: boolean }) => string;
|
|
44
61
|
}>(), {
|
|
45
62
|
criterion: null,
|
|
46
63
|
});
|
|
@@ -59,29 +76,19 @@ const SPINE_TO_NODE = 32;
|
|
|
59
76
|
const GAP_X = 16;
|
|
60
77
|
const PADDING_X = 20;
|
|
61
78
|
const PADDING_Y = 14;
|
|
62
|
-
const DOUBLE_GAP = 3;
|
|
79
|
+
const DOUBLE_GAP = 3; // gap between the two lines of a close-set double
|
|
63
80
|
const PARTIAL_TAIL = 32;
|
|
64
81
|
const DASH_PATTERN = '4 3';
|
|
65
|
-
const CHAR_W = 6.2;
|
|
66
|
-
const NODE_TEXT_PAD = 16;
|
|
67
|
-
|
|
68
|
-
type LineVariant = 'single' | 'double-solid' | 'solid-plus-dashed';
|
|
69
|
-
|
|
70
|
-
const lineVariant = computed<LineVariant>(() => {
|
|
71
|
-
if (!props.plurality?.isShared) return 'single';
|
|
72
|
-
return props.plurality.isUncertain ? 'solid-plus-dashed' : 'double-solid';
|
|
73
|
-
});
|
|
82
|
+
const CHAR_W = 6.2; // approx for 11px sans-serif
|
|
83
|
+
const NODE_TEXT_PAD = 16;
|
|
84
|
+
const FRAME_STROKE_WIDTH = 1.4; // stem + spine use this width always
|
|
74
85
|
|
|
75
86
|
/** Estimated pixel width of a string at 11px sans-serif. */
|
|
76
87
|
function estimateTextWidth(text: string): number {
|
|
77
88
|
return text.length * CHAR_W;
|
|
78
89
|
}
|
|
79
90
|
|
|
80
|
-
/**
|
|
81
|
-
* Wrap a label to at most 2 lines, breaking on word boundaries.
|
|
82
|
-
* If a single word exceeds the limit, it stays on its own line and
|
|
83
|
-
* gets truncated with an ellipsis.
|
|
84
|
-
*/
|
|
91
|
+
/** Wrap a label to at most 2 lines, breaking on word boundaries. */
|
|
85
92
|
function wrapLabel(text: string, maxCharsPerLine: number): string[] {
|
|
86
93
|
if (text.length <= maxCharsPerLine) return [text];
|
|
87
94
|
const words = text.split(/\s+/);
|
|
@@ -89,7 +96,6 @@ function wrapLabel(text: string, maxCharsPerLine: number): string[] {
|
|
|
89
96
|
let current = '';
|
|
90
97
|
for (const w of words) {
|
|
91
98
|
if (w.length > maxCharsPerLine) {
|
|
92
|
-
// word itself too long — flush current, push truncated word
|
|
93
99
|
if (current) { lines.push(current); current = ''; }
|
|
94
100
|
lines.push(w.slice(0, Math.max(1, maxCharsPerLine - 1)) + '…');
|
|
95
101
|
continue;
|
|
@@ -132,7 +138,12 @@ const layout = computed(() => {
|
|
|
132
138
|
const partitiveSlots = props.partitives.map((member, i) => {
|
|
133
139
|
const x = PADDING_X + nodeW / 2 + i * (nodeW + GAP_X);
|
|
134
140
|
const labelLines = wrapped[i + 1]; // index 0 is comprehensive
|
|
135
|
-
return {
|
|
141
|
+
return {
|
|
142
|
+
member,
|
|
143
|
+
x,
|
|
144
|
+
labelLines,
|
|
145
|
+
style: rakeStrokeStyle(member.multiplicity, member.isDelimiting),
|
|
146
|
+
};
|
|
136
147
|
});
|
|
137
148
|
|
|
138
149
|
const leftX = partitiveSlots[0]?.x ?? centerX;
|
|
@@ -157,19 +168,38 @@ const layout = computed(() => {
|
|
|
157
168
|
};
|
|
158
169
|
});
|
|
159
170
|
|
|
160
|
-
|
|
161
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Build SVG stroke attributes for the relation frame (stem + spine).
|
|
173
|
+
*
|
|
174
|
+
* The frame is structural — always a single solid line at standard
|
|
175
|
+
* width. Per-member multiplicity + delimiting are encoded on the drops
|
|
176
|
+
* only (MECE: frame vs drops).
|
|
177
|
+
*/
|
|
178
|
+
const frameStrokeAttrs = computed(() => ({
|
|
179
|
+
stroke: 'currentColor',
|
|
180
|
+
'stroke-width': FRAME_STROKE_WIDTH,
|
|
181
|
+
}));
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Build SVG stroke attributes for one of a drop's parallel lines.
|
|
185
|
+
* The drop may have 1 or 2 parallel lines per its multiplicity.
|
|
186
|
+
*/
|
|
187
|
+
function dropStrokeAttrs(style: RakeStrokeStyle, dashed: boolean, opacity = 1) {
|
|
162
188
|
return {
|
|
163
189
|
stroke: 'currentColor',
|
|
164
|
-
'stroke-width':
|
|
190
|
+
'stroke-width': style.strokeWidth,
|
|
165
191
|
opacity,
|
|
166
|
-
...(
|
|
192
|
+
...(dashed ? { 'stroke-dasharray': DASH_PATTERN } : {}),
|
|
167
193
|
};
|
|
168
194
|
}
|
|
169
195
|
|
|
170
196
|
function emitNav(uri: string) {
|
|
171
197
|
emit('navigate', uri);
|
|
172
198
|
}
|
|
199
|
+
|
|
200
|
+
function tooltipFor(member: PartitiveMemberLabeled): string | undefined {
|
|
201
|
+
return props.memberTooltip?.({ multiplicity: member.multiplicity, isDelimiting: member.isDelimiting });
|
|
202
|
+
}
|
|
173
203
|
</script>
|
|
174
204
|
|
|
175
205
|
<template>
|
|
@@ -212,71 +242,53 @@ function emitNav(uri: string) {
|
|
|
212
242
|
class="text-[9px] italic fill-emerald-800 dark:fill-emerald-200/80"
|
|
213
243
|
>{{ criterion }}</text>
|
|
214
244
|
|
|
215
|
-
<!-- Stem
|
|
245
|
+
<!-- Stem: comprehensive → spine (single solid frame) -->
|
|
216
246
|
<line
|
|
217
|
-
v-if="lineVariant === 'single'"
|
|
218
247
|
:x1="layout.centerX" :y1="layout.compY + layout.nodeH / 2"
|
|
219
248
|
:x2="layout.centerX" :y2="layout.spineY"
|
|
220
|
-
v-bind="
|
|
249
|
+
v-bind="frameStrokeAttrs"
|
|
221
250
|
/>
|
|
222
|
-
<template v-else>
|
|
223
|
-
<line
|
|
224
|
-
:x1="layout.centerX - DOUBLE_GAP / 2" :y1="layout.compY + layout.nodeH / 2"
|
|
225
|
-
:x2="layout.centerX - DOUBLE_GAP / 2" :y2="layout.spineY"
|
|
226
|
-
v-bind="strokeAttr('double-solid')"
|
|
227
|
-
/>
|
|
228
|
-
<line
|
|
229
|
-
:x1="layout.centerX + DOUBLE_GAP / 2" :y1="layout.compY + layout.nodeH / 2"
|
|
230
|
-
:x2="layout.centerX + DOUBLE_GAP / 2" :y2="layout.spineY"
|
|
231
|
-
v-bind="strokeAttr(lineVariant, 1, lineVariant === 'solid-plus-dashed' ? 'dash' : undefined)"
|
|
232
|
-
/>
|
|
233
|
-
</template>
|
|
234
251
|
|
|
235
|
-
<!-- Spine
|
|
252
|
+
<!-- Spine: leftmost tooth → rightmost tooth (single solid frame;
|
|
253
|
+
extended for completeness: 'partial') -->
|
|
236
254
|
<line
|
|
237
|
-
v-if="lineVariant === 'single'"
|
|
238
255
|
:x1="layout.leftX" :y1="layout.spineY"
|
|
239
256
|
:x2="layout.spineEndX" :y2="layout.spineY"
|
|
240
|
-
v-bind="
|
|
257
|
+
v-bind="frameStrokeAttrs"
|
|
241
258
|
/>
|
|
242
|
-
<template v-else>
|
|
243
|
-
<line
|
|
244
|
-
:x1="layout.leftX" :y1="layout.spineY - DOUBLE_GAP / 2"
|
|
245
|
-
:x2="layout.spineEndX" :y2="layout.spineY - DOUBLE_GAP / 2"
|
|
246
|
-
v-bind="strokeAttr('double-solid')"
|
|
247
|
-
/>
|
|
248
|
-
<line
|
|
249
|
-
:x1="layout.leftX" :y1="layout.spineY + DOUBLE_GAP / 2"
|
|
250
|
-
:x2="layout.spineEndX" :y2="layout.spineY + DOUBLE_GAP / 2"
|
|
251
|
-
v-bind="strokeAttr(lineVariant, 1, lineVariant === 'solid-plus-dashed' ? 'dash' : undefined)"
|
|
252
|
-
/>
|
|
253
|
-
</template>
|
|
254
259
|
|
|
255
260
|
<!-- Drops + partitive nodes -->
|
|
256
261
|
<g v-for="(slot, i) in layout.partitiveSlots" :key="i">
|
|
262
|
+
<!-- Primary drop line -->
|
|
257
263
|
<line
|
|
258
264
|
:x1="slot.x" :y1="layout.spineY"
|
|
259
265
|
:x2="slot.x" :y2="layout.partY - layout.nodeH / 2"
|
|
260
|
-
v-bind="
|
|
266
|
+
v-bind="dropStrokeAttrs(slot.style, slot.style.primaryDashed)"
|
|
267
|
+
/>
|
|
268
|
+
<!-- Secondary drop line (only for lineCount === 2; e.g. *_multiple, compulsory_at_least_one) -->
|
|
269
|
+
<line
|
|
270
|
+
v-if="slot.style.lineCount === 2"
|
|
271
|
+
:x1="slot.x + DOUBLE_GAP" :y1="layout.spineY"
|
|
272
|
+
:x2="slot.x + DOUBLE_GAP" :y2="layout.partY - layout.nodeH / 2"
|
|
273
|
+
v-bind="dropStrokeAttrs(slot.style, slot.style.secondaryDashed)"
|
|
261
274
|
/>
|
|
262
|
-
<g class="cursor-pointer" @click="emitNav(slot.member.uri)">
|
|
275
|
+
<g class="cursor-pointer" :title="tooltipFor(slot.member)" @click="emitNav(slot.member.uri)">
|
|
263
276
|
<rect
|
|
264
277
|
:x="slot.x - layout.nodeW / 2"
|
|
265
278
|
:y="layout.partY - layout.nodeH / 2"
|
|
266
279
|
:width="layout.nodeW"
|
|
267
280
|
:height="layout.nodeH"
|
|
268
281
|
rx="6"
|
|
269
|
-
:class="slot.
|
|
270
|
-
? 'fill-emerald-
|
|
282
|
+
:class="slot.style.strokeWidth > 2
|
|
283
|
+
? 'fill-emerald-100 dark:fill-emerald-900/50 stroke-current'
|
|
271
284
|
: 'fill-emerald-50 dark:fill-emerald-900/30 stroke-current'"
|
|
272
|
-
:stroke-
|
|
273
|
-
stroke-width="1.2"
|
|
285
|
+
:stroke-width="slot.style.strokeWidth > 2 ? 2.4 : 1.2"
|
|
274
286
|
/>
|
|
275
287
|
<text
|
|
276
288
|
:x="slot.x"
|
|
277
289
|
text-anchor="middle"
|
|
278
|
-
:class="slot.
|
|
279
|
-
? 'text-[11px] fill-emerald-
|
|
290
|
+
:class="slot.style.strokeWidth > 2
|
|
291
|
+
? 'text-[11px] fill-emerald-900 dark:fill-emerald-50 font-medium'
|
|
280
292
|
: 'text-[11px] fill-emerald-900 dark:fill-emerald-100'"
|
|
281
293
|
>
|
|
282
294
|
<tspan
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/**
|
|
3
3
|
* PartitiveRelationList — renders one-to-many partitive decompositions
|
|
4
|
-
* as ISO 704 rake diagrams. Each relation shows:
|
|
5
|
-
* - completeness badge (
|
|
6
|
-
* -
|
|
7
|
-
* - criterion as italic text
|
|
8
|
-
* - the rake diagram itself (PartitiveRelationDiagram)
|
|
4
|
+
* as ISO 704 rake diagrams. Each relation card shows:
|
|
5
|
+
* - completeness badge (Complete / Partial)
|
|
6
|
+
* - the comprehensive's designation as the card title
|
|
7
|
+
* - optional criterion as italic text
|
|
8
|
+
* - the rake diagram itself (PartitiveRelationDiagram), where each
|
|
9
|
+
* drop's line style encodes its multiplicity + is_delimiting per
|
|
10
|
+
* ISO 704:2022 (5 multiplicity values × 2 delimiting states).
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* Header badges communicate metadata that the diagram's line styles
|
|
13
|
+
* also encode — for users who can't perceive the visual difference
|
|
14
|
+
* (screen readers, low vision, monochrome displays).
|
|
13
15
|
*/
|
|
14
16
|
import type { PartitiveRelationWire, Manifest } from '../adapters/types';
|
|
15
17
|
import { useRouter } from 'vue-router';
|
|
16
18
|
import { useVocabularyStore } from '../stores/vocabulary';
|
|
17
19
|
import { getFactory } from '../adapters/factory';
|
|
18
20
|
import { useI18n, locale } from '../i18n';
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
certaintyLabel,
|
|
22
|
-
} from '../utils/partitive-relation-styling';
|
|
21
|
+
import { completenessLabel } from '../utils/partitive-relation-styling';
|
|
22
|
+
import { multiplicityDefinition } from '../utils/partitive-multiplicity';
|
|
23
23
|
import PartitiveRelationDiagram, {
|
|
24
24
|
type PartitiveMemberLabeled,
|
|
25
25
|
} from './PartitiveRelationDiagram.vue';
|
|
@@ -77,25 +77,24 @@ function criterionText(criterion?: Record<string, string>): string | null {
|
|
|
77
77
|
?? null;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
label = `${label} (${t('partitive.plurality.uncertain') || 'uncertain'})`;
|
|
85
|
-
}
|
|
86
|
-
if (plurality.sharedType) {
|
|
87
|
-
label = `${label}: ${plurality.sharedType}`;
|
|
88
|
-
}
|
|
89
|
-
return label;
|
|
90
|
-
}
|
|
91
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Build a per-member view-model carrying the resolved designation plus
|
|
82
|
+
* the multiplicity + is_delimiting fields the diagram needs.
|
|
83
|
+
*/
|
|
92
84
|
function labeledMembers(rel: PartitiveRelationWire): PartitiveMemberLabeled[] {
|
|
93
85
|
return rel.partitives.map(m => ({
|
|
94
86
|
uri: m.uri,
|
|
95
87
|
label: designationFor(m.uri),
|
|
96
|
-
|
|
88
|
+
multiplicity: m.multiplicity,
|
|
89
|
+
isDelimiting: m.isDelimiting,
|
|
97
90
|
}));
|
|
98
91
|
}
|
|
92
|
+
|
|
93
|
+
/** Tooltip text for a member, shown on hover. */
|
|
94
|
+
function memberTooltip(m: { multiplicity: string; isDelimiting: boolean }): string {
|
|
95
|
+
const def = multiplicityDefinition(m.multiplicity as Parameters<typeof multiplicityDefinition>[0]);
|
|
96
|
+
return m.isDelimiting ? `Delimiting ${def.label.toLowerCase()}` : def.label;
|
|
97
|
+
}
|
|
99
98
|
</script>
|
|
100
99
|
|
|
101
100
|
<template>
|
|
@@ -113,13 +112,6 @@ function labeledMembers(rel: PartitiveRelationWire): PartitiveMemberLabeled[] {
|
|
|
113
112
|
<span class="text-[10px] font-mono px-1.5 py-0.5 rounded bg-emerald-200 dark:bg-emerald-800 text-emerald-900 dark:text-emerald-100 uppercase">
|
|
114
113
|
{{ completenessLabel(rel.completeness) }}
|
|
115
114
|
</span>
|
|
116
|
-
<span
|
|
117
|
-
v-if="rel.plurality && rel.plurality.isShared"
|
|
118
|
-
class="text-[10px] font-mono px-1.5 py-0.5 rounded bg-amber-200 dark:bg-amber-800 text-amber-900 dark:text-amber-100 uppercase"
|
|
119
|
-
:title="pluralityBadge(rel.plurality)"
|
|
120
|
-
>
|
|
121
|
-
{{ pluralityBadge(rel.plurality) }}
|
|
122
|
-
</span>
|
|
123
115
|
<button
|
|
124
116
|
@click="navigate(rel.comprehensive)"
|
|
125
117
|
class="ml-auto text-sm font-medium text-emerald-900 dark:text-emerald-100 hover:underline truncate"
|
|
@@ -133,14 +125,14 @@ function labeledMembers(rel: PartitiveRelationWire): PartitiveMemberLabeled[] {
|
|
|
133
125
|
>
|
|
134
126
|
{{ criterionText(rel.criterion) }}
|
|
135
127
|
</div>
|
|
136
|
-
<!-- Rake diagram (ISO 704 partitive-relation convention) -->
|
|
128
|
+
<!-- Rake diagram (ISO 704:2022 partitive-relation convention) -->
|
|
137
129
|
<div class="px-3 py-3 overflow-x-auto">
|
|
138
130
|
<PartitiveRelationDiagram
|
|
139
131
|
:comprehensive-label="designationFor(rel.comprehensive)"
|
|
140
132
|
:partitives="labeledMembers(rel)"
|
|
141
133
|
:completeness="rel.completeness"
|
|
142
|
-
:plurality="rel.plurality"
|
|
143
134
|
:criterion="null"
|
|
135
|
+
:member-tooltip="memberTooltip"
|
|
144
136
|
@navigate="navigate"
|
|
145
137
|
/>
|
|
146
138
|
</div>
|