@getodk/xforms-engine 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/README.md +44 -0
- package/dist/.vite/manifest.json +7 -0
- package/dist/XFormDOM.d.ts +31 -0
- package/dist/XFormDataType.d.ts +26 -0
- package/dist/XFormDefinition.d.ts +14 -0
- package/dist/body/BodyDefinition.d.ts +52 -0
- package/dist/body/BodyElementDefinition.d.ts +32 -0
- package/dist/body/RepeatDefinition.d.ts +15 -0
- package/dist/body/UnsupportedBodyElementDefinition.d.ts +10 -0
- package/dist/body/control/ControlDefinition.d.ts +16 -0
- package/dist/body/control/InputDefinition.d.ts +5 -0
- package/dist/body/control/select/ItemDefinition.d.ts +13 -0
- package/dist/body/control/select/ItemsetDefinition.d.ts +16 -0
- package/dist/body/control/select/ItemsetNodesetContext.d.ts +11 -0
- package/dist/body/control/select/ItemsetNodesetExpression.d.ts +5 -0
- package/dist/body/control/select/ItemsetValueExpression.d.ts +6 -0
- package/dist/body/control/select/SelectDefinition.d.ts +23 -0
- package/dist/body/group/BaseGroupDefinition.d.ts +46 -0
- package/dist/body/group/LogicalGroupDefinition.d.ts +6 -0
- package/dist/body/group/PresentationGroupDefinition.d.ts +11 -0
- package/dist/body/group/RepeatGroupDefinition.d.ts +12 -0
- package/dist/body/group/StructuralGroupDefinition.d.ts +6 -0
- package/dist/body/text/HintDefinition.d.ts +11 -0
- package/dist/body/text/LabelDefinition.d.ts +20 -0
- package/dist/body/text/TextElementDefinition.d.ts +32 -0
- package/dist/body/text/TextElementOutputPart.d.ts +12 -0
- package/dist/body/text/TextElementPart.d.ts +12 -0
- package/dist/body/text/TextElementReferencePart.d.ts +6 -0
- package/dist/body/text/TextElementStaticPart.d.ts +6 -0
- package/dist/client/BaseNode.d.ts +138 -0
- package/dist/client/EngineConfig.d.ts +78 -0
- package/dist/client/FormLanguage.d.ts +63 -0
- package/dist/client/GroupNode.d.ts +24 -0
- package/dist/client/OpaqueReactiveObjectFactory.d.ts +70 -0
- package/dist/client/RepeatInstanceNode.d.ts +28 -0
- package/dist/client/RepeatRangeNode.d.ts +94 -0
- package/dist/client/RootNode.d.ts +31 -0
- package/dist/client/SelectNode.d.ts +60 -0
- package/dist/client/StringNode.d.ts +41 -0
- package/dist/client/SubtreeNode.d.ts +52 -0
- package/dist/client/TextRange.d.ts +55 -0
- package/dist/client/hierarchy.d.ts +48 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/node-types.d.ts +1 -0
- package/dist/expression/DependencyContext.d.ts +12 -0
- package/dist/expression/DependentExpression.d.ts +43 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +37622 -0
- package/dist/index.js.map +1 -0
- package/dist/instance/Group.d.ts +31 -0
- package/dist/instance/RepeatInstance.d.ts +60 -0
- package/dist/instance/RepeatRange.d.ts +81 -0
- package/dist/instance/Root.d.ts +70 -0
- package/dist/instance/SelectField.d.ts +45 -0
- package/dist/instance/StringField.d.ts +39 -0
- package/dist/instance/Subtree.d.ts +30 -0
- package/dist/instance/abstract/DescendantNode.d.ts +76 -0
- package/dist/instance/abstract/InstanceNode.d.ts +107 -0
- package/dist/instance/children.d.ts +2 -0
- package/dist/instance/hierarchy.d.ts +12 -0
- package/dist/instance/identity.d.ts +7 -0
- package/dist/instance/index.d.ts +8 -0
- package/dist/instance/internal-api/EvaluationContext.d.ts +34 -0
- package/dist/instance/internal-api/InstanceConfig.d.ts +8 -0
- package/dist/instance/internal-api/SubscribableDependency.d.ts +59 -0
- package/dist/instance/internal-api/TranslationContext.d.ts +4 -0
- package/dist/instance/internal-api/ValueContext.d.ts +22 -0
- package/dist/instance/resource.d.ts +10 -0
- package/dist/instance/text/FormattedTextStub.d.ts +1 -0
- package/dist/instance/text/TextChunk.d.ts +11 -0
- package/dist/instance/text/TextRange.d.ts +10 -0
- package/dist/lib/dom/query.d.ts +20 -0
- package/dist/lib/reactivity/createChildrenState.d.ts +36 -0
- package/dist/lib/reactivity/createComputedExpression.d.ts +12 -0
- package/dist/lib/reactivity/createSelectItems.d.ts +16 -0
- package/dist/lib/reactivity/createValueState.d.ts +44 -0
- package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +18 -0
- package/dist/lib/reactivity/node-state/createClientState.d.ts +9 -0
- package/dist/lib/reactivity/node-state/createCurrentState.d.ts +6 -0
- package/dist/lib/reactivity/node-state/createEngineState.d.ts +5 -0
- package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +22 -0
- package/dist/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.d.ts +6 -0
- package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +139 -0
- package/dist/lib/reactivity/node-state/representations.d.ts +25 -0
- package/dist/lib/reactivity/scope.d.ts +23 -0
- package/dist/lib/reactivity/text/createFieldHint.d.ts +5 -0
- package/dist/lib/reactivity/text/createNodeLabel.d.ts +5 -0
- package/dist/lib/reactivity/text/createTextRange.d.ts +19 -0
- package/dist/lib/reactivity/types.d.ts +21 -0
- package/dist/lib/unique-id.d.ts +27 -0
- package/dist/lib/xpath/analysis.d.ts +22 -0
- package/dist/model/BindComputation.d.ts +30 -0
- package/dist/model/BindDefinition.d.ts +31 -0
- package/dist/model/BindElement.d.ts +6 -0
- package/dist/model/DescendentNodeDefinition.d.ts +25 -0
- package/dist/model/ModelBindMap.d.ts +15 -0
- package/dist/model/ModelDefinition.d.ts +10 -0
- package/dist/model/NodeDefinition.d.ts +74 -0
- package/dist/model/RepeatInstanceDefinition.d.ts +15 -0
- package/dist/model/RepeatSequenceDefinition.d.ts +19 -0
- package/dist/model/RepeatTemplateDefinition.d.ts +29 -0
- package/dist/model/RootDefinition.d.ts +24 -0
- package/dist/model/SubtreeDefinition.d.ts +14 -0
- package/dist/model/ValueNodeDefinition.d.ts +15 -0
- package/dist/solid.js +37273 -0
- package/dist/solid.js.map +1 -0
- package/package.json +87 -0
- package/src/XFormDOM.ts +224 -0
- package/src/XFormDataType.ts +64 -0
- package/src/XFormDefinition.ts +40 -0
- package/src/body/BodyDefinition.ts +202 -0
- package/src/body/BodyElementDefinition.ts +62 -0
- package/src/body/RepeatDefinition.ts +54 -0
- package/src/body/UnsupportedBodyElementDefinition.ts +17 -0
- package/src/body/control/ControlDefinition.ts +42 -0
- package/src/body/control/InputDefinition.ts +9 -0
- package/src/body/control/select/ItemDefinition.ts +31 -0
- package/src/body/control/select/ItemsetDefinition.ts +36 -0
- package/src/body/control/select/ItemsetNodesetContext.ts +26 -0
- package/src/body/control/select/ItemsetNodesetExpression.ts +8 -0
- package/src/body/control/select/ItemsetValueExpression.ts +11 -0
- package/src/body/control/select/SelectDefinition.ts +74 -0
- package/src/body/group/BaseGroupDefinition.ts +137 -0
- package/src/body/group/LogicalGroupDefinition.ts +11 -0
- package/src/body/group/PresentationGroupDefinition.ts +28 -0
- package/src/body/group/RepeatGroupDefinition.ts +91 -0
- package/src/body/group/StructuralGroupDefinition.ts +11 -0
- package/src/body/text/HintDefinition.ts +26 -0
- package/src/body/text/LabelDefinition.ts +54 -0
- package/src/body/text/TextElementDefinition.ts +97 -0
- package/src/body/text/TextElementOutputPart.ts +27 -0
- package/src/body/text/TextElementPart.ts +31 -0
- package/src/body/text/TextElementReferencePart.ts +21 -0
- package/src/body/text/TextElementStaticPart.ts +26 -0
- package/src/client/BaseNode.ts +180 -0
- package/src/client/EngineConfig.ts +83 -0
- package/src/client/FormLanguage.ts +77 -0
- package/src/client/GroupNode.ts +33 -0
- package/src/client/OpaqueReactiveObjectFactory.ts +100 -0
- package/src/client/README.md +39 -0
- package/src/client/RepeatInstanceNode.ts +41 -0
- package/src/client/RepeatRangeNode.ts +100 -0
- package/src/client/RootNode.ts +36 -0
- package/src/client/SelectNode.ts +69 -0
- package/src/client/StringNode.ts +46 -0
- package/src/client/SubtreeNode.ts +57 -0
- package/src/client/TextRange.ts +63 -0
- package/src/client/hierarchy.ts +63 -0
- package/src/client/index.ts +29 -0
- package/src/client/node-types.ts +10 -0
- package/src/expression/DependencyContext.ts +53 -0
- package/src/expression/DependentExpression.ts +102 -0
- package/src/index.ts +35 -0
- package/src/instance/Group.ts +82 -0
- package/src/instance/RepeatInstance.ts +164 -0
- package/src/instance/RepeatRange.ts +214 -0
- package/src/instance/Root.ts +264 -0
- package/src/instance/SelectField.ts +204 -0
- package/src/instance/StringField.ts +93 -0
- package/src/instance/Subtree.ts +79 -0
- package/src/instance/abstract/DescendantNode.ts +182 -0
- package/src/instance/abstract/InstanceNode.ts +257 -0
- package/src/instance/children.ts +52 -0
- package/src/instance/hierarchy.ts +54 -0
- package/src/instance/identity.ts +11 -0
- package/src/instance/index.ts +37 -0
- package/src/instance/internal-api/EvaluationContext.ts +41 -0
- package/src/instance/internal-api/InstanceConfig.ts +9 -0
- package/src/instance/internal-api/SubscribableDependency.ts +61 -0
- package/src/instance/internal-api/TranslationContext.ts +5 -0
- package/src/instance/internal-api/ValueContext.ts +27 -0
- package/src/instance/resource.ts +75 -0
- package/src/instance/text/FormattedTextStub.ts +8 -0
- package/src/instance/text/TextChunk.ts +20 -0
- package/src/instance/text/TextRange.ts +23 -0
- package/src/lib/dom/query.ts +49 -0
- package/src/lib/reactivity/createChildrenState.ts +60 -0
- package/src/lib/reactivity/createComputedExpression.ts +114 -0
- package/src/lib/reactivity/createSelectItems.ts +163 -0
- package/src/lib/reactivity/createValueState.ts +258 -0
- package/src/lib/reactivity/materializeCurrentStateChildren.ts +121 -0
- package/src/lib/reactivity/node-state/createClientState.ts +51 -0
- package/src/lib/reactivity/node-state/createCurrentState.ts +27 -0
- package/src/lib/reactivity/node-state/createEngineState.ts +18 -0
- package/src/lib/reactivity/node-state/createSharedNodeState.ts +79 -0
- package/src/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.ts +85 -0
- package/src/lib/reactivity/node-state/createSpecifiedState.ts +229 -0
- package/src/lib/reactivity/node-state/representations.ts +64 -0
- package/src/lib/reactivity/scope.ts +106 -0
- package/src/lib/reactivity/text/createFieldHint.ts +16 -0
- package/src/lib/reactivity/text/createNodeLabel.ts +16 -0
- package/src/lib/reactivity/text/createTextRange.ts +155 -0
- package/src/lib/reactivity/types.ts +27 -0
- package/src/lib/unique-id.ts +34 -0
- package/src/lib/xpath/analysis.ts +241 -0
- package/src/model/BindComputation.ts +88 -0
- package/src/model/BindDefinition.ts +104 -0
- package/src/model/BindElement.ts +8 -0
- package/src/model/DescendentNodeDefinition.ts +56 -0
- package/src/model/ModelBindMap.ts +71 -0
- package/src/model/ModelDefinition.ts +19 -0
- package/src/model/NodeDefinition.ts +146 -0
- package/src/model/RepeatInstanceDefinition.ts +39 -0
- package/src/model/RepeatSequenceDefinition.ts +53 -0
- package/src/model/RepeatTemplateDefinition.ts +150 -0
- package/src/model/RootDefinition.ts +121 -0
- package/src/model/SubtreeDefinition.ts +50 -0
- package/src/model/ValueNodeDefinition.ts +39 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getLabelElement } from '../../lib/dom/query.ts';
|
|
2
|
+
import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
3
|
+
import type { AnyControlDefinition } from '../control/ControlDefinition.ts';
|
|
4
|
+
import type { ItemDefinition } from '../control/select/ItemDefinition.ts';
|
|
5
|
+
import type { ItemsetDefinition } from '../control/select/ItemsetDefinition.ts';
|
|
6
|
+
import type { BaseGroupDefinition } from '../group/BaseGroupDefinition.ts';
|
|
7
|
+
import type { TextElement, TextElementOwner } from './TextElementDefinition.ts';
|
|
8
|
+
import { TextElementDefinition } from './TextElementDefinition.ts';
|
|
9
|
+
|
|
10
|
+
export interface LabelElement extends TextElement {
|
|
11
|
+
readonly localName: 'label';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type StaticLabelContext = Exclude<TextElementOwner, ItemsetDefinition>;
|
|
15
|
+
|
|
16
|
+
export class LabelDefinition extends TextElementDefinition<'label'> {
|
|
17
|
+
protected static staticDefinition(
|
|
18
|
+
form: XFormDefinition,
|
|
19
|
+
definition: StaticLabelContext
|
|
20
|
+
): LabelDefinition | null {
|
|
21
|
+
const labelElement = getLabelElement(definition.element);
|
|
22
|
+
|
|
23
|
+
if (labelElement == null) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return new this(form, definition, labelElement);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static forControl(form: XFormDefinition, control: AnyControlDefinition): LabelDefinition | null {
|
|
31
|
+
return this.staticDefinition(form, control);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
+
static forGroup(form: XFormDefinition, group: BaseGroupDefinition<any>): LabelDefinition | null {
|
|
36
|
+
return this.staticDefinition(form, group);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static forItem(form: XFormDefinition, item: ItemDefinition): LabelDefinition | null {
|
|
40
|
+
return this.staticDefinition(form, item);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static forItemset(form: XFormDefinition, itemset: ItemsetDefinition): LabelDefinition | null {
|
|
44
|
+
const labelElement = getLabelElement(itemset.element);
|
|
45
|
+
|
|
46
|
+
if (labelElement == null) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return new this(form, itemset, labelElement);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
readonly type = 'label';
|
|
54
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { isCommentNode, isElementNode, isTextNode } from '@getodk/common/lib/dom/predicates.ts';
|
|
2
|
+
import type { XFormDefinition } from '../../XFormDefinition.ts';
|
|
3
|
+
import { type AnyDependentExpression } from '../../expression/DependentExpression.ts';
|
|
4
|
+
import type { AnyGroupElementDefinition } from '../BodyDefinition.ts';
|
|
5
|
+
import { BodyElementDefinition } from '../BodyElementDefinition.ts';
|
|
6
|
+
import type { InputDefinition } from '../control/InputDefinition.ts';
|
|
7
|
+
import type { ItemDefinition } from '../control/select/ItemDefinition.ts';
|
|
8
|
+
import type { ItemsetDefinition } from '../control/select/ItemsetDefinition.ts';
|
|
9
|
+
import type { AnySelectDefinition } from '../control/select/SelectDefinition.ts';
|
|
10
|
+
import { TextElementOutputPart } from './TextElementOutputPart.ts';
|
|
11
|
+
import { TextElementReferencePart } from './TextElementReferencePart.ts';
|
|
12
|
+
import { TextElementStaticPart } from './TextElementStaticPart.ts';
|
|
13
|
+
|
|
14
|
+
export type TextElementType = 'hint' | 'label';
|
|
15
|
+
|
|
16
|
+
export interface TextElement extends Element {
|
|
17
|
+
readonly localName: TextElementType;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type TextElementOwner =
|
|
21
|
+
| AnyGroupElementDefinition
|
|
22
|
+
| AnySelectDefinition
|
|
23
|
+
| InputDefinition
|
|
24
|
+
| ItemDefinition
|
|
25
|
+
| ItemsetDefinition;
|
|
26
|
+
|
|
27
|
+
export type TextElementChild = TextElementOutputPart | TextElementStaticPart;
|
|
28
|
+
|
|
29
|
+
export abstract class TextElementDefinition<
|
|
30
|
+
Type extends TextElementType,
|
|
31
|
+
> extends BodyElementDefinition<Type> {
|
|
32
|
+
readonly category = 'support';
|
|
33
|
+
abstract override readonly type: Type;
|
|
34
|
+
|
|
35
|
+
override readonly reference: string | null;
|
|
36
|
+
override readonly parentReference: string | null;
|
|
37
|
+
|
|
38
|
+
readonly referenceExpression: TextElementReferencePart | null;
|
|
39
|
+
readonly children: readonly TextElementChild[];
|
|
40
|
+
|
|
41
|
+
override get isTranslated(): boolean {
|
|
42
|
+
return this.owner.isTranslated;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override set isTranslated(value: true) {
|
|
46
|
+
this.owner.isTranslated = value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected constructor(
|
|
50
|
+
form: XFormDefinition,
|
|
51
|
+
readonly owner: TextElementOwner,
|
|
52
|
+
element: TextElement
|
|
53
|
+
) {
|
|
54
|
+
super(form, owner, element);
|
|
55
|
+
|
|
56
|
+
this.reference = owner.reference;
|
|
57
|
+
this.parentReference = owner.parentReference;
|
|
58
|
+
this.referenceExpression = TextElementReferencePart.from(this, element);
|
|
59
|
+
|
|
60
|
+
const children = Array.from(element.childNodes).flatMap((node) => {
|
|
61
|
+
if (isTextNode(node)) {
|
|
62
|
+
return new TextElementStaticPart(this, node);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (isElementNode(node)) {
|
|
66
|
+
const output = TextElementOutputPart.from(this, node);
|
|
67
|
+
|
|
68
|
+
if (output != null) {
|
|
69
|
+
return output;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (isCommentNode(node)) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// eslint-disable-next-line no-console
|
|
78
|
+
console.error('Unexpected text element child', node);
|
|
79
|
+
|
|
80
|
+
throw new Error(`Unexpected <${element.nodeName}> child element`);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this.children = children;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
override registerDependentExpression(expression: AnyDependentExpression): void {
|
|
87
|
+
this.owner.registerDependentExpression(expression);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
override toJSON(): object {
|
|
91
|
+
const { form, owner, parent, ...rest } = this;
|
|
92
|
+
|
|
93
|
+
return rest;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type AnyTextElementDefinition = TextElementDefinition<TextElementType>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AnyTextElementDefinition } from './TextElementDefinition.ts';
|
|
2
|
+
import { TextElementPart } from './TextElementPart.ts';
|
|
3
|
+
|
|
4
|
+
interface OutputElement extends Element {
|
|
5
|
+
readonly localName: 'output';
|
|
6
|
+
|
|
7
|
+
getAttribute(name: 'value'): string;
|
|
8
|
+
getAttribute(name: string): string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const isOutputElement = (element: Element): element is OutputElement => {
|
|
12
|
+
return element.localName === 'output' && element.hasAttribute('value');
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export class TextElementOutputPart extends TextElementPart<'output'> {
|
|
16
|
+
static from(context: AnyTextElementDefinition, element: Element): TextElementOutputPart | null {
|
|
17
|
+
if (isOutputElement(element)) {
|
|
18
|
+
return new this(context, element);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected constructor(context: AnyTextElementDefinition, element: OutputElement) {
|
|
25
|
+
super('output', context, element.getAttribute('value'));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DependentExpression } from '../../expression/DependentExpression.ts';
|
|
2
|
+
import type { AnyTextElementDefinition } from './TextElementDefinition.ts';
|
|
3
|
+
import type { TextElementOutputPart } from './TextElementOutputPart.ts';
|
|
4
|
+
import type { TextElementReferencePart } from './TextElementReferencePart.ts';
|
|
5
|
+
import type { TextElementStaticPart } from './TextElementStaticPart.ts';
|
|
6
|
+
|
|
7
|
+
export type TextElementPartType = 'output' | 'reference' | 'static';
|
|
8
|
+
|
|
9
|
+
export abstract class TextElementPart<
|
|
10
|
+
Type extends TextElementPartType,
|
|
11
|
+
> extends DependentExpression<'string'> {
|
|
12
|
+
readonly stringValue?: string;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
readonly type: Type,
|
|
16
|
+
context: AnyTextElementDefinition,
|
|
17
|
+
expression: string
|
|
18
|
+
) {
|
|
19
|
+
super(context, 'string', expression, {
|
|
20
|
+
semanticDependencies: {
|
|
21
|
+
translations: type !== 'static',
|
|
22
|
+
},
|
|
23
|
+
ignoreContextReference: true,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type AnyTextElementPart =
|
|
29
|
+
| TextElementOutputPart
|
|
30
|
+
| TextElementReferencePart
|
|
31
|
+
| TextElementStaticPart;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AnyTextElementDefinition, TextElement } from './TextElementDefinition.ts';
|
|
2
|
+
import { TextElementPart } from './TextElementPart.ts';
|
|
3
|
+
|
|
4
|
+
export class TextElementReferencePart extends TextElementPart<'reference'> {
|
|
5
|
+
static from(
|
|
6
|
+
context: AnyTextElementDefinition,
|
|
7
|
+
element: TextElement
|
|
8
|
+
): TextElementReferencePart | null {
|
|
9
|
+
const expression = element.getAttribute('ref');
|
|
10
|
+
|
|
11
|
+
if (expression == null) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return new this(context, expression);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
protected constructor(context: AnyTextElementDefinition, expression: string) {
|
|
19
|
+
super('reference', context, expression);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AnyTextElementDefinition } from './TextElementDefinition.ts';
|
|
2
|
+
import { TextElementPart } from './TextElementPart.ts';
|
|
3
|
+
|
|
4
|
+
const toStaticXPathExpression = (staticTextValue: string): string => {
|
|
5
|
+
const quote = staticTextValue.includes('"') ? "'" : '"';
|
|
6
|
+
|
|
7
|
+
if (staticTextValue.includes(quote)) {
|
|
8
|
+
// throw new Error('todo concat()');
|
|
9
|
+
return 'todo(concat())';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return `${quote}${staticTextValue}${quote}`;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export class TextElementStaticPart extends TextElementPart<'static'> {
|
|
16
|
+
override readonly stringValue: string;
|
|
17
|
+
|
|
18
|
+
constructor(context: AnyTextElementDefinition, node: Text) {
|
|
19
|
+
const stringValue = node.data;
|
|
20
|
+
const expression = toStaticXPathExpression(stringValue);
|
|
21
|
+
|
|
22
|
+
super('static', context, expression);
|
|
23
|
+
|
|
24
|
+
this.stringValue = stringValue;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { AnyNodeDefinition } from '../model/NodeDefinition.ts';
|
|
2
|
+
import type { InstanceNodeType } from './node-types.js';
|
|
3
|
+
import type { OpaqueReactiveObjectFactory } from './OpaqueReactiveObjectFactory.ts';
|
|
4
|
+
import type { TextRange } from './TextRange.ts';
|
|
5
|
+
|
|
6
|
+
export interface BaseNodeState {
|
|
7
|
+
/**
|
|
8
|
+
* Location path reference to the node's primary instance state. This property
|
|
9
|
+
* may change if a node's position changes, e.g. when a repeat instance is
|
|
10
|
+
* removed. Its potential reactivity allows nodes to re-run computations which
|
|
11
|
+
* depend on the node's position itself, or when any other relative reference
|
|
12
|
+
* might target different nodes as a result of the positional change.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* /data/repeat[1]/foo
|
|
16
|
+
* /data/repeat[2]/foo
|
|
17
|
+
*/
|
|
18
|
+
get reference(): string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Note: a node's `readonly` state may become `true` by inheriting that state
|
|
22
|
+
* from one of its ancestors. Computing this inheritance is handled by the
|
|
23
|
+
* engine, but it may be of interest to clients.
|
|
24
|
+
*
|
|
25
|
+
* In the future, a more granular type might convey this detail more
|
|
26
|
+
* explicitly (at the expense of a more complex type). For now, a client can
|
|
27
|
+
* infer that inheritance by visiting the
|
|
28
|
+
* {@link BaseNode.parent | parent node}.
|
|
29
|
+
*/
|
|
30
|
+
get readonly(): boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Note: a node's `relevant` state may become `false` by inheriting that state
|
|
34
|
+
* from one of its ancestors. Computing this inheritance is handled by the
|
|
35
|
+
* engine, but it may be of interest to clients.
|
|
36
|
+
*
|
|
37
|
+
* In the future, a more granular type might convey this detail more
|
|
38
|
+
* explicitly (at the expense of a more complex type). For now, a client can
|
|
39
|
+
* infer that inheritance by visiting the
|
|
40
|
+
* {@link BaseNode.parent | parent node}.
|
|
41
|
+
*/
|
|
42
|
+
get relevant(): boolean;
|
|
43
|
+
|
|
44
|
+
// Note: according to spec, `required` is NOT inherited from ancestor nodes.
|
|
45
|
+
// What this means for a `required` state on subtree nodes is an open
|
|
46
|
+
// question. It was also raised on the first engine-internals iteration, and I
|
|
47
|
+
// could have sworn it was discussed in that PR, but finding any record of
|
|
48
|
+
// this discussion has proven elusive.
|
|
49
|
+
get required(): boolean;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Interfaces for nodes which cannot provide a label should override this to
|
|
53
|
+
* specify that the property will always be `null`.
|
|
54
|
+
*/
|
|
55
|
+
get label(): TextRange<'label'> | null;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Interfaces for nodes which cannot provide a hint should override this to
|
|
59
|
+
* specify that the property will always be `null`.
|
|
60
|
+
*/
|
|
61
|
+
get hint(): TextRange<'hint'> | null;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Each node's children (if it is a parent node) will be accessed on that
|
|
65
|
+
* node's state. While some node types will technically have static children,
|
|
66
|
+
* other nodes' children will be stateful (i.e. repeats). For a client, both
|
|
67
|
+
* cases are accessed the same way for consistency.
|
|
68
|
+
*
|
|
69
|
+
* Certain kinds of nodes are considered parent nodes: they may have child
|
|
70
|
+
* nodes. In some cases (presently, repeat ranges), children may be added or
|
|
71
|
+
* removed while a user is filling a form. As such, those children must be
|
|
72
|
+
* accessed as part of the node's
|
|
73
|
+
* {@link BaseNode.currentState | current state}. (In contrast, child nodes
|
|
74
|
+
* are never moved between different parents, so their
|
|
75
|
+
* {@link BaseNode.parent | parent} is static rather than part of their
|
|
76
|
+
* current state).
|
|
77
|
+
*
|
|
78
|
+
* A node is either:
|
|
79
|
+
*
|
|
80
|
+
* - Always a parent, in which case its `children` state should always produce
|
|
81
|
+
* an array. When the parent node's children can be added or removed, an
|
|
82
|
+
* empty array should be used to represent the absence of any children in
|
|
83
|
+
* its current state.
|
|
84
|
+
* - Never a parent, in which case its `children` state should always produce
|
|
85
|
+
* `null`. Such a node will instead have a {@link value}.
|
|
86
|
+
*/
|
|
87
|
+
get children(): readonly BaseNode[] | null;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Certain kinds of nodes restrict their {@link value} to a specific set of
|
|
91
|
+
* valid values. Where they do, they will provide a collection (typically an
|
|
92
|
+
* array) of those values. This collection may be updated depending on other
|
|
93
|
+
* aspects of form state, which is why it is treated as a part of the node's
|
|
94
|
+
* state.
|
|
95
|
+
*
|
|
96
|
+
* Nodes which do not have this restriction (including nodes which cannot have
|
|
97
|
+
* a value at all) will always produce `valueOptions: null`.
|
|
98
|
+
*/
|
|
99
|
+
get valueOptions(): unknown;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Certain kinds of nodes store a value state. Where they do, they will
|
|
103
|
+
* specify the type of the value directly.
|
|
104
|
+
*
|
|
105
|
+
* Parent nodes, i.e. nodes which can contain {@link children}, do not store a
|
|
106
|
+
* value state. For those nodes, their value state should always be `null`.
|
|
107
|
+
*/
|
|
108
|
+
get value(): unknown;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
type FormNodeID = string;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Base interface for common/shared aspects of any node type.
|
|
115
|
+
*/
|
|
116
|
+
export interface BaseNode {
|
|
117
|
+
/**
|
|
118
|
+
* Specifies the node's general type. This can be useful for narrowing types,
|
|
119
|
+
* e.g. those of children.
|
|
120
|
+
*/
|
|
121
|
+
readonly nodeType: InstanceNodeType;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Each node has a unique identifier. This identifier is stable throughout
|
|
125
|
+
* the lifetime of an active session filling a form.
|
|
126
|
+
*/
|
|
127
|
+
readonly nodeId: FormNodeID;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Each node has a definition which specifies aspects of the node defined in
|
|
131
|
+
* the form. These aspects include (but are not limited to) the node's data
|
|
132
|
+
* type, its body presentation constraints (if any), its bound nodeset, and
|
|
133
|
+
* so on...
|
|
134
|
+
*
|
|
135
|
+
* @todo Interfaces for specific (non-base) node types should override this
|
|
136
|
+
* to specify the actual (concrete or union) type of their `definition`.
|
|
137
|
+
*/
|
|
138
|
+
readonly definition: AnyNodeDefinition;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Each node links back to the node representing the root of the form.
|
|
142
|
+
*
|
|
143
|
+
* @todo Interfaces for all concrete node types should override this to
|
|
144
|
+
* specify the actual root node type.
|
|
145
|
+
*/
|
|
146
|
+
readonly root: BaseNode;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Each node links back to its parent, if any. All nodes have a parent except
|
|
150
|
+
* the form's {@link root}.
|
|
151
|
+
*/
|
|
152
|
+
// TODO: the `children` state property discusses the fact that a child node
|
|
153
|
+
// cannot be reassigned to another parent. As such, it is currently treated as
|
|
154
|
+
// static. This fails to address the removal of nodes, i.e. when removing
|
|
155
|
+
// repeat instances. This suggests that perhaps `parent` should also be part
|
|
156
|
+
// of the node's state. However that would be insufficient to communicate the
|
|
157
|
+
// same information about a removed node's descendants. Some considerations:
|
|
158
|
+
//
|
|
159
|
+
// 1. If `parent` becomes part of state, how do we communicate that removal is
|
|
160
|
+
// the only possible state change (as in, a child node will never be
|
|
161
|
+
// reassigned to another parent)?
|
|
162
|
+
// 2. If `parent` does become nullable state, how best to convey the same
|
|
163
|
+
// information for removed descendants. Some ideas:
|
|
164
|
+
//
|
|
165
|
+
// - Apply null-as-removed recursively. This wouldn't technically be true
|
|
166
|
+
// for the engine's current use of a DOM backing store (but that's an
|
|
167
|
+
// implementation detail clients don't/shouldn't care about).
|
|
168
|
+
//
|
|
169
|
+
// - Borrow the browser DOM's notion of node "connected"-ness. When a node
|
|
170
|
+
// is removed, its `isConnected` property is `false`. The same is true
|
|
171
|
+
// for any of its descendants, even though they retain their own direct
|
|
172
|
+
// parent reference.
|
|
173
|
+
readonly parent: BaseNode | null;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Each node provides a discrete object representing the stateful aspects of
|
|
177
|
+
* that node which will change over time. When a client provides a {@link OpaqueReactiveObjectFactory}
|
|
178
|
+
*/
|
|
179
|
+
readonly currentState: BaseNodeState;
|
|
180
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { OpaqueReactiveObjectFactory } from './OpaqueReactiveObjectFactory.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @todo this is currently a strict subset of the web standard `Response`. Is it
|
|
5
|
+
* sufficient? Ways it might not be:
|
|
6
|
+
*
|
|
7
|
+
* - No way to convey metadata about the resource
|
|
8
|
+
* - Ambiguous if a client supplies an alternative implementation which doesn't
|
|
9
|
+
* exhaust the body on access
|
|
10
|
+
*/
|
|
11
|
+
export interface FetchResourceResponse {
|
|
12
|
+
readonly ok?: boolean;
|
|
13
|
+
readonly body?: ReadableStream<Uint8Array> | null;
|
|
14
|
+
readonly bodyUsed?: boolean;
|
|
15
|
+
|
|
16
|
+
readonly blob: () => Promise<Blob>;
|
|
17
|
+
readonly text: () => Promise<string>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @todo this is a strict subset of the web standard `fetch` interface. It
|
|
22
|
+
* implicitly assumes that the engine itself will only ever issue `GET`-like
|
|
23
|
+
* requests. It also provides no further request-like semantics to the engine.
|
|
24
|
+
* This is presumed sufficient for now, but notably doesn't expose any notion of
|
|
25
|
+
* content negotiation (e.g. the ability to supply `Accept` headers).
|
|
26
|
+
*
|
|
27
|
+
* This also completely ignores any notion of mapping
|
|
28
|
+
* {@link https://getodk.github.io/xforms-spec/#uris | `jr:` URLs} to their
|
|
29
|
+
* actual resources (likely, but not necessarily, accessed at a corresponding
|
|
30
|
+
* HTTP URL).
|
|
31
|
+
*/
|
|
32
|
+
export type FetchResource = (resource: URL) => Promise<FetchResourceResponse>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Options provided by a client to specify certain aspects of engine runtime
|
|
36
|
+
* behavior. These options will generally be intended to facilitate cooperation
|
|
37
|
+
* where there is mixed responsibility between a client and the engine, or where
|
|
38
|
+
* the engine may provide sensible defaults which a client could be expected to
|
|
39
|
+
* override or augment.
|
|
40
|
+
*/
|
|
41
|
+
export interface EngineConfig {
|
|
42
|
+
/**
|
|
43
|
+
* A client may specify a generic function for constructing stateful objects.
|
|
44
|
+
* The only hard requirement of this function is that it accepts an **object**
|
|
45
|
+
* and returns an object of the same shape. The engine will use this function
|
|
46
|
+
* to initialize client-facing state, and will mutate properties of the object
|
|
47
|
+
* when their corresponding state is changed.
|
|
48
|
+
*
|
|
49
|
+
* A client may use this function to provide its own implementation of
|
|
50
|
+
* reactivity with semantics like those described above. The mechanism of
|
|
51
|
+
* reactivity, if any, is at the discretion of the client. It is expected
|
|
52
|
+
* that clients providing this function will use a reactive subscribe-on-read
|
|
53
|
+
* mechanism to handle state updates conveyed by the engine.
|
|
54
|
+
*/
|
|
55
|
+
readonly stateFactory?: OpaqueReactiveObjectFactory;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A client may specify a generic function for retrieving resources referenced
|
|
59
|
+
* by a form, such as:
|
|
60
|
+
*
|
|
61
|
+
* - Form definitions themselves (if not provided directly to the engine by
|
|
62
|
+
* the client)
|
|
63
|
+
* - External secondary instances
|
|
64
|
+
* - Media (images, audio, video, etc.)
|
|
65
|
+
*
|
|
66
|
+
* The function is expected to be a subset of the
|
|
67
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API | Fetch API},
|
|
68
|
+
* performing `GET` requests for:
|
|
69
|
+
*
|
|
70
|
+
* - Text resources (e.g. XML, CSV, JSON/GeoJSON)
|
|
71
|
+
* - Binary resources (e.g. media)
|
|
72
|
+
* - Optionally streamed binary data of either (e.g. for optimized
|
|
73
|
+
* presentation of audio/video)
|
|
74
|
+
*
|
|
75
|
+
* If provided by a client, this function will be used by the engine to
|
|
76
|
+
* retrieve any such resource, as required for engine functionality. If
|
|
77
|
+
* absent, the engine will use the native `fetch` function (if available, a
|
|
78
|
+
* polyfill otherwise). Clients may use this function to provide resources
|
|
79
|
+
* from sources other than the network, (or even in a test client to provide
|
|
80
|
+
* e.g. resources from test fixtures).
|
|
81
|
+
*/
|
|
82
|
+
readonly fetchResource?: FetchResource;
|
|
83
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
interface BaseFormLanguage {
|
|
2
|
+
/**
|
|
3
|
+
* @see {@link ActiveLanguage} for details.
|
|
4
|
+
*/
|
|
5
|
+
readonly isSyntheticDefault?: true;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* As derived directly from the form's
|
|
9
|
+
* {@link https://getodk.github.io/xforms-spec/#languages | `itext` translations}.
|
|
10
|
+
*/
|
|
11
|
+
readonly language: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Where possible, a form's languages may detect the standardized locale
|
|
15
|
+
* corresponding to the language as specified in the form.
|
|
16
|
+
*/
|
|
17
|
+
// NOTE: this is proposed, hypothetical future functionality. Enketo has
|
|
18
|
+
// similar functionality, but we'll want to seriously consider how best to
|
|
19
|
+
// accomplish this if/as it becomes a priority.
|
|
20
|
+
readonly locale?: Intl.Locale;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @see {@link ActiveLanguage} for details.
|
|
25
|
+
*/
|
|
26
|
+
export interface SyntheticDefaultLanguage extends BaseFormLanguage {
|
|
27
|
+
readonly isSyntheticDefault: true;
|
|
28
|
+
readonly language: '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A language available for a given form, as defined by that form's {@link https://getodk.github.io/xforms-spec/#languages | `itext` translations}.
|
|
33
|
+
*
|
|
34
|
+
* @see {@link ActiveLanguage} for additional details.
|
|
35
|
+
*/
|
|
36
|
+
export interface FormLanguage extends BaseFormLanguage {
|
|
37
|
+
readonly isSyntheticDefault?: never;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A form with translations will always have one or more {@link FormLanguage}s,
|
|
42
|
+
* with the default chosen as described in the ODK XForms specification.
|
|
43
|
+
*
|
|
44
|
+
* A form that specifies no translations will always have a single
|
|
45
|
+
* {@link SyntheticDefaultLanguage}.
|
|
46
|
+
*
|
|
47
|
+
* No form will ever combine both types of language.
|
|
48
|
+
*
|
|
49
|
+
* This distinction is intended to avoid confusion about the **potential** state
|
|
50
|
+
* of a form's active language. A naive type to express the possibility, without
|
|
51
|
+
* a synthetic default, would be something like `FormLanguage | null`, which
|
|
52
|
+
* would seem to suggest to a client that a form may **only sometimes** have an
|
|
53
|
+
* active language—for instance, that there might be a way for a client to turn
|
|
54
|
+
* translation off and on.
|
|
55
|
+
*
|
|
56
|
+
* By ensuring there is always _some active language value_, and by expressing
|
|
57
|
+
* the {@link FormLanguages} type to correspond to each of the possibilities
|
|
58
|
+
* discussed above, it's hoped that the set of potential translation states a
|
|
59
|
+
* client might encounter/establish are more clear.
|
|
60
|
+
*/
|
|
61
|
+
// prettier-ignore
|
|
62
|
+
export type ActiveLanguage =
|
|
63
|
+
| FormLanguage
|
|
64
|
+
| SyntheticDefaultLanguage;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* A form may either have:
|
|
68
|
+
*
|
|
69
|
+
* - one or more available {@link FormLanguage}s, as defined by the form
|
|
70
|
+
* - exactly one {@link SyntheticDefaultLanguage}
|
|
71
|
+
*
|
|
72
|
+
* @see {@link ActiveLanguage} for additional details.
|
|
73
|
+
*/
|
|
74
|
+
// prettier-ignore
|
|
75
|
+
export type FormLanguages =
|
|
76
|
+
| readonly [FormLanguage, ...FormLanguage[]]
|
|
77
|
+
| readonly [SyntheticDefaultLanguage];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { NonRepeatGroupElementDefinition } from '../body/BodyDefinition.ts';
|
|
2
|
+
import type { SubtreeDefinition } from '../model/SubtreeDefinition.ts';
|
|
3
|
+
import type { BaseNode, BaseNodeState } from './BaseNode.ts';
|
|
4
|
+
import type { RootNode } from './RootNode.ts';
|
|
5
|
+
import type { GeneralChildNode, GeneralParentNode } from './hierarchy.ts';
|
|
6
|
+
|
|
7
|
+
export interface GroupNodeState extends BaseNodeState {
|
|
8
|
+
get hint(): null;
|
|
9
|
+
get children(): readonly GeneralChildNode[];
|
|
10
|
+
get valueOptions(): null;
|
|
11
|
+
get value(): null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// TODO: as with `SubtreeNode`'s `SubtreeDefinition`, there is a naming
|
|
15
|
+
// inconsistency emerging here.
|
|
16
|
+
export interface GroupDefinition extends SubtreeDefinition {
|
|
17
|
+
readonly bodyElement: NonRepeatGroupElementDefinition;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A node corresponding to an XForms `<group>`.
|
|
22
|
+
*/
|
|
23
|
+
// TODO: test (fix?) case where a `<group>` is implicitly connected to a
|
|
24
|
+
// subtree, but doesn't reference it directly. See
|
|
25
|
+
// https://github.com/getodk/web-forms/blob/6cfff8b4c5a2cf6a23a71ef6d4308343bccd2436/packages/odk-web-forms/src/lib/xform/model/ModelDefinition.test.ts#L480-L540
|
|
26
|
+
// for context.
|
|
27
|
+
export interface GroupNode extends BaseNode {
|
|
28
|
+
readonly nodeType: 'group';
|
|
29
|
+
readonly definition: GroupDefinition;
|
|
30
|
+
readonly root: RootNode;
|
|
31
|
+
readonly parent: GeneralParentNode;
|
|
32
|
+
readonly currentState: GroupNodeState;
|
|
33
|
+
}
|