@swiftbrowser/web 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.
Files changed (84) hide show
  1. package/README.md +600 -0
  2. package/app/index.html +125 -0
  3. package/index.html +56 -0
  4. package/node/dist/node/apps.d.ts +58 -0
  5. package/node/dist/node/apps.d.ts.map +1 -0
  6. package/node/dist/node/apps.js +60 -0
  7. package/node/dist/node/apps.js.map +1 -0
  8. package/node/dist/node/config.d.ts +42 -0
  9. package/node/dist/node/config.d.ts.map +1 -0
  10. package/node/dist/node/config.js +99 -0
  11. package/node/dist/node/config.js.map +1 -0
  12. package/node/dist/node/examples.d.ts +39 -0
  13. package/node/dist/node/examples.d.ts.map +1 -0
  14. package/node/dist/node/examples.js +116 -0
  15. package/node/dist/node/examples.js.map +1 -0
  16. package/node/dist/node/index.d.ts +47 -0
  17. package/node/dist/node/index.d.ts.map +1 -0
  18. package/node/dist/node/index.js +52 -0
  19. package/node/dist/node/index.js.map +1 -0
  20. package/node/dist/plugins/app-manifests.d.ts +61 -0
  21. package/node/dist/plugins/app-manifests.d.ts.map +1 -0
  22. package/node/dist/plugins/app-manifests.js +168 -0
  23. package/node/dist/plugins/app-manifests.js.map +1 -0
  24. package/node/dist/plugins/asset-catalogs.d.ts +119 -0
  25. package/node/dist/plugins/asset-catalogs.d.ts.map +1 -0
  26. package/node/dist/plugins/asset-catalogs.js +488 -0
  27. package/node/dist/plugins/asset-catalogs.js.map +1 -0
  28. package/node/dist/plugins/bundle-resources.d.ts +49 -0
  29. package/node/dist/plugins/bundle-resources.d.ts.map +1 -0
  30. package/node/dist/plugins/bundle-resources.js +172 -0
  31. package/node/dist/plugins/bundle-resources.js.map +1 -0
  32. package/node/dist/plugins/swift-wasm.d.ts +33 -0
  33. package/node/dist/plugins/swift-wasm.d.ts.map +1 -0
  34. package/node/dist/plugins/swift-wasm.js +241 -0
  35. package/node/dist/plugins/swift-wasm.js.map +1 -0
  36. package/node/dist/src/assets.d.ts +60 -0
  37. package/node/dist/src/assets.d.ts.map +1 -0
  38. package/node/dist/src/assets.js +54 -0
  39. package/node/dist/src/assets.js.map +1 -0
  40. package/node/dist/src/devEvents.d.ts +31 -0
  41. package/node/dist/src/devEvents.d.ts.map +1 -0
  42. package/node/dist/src/devEvents.js +9 -0
  43. package/node/dist/src/devEvents.js.map +1 -0
  44. package/package.json +56 -0
  45. package/plugins/app-manifests.ts +192 -0
  46. package/plugins/asset-catalogs.ts +558 -0
  47. package/plugins/bundle-resources.ts +195 -0
  48. package/plugins/swift-wasm.ts +259 -0
  49. package/public/_headers +23 -0
  50. package/public/apple-touch-icon.png +0 -0
  51. package/public/icon.svg +14 -0
  52. package/public/manifest.webmanifest +14 -0
  53. package/src/animation.ts +521 -0
  54. package/src/assets.ts +86 -0
  55. package/src/chartDom.ts +481 -0
  56. package/src/devEvents.ts +35 -0
  57. package/src/deviceMode.ts +80 -0
  58. package/src/ghost.ts +25 -0
  59. package/src/interaction.ts +1113 -0
  60. package/src/landing.css +261 -0
  61. package/src/landing.ts +141 -0
  62. package/src/layout/chart.ts +477 -0
  63. package/src/layout/controls.ts +116 -0
  64. package/src/layout/engine.ts +3581 -0
  65. package/src/layout/index.ts +91 -0
  66. package/src/layout/measure.ts +86 -0
  67. package/src/layout/text.ts +124 -0
  68. package/src/layout/types.ts +153 -0
  69. package/src/layout/typography.ts +188 -0
  70. package/src/layoutDom.ts +549 -0
  71. package/src/layoutEvents.ts +110 -0
  72. package/src/locale.ts +64 -0
  73. package/src/main.ts +554 -0
  74. package/src/mock.ts +752 -0
  75. package/src/nodeQueries.ts +60 -0
  76. package/src/protocol.ts +1421 -0
  77. package/src/renderer.ts +2882 -0
  78. package/src/styles.css +2834 -0
  79. package/src/styles.ts +373 -0
  80. package/src/swipe.ts +110 -0
  81. package/src/symbols.ts +595 -0
  82. package/src/typography.ts +76 -0
  83. package/src/vite-env.d.ts +26 -0
  84. package/src/wasm.ts +400 -0
