@cratis/scene.react 1.1.0 → 3.0.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 +29 -2
- package/dist/cjs/renderer/createReactRenderer.js +21 -6
- package/dist/cjs/renderer/createReactRenderer.js.map +1 -1
- package/dist/cjs/renderer/panelLayout.js +108 -0
- package/dist/cjs/renderer/panelLayout.js.map +1 -0
- package/dist/esm/for_panelLayout/when_arranging_a_panel.d.ts +2 -0
- package/dist/esm/for_panelLayout/when_arranging_a_panel.d.ts.map +1 -0
- package/dist/esm/for_panelLayout/when_arranging_a_panel.js +91 -0
- package/dist/esm/for_panelLayout/when_arranging_a_panel.js.map +1 -0
- package/dist/esm/renderer/createReactRenderer.d.ts.map +1 -1
- package/dist/esm/renderer/createReactRenderer.js +21 -6
- package/dist/esm/renderer/createReactRenderer.js.map +1 -1
- package/dist/esm/renderer/panelLayout.d.ts +5 -0
- package/dist/esm/renderer/panelLayout.d.ts.map +1 -0
- package/dist/esm/renderer/panelLayout.js +105 -0
- package/dist/esm/renderer/panelLayout.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/for_panelLayout/when_arranging_a_panel.ts +112 -0
- package/package.json +15 -5
- package/renderer/createReactRenderer.tsx +21 -5
- package/renderer/panelLayout.ts +109 -0
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Scene
|
|
2
2
|
|
|
3
|
+
**Describing a user interface without describing a platform** — Scene is the UI model of the Cratis
|
|
4
|
+
model-first layer (experimental).
|
|
5
|
+
|
|
3
6
|
The object model and runtime for Screenplay-based UIs: `Scene.Model` (platform-agnostic screens, layouts,
|
|
4
7
|
forms, contribution points, ui profiles and themes), `Scene.Engine` (binding/resolution runtime), and
|
|
5
8
|
`Scene.React` (the React renderer).
|
|
@@ -18,8 +21,13 @@ forms, contribution points, ui profiles and themes), `Scene.Engine` (binding/res
|
|
|
18
21
|
|
|
19
22
|
## Description
|
|
20
23
|
|
|
21
|
-
Scene owns the *object model and runtime* half of the Screenplay stack.
|
|
22
|
-
|
|
24
|
+
Scene owns the *object model and runtime* half of the Screenplay stack. In the model-first pipeline,
|
|
25
|
+
[Screenplay](https://github.com/Cratis/Screenplay) describes an event-sourced, CQRS system,
|
|
26
|
+
[Stage](https://github.com/Cratis/Stage) renders it into a running application, and Scene is how that
|
|
27
|
+
application's user interface is described — independently of any rendering platform.
|
|
28
|
+
|
|
29
|
+
`Scene.Model` is the platform-agnostic
|
|
30
|
+
C# object model that [Stage](https://github.com/Cratis/Stage) translates Screenplay's parsed language constructs into, and that [Studio](https://github.com/Cratis/Studio)'s
|
|
23
31
|
design-time tooling reads and writes. `Scene.Engine` walks that graph, resolves bindings (data, action, form,
|
|
24
32
|
navigation contribution) and drives whichever renderer is plugged in — it does not know React exists.
|
|
25
33
|
`Scene.React` is one renderer implementing the abstract vocabulary against real React/DOM; sibling positions
|
|
@@ -38,3 +46,22 @@ Cratis is an open community, and we are glad to help users, teams evaluating the
|
|
|
38
46
|
| Discord | Join the community on [Discord](https://discord.gg/kt4AMpV8WV) for questions and discussions |
|
|
39
47
|
| GitHub Issues | [Report bugs or request features](https://github.com/Cratis/Scene/issues) |
|
|
40
48
|
| Documentation | Read the docs at [cratis.io](https://cratis.io) |
|
|
49
|
+
|
|
50
|
+
## The Cratis ecosystem
|
|
51
|
+
|
|
52
|
+
This project is part of [Cratis](https://www.cratis.io) — free, MIT-licensed tools for building event-sourced and CQRS applications.
|
|
53
|
+
|
|
54
|
+
- **[Chronicle](https://github.com/Cratis/Chronicle)** — event-sourcing database and runtime. Orleans-based kernel, pluggable storage (MongoDB default; PostgreSQL, SQL Server, SQLite, in-memory), language-agnostic gRPC contracts. [Docs](https://www.cratis.io/chronicle/)
|
|
55
|
+
- **Chronicle clients** — first-class [.NET SDK](https://github.com/Cratis/Chronicle), plus [TypeScript](https://github.com/Cratis/Chronicle.TypeScript), [Kotlin/Java](https://github.com/Cratis/Chronicle.Kotlin), and [Elixir](https://github.com/Cratis/Chronicle.Elixir); [Python](https://github.com/Cratis/Chronicle.Python) coming soon (pre-alpha). AI agents connect through the [Chronicle MCP server](https://github.com/Cratis/Chronicle.Mcp).
|
|
56
|
+
- **[Arc](https://github.com/Cratis/Arc)** — opinionated CQRS framework for ASP.NET Core with commands, queries, validation, authorization, and TypeScript proxy generation. Works without event sourcing. [Docs](https://www.cratis.io/arc/)
|
|
57
|
+
- **[Components](https://github.com/Cratis/Components)** — React components aligned with Arc patterns. [Docs](https://www.cratis.io/components/)
|
|
58
|
+
- **[CLI](https://github.com/Cratis/cli) + Workbench** — inspect and diagnose Chronicle from the terminal or the browser. [Docs](https://www.cratis.io/cli/)
|
|
59
|
+
- **Model-first layer (experimental)** — [Studio](https://github.com/Cratis/Studio), [Screenplay](https://github.com/Cratis/Screenplay), [Stage](https://github.com/Cratis/Stage), Scene (this repository), [Prologue](https://github.com/Cratis/Prologue)
|
|
60
|
+
- **Supporting** — [Fundamentals](https://github.com/Cratis/Fundamentals), [Specifications](https://github.com/Cratis/Specifications), [Synopsis](https://github.com/Cratis/Synopsis), [Lens](https://github.com/Cratis/Lens), [Narrator](https://github.com/Cratis/Narrator), and free [AI tooling](https://github.com/Cratis/AI) (preview); [Ensemble](https://github.com/Cratis/Ensemble) coming soon (pre-release)
|
|
61
|
+
- **[Samples](https://github.com/Cratis/Samples)** — runnable event sourcing and CQRS samples for the whole stack
|
|
62
|
+
|
|
63
|
+
Everything Cratis publishes today is MIT licensed and free to use.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
Part of the [Cratis](https://www.cratis.io) platform · [MIT](./LICENSE)
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
|
+
var panelLayout = require('./panelLayout.js');
|
|
4
5
|
var UnresolvedComponent = require('./UnresolvedComponent.js');
|
|
5
6
|
|
|
6
7
|
// Copyright (c) Cratis. All rights reserved.
|
|
7
8
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
8
9
|
/**
|
|
9
|
-
* Creates a {@link Renderer} that turns a Scene element tree into React elements.
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
10
|
+
* Creates a {@link Renderer} that turns a Scene element tree into React elements.
|
|
11
|
+
*
|
|
12
|
+
* `ItemsControl` and `ContentControl` render as plain wrapping `div`s. A `Panel` renders as the
|
|
13
|
+
* arrangement it declares - a grid, a stack, a wrap or a dock - because that is what those panels are
|
|
14
|
+
* for; see `panelLayout` for the mapping. Arranging a *layout's slots* by `flow`/`freeform` is a
|
|
15
|
+
* different thing and stays Scene#4's, layered on top of this renderer rather than folded into it.
|
|
13
16
|
*/ function createReactRenderer(registry) {
|
|
14
17
|
return {
|
|
15
18
|
renderContentControl (element, content) {
|
|
@@ -35,11 +38,23 @@ var UnresolvedComponent = require('./UnresolvedComponent.js');
|
|
|
35
38
|
});
|
|
36
39
|
},
|
|
37
40
|
renderPanel (element, children) {
|
|
41
|
+
// The rendered children arrive in the same order as `element.children`, so a panel that places
|
|
42
|
+
// its children individually - a grid cell, a docked edge - can wrap each one in the box that
|
|
43
|
+
// places it without needing to re-render anything.
|
|
44
|
+
const placed = children.map((child, index)=>{
|
|
45
|
+
const style = panelLayout.childStyle(element, element.children[index], index);
|
|
46
|
+
return style ? /*#__PURE__*/ react.createElement('div', {
|
|
47
|
+
key: `${element.id}-${index}`,
|
|
48
|
+
'data-scene-placement': index,
|
|
49
|
+
style
|
|
50
|
+
}, child) : child;
|
|
51
|
+
});
|
|
38
52
|
return /*#__PURE__*/ react.createElement('div', {
|
|
39
53
|
key: element.id,
|
|
40
54
|
'data-scene-id': element.id,
|
|
41
|
-
'data-scene-kind': 'Panel'
|
|
42
|
-
|
|
55
|
+
'data-scene-kind': 'Panel',
|
|
56
|
+
style: panelLayout.panelStyle(element)
|
|
57
|
+
}, placed);
|
|
43
58
|
}
|
|
44
59
|
};
|
|
45
60
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createReactRenderer.js","sources":["../../../renderer/createReactRenderer.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { ReactNode, createElement } from 'react';\nimport { Renderer } from '@cratis/scene.engine';\nimport { ContentControl, ExternalComponent, ItemsControl, Panel } from '@cratis/scene.model';\nimport { ComponentRegistry } from './ComponentRegistry';\nimport { UnresolvedComponent } from './UnresolvedComponent';\n\n/**\n * Creates a {@link Renderer} that turns a Scene element tree into React elements
|
|
1
|
+
{"version":3,"file":"createReactRenderer.js","sources":["../../../renderer/createReactRenderer.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { ReactNode, createElement } from 'react';\nimport { Renderer } from '@cratis/scene.engine';\nimport { ContentControl, ExternalComponent, ItemsControl, Panel } from '@cratis/scene.model';\nimport { ComponentRegistry } from './ComponentRegistry';\nimport { childStyle, panelStyle } from './panelLayout';\nimport { UnresolvedComponent } from './UnresolvedComponent';\n\n/**\n * Creates a {@link Renderer} that turns a Scene element tree into React elements.\n *\n * `ItemsControl` and `ContentControl` render as plain wrapping `div`s. A `Panel` renders as the\n * arrangement it declares - a grid, a stack, a wrap or a dock - because that is what those panels are\n * for; see `panelLayout` for the mapping. Arranging a *layout's slots* by `flow`/`freeform` is a\n * different thing and stays Scene#4's, layered on top of this renderer rather than folded into it.\n */\nexport function createReactRenderer(registry: ComponentRegistry): Renderer<ReactNode> {\n return {\n renderContentControl(element: ContentControl, content: ReactNode): ReactNode {\n return createElement('div', { key: element.id, 'data-scene-id': element.id, 'data-scene-kind': 'ContentControl' }, content);\n },\n\n renderItemsControl(element: ItemsControl, items: ReactNode[]): ReactNode {\n return createElement('div', { key: element.id, 'data-scene-id': element.id, 'data-scene-kind': 'ItemsControl' }, items);\n },\n\n renderExternalComponent(element: ExternalComponent, slots: Record<string, ReactNode[]>): ReactNode {\n const Component = registry[element.componentName] ?? UnresolvedComponent;\n return createElement(Component, { key: element.id, element, slots });\n },\n\n renderPanel(element: Panel, children: ReactNode[]): ReactNode {\n // The rendered children arrive in the same order as `element.children`, so a panel that places\n // its children individually - a grid cell, a docked edge - can wrap each one in the box that\n // places it without needing to re-render anything.\n const placed = children.map((child, index) => {\n const style = childStyle(element, element.children[index], index);\n return style\n ? createElement('div', { key: `${element.id}-${index}`, 'data-scene-placement': index, style }, child)\n : child;\n });\n\n return createElement(\n 'div',\n { key: element.id, 'data-scene-id': element.id, 'data-scene-kind': 'Panel', style: panelStyle(element) },\n placed);\n },\n };\n}\n"],"names":["createReactRenderer","registry","renderContentControl","element","content","createElement","key","id","renderItemsControl","items","renderExternalComponent","slots","Component","componentName","UnresolvedComponent","renderPanel","children","placed","map","child","index","style","childStyle","panelStyle"],"mappings":";;;;;;AAAA;AACA;AASA;;;;;;;IAQO,SAASA,mBAAAA,CAAoBC,QAA2B,EAAA;IAC3D,OAAO;QACHC,oBAAAA,CAAAA,CAAqBC,OAAuB,EAAEC,OAAkB,EAAA;AAC5D,YAAA,qBAAOC,oBAAc,KAAA,EAAO;AAAEC,gBAAAA,GAAAA,EAAKH,QAAQI,EAAE;AAAE,gBAAA,eAAA,EAAiBJ,QAAQI,EAAE;gBAAE,iBAAA,EAAmB;aAAiB,EAAGH,OAAAA,CAAAA;AACvH,QAAA,CAAA;QAEAI,kBAAAA,CAAAA,CAAmBL,OAAqB,EAAEM,KAAkB,EAAA;AACxD,YAAA,qBAAOJ,oBAAc,KAAA,EAAO;AAAEC,gBAAAA,GAAAA,EAAKH,QAAQI,EAAE;AAAE,gBAAA,eAAA,EAAiBJ,QAAQI,EAAE;gBAAE,iBAAA,EAAmB;aAAe,EAAGE,KAAAA,CAAAA;AACrH,QAAA,CAAA;QAEAC,uBAAAA,CAAAA,CAAwBP,OAA0B,EAAEQ,KAAkC,EAAA;AAClF,YAAA,MAAMC,YAAYX,QAAQ,CAACE,OAAAA,CAAQU,aAAa,CAAC,IAAIC,uCAAAA;AACrD,YAAA,qBAAOT,oBAAcO,SAAAA,EAAW;AAAEN,gBAAAA,GAAAA,EAAKH,QAAQI,EAAE;AAAEJ,gBAAAA,OAAAA;AAASQ,gBAAAA;AAAM,aAAA,CAAA;AACtE,QAAA,CAAA;QAEAI,WAAAA,CAAAA,CAAYZ,OAAc,EAAEa,QAAqB,EAAA;;;;AAI7C,YAAA,MAAMC,MAAAA,GAASD,QAAAA,CAASE,GAAG,CAAC,CAACC,KAAAA,EAAOC,KAAAA,GAAAA;AAChC,gBAAA,MAAMC,QAAQC,sBAAAA,CAAWnB,OAAAA,EAASA,QAAQa,QAAQ,CAACI,MAAM,EAAEA,KAAAA,CAAAA;gBAC3D,OAAOC,KAAAA,iBACDhB,oBAAc,KAAA,EAAO;AAAEC,oBAAAA,GAAAA,EAAK,GAAGH,OAAAA,CAAQI,EAAE,CAAC,CAAC,EAAEa,KAAAA,CAAAA,CAAO;oBAAE,sBAAA,EAAwBA,KAAAA;AAAOC,oBAAAA;AAAM,iBAAA,EAAGF,KAAAA,CAAAA,GAC9FA,KAAAA;AACV,YAAA,CAAA,CAAA;AAEA,YAAA,qBAAOd,oBACH,KAAA,EACA;AAAEC,gBAAAA,GAAAA,EAAKH,QAAQI,EAAE;AAAE,gBAAA,eAAA,EAAiBJ,QAAQI,EAAE;gBAAE,iBAAA,EAAmB,OAAA;AAASc,gBAAAA,KAAAA,EAAOE,sBAAAA,CAAWpB,OAAAA;aAAS,EACvGc,MAAAA,CAAAA;AACR,QAAA;AACJ,KAAA;AACJ;;;;"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var scene_engine = require('@cratis/scene.engine');
|
|
4
|
+
var scene_model = require('@cratis/scene.model');
|
|
5
|
+
|
|
6
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
7
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
8
|
+
/**
|
|
9
|
+
* Turns a panel into the CSS that arranges it, and a child into the CSS that places it inside one.
|
|
10
|
+
*
|
|
11
|
+
* The panels say what an arrangement *is*; this says what it looks like on the web. A second renderer -
|
|
12
|
+
* native, desktop - answers the same question with its own platform's layout primitives, which is why
|
|
13
|
+
* this lives in `Scene.React` rather than in the model or the engine.
|
|
14
|
+
*
|
|
15
|
+
* Where a panel needs to say something about an individual child - which grid cell, which edge - it does
|
|
16
|
+
* so through that child's own `properties` bag, because the model has no attached properties of its own.
|
|
17
|
+
* The keys are the ones the model's documentation names: `Grid.Row`, `Grid.Column`, `Grid.RowSpan`,
|
|
18
|
+
* `Grid.ColumnSpan` and `Dock`.
|
|
19
|
+
*/ const track = (length)=>{
|
|
20
|
+
switch(length.unitType){
|
|
21
|
+
case scene_model.GridUnitType.Auto:
|
|
22
|
+
return 'auto';
|
|
23
|
+
case scene_model.GridUnitType.Absolute:
|
|
24
|
+
return `${length.value}px`;
|
|
25
|
+
case scene_model.GridUnitType.Star:
|
|
26
|
+
return `${length.value}fr`;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const bound = (minimum, maximum, size)=>minimum === 0 && !Number.isFinite(maximum) ? size : `minmax(${minimum}px, ${Number.isFinite(maximum) ? `${maximum}px` : size})`;
|
|
30
|
+
const rowTrack = (row)=>bound(row.minimumHeight, row.maximumHeight, track(row.height));
|
|
31
|
+
const columnTrack = (column)=>bound(column.minimumWidth, column.maximumWidth, track(column.width));
|
|
32
|
+
const number = (value)=>typeof value === 'number' ? value : undefined;
|
|
33
|
+
/** The CSS that arranges a panel's own children. */ function panelStyle(panel) {
|
|
34
|
+
if (scene_engine.isGrid(panel)) {
|
|
35
|
+
return {
|
|
36
|
+
display: 'grid',
|
|
37
|
+
gridTemplateRows: panel.rows.map(rowTrack).join(' ') || undefined,
|
|
38
|
+
gridTemplateColumns: panel.columns.map(columnTrack).join(' ') || undefined
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (scene_engine.isDockPanel(panel)) {
|
|
42
|
+
// Docking has no single CSS primitive. A column of rows with a middle that grows is the shape a
|
|
43
|
+
// dock panel actually produces, and `lastChildFill` decides whether the final child is that
|
|
44
|
+
// middle or just another docked edge.
|
|
45
|
+
return {
|
|
46
|
+
display: 'flex',
|
|
47
|
+
flexDirection: 'column'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (scene_engine.isStackPanel(panel)) {
|
|
51
|
+
return {
|
|
52
|
+
display: 'flex',
|
|
53
|
+
flexDirection: panel.orientation === scene_model.Orientation.Horizontal ? 'row' : 'column',
|
|
54
|
+
gap: panel.spacing ? `${panel.spacing}px` : undefined
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (scene_engine.isWrapPanel(panel)) {
|
|
58
|
+
return {
|
|
59
|
+
display: 'flex',
|
|
60
|
+
flexWrap: 'wrap',
|
|
61
|
+
flexDirection: panel.orientation === scene_model.Orientation.Horizontal ? 'row' : 'column'
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
/** The CSS that places one child within its panel, or `undefined` when the panel places children implicitly. */ function childStyle(panel, child, index) {
|
|
67
|
+
const properties = child.properties ?? {};
|
|
68
|
+
if (scene_engine.isGrid(panel)) {
|
|
69
|
+
const row = number(properties['Grid.Row']);
|
|
70
|
+
const column = number(properties['Grid.Column']);
|
|
71
|
+
const rowSpan = number(properties['Grid.RowSpan']) ?? 1;
|
|
72
|
+
const columnSpan = number(properties['Grid.ColumnSpan']) ?? 1;
|
|
73
|
+
if (row === undefined && column === undefined) return undefined;
|
|
74
|
+
return {
|
|
75
|
+
gridRow: row === undefined ? undefined : `${row + 1} / span ${rowSpan}`,
|
|
76
|
+
gridColumn: column === undefined ? undefined : `${column + 1} / span ${columnSpan}`
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (scene_engine.isDockPanel(panel)) {
|
|
80
|
+
const dock = properties['Dock'];
|
|
81
|
+
const isFill = panel.lastChildFill && index === (panel.children?.length ?? 0) - 1;
|
|
82
|
+
if (isFill) return {
|
|
83
|
+
flex: '1 1 auto',
|
|
84
|
+
minHeight: 0
|
|
85
|
+
};
|
|
86
|
+
// Left and right dock along the cross axis of the column the panel lays out, so they become a row
|
|
87
|
+
// of their own rather than a sibling in the column.
|
|
88
|
+
return dock === scene_model.Dock.Left || dock === scene_model.Dock.Right ? {
|
|
89
|
+
alignSelf: dock === scene_model.Dock.Left ? 'flex-start' : 'flex-end',
|
|
90
|
+
flex: '0 0 auto'
|
|
91
|
+
} : {
|
|
92
|
+
flex: '0 0 auto'
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (scene_engine.isWrapPanel(panel)) {
|
|
96
|
+
if (panel.itemWidth === undefined && panel.itemHeight === undefined) return undefined;
|
|
97
|
+
return {
|
|
98
|
+
width: panel.itemWidth,
|
|
99
|
+
height: panel.itemHeight,
|
|
100
|
+
flex: '0 0 auto'
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
exports.childStyle = childStyle;
|
|
107
|
+
exports.panelStyle = panelStyle;
|
|
108
|
+
//# sourceMappingURL=panelLayout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panelLayout.js","sources":["../../../renderer/panelLayout.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport type { CSSProperties } from 'react';\nimport { isDockPanel, isGrid, isStackPanel, isWrapPanel } from '@cratis/scene.engine';\nimport { Dock, GridUnitType, Orientation } from '@cratis/scene.model';\nimport type { ColumnDefinition, GridLength, Panel, RowDefinition, SceneElement } from '@cratis/scene.model';\n\n/**\n * Turns a panel into the CSS that arranges it, and a child into the CSS that places it inside one.\n *\n * The panels say what an arrangement *is*; this says what it looks like on the web. A second renderer -\n * native, desktop - answers the same question with its own platform's layout primitives, which is why\n * this lives in `Scene.React` rather than in the model or the engine.\n *\n * Where a panel needs to say something about an individual child - which grid cell, which edge - it does\n * so through that child's own `properties` bag, because the model has no attached properties of its own.\n * The keys are the ones the model's documentation names: `Grid.Row`, `Grid.Column`, `Grid.RowSpan`,\n * `Grid.ColumnSpan` and `Dock`.\n */\n\nconst track = (length: GridLength): string => {\n switch (length.unitType) {\n case GridUnitType.Auto: return 'auto';\n case GridUnitType.Absolute: return `${length.value}px`;\n case GridUnitType.Star: return `${length.value}fr`;\n }\n};\n\nconst bound = (minimum: number, maximum: number, size: string): string =>\n minimum === 0 && !Number.isFinite(maximum)\n ? size\n : `minmax(${minimum}px, ${Number.isFinite(maximum) ? `${maximum}px` : size})`;\n\nconst rowTrack = (row: RowDefinition): string => bound(row.minimumHeight, row.maximumHeight, track(row.height));\nconst columnTrack = (column: ColumnDefinition): string => bound(column.minimumWidth, column.maximumWidth, track(column.width));\n\nconst number = (value: unknown): number | undefined => (typeof value === 'number' ? value : undefined);\n\n/** The CSS that arranges a panel's own children. */\nexport function panelStyle(panel: Panel): CSSProperties | undefined {\n if (isGrid(panel)) {\n return {\n display: 'grid',\n gridTemplateRows: panel.rows.map(rowTrack).join(' ') || undefined,\n gridTemplateColumns: panel.columns.map(columnTrack).join(' ') || undefined,\n };\n }\n\n if (isDockPanel(panel)) {\n // Docking has no single CSS primitive. A column of rows with a middle that grows is the shape a\n // dock panel actually produces, and `lastChildFill` decides whether the final child is that\n // middle or just another docked edge.\n return { display: 'flex', flexDirection: 'column' };\n }\n\n if (isStackPanel(panel)) {\n return {\n display: 'flex',\n flexDirection: panel.orientation === Orientation.Horizontal ? 'row' : 'column',\n gap: panel.spacing ? `${panel.spacing}px` : undefined,\n };\n }\n\n if (isWrapPanel(panel)) {\n return {\n display: 'flex',\n flexWrap: 'wrap',\n flexDirection: panel.orientation === Orientation.Horizontal ? 'row' : 'column',\n };\n }\n\n return undefined;\n}\n\n/** The CSS that places one child within its panel, or `undefined` when the panel places children implicitly. */\nexport function childStyle(panel: Panel, child: SceneElement, index: number): CSSProperties | undefined {\n const properties = child.properties ?? {};\n\n if (isGrid(panel)) {\n const row = number(properties['Grid.Row']);\n const column = number(properties['Grid.Column']);\n const rowSpan = number(properties['Grid.RowSpan']) ?? 1;\n const columnSpan = number(properties['Grid.ColumnSpan']) ?? 1;\n if (row === undefined && column === undefined) return undefined;\n return {\n gridRow: row === undefined ? undefined : `${row + 1} / span ${rowSpan}`,\n gridColumn: column === undefined ? undefined : `${column + 1} / span ${columnSpan}`,\n };\n }\n\n if (isDockPanel(panel)) {\n const dock = properties['Dock'] as Dock | undefined;\n const isFill = panel.lastChildFill && index === (panel.children?.length ?? 0) - 1;\n if (isFill) return { flex: '1 1 auto', minHeight: 0 };\n // Left and right dock along the cross axis of the column the panel lays out, so they become a row\n // of their own rather than a sibling in the column.\n return dock === Dock.Left || dock === Dock.Right\n ? { alignSelf: dock === Dock.Left ? 'flex-start' : 'flex-end', flex: '0 0 auto' }\n : { flex: '0 0 auto' };\n }\n\n if (isWrapPanel(panel)) {\n if (panel.itemWidth === undefined && panel.itemHeight === undefined) return undefined;\n return { width: panel.itemWidth, height: panel.itemHeight, flex: '0 0 auto' };\n }\n\n return undefined;\n}\n"],"names":["track","length","unitType","GridUnitType","Auto","Absolute","value","Star","bound","minimum","maximum","size","Number","isFinite","rowTrack","row","minimumHeight","maximumHeight","height","columnTrack","column","minimumWidth","maximumWidth","width","number","undefined","panelStyle","panel","isGrid","display","gridTemplateRows","rows","map","join","gridTemplateColumns","columns","isDockPanel","flexDirection","isStackPanel","orientation","Orientation","Horizontal","gap","spacing","isWrapPanel","flexWrap","childStyle","child","index","properties","rowSpan","columnSpan","gridRow","gridColumn","dock","isFill","lastChildFill","children","flex","minHeight","Dock","Left","Right","alignSelf","itemWidth","itemHeight"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when_arranging_a_panel.d.ts","sourceRoot":"","sources":["../../../for_panelLayout/when_arranging_a_panel.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Dock, GridUnitType, HorizontalAlignment, Orientation, VerticalAlignment, Visibility } from '@cratis/scene.model';
|
|
2
|
+
import { childStyle, panelStyle } from '../renderer/panelLayout';
|
|
3
|
+
const base = {
|
|
4
|
+
id: 'panel',
|
|
5
|
+
properties: {},
|
|
6
|
+
visibility: Visibility.Visible,
|
|
7
|
+
isEnabled: true,
|
|
8
|
+
opacity: 1,
|
|
9
|
+
zIndex: 0,
|
|
10
|
+
size: {},
|
|
11
|
+
name: 'panel',
|
|
12
|
+
minimumSize: {},
|
|
13
|
+
maximumSize: {},
|
|
14
|
+
margin: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
15
|
+
horizontalAlignment: HorizontalAlignment.Stretch,
|
|
16
|
+
verticalAlignment: VerticalAlignment.Stretch,
|
|
17
|
+
children: [],
|
|
18
|
+
};
|
|
19
|
+
const child = (properties = {}) => ({ id: 'child', properties });
|
|
20
|
+
const star = (value) => ({ value, unitType: GridUnitType.Star });
|
|
21
|
+
const row = (over = {}) => ({ height: star(1), minimumHeight: 0, maximumHeight: Number.POSITIVE_INFINITY, ...over });
|
|
22
|
+
const column = (over = {}) => ({ width: star(1), minimumWidth: 0, maximumWidth: Number.POSITIVE_INFINITY, ...over });
|
|
23
|
+
describe('when arranging a panel', () => {
|
|
24
|
+
it('should lay a stack panel out along its orientation', () => {
|
|
25
|
+
const panel = { ...base, orientation: Orientation.Horizontal, spacing: 8 };
|
|
26
|
+
panelStyle(panel).flexDirection.should.equal('row');
|
|
27
|
+
});
|
|
28
|
+
it('should space a stack panel by its spacing', () => {
|
|
29
|
+
const panel = { ...base, orientation: Orientation.Vertical, spacing: 12 };
|
|
30
|
+
panelStyle(panel).gap.should.equal('12px');
|
|
31
|
+
});
|
|
32
|
+
it('should wrap a wrap panel', () => {
|
|
33
|
+
const panel = { ...base, orientation: Orientation.Horizontal };
|
|
34
|
+
panelStyle(panel).flexWrap.should.equal('wrap');
|
|
35
|
+
});
|
|
36
|
+
it('should turn star tracks into fractions', () => {
|
|
37
|
+
const panel = { ...base, rows: [row(), row({ height: star(2) })], columns: [] };
|
|
38
|
+
panelStyle(panel).gridTemplateRows.should.equal('1fr 2fr');
|
|
39
|
+
});
|
|
40
|
+
it('should turn an absolute track into pixels', () => {
|
|
41
|
+
const panel = { ...base, rows: [], columns: [column({ width: { value: 240, unitType: GridUnitType.Absolute } })] };
|
|
42
|
+
panelStyle(panel).gridTemplateColumns.should.equal('240px');
|
|
43
|
+
});
|
|
44
|
+
it('should turn a content-sized track into auto', () => {
|
|
45
|
+
const panel = { ...base, rows: [], columns: [column({ width: { value: 0, unitType: GridUnitType.Auto } })] };
|
|
46
|
+
panelStyle(panel).gridTemplateColumns.should.equal('auto');
|
|
47
|
+
});
|
|
48
|
+
it('should bound a track that states a minimum', () => {
|
|
49
|
+
const panel = { ...base, rows: [row({ minimumHeight: 40 })], columns: [] };
|
|
50
|
+
panelStyle(panel).gridTemplateRows.should.equal('minmax(40px, 1fr)');
|
|
51
|
+
});
|
|
52
|
+
it('should leave a plain panel unstyled', () => (panelStyle({ ...base }) === undefined).should.be.true);
|
|
53
|
+
});
|
|
54
|
+
describe('when placing a child in a panel', () => {
|
|
55
|
+
it('should place a grid child in the cell it names, one-based for CSS', () => {
|
|
56
|
+
const panel = { ...base, rows: [row()], columns: [column()] };
|
|
57
|
+
childStyle(panel, child({ 'Grid.Row': 1, 'Grid.Column': 2 }), 0).gridRow.should.equal('2 / span 1');
|
|
58
|
+
});
|
|
59
|
+
it('should span a grid child that asks for it', () => {
|
|
60
|
+
const panel = { ...base, rows: [row()], columns: [column()] };
|
|
61
|
+
childStyle(panel, child({ 'Grid.Column': 0, 'Grid.ColumnSpan': 3 }), 0).gridColumn.should.equal('1 / span 3');
|
|
62
|
+
});
|
|
63
|
+
it('should leave a grid child that names no cell to the implicit flow', () => {
|
|
64
|
+
const panel = { ...base, rows: [row()], columns: [column()] };
|
|
65
|
+
(childStyle(panel, child(), 0) === undefined).should.be.true;
|
|
66
|
+
});
|
|
67
|
+
it('should let a dock panel last child fill what is left', () => {
|
|
68
|
+
const kid = child();
|
|
69
|
+
const panel = { ...base, lastChildFill: true, children: [kid] };
|
|
70
|
+
childStyle(panel, kid, 0).flex.should.equal('1 1 auto');
|
|
71
|
+
});
|
|
72
|
+
it('should keep a docked child at its natural size', () => {
|
|
73
|
+
const first = child({ Dock: Dock.Top });
|
|
74
|
+
const panel = { ...base, lastChildFill: true, children: [first, child()] };
|
|
75
|
+
childStyle(panel, first, 0).flex.should.equal('0 0 auto');
|
|
76
|
+
});
|
|
77
|
+
it('should not let the last child fill when the panel says not to', () => {
|
|
78
|
+
const kid = child();
|
|
79
|
+
const panel = { ...base, lastChildFill: false, children: [kid] };
|
|
80
|
+
childStyle(panel, kid, 0).flex.should.equal('0 0 auto');
|
|
81
|
+
});
|
|
82
|
+
it('should size wrap panel children when the panel sizes them', () => {
|
|
83
|
+
const panel = { ...base, orientation: Orientation.Horizontal, itemWidth: 120 };
|
|
84
|
+
childStyle(panel, child(), 0).width.should.equal(120);
|
|
85
|
+
});
|
|
86
|
+
it('should leave wrap panel children their own size when the panel sizes neither axis', () => {
|
|
87
|
+
const panel = { ...base, orientation: Orientation.Horizontal };
|
|
88
|
+
(childStyle(panel, child(), 0) === undefined).should.be.true;
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=when_arranging_a_panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"when_arranging_a_panel.js","sourceRoot":"","sources":["../../../for_panelLayout/when_arranging_a_panel.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createReactRenderer.d.ts","sourceRoot":"","sources":["../../../renderer/createReactRenderer.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAiB,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"createReactRenderer.d.ts","sourceRoot":"","sources":["../../../renderer/createReactRenderer.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAiB,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAYxD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,QAAQ,CAAC,SAAS,CAAC,CAgCpF"}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { createElement } from 'react';
|
|
2
|
+
import { childStyle, panelStyle } from './panelLayout.js';
|
|
2
3
|
import { UnresolvedComponent } from './UnresolvedComponent.js';
|
|
3
4
|
|
|
4
5
|
// Copyright (c) Cratis. All rights reserved.
|
|
5
6
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
6
7
|
/**
|
|
7
|
-
* Creates a {@link Renderer} that turns a Scene element tree into React elements.
|
|
8
|
-
*
|
|
9
|
-
* `
|
|
10
|
-
*
|
|
8
|
+
* Creates a {@link Renderer} that turns a Scene element tree into React elements.
|
|
9
|
+
*
|
|
10
|
+
* `ItemsControl` and `ContentControl` render as plain wrapping `div`s. A `Panel` renders as the
|
|
11
|
+
* arrangement it declares - a grid, a stack, a wrap or a dock - because that is what those panels are
|
|
12
|
+
* for; see `panelLayout` for the mapping. Arranging a *layout's slots* by `flow`/`freeform` is a
|
|
13
|
+
* different thing and stays Scene#4's, layered on top of this renderer rather than folded into it.
|
|
11
14
|
*/ function createReactRenderer(registry) {
|
|
12
15
|
return {
|
|
13
16
|
renderContentControl (element, content) {
|
|
@@ -33,11 +36,23 @@ import { UnresolvedComponent } from './UnresolvedComponent.js';
|
|
|
33
36
|
});
|
|
34
37
|
},
|
|
35
38
|
renderPanel (element, children) {
|
|
39
|
+
// The rendered children arrive in the same order as `element.children`, so a panel that places
|
|
40
|
+
// its children individually - a grid cell, a docked edge - can wrap each one in the box that
|
|
41
|
+
// places it without needing to re-render anything.
|
|
42
|
+
const placed = children.map((child, index)=>{
|
|
43
|
+
const style = childStyle(element, element.children[index], index);
|
|
44
|
+
return style ? /*#__PURE__*/ createElement('div', {
|
|
45
|
+
key: `${element.id}-${index}`,
|
|
46
|
+
'data-scene-placement': index,
|
|
47
|
+
style
|
|
48
|
+
}, child) : child;
|
|
49
|
+
});
|
|
36
50
|
return /*#__PURE__*/ createElement('div', {
|
|
37
51
|
key: element.id,
|
|
38
52
|
'data-scene-id': element.id,
|
|
39
|
-
'data-scene-kind': 'Panel'
|
|
40
|
-
|
|
53
|
+
'data-scene-kind': 'Panel',
|
|
54
|
+
style: panelStyle(element)
|
|
55
|
+
}, placed);
|
|
41
56
|
}
|
|
42
57
|
};
|
|
43
58
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createReactRenderer.js","sources":["../../../renderer/createReactRenderer.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { ReactNode, createElement } from 'react';\nimport { Renderer } from '@cratis/scene.engine';\nimport { ContentControl, ExternalComponent, ItemsControl, Panel } from '@cratis/scene.model';\nimport { ComponentRegistry } from './ComponentRegistry';\nimport { UnresolvedComponent } from './UnresolvedComponent';\n\n/**\n * Creates a {@link Renderer} that turns a Scene element tree into React elements
|
|
1
|
+
{"version":3,"file":"createReactRenderer.js","sources":["../../../renderer/createReactRenderer.tsx"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { ReactNode, createElement } from 'react';\nimport { Renderer } from '@cratis/scene.engine';\nimport { ContentControl, ExternalComponent, ItemsControl, Panel } from '@cratis/scene.model';\nimport { ComponentRegistry } from './ComponentRegistry';\nimport { childStyle, panelStyle } from './panelLayout';\nimport { UnresolvedComponent } from './UnresolvedComponent';\n\n/**\n * Creates a {@link Renderer} that turns a Scene element tree into React elements.\n *\n * `ItemsControl` and `ContentControl` render as plain wrapping `div`s. A `Panel` renders as the\n * arrangement it declares - a grid, a stack, a wrap or a dock - because that is what those panels are\n * for; see `panelLayout` for the mapping. Arranging a *layout's slots* by `flow`/`freeform` is a\n * different thing and stays Scene#4's, layered on top of this renderer rather than folded into it.\n */\nexport function createReactRenderer(registry: ComponentRegistry): Renderer<ReactNode> {\n return {\n renderContentControl(element: ContentControl, content: ReactNode): ReactNode {\n return createElement('div', { key: element.id, 'data-scene-id': element.id, 'data-scene-kind': 'ContentControl' }, content);\n },\n\n renderItemsControl(element: ItemsControl, items: ReactNode[]): ReactNode {\n return createElement('div', { key: element.id, 'data-scene-id': element.id, 'data-scene-kind': 'ItemsControl' }, items);\n },\n\n renderExternalComponent(element: ExternalComponent, slots: Record<string, ReactNode[]>): ReactNode {\n const Component = registry[element.componentName] ?? UnresolvedComponent;\n return createElement(Component, { key: element.id, element, slots });\n },\n\n renderPanel(element: Panel, children: ReactNode[]): ReactNode {\n // The rendered children arrive in the same order as `element.children`, so a panel that places\n // its children individually - a grid cell, a docked edge - can wrap each one in the box that\n // places it without needing to re-render anything.\n const placed = children.map((child, index) => {\n const style = childStyle(element, element.children[index], index);\n return style\n ? createElement('div', { key: `${element.id}-${index}`, 'data-scene-placement': index, style }, child)\n : child;\n });\n\n return createElement(\n 'div',\n { key: element.id, 'data-scene-id': element.id, 'data-scene-kind': 'Panel', style: panelStyle(element) },\n placed);\n },\n };\n}\n"],"names":["createReactRenderer","registry","renderContentControl","element","content","createElement","key","id","renderItemsControl","items","renderExternalComponent","slots","Component","componentName","UnresolvedComponent","renderPanel","children","placed","map","child","index","style","childStyle","panelStyle"],"mappings":";;;;AAAA;AACA;AASA;;;;;;;IAQO,SAASA,mBAAAA,CAAoBC,QAA2B,EAAA;IAC3D,OAAO;QACHC,oBAAAA,CAAAA,CAAqBC,OAAuB,EAAEC,OAAkB,EAAA;AAC5D,YAAA,qBAAOC,cAAc,KAAA,EAAO;AAAEC,gBAAAA,GAAAA,EAAKH,QAAQI,EAAE;AAAE,gBAAA,eAAA,EAAiBJ,QAAQI,EAAE;gBAAE,iBAAA,EAAmB;aAAiB,EAAGH,OAAAA,CAAAA;AACvH,QAAA,CAAA;QAEAI,kBAAAA,CAAAA,CAAmBL,OAAqB,EAAEM,KAAkB,EAAA;AACxD,YAAA,qBAAOJ,cAAc,KAAA,EAAO;AAAEC,gBAAAA,GAAAA,EAAKH,QAAQI,EAAE;AAAE,gBAAA,eAAA,EAAiBJ,QAAQI,EAAE;gBAAE,iBAAA,EAAmB;aAAe,EAAGE,KAAAA,CAAAA;AACrH,QAAA,CAAA;QAEAC,uBAAAA,CAAAA,CAAwBP,OAA0B,EAAEQ,KAAkC,EAAA;AAClF,YAAA,MAAMC,YAAYX,QAAQ,CAACE,OAAAA,CAAQU,aAAa,CAAC,IAAIC,mBAAAA;AACrD,YAAA,qBAAOT,cAAcO,SAAAA,EAAW;AAAEN,gBAAAA,GAAAA,EAAKH,QAAQI,EAAE;AAAEJ,gBAAAA,OAAAA;AAASQ,gBAAAA;AAAM,aAAA,CAAA;AACtE,QAAA,CAAA;QAEAI,WAAAA,CAAAA,CAAYZ,OAAc,EAAEa,QAAqB,EAAA;;;;AAI7C,YAAA,MAAMC,MAAAA,GAASD,QAAAA,CAASE,GAAG,CAAC,CAACC,KAAAA,EAAOC,KAAAA,GAAAA;AAChC,gBAAA,MAAMC,QAAQC,UAAAA,CAAWnB,OAAAA,EAASA,QAAQa,QAAQ,CAACI,MAAM,EAAEA,KAAAA,CAAAA;gBAC3D,OAAOC,KAAAA,iBACDhB,cAAc,KAAA,EAAO;AAAEC,oBAAAA,GAAAA,EAAK,GAAGH,OAAAA,CAAQI,EAAE,CAAC,CAAC,EAAEa,KAAAA,CAAAA,CAAO;oBAAE,sBAAA,EAAwBA,KAAAA;AAAOC,oBAAAA;AAAM,iBAAA,EAAGF,KAAAA,CAAAA,GAC9FA,KAAAA;AACV,YAAA,CAAA,CAAA;AAEA,YAAA,qBAAOd,cACH,KAAA,EACA;AAAEC,gBAAAA,GAAAA,EAAKH,QAAQI,EAAE;AAAE,gBAAA,eAAA,EAAiBJ,QAAQI,EAAE;gBAAE,iBAAA,EAAmB,OAAA;AAASc,gBAAAA,KAAAA,EAAOE,UAAAA,CAAWpB,OAAAA;aAAS,EACvGc,MAAAA,CAAAA;AACR,QAAA;AACJ,KAAA;AACJ;;;;"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
import type { Panel, SceneElement } from '@cratis/scene.model';
|
|
3
|
+
export declare function panelStyle(panel: Panel): CSSProperties | undefined;
|
|
4
|
+
export declare function childStyle(panel: Panel, child: SceneElement, index: number): CSSProperties | undefined;
|
|
5
|
+
//# sourceMappingURL=panelLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panelLayout.d.ts","sourceRoot":"","sources":["../../../renderer/panelLayout.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAG3C,OAAO,KAAK,EAAgC,KAAK,EAAiB,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAkC5G,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,aAAa,GAAG,SAAS,CAiClE;AAGD,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAgCtG"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { isGrid, isDockPanel, isWrapPanel, isStackPanel } from '@cratis/scene.engine';
|
|
2
|
+
import { Dock, Orientation, GridUnitType } from '@cratis/scene.model';
|
|
3
|
+
|
|
4
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
5
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
6
|
+
/**
|
|
7
|
+
* Turns a panel into the CSS that arranges it, and a child into the CSS that places it inside one.
|
|
8
|
+
*
|
|
9
|
+
* The panels say what an arrangement *is*; this says what it looks like on the web. A second renderer -
|
|
10
|
+
* native, desktop - answers the same question with its own platform's layout primitives, which is why
|
|
11
|
+
* this lives in `Scene.React` rather than in the model or the engine.
|
|
12
|
+
*
|
|
13
|
+
* Where a panel needs to say something about an individual child - which grid cell, which edge - it does
|
|
14
|
+
* so through that child's own `properties` bag, because the model has no attached properties of its own.
|
|
15
|
+
* The keys are the ones the model's documentation names: `Grid.Row`, `Grid.Column`, `Grid.RowSpan`,
|
|
16
|
+
* `Grid.ColumnSpan` and `Dock`.
|
|
17
|
+
*/ const track = (length)=>{
|
|
18
|
+
switch(length.unitType){
|
|
19
|
+
case GridUnitType.Auto:
|
|
20
|
+
return 'auto';
|
|
21
|
+
case GridUnitType.Absolute:
|
|
22
|
+
return `${length.value}px`;
|
|
23
|
+
case GridUnitType.Star:
|
|
24
|
+
return `${length.value}fr`;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const bound = (minimum, maximum, size)=>minimum === 0 && !Number.isFinite(maximum) ? size : `minmax(${minimum}px, ${Number.isFinite(maximum) ? `${maximum}px` : size})`;
|
|
28
|
+
const rowTrack = (row)=>bound(row.minimumHeight, row.maximumHeight, track(row.height));
|
|
29
|
+
const columnTrack = (column)=>bound(column.minimumWidth, column.maximumWidth, track(column.width));
|
|
30
|
+
const number = (value)=>typeof value === 'number' ? value : undefined;
|
|
31
|
+
/** The CSS that arranges a panel's own children. */ function panelStyle(panel) {
|
|
32
|
+
if (isGrid(panel)) {
|
|
33
|
+
return {
|
|
34
|
+
display: 'grid',
|
|
35
|
+
gridTemplateRows: panel.rows.map(rowTrack).join(' ') || undefined,
|
|
36
|
+
gridTemplateColumns: panel.columns.map(columnTrack).join(' ') || undefined
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (isDockPanel(panel)) {
|
|
40
|
+
// Docking has no single CSS primitive. A column of rows with a middle that grows is the shape a
|
|
41
|
+
// dock panel actually produces, and `lastChildFill` decides whether the final child is that
|
|
42
|
+
// middle or just another docked edge.
|
|
43
|
+
return {
|
|
44
|
+
display: 'flex',
|
|
45
|
+
flexDirection: 'column'
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (isStackPanel(panel)) {
|
|
49
|
+
return {
|
|
50
|
+
display: 'flex',
|
|
51
|
+
flexDirection: panel.orientation === Orientation.Horizontal ? 'row' : 'column',
|
|
52
|
+
gap: panel.spacing ? `${panel.spacing}px` : undefined
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (isWrapPanel(panel)) {
|
|
56
|
+
return {
|
|
57
|
+
display: 'flex',
|
|
58
|
+
flexWrap: 'wrap',
|
|
59
|
+
flexDirection: panel.orientation === Orientation.Horizontal ? 'row' : 'column'
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
/** The CSS that places one child within its panel, or `undefined` when the panel places children implicitly. */ function childStyle(panel, child, index) {
|
|
65
|
+
const properties = child.properties ?? {};
|
|
66
|
+
if (isGrid(panel)) {
|
|
67
|
+
const row = number(properties['Grid.Row']);
|
|
68
|
+
const column = number(properties['Grid.Column']);
|
|
69
|
+
const rowSpan = number(properties['Grid.RowSpan']) ?? 1;
|
|
70
|
+
const columnSpan = number(properties['Grid.ColumnSpan']) ?? 1;
|
|
71
|
+
if (row === undefined && column === undefined) return undefined;
|
|
72
|
+
return {
|
|
73
|
+
gridRow: row === undefined ? undefined : `${row + 1} / span ${rowSpan}`,
|
|
74
|
+
gridColumn: column === undefined ? undefined : `${column + 1} / span ${columnSpan}`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (isDockPanel(panel)) {
|
|
78
|
+
const dock = properties['Dock'];
|
|
79
|
+
const isFill = panel.lastChildFill && index === (panel.children?.length ?? 0) - 1;
|
|
80
|
+
if (isFill) return {
|
|
81
|
+
flex: '1 1 auto',
|
|
82
|
+
minHeight: 0
|
|
83
|
+
};
|
|
84
|
+
// Left and right dock along the cross axis of the column the panel lays out, so they become a row
|
|
85
|
+
// of their own rather than a sibling in the column.
|
|
86
|
+
return dock === Dock.Left || dock === Dock.Right ? {
|
|
87
|
+
alignSelf: dock === Dock.Left ? 'flex-start' : 'flex-end',
|
|
88
|
+
flex: '0 0 auto'
|
|
89
|
+
} : {
|
|
90
|
+
flex: '0 0 auto'
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (isWrapPanel(panel)) {
|
|
94
|
+
if (panel.itemWidth === undefined && panel.itemHeight === undefined) return undefined;
|
|
95
|
+
return {
|
|
96
|
+
width: panel.itemWidth,
|
|
97
|
+
height: panel.itemHeight,
|
|
98
|
+
flex: '0 0 auto'
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { childStyle, panelStyle };
|
|
105
|
+
//# sourceMappingURL=panelLayout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"panelLayout.js","sources":["../../../renderer/panelLayout.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport type { CSSProperties } from 'react';\nimport { isDockPanel, isGrid, isStackPanel, isWrapPanel } from '@cratis/scene.engine';\nimport { Dock, GridUnitType, Orientation } from '@cratis/scene.model';\nimport type { ColumnDefinition, GridLength, Panel, RowDefinition, SceneElement } from '@cratis/scene.model';\n\n/**\n * Turns a panel into the CSS that arranges it, and a child into the CSS that places it inside one.\n *\n * The panels say what an arrangement *is*; this says what it looks like on the web. A second renderer -\n * native, desktop - answers the same question with its own platform's layout primitives, which is why\n * this lives in `Scene.React` rather than in the model or the engine.\n *\n * Where a panel needs to say something about an individual child - which grid cell, which edge - it does\n * so through that child's own `properties` bag, because the model has no attached properties of its own.\n * The keys are the ones the model's documentation names: `Grid.Row`, `Grid.Column`, `Grid.RowSpan`,\n * `Grid.ColumnSpan` and `Dock`.\n */\n\nconst track = (length: GridLength): string => {\n switch (length.unitType) {\n case GridUnitType.Auto: return 'auto';\n case GridUnitType.Absolute: return `${length.value}px`;\n case GridUnitType.Star: return `${length.value}fr`;\n }\n};\n\nconst bound = (minimum: number, maximum: number, size: string): string =>\n minimum === 0 && !Number.isFinite(maximum)\n ? size\n : `minmax(${minimum}px, ${Number.isFinite(maximum) ? `${maximum}px` : size})`;\n\nconst rowTrack = (row: RowDefinition): string => bound(row.minimumHeight, row.maximumHeight, track(row.height));\nconst columnTrack = (column: ColumnDefinition): string => bound(column.minimumWidth, column.maximumWidth, track(column.width));\n\nconst number = (value: unknown): number | undefined => (typeof value === 'number' ? value : undefined);\n\n/** The CSS that arranges a panel's own children. */\nexport function panelStyle(panel: Panel): CSSProperties | undefined {\n if (isGrid(panel)) {\n return {\n display: 'grid',\n gridTemplateRows: panel.rows.map(rowTrack).join(' ') || undefined,\n gridTemplateColumns: panel.columns.map(columnTrack).join(' ') || undefined,\n };\n }\n\n if (isDockPanel(panel)) {\n // Docking has no single CSS primitive. A column of rows with a middle that grows is the shape a\n // dock panel actually produces, and `lastChildFill` decides whether the final child is that\n // middle or just another docked edge.\n return { display: 'flex', flexDirection: 'column' };\n }\n\n if (isStackPanel(panel)) {\n return {\n display: 'flex',\n flexDirection: panel.orientation === Orientation.Horizontal ? 'row' : 'column',\n gap: panel.spacing ? `${panel.spacing}px` : undefined,\n };\n }\n\n if (isWrapPanel(panel)) {\n return {\n display: 'flex',\n flexWrap: 'wrap',\n flexDirection: panel.orientation === Orientation.Horizontal ? 'row' : 'column',\n };\n }\n\n return undefined;\n}\n\n/** The CSS that places one child within its panel, or `undefined` when the panel places children implicitly. */\nexport function childStyle(panel: Panel, child: SceneElement, index: number): CSSProperties | undefined {\n const properties = child.properties ?? {};\n\n if (isGrid(panel)) {\n const row = number(properties['Grid.Row']);\n const column = number(properties['Grid.Column']);\n const rowSpan = number(properties['Grid.RowSpan']) ?? 1;\n const columnSpan = number(properties['Grid.ColumnSpan']) ?? 1;\n if (row === undefined && column === undefined) return undefined;\n return {\n gridRow: row === undefined ? undefined : `${row + 1} / span ${rowSpan}`,\n gridColumn: column === undefined ? undefined : `${column + 1} / span ${columnSpan}`,\n };\n }\n\n if (isDockPanel(panel)) {\n const dock = properties['Dock'] as Dock | undefined;\n const isFill = panel.lastChildFill && index === (panel.children?.length ?? 0) - 1;\n if (isFill) return { flex: '1 1 auto', minHeight: 0 };\n // Left and right dock along the cross axis of the column the panel lays out, so they become a row\n // of their own rather than a sibling in the column.\n return dock === Dock.Left || dock === Dock.Right\n ? { alignSelf: dock === Dock.Left ? 'flex-start' : 'flex-end', flex: '0 0 auto' }\n : { flex: '0 0 auto' };\n }\n\n if (isWrapPanel(panel)) {\n if (panel.itemWidth === undefined && panel.itemHeight === undefined) return undefined;\n return { width: panel.itemWidth, height: panel.itemHeight, flex: '0 0 auto' };\n }\n\n return undefined;\n}\n"],"names":["track","length","unitType","GridUnitType","Auto","Absolute","value","Star","bound","minimum","maximum","size","Number","isFinite","rowTrack","row","minimumHeight","maximumHeight","height","columnTrack","column","minimumWidth","maximumWidth","width","number","undefined","panelStyle","panel","isGrid","display","gridTemplateRows","rows","map","join","gridTemplateColumns","columns","isDockPanel","flexDirection","isStackPanel","orientation","Orientation","Horizontal","gap","spacing","isWrapPanel","flexWrap","childStyle","child","index","properties","rowSpan","columnSpan","gridRow","gridColumn","dock","isFill","lastChildFill","children","flex","minHeight","Dock","Left","Right","alignSelf","itemWidth","itemHeight"],"mappings":"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"}
|