panelmark-html 0.1.0__tar.gz
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.
- panelmark_html-0.1.0/LICENSE +21 -0
- panelmark_html-0.1.0/PKG-INFO +158 -0
- panelmark_html-0.1.0/README.md +113 -0
- panelmark_html-0.1.0/panelmark_html/__init__.py +55 -0
- panelmark_html-0.1.0/panelmark_html/css.py +135 -0
- panelmark_html-0.1.0/panelmark_html/renderer.py +166 -0
- panelmark_html-0.1.0/panelmark_html.egg-info/PKG-INFO +158 -0
- panelmark_html-0.1.0/panelmark_html.egg-info/SOURCES.txt +15 -0
- panelmark_html-0.1.0/panelmark_html.egg-info/dependency_links.txt +1 -0
- panelmark_html-0.1.0/panelmark_html.egg-info/requires.txt +5 -0
- panelmark_html-0.1.0/panelmark_html.egg-info/top_level.txt +1 -0
- panelmark_html-0.1.0/pyproject.toml +37 -0
- panelmark_html-0.1.0/setup.cfg +4 -0
- panelmark_html-0.1.0/tests/test_css.py +96 -0
- panelmark_html-0.1.0/tests/test_interaction_hooks.py +200 -0
- panelmark_html-0.1.0/tests/test_renderer.py +191 -0
- panelmark_html-0.1.0/tests/test_snapshots.py +298 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Timothy Morris PhD
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: panelmark-html
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Static HTML/CSS renderer for panelmark shells.
|
|
5
|
+
Author-email: Timothy Morris PhD <sirrommit@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2024 Timothy Morris PhD
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Repository, https://github.com/sirrommit/panelmark-html
|
|
29
|
+
Keywords: ui,layout,html,renderer,panelmark
|
|
30
|
+
Classifier: Development Status :: 3 - Alpha
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Requires-Python: >=3.10
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Requires-Dist: panelmark>=0.1.0
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: pytest; extra == "dev"
|
|
44
|
+
Requires-Dist: panelmark; extra == "dev"
|
|
45
|
+
|
|
46
|
+
# panelmark-html
|
|
47
|
+
|
|
48
|
+
**panelmark-html** is the static HTML/CSS renderer for the
|
|
49
|
+
[panelmark](https://github.com/sirrommit/panelmark) ecosystem. Given a
|
|
50
|
+
`panelmark` shell and its assigned interactions, it produces an HTML document
|
|
51
|
+
or fragment representing the shell's panel structure.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## What this package is
|
|
56
|
+
|
|
57
|
+
- A static renderer: layout model in, HTML string out.
|
|
58
|
+
- Useful for server-side rendering into Flask/Django templates, generating
|
|
59
|
+
reports or dashboards, and automated snapshot tests.
|
|
60
|
+
- The structural foundation that `panelmark-web` will build live interactivity
|
|
61
|
+
on top of.
|
|
62
|
+
|
|
63
|
+
## What this package is not
|
|
64
|
+
|
|
65
|
+
- It does not handle browser events, keyboard or mouse input, or focus
|
|
66
|
+
transitions.
|
|
67
|
+
- It does not open sockets, HTTP routes, or sessions.
|
|
68
|
+
- It does not render interaction-internal state (item lists, form fields,
|
|
69
|
+
text content). Panel bodies are empty placeholders with stable DOM hooks;
|
|
70
|
+
filling them in with live content is the job of `panelmark-web`.
|
|
71
|
+
- It does not require JavaScript for its output to be valid HTML.
|
|
72
|
+
|
|
73
|
+
## Relationship to panelmark-web
|
|
74
|
+
|
|
75
|
+
`panelmark-html` and `panelmark-web` are separate packages with distinct scopes.
|
|
76
|
+
|
|
77
|
+
| Package | Role |
|
|
78
|
+
|---------|------|
|
|
79
|
+
| **panelmark-html** | Static structure: panel layout, borders, headings, stable DOM hooks |
|
|
80
|
+
| **panelmark-web** | Live layer: WebSocket sessions, browser events, interaction rendering, draw-command updates |
|
|
81
|
+
|
|
82
|
+
`panelmark-web` depends on `panelmark-html` for its rendered structure. The
|
|
83
|
+
DOM hooks and CSS classes defined here are the stable contract between the two
|
|
84
|
+
packages.
|
|
85
|
+
|
|
86
|
+
## Installation
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
pip install panelmark-html
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Dependencies
|
|
93
|
+
|
|
94
|
+
- `panelmark` — core layout model and shell state machine
|
|
95
|
+
- No web framework dependency
|
|
96
|
+
- No JavaScript build step
|
|
97
|
+
|
|
98
|
+
## Status
|
|
99
|
+
|
|
100
|
+
**Package maturity:** Pre-alpha. The public Python API (`render_fragment`,
|
|
101
|
+
`render_document`, `get_base_css`, `HTMLRenderer`) and higher-level rendering
|
|
102
|
+
features may still evolve.
|
|
103
|
+
|
|
104
|
+
**Hook contract:** The region-level DOM hooks (`data-pm-region`, `id`,
|
|
105
|
+
`data-pm-*` attributes) and CSS classes (`.pm-shell`, `.pm-split-*`,
|
|
106
|
+
`.pm-panel`, `.pm-panel-body`) are the intended stable substrate for
|
|
107
|
+
`panelmark-web` and are documented as such in
|
|
108
|
+
[docs/hook-contract.md](docs/hook-contract.md). These will not change
|
|
109
|
+
without a major version bump.
|
|
110
|
+
|
|
111
|
+
## Quick start
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from panelmark import Shell
|
|
115
|
+
from panelmark_html import render_fragment, get_base_css
|
|
116
|
+
|
|
117
|
+
LAYOUT = """
|
|
118
|
+
|=== <bold>My App</> ===|
|
|
119
|
+
|{$main$ }|
|
|
120
|
+
|==================|
|
|
121
|
+
|{2R $status$ }|
|
|
122
|
+
|==================|
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
shell = Shell(LAYOUT)
|
|
126
|
+
html = render_fragment(shell)
|
|
127
|
+
# Panel bodies are empty — fill them with panelmark-web or your own renderer.
|
|
128
|
+
print(html)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
For a complete HTML document including base CSS:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
document = render_document(shell)
|
|
135
|
+
# Returns a full <html>...<body>...</body></html> string with embedded CSS.
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## API
|
|
139
|
+
|
|
140
|
+
| Symbol | Description |
|
|
141
|
+
|--------|-------------|
|
|
142
|
+
| `render_fragment(shell)` | Renders the shell as an HTML fragment (no `<html>` or `<head>` wrapper). Returns a string. |
|
|
143
|
+
| `render_document(shell)` | Renders the shell as a complete HTML document, including the base CSS inline. Returns a string. |
|
|
144
|
+
| `get_base_css()` | Returns the base CSS string for manual inclusion in your own template. |
|
|
145
|
+
| `HTMLRenderer` | Low-level renderer class. Use this for custom integration or when you need per-region control. |
|
|
146
|
+
|
|
147
|
+
All symbols are importable from `panelmark_html`. Full API reference:
|
|
148
|
+
[panelmark-html rendering API](https://github.com/sirrommit/panelmark-docs/blob/main/docs/panelmark-html/rendering-api.md)
|
|
149
|
+
|
|
150
|
+
## Documentation
|
|
151
|
+
|
|
152
|
+
| Document | Description |
|
|
153
|
+
|----------|-------------|
|
|
154
|
+
| [Hook Contract](docs/hook-contract.md) | Stable DOM interface: element structure, CSS classes, `data-pm-*` attributes, CSS custom properties. **Canonical location — this repo.** |
|
|
155
|
+
| [Rendering API Reference](https://github.com/sirrommit/panelmark-docs/blob/main/docs/panelmark-html/rendering-api.md) | Full reference for `render_fragment`, `render_document`, `get_base_css`, and `HTMLRenderer` |
|
|
156
|
+
| [Ecosystem Overview](https://github.com/sirrommit/panelmark-docs/blob/main/docs/ecosystem.md) | How panelmark-html fits into the panelmark package ecosystem |
|
|
157
|
+
| [Shell Language](https://github.com/sirrommit/panelmark-docs/blob/main/docs/shell-language/overview.md) | ASCII-art layout syntax reference |
|
|
158
|
+
| [panelmark-web](https://github.com/sirrommit/panelmark-web) | Live web runtime that builds on this package's hook contract |
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# panelmark-html
|
|
2
|
+
|
|
3
|
+
**panelmark-html** is the static HTML/CSS renderer for the
|
|
4
|
+
[panelmark](https://github.com/sirrommit/panelmark) ecosystem. Given a
|
|
5
|
+
`panelmark` shell and its assigned interactions, it produces an HTML document
|
|
6
|
+
or fragment representing the shell's panel structure.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## What this package is
|
|
11
|
+
|
|
12
|
+
- A static renderer: layout model in, HTML string out.
|
|
13
|
+
- Useful for server-side rendering into Flask/Django templates, generating
|
|
14
|
+
reports or dashboards, and automated snapshot tests.
|
|
15
|
+
- The structural foundation that `panelmark-web` will build live interactivity
|
|
16
|
+
on top of.
|
|
17
|
+
|
|
18
|
+
## What this package is not
|
|
19
|
+
|
|
20
|
+
- It does not handle browser events, keyboard or mouse input, or focus
|
|
21
|
+
transitions.
|
|
22
|
+
- It does not open sockets, HTTP routes, or sessions.
|
|
23
|
+
- It does not render interaction-internal state (item lists, form fields,
|
|
24
|
+
text content). Panel bodies are empty placeholders with stable DOM hooks;
|
|
25
|
+
filling them in with live content is the job of `panelmark-web`.
|
|
26
|
+
- It does not require JavaScript for its output to be valid HTML.
|
|
27
|
+
|
|
28
|
+
## Relationship to panelmark-web
|
|
29
|
+
|
|
30
|
+
`panelmark-html` and `panelmark-web` are separate packages with distinct scopes.
|
|
31
|
+
|
|
32
|
+
| Package | Role |
|
|
33
|
+
|---------|------|
|
|
34
|
+
| **panelmark-html** | Static structure: panel layout, borders, headings, stable DOM hooks |
|
|
35
|
+
| **panelmark-web** | Live layer: WebSocket sessions, browser events, interaction rendering, draw-command updates |
|
|
36
|
+
|
|
37
|
+
`panelmark-web` depends on `panelmark-html` for its rendered structure. The
|
|
38
|
+
DOM hooks and CSS classes defined here are the stable contract between the two
|
|
39
|
+
packages.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
pip install panelmark-html
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Dependencies
|
|
48
|
+
|
|
49
|
+
- `panelmark` — core layout model and shell state machine
|
|
50
|
+
- No web framework dependency
|
|
51
|
+
- No JavaScript build step
|
|
52
|
+
|
|
53
|
+
## Status
|
|
54
|
+
|
|
55
|
+
**Package maturity:** Pre-alpha. The public Python API (`render_fragment`,
|
|
56
|
+
`render_document`, `get_base_css`, `HTMLRenderer`) and higher-level rendering
|
|
57
|
+
features may still evolve.
|
|
58
|
+
|
|
59
|
+
**Hook contract:** The region-level DOM hooks (`data-pm-region`, `id`,
|
|
60
|
+
`data-pm-*` attributes) and CSS classes (`.pm-shell`, `.pm-split-*`,
|
|
61
|
+
`.pm-panel`, `.pm-panel-body`) are the intended stable substrate for
|
|
62
|
+
`panelmark-web` and are documented as such in
|
|
63
|
+
[docs/hook-contract.md](docs/hook-contract.md). These will not change
|
|
64
|
+
without a major version bump.
|
|
65
|
+
|
|
66
|
+
## Quick start
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from panelmark import Shell
|
|
70
|
+
from panelmark_html import render_fragment, get_base_css
|
|
71
|
+
|
|
72
|
+
LAYOUT = """
|
|
73
|
+
|=== <bold>My App</> ===|
|
|
74
|
+
|{$main$ }|
|
|
75
|
+
|==================|
|
|
76
|
+
|{2R $status$ }|
|
|
77
|
+
|==================|
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
shell = Shell(LAYOUT)
|
|
81
|
+
html = render_fragment(shell)
|
|
82
|
+
# Panel bodies are empty — fill them with panelmark-web or your own renderer.
|
|
83
|
+
print(html)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For a complete HTML document including base CSS:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
document = render_document(shell)
|
|
90
|
+
# Returns a full <html>...<body>...</body></html> string with embedded CSS.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API
|
|
94
|
+
|
|
95
|
+
| Symbol | Description |
|
|
96
|
+
|--------|-------------|
|
|
97
|
+
| `render_fragment(shell)` | Renders the shell as an HTML fragment (no `<html>` or `<head>` wrapper). Returns a string. |
|
|
98
|
+
| `render_document(shell)` | Renders the shell as a complete HTML document, including the base CSS inline. Returns a string. |
|
|
99
|
+
| `get_base_css()` | Returns the base CSS string for manual inclusion in your own template. |
|
|
100
|
+
| `HTMLRenderer` | Low-level renderer class. Use this for custom integration or when you need per-region control. |
|
|
101
|
+
|
|
102
|
+
All symbols are importable from `panelmark_html`. Full API reference:
|
|
103
|
+
[panelmark-html rendering API](https://github.com/sirrommit/panelmark-docs/blob/main/docs/panelmark-html/rendering-api.md)
|
|
104
|
+
|
|
105
|
+
## Documentation
|
|
106
|
+
|
|
107
|
+
| Document | Description |
|
|
108
|
+
|----------|-------------|
|
|
109
|
+
| [Hook Contract](docs/hook-contract.md) | Stable DOM interface: element structure, CSS classes, `data-pm-*` attributes, CSS custom properties. **Canonical location — this repo.** |
|
|
110
|
+
| [Rendering API Reference](https://github.com/sirrommit/panelmark-docs/blob/main/docs/panelmark-html/rendering-api.md) | Full reference for `render_fragment`, `render_document`, `get_base_css`, and `HTMLRenderer` |
|
|
111
|
+
| [Ecosystem Overview](https://github.com/sirrommit/panelmark-docs/blob/main/docs/ecosystem.md) | How panelmark-html fits into the panelmark package ecosystem |
|
|
112
|
+
| [Shell Language](https://github.com/sirrommit/panelmark-docs/blob/main/docs/shell-language/overview.md) | ASCII-art layout syntax reference |
|
|
113
|
+
| [panelmark-web](https://github.com/sirrommit/panelmark-web) | Live web runtime that builds on this package's hook contract |
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""panelmark-html — static HTML/CSS renderer for panelmark shells.
|
|
2
|
+
|
|
3
|
+
This package converts a panelmark Shell into an HTML document or fragment.
|
|
4
|
+
It does not handle browser events, network transport, or live interaction
|
|
5
|
+
state. For a live web application, use panelmark-web, which builds on top
|
|
6
|
+
of this package.
|
|
7
|
+
|
|
8
|
+
Public API
|
|
9
|
+
----------
|
|
10
|
+
render_fragment(shell, *, include_css=False) -> str
|
|
11
|
+
render_document(shell, *, title="panelmark", css_href=None, extra_head="") -> str
|
|
12
|
+
get_base_css() -> str
|
|
13
|
+
HTMLRenderer
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .renderer import HTMLRenderer
|
|
17
|
+
from .css import get_base_css
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def render_fragment(shell, *, include_css: bool = False) -> str:
|
|
21
|
+
"""Return an HTML fragment for *shell*.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
shell:
|
|
26
|
+
A ``panelmark.Shell`` instance.
|
|
27
|
+
include_css:
|
|
28
|
+
If True, prepend a ``<style>`` block with the base CSS.
|
|
29
|
+
"""
|
|
30
|
+
return HTMLRenderer().render_fragment(shell, include_css=include_css)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def render_document(shell, *, title: str = 'panelmark',
|
|
34
|
+
css_href: str | None = None,
|
|
35
|
+
extra_head: str = '') -> str:
|
|
36
|
+
"""Return a full HTML document for *shell*.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
shell:
|
|
41
|
+
A ``panelmark.Shell`` instance.
|
|
42
|
+
title:
|
|
43
|
+
Value for the ``<title>`` element.
|
|
44
|
+
css_href:
|
|
45
|
+
If provided, emit a ``<link>`` to this stylesheet instead of
|
|
46
|
+
inlining the base CSS.
|
|
47
|
+
extra_head:
|
|
48
|
+
Optional raw HTML string appended inside ``<head>``.
|
|
49
|
+
"""
|
|
50
|
+
return HTMLRenderer().render_document(
|
|
51
|
+
shell, title=title, css_href=css_href, extra_head=extra_head
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
__all__ = ['HTMLRenderer', 'render_fragment', 'render_document', 'get_base_css']
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
_BASE_CSS = """\
|
|
2
|
+
/* panelmark-html base styles
|
|
3
|
+
*
|
|
4
|
+
* All visual values are controlled by CSS custom properties.
|
|
5
|
+
* Override any variable in your own stylesheet to theme the shell.
|
|
6
|
+
*
|
|
7
|
+
* Custom properties
|
|
8
|
+
* -----------------
|
|
9
|
+
* --pm-border-color border / divider colour
|
|
10
|
+
* --pm-border-width thickness of all borders and dividers
|
|
11
|
+
* --pm-gap gap between sibling panels (not used by default;
|
|
12
|
+
* override to add spacing between panels)
|
|
13
|
+
* --pm-radius corner radius on panels
|
|
14
|
+
* --pm-heading-font-weight heading font weight
|
|
15
|
+
* --pm-panel-padding padding inside panel body and heading
|
|
16
|
+
* --pm-focused-border-color border colour on the focused panel
|
|
17
|
+
* --pm-focused-border-width border thickness on the focused panel
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
:root {
|
|
21
|
+
--pm-border-color: #888;
|
|
22
|
+
--pm-border-width: 1px;
|
|
23
|
+
--pm-gap: 0px;
|
|
24
|
+
--pm-radius: 0px;
|
|
25
|
+
--pm-heading-font-weight: bold;
|
|
26
|
+
--pm-panel-padding: 0.5rem;
|
|
27
|
+
--pm-focused-border-color: #4a9eff;
|
|
28
|
+
--pm-focused-border-width: 2px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Shell container --------------------------------------------------------- */
|
|
32
|
+
|
|
33
|
+
.pm-shell {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
box-sizing: border-box;
|
|
37
|
+
width: 100%;
|
|
38
|
+
height: 100%;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
font-family: monospace, monospace;
|
|
41
|
+
font-size: 0.875rem;
|
|
42
|
+
border: var(--pm-border-width) solid var(--pm-border-color);
|
|
43
|
+
border-radius: var(--pm-radius);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Splits ------------------------------------------------------------------ */
|
|
47
|
+
|
|
48
|
+
.pm-split {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex: 1 1 0;
|
|
51
|
+
min-height: 0;
|
|
52
|
+
min-width: 0;
|
|
53
|
+
gap: var(--pm-gap);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.pm-split-h {
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.pm-split-v {
|
|
61
|
+
flex-direction: row;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Single-pixel dividers between sibling panels, without double borders */
|
|
65
|
+
.pm-split-h > * + * {
|
|
66
|
+
border-top: var(--pm-border-width) solid var(--pm-border-color);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.pm-split-v > * + * {
|
|
70
|
+
border-left: var(--pm-border-width) solid var(--pm-border-color);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Panels ------------------------------------------------------------------ */
|
|
74
|
+
|
|
75
|
+
.pm-panel[data-pm-focused="true"] {
|
|
76
|
+
outline: var(--pm-focused-border-width) solid var(--pm-focused-border-color);
|
|
77
|
+
outline-offset: -1px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.pm-panel {
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-direction: column;
|
|
83
|
+
flex: 1 1 0;
|
|
84
|
+
min-height: 0;
|
|
85
|
+
min-width: 0;
|
|
86
|
+
box-sizing: border-box;
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.pm-panel-heading {
|
|
91
|
+
flex-shrink: 0;
|
|
92
|
+
box-sizing: border-box;
|
|
93
|
+
padding: 0.25rem var(--pm-panel-padding);
|
|
94
|
+
font-weight: var(--pm-heading-font-weight);
|
|
95
|
+
border-bottom: var(--pm-border-width) solid var(--pm-border-color);
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
text-overflow: ellipsis;
|
|
98
|
+
white-space: nowrap;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.pm-panel-body {
|
|
102
|
+
flex: 1 1 0;
|
|
103
|
+
min-height: 0;
|
|
104
|
+
box-sizing: border-box;
|
|
105
|
+
padding: var(--pm-panel-padding);
|
|
106
|
+
overflow: auto;
|
|
107
|
+
}
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_base_css() -> str:
|
|
112
|
+
"""Return the base CSS string for panelmark-html layouts.
|
|
113
|
+
|
|
114
|
+
The returned string uses CSS custom properties (variables) for all
|
|
115
|
+
visual values so that embedding applications can theme the shell
|
|
116
|
+
without modifying this stylesheet.
|
|
117
|
+
|
|
118
|
+
The focused-panel highlight is controlled by
|
|
119
|
+
``--pm-focused-border-color`` and ``--pm-focused-border-width``.
|
|
120
|
+
These are used by the ``[data-pm-focused="true"]`` rule and are
|
|
121
|
+
updated live by ``panelmark-web`` after each key event.
|
|
122
|
+
|
|
123
|
+
Suggested usage::
|
|
124
|
+
|
|
125
|
+
# Inline in a document
|
|
126
|
+
page = render_document(shell, title="My App")
|
|
127
|
+
|
|
128
|
+
# Link an external copy
|
|
129
|
+
page = render_document(shell, css_href="/static/panelmark.css")
|
|
130
|
+
# then write get_base_css() to that path at build time
|
|
131
|
+
|
|
132
|
+
# Embed in a fragment
|
|
133
|
+
fragment = render_fragment(shell, include_css=True)
|
|
134
|
+
"""
|
|
135
|
+
return _BASE_CSS
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
from html import escape
|
|
2
|
+
|
|
3
|
+
from panelmark.layout import HSplit, VSplit, Panel
|
|
4
|
+
|
|
5
|
+
from .css import get_base_css
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HTMLRenderer:
|
|
9
|
+
"""Converts a panelmark Shell into an HTML fragment or full document.
|
|
10
|
+
|
|
11
|
+
Renders the shell's panel structure with stable DOM hooks. Each named
|
|
12
|
+
panel that has an assigned interaction receives ``data-pm-interaction``,
|
|
13
|
+
``data-pm-focusable``, and ``data-pm-focused`` attributes. Named panels
|
|
14
|
+
without an assigned interaction receive ``data-pm-empty="true"``. Panel
|
|
15
|
+
bodies are always empty placeholders; interaction body content is deferred
|
|
16
|
+
to ``panelmark-web``.
|
|
17
|
+
|
|
18
|
+
CSS rules are provided by ``get_base_css()``.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def render_fragment(self, shell, *, include_css: bool = False) -> str:
|
|
22
|
+
"""Return an HTML fragment for *shell*.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
shell:
|
|
27
|
+
A ``panelmark.Shell`` instance.
|
|
28
|
+
include_css:
|
|
29
|
+
If True, prepend a ``<style>`` block with the base CSS.
|
|
30
|
+
"""
|
|
31
|
+
parts = []
|
|
32
|
+
if include_css:
|
|
33
|
+
css = get_base_css()
|
|
34
|
+
if css:
|
|
35
|
+
parts.append(f'<style>\n{css}\n</style>\n')
|
|
36
|
+
parts.append(self._render_shell(shell))
|
|
37
|
+
return ''.join(parts)
|
|
38
|
+
|
|
39
|
+
def render_document(self, shell, *, title: str = 'panelmark',
|
|
40
|
+
css_href: str | None = None,
|
|
41
|
+
extra_head: str = '') -> str:
|
|
42
|
+
"""Return a full HTML document for *shell*.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
shell:
|
|
47
|
+
A ``panelmark.Shell`` instance.
|
|
48
|
+
title:
|
|
49
|
+
Value for the ``<title>`` element.
|
|
50
|
+
css_href:
|
|
51
|
+
If provided, emit a ``<link>`` to this stylesheet instead of
|
|
52
|
+
inlining the base CSS.
|
|
53
|
+
extra_head:
|
|
54
|
+
Optional raw HTML string appended inside ``<head>`` before
|
|
55
|
+
``</head>``.
|
|
56
|
+
"""
|
|
57
|
+
head_lines = [
|
|
58
|
+
' <meta charset="UTF-8">',
|
|
59
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1.0">',
|
|
60
|
+
f' <title>{escape(title)}</title>',
|
|
61
|
+
]
|
|
62
|
+
if css_href:
|
|
63
|
+
head_lines.append(
|
|
64
|
+
f' <link rel="stylesheet" href="{escape(css_href, quote=True)}">'
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
css = get_base_css()
|
|
68
|
+
if css:
|
|
69
|
+
head_lines.append(f' <style>\n{css}\n </style>')
|
|
70
|
+
if extra_head:
|
|
71
|
+
head_lines.append(f' {extra_head}')
|
|
72
|
+
|
|
73
|
+
head = '\n'.join(head_lines)
|
|
74
|
+
fragment = self._render_shell(shell)
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
'<!DOCTYPE html>\n'
|
|
78
|
+
'<html lang="en">\n'
|
|
79
|
+
'<head>\n'
|
|
80
|
+
f'{head}\n'
|
|
81
|
+
'</head>\n'
|
|
82
|
+
'<body>\n'
|
|
83
|
+
f'{fragment}'
|
|
84
|
+
'</body>\n'
|
|
85
|
+
'</html>\n'
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# ------------------------------------------------------------------
|
|
89
|
+
# Internal rendering
|
|
90
|
+
# ------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
def _render_shell(self, shell) -> str:
|
|
93
|
+
node = shell.layout.root
|
|
94
|
+
if node is None:
|
|
95
|
+
return '<div class="pm-shell" data-pm-shell></div>\n'
|
|
96
|
+
inner = self._render_node(node, shell, indent=2)
|
|
97
|
+
return f'<div class="pm-shell" data-pm-shell>\n{inner}</div>\n'
|
|
98
|
+
|
|
99
|
+
def _render_node(self, node, shell, indent: int = 0) -> str:
|
|
100
|
+
if node is None:
|
|
101
|
+
return ''
|
|
102
|
+
pad = ' ' * indent
|
|
103
|
+
|
|
104
|
+
if isinstance(node, Panel):
|
|
105
|
+
return self._render_panel(node, shell, indent)
|
|
106
|
+
|
|
107
|
+
if isinstance(node, VSplit):
|
|
108
|
+
left = self._render_node(node.left, shell, indent + 2)
|
|
109
|
+
right = self._render_node(node.right, shell, indent + 2)
|
|
110
|
+
return (
|
|
111
|
+
f'{pad}<div class="pm-split pm-split-v">\n'
|
|
112
|
+
f'{left}'
|
|
113
|
+
f'{right}'
|
|
114
|
+
f'{pad}</div>\n'
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if isinstance(node, HSplit):
|
|
118
|
+
top = self._render_node(node.top, shell, indent + 2)
|
|
119
|
+
bottom = self._render_node(node.bottom, shell, indent + 2)
|
|
120
|
+
return (
|
|
121
|
+
f'{pad}<div class="pm-split pm-split-h">\n'
|
|
122
|
+
f'{top}'
|
|
123
|
+
f'{bottom}'
|
|
124
|
+
f'{pad}</div>\n'
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return ''
|
|
128
|
+
|
|
129
|
+
def _render_panel(self, node: Panel, shell, indent: int = 0) -> str:
|
|
130
|
+
pad = ' ' * indent
|
|
131
|
+
inner = ' ' * (indent + 2)
|
|
132
|
+
|
|
133
|
+
attrs = ['class="pm-panel"']
|
|
134
|
+
if node.name:
|
|
135
|
+
attrs.append(f'data-pm-region="{escape(node.name, quote=True)}"')
|
|
136
|
+
attrs.append('data-pm-kind="panel"')
|
|
137
|
+
attrs.append(f'id="pm-region-{escape(node.name, quote=True)}"')
|
|
138
|
+
|
|
139
|
+
if node.heading:
|
|
140
|
+
attrs.append(f'data-pm-heading="{escape(node.heading, quote=True)}"')
|
|
141
|
+
|
|
142
|
+
interaction = shell.interactions.get(node.name)
|
|
143
|
+
if interaction is not None:
|
|
144
|
+
cls = type(interaction)
|
|
145
|
+
qualified = f'{cls.__module__}.{cls.__qualname__}'
|
|
146
|
+
focusable = 'true' if interaction.is_focusable else 'false'
|
|
147
|
+
focused = 'true' if shell.focus == node.name else 'false'
|
|
148
|
+
attrs.append(f'data-pm-interaction="{escape(qualified, quote=True)}"')
|
|
149
|
+
attrs.append(f'data-pm-focusable="{focusable}"')
|
|
150
|
+
attrs.append(f'data-pm-focused="{focused}"')
|
|
151
|
+
else:
|
|
152
|
+
attrs.append('data-pm-empty="true"')
|
|
153
|
+
|
|
154
|
+
lines = [f'{pad}<section {" ".join(attrs)}>\n']
|
|
155
|
+
|
|
156
|
+
if node.heading:
|
|
157
|
+
lines.append(
|
|
158
|
+
f'{inner}<header class="pm-panel-heading">'
|
|
159
|
+
f'{escape(node.heading)}'
|
|
160
|
+
f'</header>\n'
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
lines.append(f'{inner}<div class="pm-panel-body"></div>\n')
|
|
164
|
+
lines.append(f'{pad}</section>\n')
|
|
165
|
+
|
|
166
|
+
return ''.join(lines)
|