@@ -0,0 +1,261 @@
1
+ /* Landing page (index.html). Light and dark follow the OS. */
2
+
3
+ :root {
4
+ --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Inter, system-ui,
5
+ sans-serif;
6
+ --bg: #f2f2f7;
7
+ --card: #ffffff;
8
+ --fg: #1c1c1e;
9
+ --muted: #6e6e73;
10
+ --accent: #007aff;
11
+ --accent-fg: #ffffff;
12
+ --border: rgba(60, 60, 67, 0.18);
13
+ --qr-bg: #ffffff;
14
+ }
15
+
16
+ @media (prefers-color-scheme: dark) {
17
+ :root {
18
+ --bg: #000000;
19
+ --card: #1c1c1e;
20
+ --fg: #f2f2f7;
21
+ --muted: #98989f;
22
+ --accent: #0a84ff;
23
+ --border: rgba(84, 84, 88, 0.6);
24
+ --qr-bg: #ffffff;
25
+ }
26
+ }
27
+
28
+ *,
29
+ *::before,
30
+ *::after {
31
+ box-sizing: border-box;
32
+ }
33
+
34
+ html {
35
+ -webkit-text-size-adjust: 100%;
36
+ text-size-adjust: 100%;
37
+ }
38
+
39
+ body {
40
+ margin: 0;
41
+ padding: max(24px, env(safe-area-inset-top, 0px)) max(16px, env(safe-area-inset-right, 0px))
42
+ max(32px, env(safe-area-inset-bottom, 0px)) max(16px, env(safe-area-inset-left, 0px));
43
+ background: var(--bg);
44
+ color: var(--fg);
45
+ font-family: var(--font);
46
+ font-size: 17px;
47
+ line-height: 1.4;
48
+ -webkit-font-smoothing: antialiased;
49
+ }
50
+
51
+ a {
52
+ color: var(--accent);
53
+ }
54
+
55
+ code {
56
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
57
+ font-size: 0.9em;
58
+ padding: 1px 4px;
59
+ border-radius: 4px;
60
+ background: rgba(120, 120, 128, 0.16);
61
+ }
62
+
63
+ .visually-hidden {
64
+ position: absolute;
65
+ width: 1px;
66
+ height: 1px;
67
+ overflow: hidden;
68
+ clip: rect(0 0 0 0);
69
+ white-space: nowrap;
70
+ }
71
+
72
+ .hero,
73
+ main,
74
+ .build-info {
75
+ max-width: 920px;
76
+ margin: 0 auto;
77
+ }
78
+
79
+ .hero {
80
+ display: flex;
81
+ flex-direction: column;
82
+ align-items: flex-start;
83
+ gap: 8px;
84
+ padding-bottom: 24px;
85
+ }
86
+
87
+ .hero-icon {
88
+ width: 56px;
89
+ height: 56px;
90
+ border-radius: 13px;
91
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
92
+ }
93
+
94
+ .hero h1 {
95
+ margin: 8px 0 0;
96
+ font-size: 34px;
97
+ line-height: 1.15;
98
+ font-weight: 700;
99
+ letter-spacing: -0.02em;
100
+ }
101
+
102
+ .hero-lead {
103
+ margin: 0;
104
+ max-width: 60ch;
105
+ color: var(--muted);
106
+ }
107
+
108
+ /* App cards --------------------------------------------------------------- */
109
+
110
+ .app-grid {
111
+ list-style: none;
112
+ margin: 0;
113
+ padding: 0;
114
+ display: grid;
115
+ grid-template-columns: repeat(auto-fill, minmax(min(100%, 400px), 1fr));
116
+ gap: 16px;
117
+ }
118
+
119
+ .app-card {
120
+ display: flex;
121
+ gap: 16px;
122
+ padding: 16px;
123
+ border-radius: 16px;
124
+ background: var(--card);
125
+ box-shadow: 0 0 0 1px var(--border);
126
+ }
127
+
128
+ .app-qr {
129
+ flex: 0 0 auto;
130
+ width: 128px;
131
+ height: 128px;
132
+ padding: 6px;
133
+ border-radius: 10px;
134
+ background: var(--qr-bg);
135
+ box-shadow: 0 0 0 1px var(--border);
136
+ overflow: hidden;
137
+ }
138
+
139
+ .app-qr svg {
140
+ display: block;
141
+ width: 100%;
142
+ height: 100%;
143
+ }
144
+
145
+ .app-qr--failed {
146
+ font-size: 11px;
147
+ word-break: break-all;
148
+ color: #1c1c1e;
149
+ }
150
+
151
+ .app-body {
152
+ display: flex;
153
+ flex-direction: column;
154
+ min-width: 0;
155
+ flex: 1 1 auto;
156
+ }
157
+
158
+ .app-heading {
159
+ display: flex;
160
+ align-items: center;
161
+ gap: 10px;
162
+ min-width: 0;
163
+ }
164
+
165
+ .app-icon {
166
+ flex: 0 0 auto;
167
+ width: 40px;
168
+ height: 40px;
169
+ border-radius: 9px;
170
+ box-shadow: 0 0 0 1px var(--border);
171
+ object-fit: cover;
172
+ }
173
+
174
+ .app-name {
175
+ margin: 0;
176
+ font-size: 22px;
177
+ font-weight: 600;
178
+ letter-spacing: -0.01em;
179
+ }
180
+
181
+ .app-description {
182
+ margin: 2px 0 12px;
183
+ color: var(--muted);
184
+ font-size: 15px;
185
+ }
186
+
187
+ .app-open {
188
+ align-self: flex-start;
189
+ margin-top: auto;
190
+ padding: 8px 18px;
191
+ border-radius: 999px;
192
+ background: var(--accent);
193
+ color: var(--accent-fg);
194
+ font-weight: 600;
195
+ font-size: 15px;
196
+ text-decoration: none;
197
+ }
198
+
199
+ .app-open:hover {
200
+ filter: brightness(1.08);
201
+ }
202
+
203
+ .app-url {
204
+ margin-top: 8px;
205
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
206
+ font-size: 11px;
207
+ color: var(--muted);
208
+ text-decoration: none;
209
+ overflow: hidden;
210
+ text-overflow: ellipsis;
211
+ white-space: nowrap;
212
+ }
213
+
214
+ .app-empty {
215
+ color: var(--muted);
216
+ }
217
+
218
+ @media (max-width: 420px) {
219
+ .app-qr {
220
+ width: 104px;
221
+ height: 104px;
222
+ }
223
+ }
224
+
225
+ /* Phone help ------------------------------------------------------------- */
226
+
227
+ .phone-help {
228
+ margin-top: 40px;
229
+ padding: 20px 20px 20px 16px;
230
+ border-radius: 16px;
231
+ background: var(--card);
232
+ box-shadow: 0 0 0 1px var(--border);
233
+ }
234
+
235
+ .phone-help h2 {
236
+ margin: 0 0 8px;
237
+ font-size: 20px;
238
+ font-weight: 600;
239
+ }
240
+
241
+ .phone-help ol {
242
+ margin: 0;
243
+ padding-left: 24px;
244
+ }
245
+
246
+ .phone-help li + li {
247
+ margin-top: 8px;
248
+ }
249
+
250
+ /* Build info ------------------------------------------------------------- */
251
+
252
+ .build-info {
253
+ margin-top: 32px;
254
+ color: var(--muted);
255
+ font-size: 13px;
256
+ }
257
+
258
+ .build-commit,
259
+ .build-branch {
260
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
261
+ }
package/src/landing.ts ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Landing page (`/`): one card per registered app with a link to
3
+ * `/app/?app=<name>` and a QR code of the absolute URL, plus the build info.
4
+ *
5
+ * The app list and the build info are injected at build time by `define` in
6
+ * node/config.ts (`__SB_APPS__`, `__SB_COMMIT__`, `__SB_BRANCH__`,
7
+ * `__SB_BUILT_AT__`; see src/vite-env.d.ts). The example apps' descriptions
8
+ * live in node/examples.ts.
9
+ */
10
+
11
+ import { toString as qrToString } from 'qrcode';
12
+
13
+ const REPO_URL = 'https://github.com/dabbott/SwiftBrowser';
14
+
15
+ /** The card's subtitle: the spec's description, else a generic line. */
16
+ export function describeApp(app: Pick<SBLandingApp, 'name' | 'description'>): string {
17
+ return app.description ?? `the ${app.name} app`;
18
+ }
19
+
20
+ /** Path of the app page for an example (relative, so links work on any host). */
21
+ export function appPath(name: string): string {
22
+ return `/app/?app=${encodeURIComponent(name)}`;
23
+ }
24
+
25
+ /** Absolute URL a phone should open (the QR code's payload). */
26
+ export function appUrl(name: string): string {
27
+ return `${location.origin}${appPath(name)}`;
28
+ }
29
+
30
+ function el<K extends keyof HTMLElementTagNameMap>(
31
+ tag: K,
32
+ className?: string,
33
+ text?: string,
34
+ ): HTMLElementTagNameMap[K] {
35
+ const node = document.createElement(tag);
36
+ if (className) node.className = className;
37
+ if (text !== undefined) node.textContent = text;
38
+ return node;
39
+ }
40
+
41
+ async function renderQr(container: HTMLElement, url: string): Promise<void> {
42
+ try {
43
+ // The SVG is generated from the URL (modules as a path), nothing is
44
+ // interpolated into the markup.
45
+ const svg = await qrToString(url, { type: 'svg', margin: 1, errorCorrectionLevel: 'M' });
46
+ container.innerHTML = svg;
47
+ const node = container.querySelector('svg');
48
+ if (node) {
49
+ node.setAttribute('role', 'img');
50
+ node.setAttribute('aria-label', `QR code for ${url}`);
51
+ node.removeAttribute('width');
52
+ node.removeAttribute('height');
53
+ }
54
+ } catch (error) {
55
+ console.error('[sb] could not render QR code', error);
56
+ container.textContent = url;
57
+ container.classList.add('app-qr--failed');
58
+ }
59
+ }
60
+
61
+ function appCard(app: SBLandingApp): HTMLLIElement {
62
+ const { name, displayName } = app;
63
+ const card = el('li', 'app-card');
64
+ card.dataset['app'] = name;
65
+
66
+ const qr = el('div', 'app-qr');
67
+ qr.title = appUrl(name);
68
+ void renderQr(qr, appUrl(name));
69
+
70
+ const body = el('div', 'app-body');
71
+ const heading = el('div', 'app-heading');
72
+ if (app.icon) {
73
+ const icon = el('img', 'app-icon');
74
+ icon.src = app.icon;
75
+ icon.alt = '';
76
+ icon.width = 40;
77
+ icon.height = 40;
78
+ heading.append(icon);
79
+ }
80
+ const title = el('h3', 'app-name', displayName);
81
+ heading.append(title);
82
+ const description = el('p', 'app-description', describeApp(app));
83
+ const open = el('a', 'app-open', 'Open');
84
+ open.href = appPath(name);
85
+ open.setAttribute('aria-label', `Open ${displayName}`);
86
+ const url = el('a', 'app-url', appUrl(name).replace(/^https?:\/\//, ''));
87
+ url.href = appPath(name);
88
+ url.setAttribute('aria-hidden', 'true');
89
+ url.tabIndex = -1;
90
+
91
+ body.append(heading, description, open, url);
92
+ card.append(qr, body);
93
+ return card;
94
+ }
95
+
96
+ function renderApps(): void {
97
+ const list = document.querySelector<HTMLUListElement>('#apps');
98
+ if (!list) return;
99
+ const apps = Array.from(__SB_APPS__);
100
+ if (apps.length === 0) {
101
+ list.replaceWith(el('p', 'app-empty', 'No apps were registered at build time.'));
102
+ return;
103
+ }
104
+ for (const app of apps) list.append(appCard(app));
105
+ }
106
+
107
+ function renderBuildInfo(): void {
108
+ const footer = document.querySelector<HTMLElement>('#build-info');
109
+ if (!footer) return;
110
+ footer.textContent = '';
111
+
112
+ const commit = __SB_COMMIT__;
113
+ const branch = __SB_BRANCH__;
114
+ const built = new Date(__SB_BUILT_AT__);
115
+
116
+ footer.append('Built from ');
117
+ const sha = commit === 'dev' ? el('span', 'build-commit', 'dev') : el('a', 'build-commit', commit.slice(0, 7));
118
+ sha.dataset['commit'] = commit;
119
+ if (sha instanceof HTMLAnchorElement) {
120
+ sha.href = `${REPO_URL}/commit/${commit}`;
121
+ sha.title = commit;
122
+ }
123
+ footer.append(sha);
124
+ if (branch) {
125
+ footer.append(' on ');
126
+ footer.append(el('span', 'build-branch', branch));
127
+ }
128
+ if (!Number.isNaN(built.getTime())) {
129
+ footer.append(' · ');
130
+ const time = el('time', 'build-date', built.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }));
131
+ time.dateTime = built.toISOString();
132
+ footer.append(time);
133
+ }
134
+ footer.append(' · ');
135
+ const repo = el('a', 'build-repo', 'dabbott/SwiftBrowser');
136
+ repo.href = REPO_URL;
137
+ footer.append(repo);
138
+ }
139
+
140
+ renderApps();
141
+ renderBuildInfo();