@issuegraph/editor 0.1.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/LICENSE +201 -0
- package/README.md +238 -0
- package/dist/audit/findings.d.ts +225 -0
- package/dist/audit/findings.d.ts.map +1 -0
- package/dist/audit/findings.js +348 -0
- package/dist/audit/findings.js.map +1 -0
- package/dist/audit/styles.d.ts +37 -0
- package/dist/audit/styles.d.ts.map +1 -0
- package/dist/audit/styles.js +72 -0
- package/dist/audit/styles.js.map +1 -0
- package/dist/audit/surface.d.ts +155 -0
- package/dist/audit/surface.d.ts.map +1 -0
- package/dist/audit/surface.js +202 -0
- package/dist/audit/surface.js.map +1 -0
- package/dist/create/draft.d.ts +119 -0
- package/dist/create/draft.d.ts.map +1 -0
- package/dist/create/draft.js +114 -0
- package/dist/create/draft.js.map +1 -0
- package/dist/create/keys.d.ts +225 -0
- package/dist/create/keys.d.ts.map +1 -0
- package/dist/create/keys.js +228 -0
- package/dist/create/keys.js.map +1 -0
- package/dist/create/placement.d.ts +69 -0
- package/dist/create/placement.d.ts.map +1 -0
- package/dist/create/placement.js +76 -0
- package/dist/create/placement.js.map +1 -0
- package/dist/firstpass/batch.d.ts +190 -0
- package/dist/firstpass/batch.d.ts.map +1 -0
- package/dist/firstpass/batch.js +143 -0
- package/dist/firstpass/batch.js.map +1 -0
- package/dist/firstpass/candidates.d.ts +128 -0
- package/dist/firstpass/candidates.d.ts.map +1 -0
- package/dist/firstpass/candidates.js +39 -0
- package/dist/firstpass/candidates.js.map +1 -0
- package/dist/firstpass/keys.d.ts +90 -0
- package/dist/firstpass/keys.d.ts.map +1 -0
- package/dist/firstpass/keys.js +112 -0
- package/dist/firstpass/keys.js.map +1 -0
- package/dist/firstpass/queue.d.ts +183 -0
- package/dist/firstpass/queue.d.ts.map +1 -0
- package/dist/firstpass/queue.js +159 -0
- package/dist/firstpass/queue.js.map +1 -0
- package/dist/firstpass/render.d.ts +100 -0
- package/dist/firstpass/render.d.ts.map +1 -0
- package/dist/firstpass/render.js +216 -0
- package/dist/firstpass/render.js.map +1 -0
- package/dist/firstpass/styles.d.ts +36 -0
- package/dist/firstpass/styles.d.ts.map +1 -0
- package/dist/firstpass/styles.js +148 -0
- package/dist/firstpass/styles.js.map +1 -0
- package/dist/firstpass/view.d.ts +82 -0
- package/dist/firstpass/view.d.ts.map +1 -0
- package/dist/firstpass/view.js +60 -0
- package/dist/firstpass/view.js.map +1 -0
- package/dist/firstpass/words.d.ts +105 -0
- package/dist/firstpass/words.d.ts.map +1 -0
- package/dist/firstpass/words.js +28 -0
- package/dist/firstpass/words.js.map +1 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +147 -0
- package/dist/index.js.map +1 -0
- package/dist/overlay/grammar.d.ts +259 -0
- package/dist/overlay/grammar.d.ts.map +1 -0
- package/dist/overlay/grammar.js +223 -0
- package/dist/overlay/grammar.js.map +1 -0
- package/dist/overlay/render.d.ts +125 -0
- package/dist/overlay/render.d.ts.map +1 -0
- package/dist/overlay/render.js +439 -0
- package/dist/overlay/render.js.map +1 -0
- package/dist/overlay/styles.d.ts +41 -0
- package/dist/overlay/styles.d.ts.map +1 -0
- package/dist/overlay/styles.js +124 -0
- package/dist/overlay/styles.js.map +1 -0
- package/dist/picker/render.d.ts +80 -0
- package/dist/picker/render.d.ts.map +1 -0
- package/dist/picker/render.js +112 -0
- package/dist/picker/render.js.map +1 -0
- package/dist/picker/styles.d.ts +26 -0
- package/dist/picker/styles.d.ts.map +1 -0
- package/dist/picker/styles.js +116 -0
- package/dist/picker/styles.js.map +1 -0
- package/dist/picker/view.d.ts +135 -0
- package/dist/picker/view.d.ts.map +1 -0
- package/dist/picker/view.js +117 -0
- package/dist/picker/view.js.map +1 -0
- package/dist/picker/words.d.ts +53 -0
- package/dist/picker/words.d.ts.map +1 -0
- package/dist/picker/words.js +28 -0
- package/dist/picker/words.js.map +1 -0
- package/dist/reevaluate/render.d.ts +77 -0
- package/dist/reevaluate/render.d.ts.map +1 -0
- package/dist/reevaluate/render.js +211 -0
- package/dist/reevaluate/render.js.map +1 -0
- package/dist/reevaluate/styles.d.ts +24 -0
- package/dist/reevaluate/styles.d.ts.map +1 -0
- package/dist/reevaluate/styles.js +182 -0
- package/dist/reevaluate/styles.js.map +1 -0
- package/dist/reevaluate/view.d.ts +102 -0
- package/dist/reevaluate/view.d.ts.map +1 -0
- package/dist/reevaluate/view.js +111 -0
- package/dist/reevaluate/view.js.map +1 -0
- package/dist/reevaluate/words.d.ts +54 -0
- package/dist/reevaluate/words.d.ts.map +1 -0
- package/dist/reevaluate/words.js +29 -0
- package/dist/reevaluate/words.js.map +1 -0
- package/dist/scale/commands.d.ts +60 -0
- package/dist/scale/commands.d.ts.map +1 -0
- package/dist/scale/commands.js +49 -0
- package/dist/scale/commands.js.map +1 -0
- package/dist/scale/ladder.d.ts +141 -0
- package/dist/scale/ladder.d.ts.map +1 -0
- package/dist/scale/ladder.js +243 -0
- package/dist/scale/ladder.js.map +1 -0
- package/dist/scale/render.d.ts +85 -0
- package/dist/scale/render.d.ts.map +1 -0
- package/dist/scale/render.js +194 -0
- package/dist/scale/render.js.map +1 -0
- package/dist/scale/styles.d.ts +21 -0
- package/dist/scale/styles.d.ts.map +1 -0
- package/dist/scale/styles.js +105 -0
- package/dist/scale/styles.js.map +1 -0
- package/dist/workspace/inspector.d.ts +117 -0
- package/dist/workspace/inspector.d.ts.map +1 -0
- package/dist/workspace/inspector.js +140 -0
- package/dist/workspace/inspector.js.map +1 -0
- package/dist/workspace/rail.d.ts +122 -0
- package/dist/workspace/rail.d.ts.map +1 -0
- package/dist/workspace/rail.js +216 -0
- package/dist/workspace/rail.js.map +1 -0
- package/dist/workspace/render.d.ts +139 -0
- package/dist/workspace/render.d.ts.map +1 -0
- package/dist/workspace/render.js +366 -0
- package/dist/workspace/render.js.map +1 -0
- package/dist/workspace/selection.d.ts +92 -0
- package/dist/workspace/selection.d.ts.map +1 -0
- package/dist/workspace/selection.js +83 -0
- package/dist/workspace/selection.js.map +1 -0
- package/dist/workspace/styles.d.ts +39 -0
- package/dist/workspace/styles.d.ts.map +1 -0
- package/dist/workspace/styles.js +313 -0
- package/dist/workspace/styles.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type picker, the direction statement and the flip control, as markup.
|
|
3
|
+
*
|
|
4
|
+
* ## Every readable byte comes from the host or from the document
|
|
5
|
+
*
|
|
6
|
+
* This surface renders no word of its own — not in a text node, and not in an
|
|
7
|
+
* `aria-label`. That is stricter than the rest of this package (the scale
|
|
8
|
+
* ladder writes its own chip labels), and it is stricter deliberately: what is
|
|
9
|
+
* being worded here is a RELATIONSHIP, so a phrase written in would be an
|
|
10
|
+
* English clause in the one place the design says a reader most often gets the
|
|
11
|
+
* encoding wrong. `render.test.ts` enforces it by asserting that every text node
|
|
12
|
+
* is either a {@link ./words.ts PickerWords} entry or an issue reference — a
|
|
13
|
+
* total claim rather than a spot check, so a word added later fails rather than
|
|
14
|
+
* slipping in.
|
|
15
|
+
*
|
|
16
|
+
* ## The default word order is a DEFAULT
|
|
17
|
+
*
|
|
18
|
+
* The statement is drawn subject · phrase · object, which is a word order and
|
|
19
|
+
* therefore a presentation choice this layer is not entitled to impose. It is
|
|
20
|
+
* offered as the ordinary case and bypassed the same way the re-evaluate
|
|
21
|
+
* summary's is: {@link ./view.ts PickerView} carries the ordered pair and the
|
|
22
|
+
* kind and no order beyond that, so a host that needs another arrangement
|
|
23
|
+
* renders the view model. Nothing here is load-bearing for it.
|
|
24
|
+
*
|
|
25
|
+
* ## It publishes commands and wires nothing
|
|
26
|
+
*
|
|
27
|
+
* `data-ig-command="retype"` with a `data-ig-kind`, and
|
|
28
|
+
* `data-ig-command="flip"`. Listener wiring, focus and dispatch belong to the
|
|
29
|
+
* mount, exactly as the scale ladder and the re-evaluate surface already defer
|
|
30
|
+
* them — and the proposal each command stands for is on the view model, so a
|
|
31
|
+
* mount reads it rather than reconstructing it from attributes.
|
|
32
|
+
*
|
|
33
|
+
* ## It names itself without claiming a heading level
|
|
34
|
+
*
|
|
35
|
+
* The label is a paragraph, and the list carries the same words as its
|
|
36
|
+
* `aria-label`. A heading would be a claim about the HOST's document outline —
|
|
37
|
+
* whether this picker is a section of something, and at what depth — which a
|
|
38
|
+
* package rendered into an unknown page cannot make. A host that wants it in
|
|
39
|
+
* the outline wraps it in a heading of its own choosing.
|
|
40
|
+
*/
|
|
41
|
+
import { type Theme } from '@issuegraph/viewer';
|
|
42
|
+
import type { EdgeId, GraphDocument } from '@issuegraph/store';
|
|
43
|
+
import { type PickerView } from './view.ts';
|
|
44
|
+
import type { PickerWords } from './words.ts';
|
|
45
|
+
export interface PickerOptions {
|
|
46
|
+
/**
|
|
47
|
+
* The words. Required — see {@link PickerWords} for why this package will not
|
|
48
|
+
* invent them.
|
|
49
|
+
*/
|
|
50
|
+
readonly words: PickerWords;
|
|
51
|
+
readonly theme?: Theme | undefined;
|
|
52
|
+
/** The selector the theme's custom properties are written onto. */
|
|
53
|
+
readonly themeSelector?: string | undefined;
|
|
54
|
+
}
|
|
55
|
+
export interface PickerResult {
|
|
56
|
+
readonly view: PickerView;
|
|
57
|
+
/** The picker, the statement and the flip control, under one root. */
|
|
58
|
+
readonly markup: string;
|
|
59
|
+
/**
|
|
60
|
+
* The theme and this surface's own stylesheet. Install both.
|
|
61
|
+
*
|
|
62
|
+
* The VIEWER's stylesheet is deliberately not among them, which is where the
|
|
63
|
+
* scale ladder and the re-evaluate surface differ — both draw viewer elements
|
|
64
|
+
* and so must ship the rules for them. This surface draws none, so including
|
|
65
|
+
* them would ship a sheet whose every selector matches nothing here, and a
|
|
66
|
+
* host that installs the picker alone would take the whole canvas stylesheet
|
|
67
|
+
* with it. A host drawing a viewer installs the viewer's own.
|
|
68
|
+
*/
|
|
69
|
+
readonly styles: string;
|
|
70
|
+
readonly diagnostics: readonly string[];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The picker for one existing edge.
|
|
74
|
+
*
|
|
75
|
+
* Takes a DOCUMENT and an edge id — never a store and never a source. A host
|
|
76
|
+
* hands the proposals on {@link PickerResult.view} to `Store.propose`, which is
|
|
77
|
+
* the only thing in the family that dispatches.
|
|
78
|
+
*/
|
|
79
|
+
export declare function renderPicker(document: GraphDocument, edgeId: EdgeId, options: PickerOptions): PickerResult;
|
|
80
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/picker/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAEL,KAAK,KAAK,EAKX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE/D,OAAO,EAA4C,KAAK,UAAU,EAAc,MAAM,WAAW,CAAC;AAElG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IACnC,mEAAmE;IACnE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;;;;OASG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAiDD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,aAAa,GACrB,YAAY,CAiCd"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type picker, the direction statement and the flip control, as markup.
|
|
3
|
+
*
|
|
4
|
+
* ## Every readable byte comes from the host or from the document
|
|
5
|
+
*
|
|
6
|
+
* This surface renders no word of its own — not in a text node, and not in an
|
|
7
|
+
* `aria-label`. That is stricter than the rest of this package (the scale
|
|
8
|
+
* ladder writes its own chip labels), and it is stricter deliberately: what is
|
|
9
|
+
* being worded here is a RELATIONSHIP, so a phrase written in would be an
|
|
10
|
+
* English clause in the one place the design says a reader most often gets the
|
|
11
|
+
* encoding wrong. `render.test.ts` enforces it by asserting that every text node
|
|
12
|
+
* is either a {@link ./words.ts PickerWords} entry or an issue reference — a
|
|
13
|
+
* total claim rather than a spot check, so a word added later fails rather than
|
|
14
|
+
* slipping in.
|
|
15
|
+
*
|
|
16
|
+
* ## The default word order is a DEFAULT
|
|
17
|
+
*
|
|
18
|
+
* The statement is drawn subject · phrase · object, which is a word order and
|
|
19
|
+
* therefore a presentation choice this layer is not entitled to impose. It is
|
|
20
|
+
* offered as the ordinary case and bypassed the same way the re-evaluate
|
|
21
|
+
* summary's is: {@link ./view.ts PickerView} carries the ordered pair and the
|
|
22
|
+
* kind and no order beyond that, so a host that needs another arrangement
|
|
23
|
+
* renders the view model. Nothing here is load-bearing for it.
|
|
24
|
+
*
|
|
25
|
+
* ## It publishes commands and wires nothing
|
|
26
|
+
*
|
|
27
|
+
* `data-ig-command="retype"` with a `data-ig-kind`, and
|
|
28
|
+
* `data-ig-command="flip"`. Listener wiring, focus and dispatch belong to the
|
|
29
|
+
* mount, exactly as the scale ladder and the re-evaluate surface already defer
|
|
30
|
+
* them — and the proposal each command stands for is on the view model, so a
|
|
31
|
+
* mount reads it rather than reconstructing it from attributes.
|
|
32
|
+
*
|
|
33
|
+
* ## It names itself without claiming a heading level
|
|
34
|
+
*
|
|
35
|
+
* The label is a paragraph, and the list carries the same words as its
|
|
36
|
+
* `aria-label`. A heading would be a claim about the HOST's document outline —
|
|
37
|
+
* whether this picker is a section of something, and at what depth — which a
|
|
38
|
+
* package rendered into an unknown page cannot make. A host that wants it in
|
|
39
|
+
* the outline wraps it in a heading of its own choosing.
|
|
40
|
+
*/
|
|
41
|
+
import { element, renderMarkup, resolveTheme, themeCss, } from '@issuegraph/viewer';
|
|
42
|
+
import { pickerView } from "./view.js";
|
|
43
|
+
import { pickerStylesheet } from "./styles.js";
|
|
44
|
+
/** One row of the picker. The phrase is the host's; everything else is data. */
|
|
45
|
+
function optionSpec(option, words) {
|
|
46
|
+
return element('li', {
|
|
47
|
+
class: 'ig-picker-kind',
|
|
48
|
+
'data-ig-kind': option.kind,
|
|
49
|
+
'data-directed': option.directed ? 'true' : 'false',
|
|
50
|
+
'data-current': option.current ? 'true' : 'false',
|
|
51
|
+
}, [
|
|
52
|
+
element('button', {
|
|
53
|
+
type: 'button',
|
|
54
|
+
class: 'ig-picker-choice',
|
|
55
|
+
'data-ig-command': 'retype',
|
|
56
|
+
'data-ig-kind': option.kind,
|
|
57
|
+
// A radio group's checked member, spelled for assistive technology.
|
|
58
|
+
// The current kind is still OFFERED — hiding it would be a second
|
|
59
|
+
// validity rule out here, and the store already refuses the edit.
|
|
60
|
+
'aria-pressed': option.current ? 'true' : 'false',
|
|
61
|
+
}, [
|
|
62
|
+
element('span', { class: 'ig-picker-phrase' }, [words.kinds[option.kind]]),
|
|
63
|
+
option.current ? element('span', { class: 'ig-picker-current' }, [words.current]) : null,
|
|
64
|
+
]),
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The direction statement.
|
|
69
|
+
*
|
|
70
|
+
* The two references are the DOCUMENT's, not this package's words, and each
|
|
71
|
+
* carries the role it plays so a host can restyle or reorder them without
|
|
72
|
+
* parsing the sentence back apart.
|
|
73
|
+
*/
|
|
74
|
+
function directionSpec(direction, words) {
|
|
75
|
+
return element('p', { class: 'ig-picker-direction', 'data-ig-kind': direction.kind }, [
|
|
76
|
+
element('span', { class: 'ig-picker-ref', 'data-ig-role': 'from' }, [direction.from]),
|
|
77
|
+
element('span', { class: 'ig-picker-phrase' }, [words.kinds[direction.kind]]),
|
|
78
|
+
element('span', { class: 'ig-picker-ref', 'data-ig-role': 'to' }, [direction.to]),
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The picker for one existing edge.
|
|
83
|
+
*
|
|
84
|
+
* Takes a DOCUMENT and an edge id — never a store and never a source. A host
|
|
85
|
+
* hands the proposals on {@link PickerResult.view} to `Store.propose`, which is
|
|
86
|
+
* the only thing in the family that dispatches.
|
|
87
|
+
*/
|
|
88
|
+
export function renderPicker(document, edgeId, options) {
|
|
89
|
+
const view = pickerView(document, edgeId);
|
|
90
|
+
const theme = resolveTheme(options.theme);
|
|
91
|
+
const { words } = options;
|
|
92
|
+
const root = element('section', { class: 'ig-picker', 'data-ig-edge': view.edgeId }, [
|
|
93
|
+
element('p', { class: 'ig-picker-heading' }, [words.heading]),
|
|
94
|
+
view.options.length === 0
|
|
95
|
+
? null
|
|
96
|
+
: element('ul', { class: 'ig-picker-kinds', 'aria-label': words.heading }, view.options.map((option) => optionSpec(option, words))),
|
|
97
|
+
// Symmetric kinds render NEITHER of the next two. The absence is the
|
|
98
|
+
// finding: a control to reverse a relationship that carries no direction
|
|
99
|
+
// would claim the format says something it does not.
|
|
100
|
+
view.direction === null ? null : directionSpec(view.direction, words),
|
|
101
|
+
view.flip === null
|
|
102
|
+
? null
|
|
103
|
+
: element('button', { type: 'button', class: 'ig-picker-flip', 'data-ig-command': 'flip' }, [words.flip]),
|
|
104
|
+
]);
|
|
105
|
+
return {
|
|
106
|
+
view,
|
|
107
|
+
markup: renderMarkup(root),
|
|
108
|
+
styles: `${themeCss(theme, options.themeSelector ?? ':root')}\n${pickerStylesheet}`,
|
|
109
|
+
diagnostics: [...view.diagnostics],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/picker/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAGL,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,QAAQ,GACT,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAA6D,UAAU,EAAE,MAAM,WAAW,CAAC;AAClG,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAgC/C,gFAAgF;AAChF,SAAS,UAAU,CAAC,MAAkB,EAAE,KAAkB;IACxD,OAAO,OAAO,CACZ,IAAI,EACJ;QACE,KAAK,EAAE,gBAAgB;QACvB,cAAc,EAAE,MAAM,CAAC,IAAI;QAC3B,eAAe,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;QACnD,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;KAClD,EACD;QACE,OAAO,CACL,QAAQ,EACR;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,kBAAkB;YACzB,iBAAiB,EAAE,QAAQ;YAC3B,cAAc,EAAE,MAAM,CAAC,IAAI;YAC3B,oEAAoE;YACpE,kEAAkE;YAClE,kEAAkE;YAClE,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;SAClD,EACD;YACE,OAAO,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YAC1E,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;SACzF,CACF;KACF,CACF,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,SAA6B,EAAE,KAAkB;IACtE,OAAO,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,SAAS,CAAC,IAAI,EAAE,EAAE;QACpF,OAAO,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACrF,OAAO,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7E,OAAO,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;KAClF,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,QAAuB,EACvB,MAAc,EACd,OAAsB;IAEtB,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAE1B,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;QACnF,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7D,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YACvB,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,OAAO,CACL,IAAI,EACJ,EAAE,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,KAAK,CAAC,OAAO,EAAE,EACzD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CACxD;QACL,qEAAqE;QACrE,yEAAyE;QACzE,qDAAqD;QACrD,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC;QACrE,IAAI,CAAC,IAAI,KAAK,IAAI;YAChB,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,OAAO,CACL,QAAQ,EACR,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,EAAE,EACtE,CAAC,KAAK,CAAC,IAAI,CAAC,CACb;KACN,CAAC,CAAC;IAEH,OAAO;QACL,IAAI;QACJ,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC;QAC1B,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,IAAI,OAAO,CAAC,KAAK,gBAAgB,EAAE;QACnF,WAAW,EAAE,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC;KACnC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The picker's structural stylesheet.
|
|
3
|
+
*
|
|
4
|
+
* Same contract as the ladder's and the overlay's: layout, weight and state,
|
|
5
|
+
* never a value. Every colour, length and font is a var(--ig-...) the host's
|
|
6
|
+
* theme resolves, and styles.test.ts scans these bytes for a literal colour, a
|
|
7
|
+
* fixed length, and a token the theme does not define — so the rule is enforced
|
|
8
|
+
* rather than remembered.
|
|
9
|
+
*
|
|
10
|
+
* IT ADDS SELECTORS RATHER THAN REDEFINING THEM. Nothing here reaches into the
|
|
11
|
+
* viewer's own classes; this file styles only what the picker introduces.
|
|
12
|
+
*
|
|
13
|
+
* ## The statement is laid out as a FLEX ROW, and that is load-bearing
|
|
14
|
+
*
|
|
15
|
+
* render.ts draws the statement subject, phrase, object, and calls that word
|
|
16
|
+
* order a default a host may bypass. A flex row is what makes the bypass cheap:
|
|
17
|
+
* the CSS order property reorders flex children, so a host whose language puts
|
|
18
|
+
* the relationship elsewhere restyles rather than re-implements. Laying it out
|
|
19
|
+
* as inline text would have made the claim in render.ts true only in principle.
|
|
20
|
+
*
|
|
21
|
+
* Shipped as a string for the same reason the viewer's is: an entry that
|
|
22
|
+
* imports CSS cannot be loaded by a bare Node runtime, and a string needs no
|
|
23
|
+
* bundler.
|
|
24
|
+
*/
|
|
25
|
+
export declare const pickerStylesheet = "\n.ig-picker {\n display: flex;\n flex-direction: column;\n gap: var(--ig-space);\n font-family: var(--ig-font-ui);\n font-size: var(--ig-font-size);\n line-height: var(--ig-line-height);\n color: var(--ig-text-body);\n}\n\n.ig-picker-heading {\n margin: 0;\n font-weight: normal;\n font-size: var(--ig-font-size-small);\n color: var(--ig-text-muted);\n}\n\n.ig-picker-kinds {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: var(--ig-space-tight);\n}\n\n.ig-picker-choice {\n display: flex;\n align-items: baseline;\n gap: var(--ig-space-tight);\n width: 100%;\n text-align: left;\n background: var(--ig-surface);\n color: var(--ig-text-body);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n padding: var(--ig-space-tight);\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n.ig-picker-choice:focus-visible,\n.ig-picker-flip:focus-visible {\n outline: var(--ig-focus-ring) solid var(--ig-focus);\n outline-offset: var(--ig-stroke);\n}\n\n/* The kind the edge already carries. Marked on the ROW rather than on the\n button, so a host restyling the control keeps the distinction. */\n.ig-picker-kind[data-current='true'] .ig-picker-choice {\n background: var(--ig-surface-2);\n border-color: var(--ig-accent);\n color: var(--ig-text);\n}\n\n.ig-picker-current {\n margin-left: auto;\n font-size: var(--ig-font-size-small);\n color: var(--ig-text-muted);\n}\n\n/* Wraps rather than truncates: a qualified reference is long, and a statement\n whose object is cut off says something other than what the format holds. */\n.ig-picker-direction {\n margin: 0;\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--ig-space-tight);\n color: var(--ig-text-body);\n}\n\n.ig-picker-ref {\n font-family: var(--ig-font-mono);\n color: var(--ig-accent);\n}\n\n.ig-picker-flip {\n align-self: flex-start;\n background: var(--ig-surface);\n color: var(--ig-text-body);\n border: var(--ig-stroke) solid var(--ig-line);\n border-radius: var(--ig-radius);\n padding: var(--ig-space-tight);\n font-family: inherit;\n font-size: var(--ig-font-size-small);\n line-height: inherit;\n}\n";
|
|
26
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/picker/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,eAAO,MAAM,gBAAgB,ktEA0F5B,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The picker's structural stylesheet.
|
|
3
|
+
*
|
|
4
|
+
* Same contract as the ladder's and the overlay's: layout, weight and state,
|
|
5
|
+
* never a value. Every colour, length and font is a var(--ig-...) the host's
|
|
6
|
+
* theme resolves, and styles.test.ts scans these bytes for a literal colour, a
|
|
7
|
+
* fixed length, and a token the theme does not define — so the rule is enforced
|
|
8
|
+
* rather than remembered.
|
|
9
|
+
*
|
|
10
|
+
* IT ADDS SELECTORS RATHER THAN REDEFINING THEM. Nothing here reaches into the
|
|
11
|
+
* viewer's own classes; this file styles only what the picker introduces.
|
|
12
|
+
*
|
|
13
|
+
* ## The statement is laid out as a FLEX ROW, and that is load-bearing
|
|
14
|
+
*
|
|
15
|
+
* render.ts draws the statement subject, phrase, object, and calls that word
|
|
16
|
+
* order a default a host may bypass. A flex row is what makes the bypass cheap:
|
|
17
|
+
* the CSS order property reorders flex children, so a host whose language puts
|
|
18
|
+
* the relationship elsewhere restyles rather than re-implements. Laying it out
|
|
19
|
+
* as inline text would have made the claim in render.ts true only in principle.
|
|
20
|
+
*
|
|
21
|
+
* Shipped as a string for the same reason the viewer's is: an entry that
|
|
22
|
+
* imports CSS cannot be loaded by a bare Node runtime, and a string needs no
|
|
23
|
+
* bundler.
|
|
24
|
+
*/
|
|
25
|
+
export const pickerStylesheet = `
|
|
26
|
+
.ig-picker {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
gap: var(--ig-space);
|
|
30
|
+
font-family: var(--ig-font-ui);
|
|
31
|
+
font-size: var(--ig-font-size);
|
|
32
|
+
line-height: var(--ig-line-height);
|
|
33
|
+
color: var(--ig-text-body);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ig-picker-heading {
|
|
37
|
+
margin: 0;
|
|
38
|
+
font-weight: normal;
|
|
39
|
+
font-size: var(--ig-font-size-small);
|
|
40
|
+
color: var(--ig-text-muted);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ig-picker-kinds {
|
|
44
|
+
list-style: none;
|
|
45
|
+
margin: 0;
|
|
46
|
+
padding: 0;
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
gap: var(--ig-space-tight);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.ig-picker-choice {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: baseline;
|
|
55
|
+
gap: var(--ig-space-tight);
|
|
56
|
+
width: 100%;
|
|
57
|
+
text-align: left;
|
|
58
|
+
background: var(--ig-surface);
|
|
59
|
+
color: var(--ig-text-body);
|
|
60
|
+
border: var(--ig-stroke) solid var(--ig-line);
|
|
61
|
+
border-radius: var(--ig-radius);
|
|
62
|
+
padding: var(--ig-space-tight);
|
|
63
|
+
font-family: inherit;
|
|
64
|
+
font-size: inherit;
|
|
65
|
+
line-height: inherit;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ig-picker-choice:focus-visible,
|
|
69
|
+
.ig-picker-flip:focus-visible {
|
|
70
|
+
outline: var(--ig-focus-ring) solid var(--ig-focus);
|
|
71
|
+
outline-offset: var(--ig-stroke);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* The kind the edge already carries. Marked on the ROW rather than on the
|
|
75
|
+
button, so a host restyling the control keeps the distinction. */
|
|
76
|
+
.ig-picker-kind[data-current='true'] .ig-picker-choice {
|
|
77
|
+
background: var(--ig-surface-2);
|
|
78
|
+
border-color: var(--ig-accent);
|
|
79
|
+
color: var(--ig-text);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.ig-picker-current {
|
|
83
|
+
margin-left: auto;
|
|
84
|
+
font-size: var(--ig-font-size-small);
|
|
85
|
+
color: var(--ig-text-muted);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Wraps rather than truncates: a qualified reference is long, and a statement
|
|
89
|
+
whose object is cut off says something other than what the format holds. */
|
|
90
|
+
.ig-picker-direction {
|
|
91
|
+
margin: 0;
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-wrap: wrap;
|
|
94
|
+
align-items: baseline;
|
|
95
|
+
gap: var(--ig-space-tight);
|
|
96
|
+
color: var(--ig-text-body);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.ig-picker-ref {
|
|
100
|
+
font-family: var(--ig-font-mono);
|
|
101
|
+
color: var(--ig-accent);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.ig-picker-flip {
|
|
105
|
+
align-self: flex-start;
|
|
106
|
+
background: var(--ig-surface);
|
|
107
|
+
color: var(--ig-text-body);
|
|
108
|
+
border: var(--ig-stroke) solid var(--ig-line);
|
|
109
|
+
border-radius: var(--ig-radius);
|
|
110
|
+
padding: var(--ig-space-tight);
|
|
111
|
+
font-family: inherit;
|
|
112
|
+
font-size: var(--ig-font-size-small);
|
|
113
|
+
line-height: inherit;
|
|
114
|
+
}
|
|
115
|
+
`;
|
|
116
|
+
//# sourceMappingURL=styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../../src/picker/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0F/B,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type picker, as a view model: which kinds an edge can be retyped to,
|
|
3
|
+
* which way round it currently reads, and the single proposal each act emits.
|
|
4
|
+
*
|
|
5
|
+
* ## One user act, one `Proposal`
|
|
6
|
+
*
|
|
7
|
+
* `@issuegraph/store` closed the operation set at four — `create`, `delete`,
|
|
8
|
+
* `retype`, `flip` — and `mutation.ts` says why in as many words: `retype` and
|
|
9
|
+
* `flip` "are their own operations rather than a delete followed by a create,
|
|
10
|
+
* because the design requires a retype to be one operation, one round trip and
|
|
11
|
+
* one undo entry". This module is the surface that emits them, so its whole job
|
|
12
|
+
* is to hand out ONE proposal per affordance. Composing two here would undo the
|
|
13
|
+
* property the store went out of its way to model.
|
|
14
|
+
*
|
|
15
|
+
* ## Directedness is READ, never listed
|
|
16
|
+
*
|
|
17
|
+
* §17b: a directed type renders as a sentence with an explicit flip control,
|
|
18
|
+
* "because 'which way round' is the most common encoding mistake"; a symmetric
|
|
19
|
+
* type shows no flip. Which kinds are which is a fact about the FORMAT, and
|
|
20
|
+
* `@issuegraph/core` already owns it — `isSymmetricEdgeField` over
|
|
21
|
+
* `EDGE_FIELDS`. So this module imports it rather than restating the split.
|
|
22
|
+
*
|
|
23
|
+
* That is why `@issuegraph/core` is a dependency of this package and not only
|
|
24
|
+
* of its two siblings. Core is the layer both the viewer and the store already
|
|
25
|
+
* sit on, and the seam rule in `eslint.config.mjs` refuses a sibling's SUBPATH,
|
|
26
|
+
* never its bare specifier — reaching for the shared foundation is not reaching
|
|
27
|
+
* past a surface. The rejected alternative was a local
|
|
28
|
+
* `const DIRECTED = ['blocked-by', …]`, which is the drifting second
|
|
29
|
+
* implementation the package family removes everywhere else it appears: a sixth
|
|
30
|
+
* field added to the format would leave it silently wrong, and nothing would
|
|
31
|
+
* fail.
|
|
32
|
+
*
|
|
33
|
+
* ## It offers the kind the edge already has, on purpose
|
|
34
|
+
*
|
|
35
|
+
* A picker that hid the current kind would be a SECOND validity rule living out
|
|
36
|
+
* here, and the store already refuses that edit — `structuralRefusal` answers
|
|
37
|
+
* `unchanged-kind` and the record never reaches a `DataSource`. So the option
|
|
38
|
+
* is offered and marked {@link KindOption.current}, which is what a radio group
|
|
39
|
+
* needs anyway: the current value is the checked one. Validity stays in one
|
|
40
|
+
* place, and this module stays presentation.
|
|
41
|
+
*
|
|
42
|
+
* ## Retyping ACROSS the split keeps the pair, and that is a decision
|
|
43
|
+
*
|
|
44
|
+
* A `serialize-with` edge retyped to `blocked-by` becomes directed, and the
|
|
45
|
+
* direction it lands with is the carrier pair the format already recorded —
|
|
46
|
+
* `StoredEdge` keeps that pair even for a symmetric kind, precisely so an
|
|
47
|
+
* editor knows which issue carries the field. Nothing here infers a direction;
|
|
48
|
+
* it carries the one that was already written down.
|
|
49
|
+
*
|
|
50
|
+
* The rejected shape was a per-option PREVIEW: every option carrying the
|
|
51
|
+
* statement it would produce, so the direction is chosen at the same moment as
|
|
52
|
+
* the kind. It was rejected because it doubles what the picker says in order to
|
|
53
|
+
* pre-empt a case the surface already answers — the retype lands, the picker
|
|
54
|
+
* re-derives from the new document, and the direction is then STATED with a
|
|
55
|
+
* flip beside it, one act from correct. §17b's rule is that direction is stated
|
|
56
|
+
* rather than inferred, and it is stated. Revisit this if the flip turns out to
|
|
57
|
+
* be reached often enough to be the real cost.
|
|
58
|
+
*
|
|
59
|
+
* ## It writes no words and holds no state
|
|
60
|
+
*
|
|
61
|
+
* {@link DirectionStatement} is the ordered pair and the kind. The sentence is
|
|
62
|
+
* the host's, for the same reason `change.ts` ships counts rather than prose and
|
|
63
|
+
* `overlay/grammar.ts` ships an `InvalidCode` rather than a message: a default
|
|
64
|
+
* sentence here would be a language choice a consumer could not theme away.
|
|
65
|
+
*/
|
|
66
|
+
import { type EdgeId, type EdgeKind, type GraphDocument, type IssueRef, type Proposal } from '@issuegraph/store';
|
|
67
|
+
/**
|
|
68
|
+
* Which way round a relationship reads, as data.
|
|
69
|
+
*
|
|
70
|
+
* `from` and `to` are the edge's own pair, which for a directed kind IS the
|
|
71
|
+
* reading order — `StoredEdge` keeps "A blocked-by B" as `from: A`, `to: B`.
|
|
72
|
+
* The kind travels with them because a pair alone cannot be worded.
|
|
73
|
+
*
|
|
74
|
+
* There is deliberately no `text`. A host renders "#530 is blocked by #602" in
|
|
75
|
+
* its own language and its own word order; see {@link ./words.ts PickerWords}.
|
|
76
|
+
*/
|
|
77
|
+
export interface DirectionStatement {
|
|
78
|
+
readonly kind: EdgeKind;
|
|
79
|
+
readonly from: IssueRef;
|
|
80
|
+
readonly to: IssueRef;
|
|
81
|
+
}
|
|
82
|
+
/** One entry in the type picker. */
|
|
83
|
+
export interface KindOption {
|
|
84
|
+
readonly kind: EdgeKind;
|
|
85
|
+
/**
|
|
86
|
+
* Whether this kind carries a direction, read from the format rather than
|
|
87
|
+
* decided here. A host uses it to word the option; the picker uses the
|
|
88
|
+
* TARGET's directedness to decide whether a flip control exists at all.
|
|
89
|
+
*/
|
|
90
|
+
readonly directed: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* The kind the edge already carries. Presentation only — the store owns
|
|
93
|
+
* whether choosing it is a valid edit, and it refuses it as `unchanged-kind`.
|
|
94
|
+
*/
|
|
95
|
+
readonly current: boolean;
|
|
96
|
+
/** The one proposal choosing this option emits. Never two. */
|
|
97
|
+
readonly proposal: Proposal;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The control that reverses a directed relationship.
|
|
101
|
+
*
|
|
102
|
+
* `null` on a symmetric kind — not "present but disabled". A symmetric edge has
|
|
103
|
+
* no direction to reverse, so a control for it is a claim the format does not
|
|
104
|
+
* make, and the store would refuse the edit as `symmetric-edge` anyway.
|
|
105
|
+
*/
|
|
106
|
+
export interface FlipControl {
|
|
107
|
+
/** What the relationship would read as afterwards. The reverse pair. */
|
|
108
|
+
readonly reversed: DirectionStatement;
|
|
109
|
+
/** Exactly one proposal. `flip` is an operation, not a delete plus a create. */
|
|
110
|
+
readonly proposal: Proposal;
|
|
111
|
+
}
|
|
112
|
+
export interface PickerView {
|
|
113
|
+
/** The edge this picker was opened on, whether or not it was found. */
|
|
114
|
+
readonly edgeId: EdgeId;
|
|
115
|
+
/** Every kind in the format's order. Empty only when the edge is gone. */
|
|
116
|
+
readonly options: readonly KindOption[];
|
|
117
|
+
/** `null` for a symmetric kind: there is no direction to state. */
|
|
118
|
+
readonly direction: DirectionStatement | null;
|
|
119
|
+
/** `null` exactly when {@link direction} is. The two travel together. */
|
|
120
|
+
readonly flip: FlipControl | null;
|
|
121
|
+
readonly diagnostics: readonly string[];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The picker for one existing edge.
|
|
125
|
+
*
|
|
126
|
+
* Pure and synchronous, and it takes a document rather than a store — which is
|
|
127
|
+
* what makes "emits proposals, never touches a `DataSource`" structural. There
|
|
128
|
+
* is no port in scope here to touch. A host hands the proposals it gets back to
|
|
129
|
+
* `Store.propose`, which is the only thing that dispatches.
|
|
130
|
+
*
|
|
131
|
+
* The create path is not here. It lands with the change that assembles the
|
|
132
|
+
* workspace, alongside the other affordances `index.ts` defers.
|
|
133
|
+
*/
|
|
134
|
+
export declare function pickerView(document: GraphDocument, edgeId: EdgeId): PickerView;
|
|
135
|
+
//# sourceMappingURL=view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/picker/view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAGH,OAAO,EACL,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,QAAQ,EAEd,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;CACvB;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,CAAC;IACtC,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC9C,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAkBD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAwB9E"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type picker, as a view model: which kinds an edge can be retyped to,
|
|
3
|
+
* which way round it currently reads, and the single proposal each act emits.
|
|
4
|
+
*
|
|
5
|
+
* ## One user act, one `Proposal`
|
|
6
|
+
*
|
|
7
|
+
* `@issuegraph/store` closed the operation set at four — `create`, `delete`,
|
|
8
|
+
* `retype`, `flip` — and `mutation.ts` says why in as many words: `retype` and
|
|
9
|
+
* `flip` "are their own operations rather than a delete followed by a create,
|
|
10
|
+
* because the design requires a retype to be one operation, one round trip and
|
|
11
|
+
* one undo entry". This module is the surface that emits them, so its whole job
|
|
12
|
+
* is to hand out ONE proposal per affordance. Composing two here would undo the
|
|
13
|
+
* property the store went out of its way to model.
|
|
14
|
+
*
|
|
15
|
+
* ## Directedness is READ, never listed
|
|
16
|
+
*
|
|
17
|
+
* §17b: a directed type renders as a sentence with an explicit flip control,
|
|
18
|
+
* "because 'which way round' is the most common encoding mistake"; a symmetric
|
|
19
|
+
* type shows no flip. Which kinds are which is a fact about the FORMAT, and
|
|
20
|
+
* `@issuegraph/core` already owns it — `isSymmetricEdgeField` over
|
|
21
|
+
* `EDGE_FIELDS`. So this module imports it rather than restating the split.
|
|
22
|
+
*
|
|
23
|
+
* That is why `@issuegraph/core` is a dependency of this package and not only
|
|
24
|
+
* of its two siblings. Core is the layer both the viewer and the store already
|
|
25
|
+
* sit on, and the seam rule in `eslint.config.mjs` refuses a sibling's SUBPATH,
|
|
26
|
+
* never its bare specifier — reaching for the shared foundation is not reaching
|
|
27
|
+
* past a surface. The rejected alternative was a local
|
|
28
|
+
* `const DIRECTED = ['blocked-by', …]`, which is the drifting second
|
|
29
|
+
* implementation the package family removes everywhere else it appears: a sixth
|
|
30
|
+
* field added to the format would leave it silently wrong, and nothing would
|
|
31
|
+
* fail.
|
|
32
|
+
*
|
|
33
|
+
* ## It offers the kind the edge already has, on purpose
|
|
34
|
+
*
|
|
35
|
+
* A picker that hid the current kind would be a SECOND validity rule living out
|
|
36
|
+
* here, and the store already refuses that edit — `structuralRefusal` answers
|
|
37
|
+
* `unchanged-kind` and the record never reaches a `DataSource`. So the option
|
|
38
|
+
* is offered and marked {@link KindOption.current}, which is what a radio group
|
|
39
|
+
* needs anyway: the current value is the checked one. Validity stays in one
|
|
40
|
+
* place, and this module stays presentation.
|
|
41
|
+
*
|
|
42
|
+
* ## Retyping ACROSS the split keeps the pair, and that is a decision
|
|
43
|
+
*
|
|
44
|
+
* A `serialize-with` edge retyped to `blocked-by` becomes directed, and the
|
|
45
|
+
* direction it lands with is the carrier pair the format already recorded —
|
|
46
|
+
* `StoredEdge` keeps that pair even for a symmetric kind, precisely so an
|
|
47
|
+
* editor knows which issue carries the field. Nothing here infers a direction;
|
|
48
|
+
* it carries the one that was already written down.
|
|
49
|
+
*
|
|
50
|
+
* The rejected shape was a per-option PREVIEW: every option carrying the
|
|
51
|
+
* statement it would produce, so the direction is chosen at the same moment as
|
|
52
|
+
* the kind. It was rejected because it doubles what the picker says in order to
|
|
53
|
+
* pre-empt a case the surface already answers — the retype lands, the picker
|
|
54
|
+
* re-derives from the new document, and the direction is then STATED with a
|
|
55
|
+
* flip beside it, one act from correct. §17b's rule is that direction is stated
|
|
56
|
+
* rather than inferred, and it is stated. Revisit this if the flip turns out to
|
|
57
|
+
* be reached often enough to be the real cost.
|
|
58
|
+
*
|
|
59
|
+
* ## It writes no words and holds no state
|
|
60
|
+
*
|
|
61
|
+
* {@link DirectionStatement} is the ordered pair and the kind. The sentence is
|
|
62
|
+
* the host's, for the same reason `change.ts` ships counts rather than prose and
|
|
63
|
+
* `overlay/grammar.ts` ships an `InvalidCode` rather than a message: a default
|
|
64
|
+
* sentence here would be a language choice a consumer could not theme away.
|
|
65
|
+
*/
|
|
66
|
+
import { EDGE_FIELDS, isSymmetricEdgeField } from '@issuegraph/core';
|
|
67
|
+
import { findEdge, } from '@issuegraph/store';
|
|
68
|
+
/** An empty picker, for a target that is not in the document. */
|
|
69
|
+
function nothingToPick(edgeId) {
|
|
70
|
+
return {
|
|
71
|
+
edgeId,
|
|
72
|
+
options: [],
|
|
73
|
+
direction: null,
|
|
74
|
+
flip: null,
|
|
75
|
+
// REPORTED RATHER THAN DRAWN AS AN EMPTY PICKER. A picker with no options
|
|
76
|
+
// and no reason looks like a kind vocabulary that came back empty, and the
|
|
77
|
+
// false conclusion is "this edge can be nothing" rather than "this edge is
|
|
78
|
+
// no longer here". The state is reachable in ordinary use: a sibling write
|
|
79
|
+
// can land a delete of the edge while its picker is open.
|
|
80
|
+
diagnostics: [`${edgeId} is no longer an edge in this document; there is nothing to retype`],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The picker for one existing edge.
|
|
85
|
+
*
|
|
86
|
+
* Pure and synchronous, and it takes a document rather than a store — which is
|
|
87
|
+
* what makes "emits proposals, never touches a `DataSource`" structural. There
|
|
88
|
+
* is no port in scope here to touch. A host hands the proposals it gets back to
|
|
89
|
+
* `Store.propose`, which is the only thing that dispatches.
|
|
90
|
+
*
|
|
91
|
+
* The create path is not here. It lands with the change that assembles the
|
|
92
|
+
* workspace, alongside the other affordances `index.ts` defers.
|
|
93
|
+
*/
|
|
94
|
+
export function pickerView(document, edgeId) {
|
|
95
|
+
const edge = findEdge(document, edgeId);
|
|
96
|
+
if (edge === undefined)
|
|
97
|
+
return nothingToPick(edgeId);
|
|
98
|
+
const options = EDGE_FIELDS.map((kind) => {
|
|
99
|
+
const proposal = { op: 'retype', edgeId: edge.id, nextKind: kind };
|
|
100
|
+
return { kind, directed: !isSymmetricEdgeField(kind), current: kind === edge.kind, proposal };
|
|
101
|
+
});
|
|
102
|
+
if (isSymmetricEdgeField(edge.kind)) {
|
|
103
|
+
return { edgeId, options, direction: null, flip: null, diagnostics: [] };
|
|
104
|
+
}
|
|
105
|
+
const proposal = { op: 'flip', edgeId: edge.id };
|
|
106
|
+
return {
|
|
107
|
+
edgeId,
|
|
108
|
+
options,
|
|
109
|
+
direction: { kind: edge.kind, from: edge.from, to: edge.to },
|
|
110
|
+
flip: {
|
|
111
|
+
reversed: { kind: edge.kind, from: edge.to, to: edge.from },
|
|
112
|
+
proposal,
|
|
113
|
+
},
|
|
114
|
+
diagnostics: [],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../../src/picker/view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAEH,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAML,QAAQ,GACT,MAAM,mBAAmB,CAAC;AA8D3B,iEAAiE;AACjE,SAAS,aAAa,CAAC,MAAc;IACnC,OAAO;QACL,MAAM;QACN,OAAO,EAAE,EAAE;QACX,SAAS,EAAE,IAAI;QACf,IAAI,EAAE,IAAI;QACV,0EAA0E;QAC1E,2EAA2E;QAC3E,2EAA2E;QAC3E,2EAA2E;QAC3E,0DAA0D;QAC1D,WAAW,EAAE,CAAC,GAAG,MAAM,oEAAoE,CAAC;KAC7F,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,QAAuB,EAAE,MAAc;IAChE,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;IAErD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAc,EAAE;QACnD,MAAM,QAAQ,GAAa,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QAC7E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;IAChG,CAAC,CAAC,CAAC;IAEH,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAC3E,CAAC;IAED,MAAM,QAAQ,GAAa,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;IAC3D,OAAO;QACL,MAAM;QACN,OAAO;QACP,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE;QAC5D,IAAI,EAAE;YACJ,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE;YAC3D,QAAQ;SACT;QACD,WAAW,EAAE,EAAE;KAChB,CAAC;AACJ,CAAC"}
|