generator-white-label 5.0.0 → 8.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.
Files changed (299) hide show
  1. package/.editorconfig +15 -0
  2. package/.yarnrc.yml +6 -0
  3. package/CLI.md +82 -0
  4. package/LICENSE +9 -0
  5. package/PACKAGE_MANAGERS.md +35 -0
  6. package/README.md +204 -124
  7. package/app/404.tsx +28 -0
  8. package/app/README.md +66 -8
  9. package/app/assets/data/view/global.json +7 -47
  10. package/app/assets/data/view/index.json +9 -1
  11. package/app/assets/script/index.tsx +13 -0
  12. package/app/assets/script/tasks/TaskApplication.ts +77 -0
  13. package/app/assets/script/tasks/TaskMediator.ts +20 -0
  14. package/app/assets/script/tasks/TaskModel.ts +32 -0
  15. package/app/assets/script/tasks/TaskRouter.ts +30 -0
  16. package/app/assets/script/tasks/TaskView.tsx +24 -0
  17. package/app/assets/style/global.css +111 -0
  18. package/app/assets/style/print.css +8 -0
  19. package/app/assets/view/CodeBlock.tsx +17 -0
  20. package/app/assets/view/examples/tasks/TaskExample.tsx +63 -0
  21. package/app/assets/view/examples/tasks/task-state.ts +46 -0
  22. package/app/assets/view/layout/SiteFooter.tsx +11 -0
  23. package/app/assets/view/layout/SiteHeader.tsx +23 -0
  24. package/app/assets/view/page-data.ts +14 -0
  25. package/app/assets/view/sections/GettingStartedSection.tsx +27 -0
  26. package/app/assets/view/sections/HeroSection.tsx +21 -0
  27. package/app/assets/view/sections/LiveExampleSection.tsx +29 -0
  28. package/app/assets/view/sections/PackageDocsSection.tsx +69 -0
  29. package/app/assets/view/sections/SourceGuideSection.tsx +28 -0
  30. package/app/index.tsx +54 -0
  31. package/cli/index.ts +117 -0
  32. package/dist/app/404.d.ts +1 -0
  33. package/dist/app/404.js +6 -0
  34. package/dist/app/404.js.map +1 -0
  35. package/dist/app/assets/script/index.d.ts +8 -2
  36. package/dist/app/assets/script/index.js +11 -1
  37. package/dist/app/assets/script/index.js.map +1 -1
  38. package/dist/app/assets/script/tasks/TaskApplication.d.ts +20 -0
  39. package/dist/app/assets/script/tasks/TaskApplication.js +63 -0
  40. package/dist/app/assets/script/tasks/TaskApplication.js.map +1 -0
  41. package/dist/app/assets/script/tasks/TaskMediator.d.ts +15 -0
  42. package/dist/app/assets/script/tasks/TaskMediator.js +5 -0
  43. package/dist/app/assets/script/tasks/TaskMediator.js.map +1 -0
  44. package/dist/app/assets/script/tasks/TaskModel.d.ts +13 -0
  45. package/dist/app/assets/script/tasks/TaskModel.js +33 -0
  46. package/dist/app/assets/script/tasks/TaskModel.js.map +1 -0
  47. package/dist/app/assets/script/tasks/TaskRouter.d.ts +12 -0
  48. package/dist/app/assets/script/tasks/TaskRouter.js +28 -0
  49. package/dist/app/assets/script/tasks/TaskRouter.js.map +1 -0
  50. package/dist/app/assets/script/tasks/TaskView.d.ts +9 -0
  51. package/dist/app/assets/script/tasks/TaskView.js +24 -0
  52. package/dist/app/assets/script/tasks/TaskView.js.map +1 -0
  53. package/dist/app/assets/view/CodeBlock.d.ts +11 -0
  54. package/dist/app/assets/view/CodeBlock.js +12 -0
  55. package/dist/app/assets/view/CodeBlock.js.map +1 -0
  56. package/dist/app/assets/view/examples/tasks/TaskExample.d.ts +11 -0
  57. package/dist/app/assets/view/examples/tasks/TaskExample.js +16 -0
  58. package/dist/app/assets/view/examples/tasks/TaskExample.js.map +1 -0
  59. package/dist/app/assets/view/examples/tasks/task-state.d.ts +22 -0
  60. package/dist/app/assets/view/examples/tasks/task-state.js +30 -0
  61. package/dist/app/assets/view/examples/tasks/task-state.js.map +1 -0
  62. package/dist/app/assets/view/layout/SiteFooter.d.ts +2 -0
  63. package/dist/app/assets/view/layout/SiteFooter.js +6 -0
  64. package/dist/app/assets/view/layout/SiteFooter.js.map +1 -0
  65. package/dist/app/assets/view/layout/SiteHeader.d.ts +8 -0
  66. package/dist/app/assets/view/layout/SiteHeader.js +12 -0
  67. package/dist/app/assets/view/layout/SiteHeader.js.map +1 -0
  68. package/dist/app/assets/view/page-data.d.ts +16 -0
  69. package/dist/app/assets/view/page-data.js +2 -0
  70. package/dist/app/assets/view/page-data.js.map +1 -0
  71. package/dist/app/assets/view/sections/GettingStartedSection.d.ts +2 -0
  72. package/dist/app/assets/view/sections/GettingStartedSection.js +11 -0
  73. package/dist/app/assets/view/sections/GettingStartedSection.js.map +1 -0
  74. package/dist/app/assets/view/sections/HeroSection.d.ts +8 -0
  75. package/dist/app/assets/view/sections/HeroSection.js +12 -0
  76. package/dist/app/assets/view/sections/HeroSection.js.map +1 -0
  77. package/dist/app/assets/view/sections/LiveExampleSection.d.ts +6 -0
  78. package/dist/app/assets/view/sections/LiveExampleSection.js +13 -0
  79. package/dist/app/assets/view/sections/LiveExampleSection.js.map +1 -0
  80. package/dist/app/assets/view/sections/PackageDocsSection.d.ts +2 -0
  81. package/dist/app/assets/view/sections/PackageDocsSection.js +31 -0
  82. package/dist/app/assets/view/sections/PackageDocsSection.js.map +1 -0
  83. package/dist/app/assets/view/sections/SourceGuideSection.d.ts +7 -0
  84. package/dist/app/assets/view/sections/SourceGuideSection.js +11 -0
  85. package/dist/app/assets/view/sections/SourceGuideSection.js.map +1 -0
  86. package/dist/app/index.d.ts +8 -0
  87. package/dist/app/index.js +22 -0
  88. package/dist/app/index.js.map +1 -0
  89. package/dist/cli/index.d.ts +13 -0
  90. package/dist/cli/index.js +97 -0
  91. package/dist/cli/index.js.map +1 -0
  92. package/dist/scaffold/index.d.ts +64 -0
  93. package/dist/scaffold/index.js +109 -0
  94. package/dist/scaffold/index.js.map +1 -0
  95. package/dist/scripts/build.d.ts +5 -13
  96. package/dist/scripts/build.js +48 -79
  97. package/dist/scripts/build.js.map +1 -1
  98. package/package.json +77 -40
  99. package/pnpm-workspace.yaml +4 -0
  100. package/scaffold/index.ts +127 -0
  101. package/scaffold/no-jsx/README.md +47 -0
  102. package/scaffold/no-jsx/app/404.ts +10 -0
  103. package/scaffold/no-jsx/app/assets/script/index.ts +7 -0
  104. package/scaffold/no-jsx/app/assets/script/tasks/TaskView.ts +19 -0
  105. package/scaffold/no-jsx/app/assets/view/examples/tasks/TaskExample.ts +39 -0
  106. package/scaffold/no-jsx/app/index.ts +58 -0
  107. package/scripts/build.ts +55 -86
  108. package/server/handler.ts +75 -0
  109. package/template-test/site.test.js +233 -0
  110. package/tsconfig.site.json +6 -5
  111. package/tsconfig.site.no-jsx.json +24 -0
  112. package/app/404.hbs +0 -20
  113. package/app/assets/data/coutries.json +0 -992
  114. package/app/assets/data/states.json +0 -204
  115. package/app/assets/data/view/toolkit/colors.json +0 -17
  116. package/app/assets/data/view/toolkit/components.json +0 -39
  117. package/app/assets/data/view/toolkit/elements.json +0 -119
  118. package/app/assets/data/view/toolkit/grids.json +0 -17
  119. package/app/assets/data/view/toolkit/icons.json +0 -17
  120. package/app/assets/data/view/toolkit/index.json +0 -17
  121. package/app/assets/data/view/toolkit/type.json +0 -17
  122. package/app/assets/font/opensans/LICENSE.txt +0 -202
  123. package/app/assets/font/opensans/opensans-bold-webfont.woff2 +0 -0
  124. package/app/assets/font/opensans/opensans-bolditalic-webfont.woff2 +0 -0
  125. package/app/assets/font/opensans/opensans-extrabold-webfont.woff2 +0 -0
  126. package/app/assets/font/opensans/opensans-extrabolditalic-webfont.woff2 +0 -0
  127. package/app/assets/font/opensans/opensans-italic-webfont.woff2 +0 -0
  128. package/app/assets/font/opensans/opensans-light-webfont.woff2 +0 -0
  129. package/app/assets/font/opensans/opensans-lightitalic-webfont.woff2 +0 -0
  130. package/app/assets/font/opensans/opensans-regular-webfont.woff2 +0 -0
  131. package/app/assets/font/opensans/opensans-semibold-webfont.woff2 +0 -0
  132. package/app/assets/font/opensans/opensans-semibolditalic-webfont.woff2 +0 -0
  133. package/app/assets/font/opensans/stylesheet.css +0 -120
  134. package/app/assets/image/apple-touch-icon.png +0 -0
  135. package/app/assets/image/demo/fallback.avif +0 -0
  136. package/app/assets/image/demo/fallback.jpg +0 -0
  137. package/app/assets/image/demo/fallback.webp +0 -0
  138. package/app/assets/image/demo/large.avif +0 -0
  139. package/app/assets/image/demo/large.webp +0 -0
  140. package/app/assets/image/demo/small.avif +0 -0
  141. package/app/assets/image/demo/small.webp +0 -0
  142. package/app/assets/image/favicon.png +0 -0
  143. package/app/assets/image/graphic-css3.svg +0 -30
  144. package/app/assets/image/graphic-html5.svg +0 -29
  145. package/app/assets/markup/element/a.hbs +0 -29
  146. package/app/assets/markup/element/button.hbs +0 -30
  147. package/app/assets/markup/element/div.hbs +0 -11
  148. package/app/assets/markup/element/head.hbs +0 -78
  149. package/app/assets/markup/element/iframe.hbs +0 -20
  150. package/app/assets/markup/element/img.hbs +0 -30
  151. package/app/assets/markup/element/input.hbs +0 -43
  152. package/app/assets/markup/element/label.hbs +0 -14
  153. package/app/assets/markup/element/legend.hbs +0 -5
  154. package/app/assets/markup/element/meta.hbs +0 -11
  155. package/app/assets/markup/element/nav.hbs +0 -13
  156. package/app/assets/markup/element/p.hbs +0 -8
  157. package/app/assets/markup/element/picture.hbs +0 -58
  158. package/app/assets/markup/element/script.hbs +0 -3
  159. package/app/assets/markup/element/select.hbs +0 -35
  160. package/app/assets/markup/element/span.hbs +0 -8
  161. package/app/assets/markup/element/svg.hbs +0 -11
  162. package/app/assets/markup/element/table.hbs +0 -50
  163. package/app/assets/markup/element/textarea.hbs +0 -24
  164. package/app/assets/markup/element/ul.hbs +0 -13
  165. package/app/assets/markup/modal/modal-example-1.handlebars +0 -8
  166. package/app/assets/markup/toolkit/accessibility-skip-to-element-1.handlebars +0 -1
  167. package/app/assets/markup/toolkit/accessibility-speaker-1.handlebars +0 -1
  168. package/app/assets/markup/toolkit/anchor-1.handlebars +0 -1
  169. package/app/assets/markup/toolkit/button-1.handlebars +0 -1
  170. package/app/assets/markup/toolkit/footer-1.handlebars +0 -3
  171. package/app/assets/markup/toolkit/header-1.handlebars +0 -1
  172. package/app/assets/markup/toolkit/heading-1.handlebars +0 -1
  173. package/app/assets/markup/toolkit/heading-2.handlebars +0 -1
  174. package/app/assets/markup/toolkit/heading-3.handlebars +0 -1
  175. package/app/assets/markup/toolkit/heading-4.handlebars +0 -1
  176. package/app/assets/markup/toolkit/image-1.handlebars +0 -1
  177. package/app/assets/markup/toolkit/input-checkbox-1.handlebars +0 -1
  178. package/app/assets/markup/toolkit/input-error-1.handlebars +0 -8
  179. package/app/assets/markup/toolkit/input-radio-1.handlebars +0 -1
  180. package/app/assets/markup/toolkit/input-text-1.handlebars +0 -1
  181. package/app/assets/markup/toolkit/label-1.handlebars +0 -1
  182. package/app/assets/markup/toolkit/legend-1.handlebars +0 -1
  183. package/app/assets/markup/toolkit/list-1.handlebars +0 -1
  184. package/app/assets/markup/toolkit/modal-1.handlebars +0 -4
  185. package/app/assets/markup/toolkit/nav-1.handlebars +0 -1
  186. package/app/assets/markup/toolkit/picture-1.handlebars +0 -1
  187. package/app/assets/markup/toolkit/select-1.handlebars +0 -1
  188. package/app/assets/markup/toolkit/table-1.handlebars +0 -1
  189. package/app/assets/markup/toolkit/text-1.handlebars +0 -1
  190. package/app/assets/markup/toolkit/textarea-1.handlebars +0 -1
  191. package/app/assets/markup/toolkit/youtube-player-1.handlebars +0 -1
  192. package/app/assets/script/api/facebook.ts +0 -41
  193. package/app/assets/script/api/youtube.ts +0 -22
  194. package/app/assets/script/index.ts +0 -2
  195. package/app/assets/script/mediator/global.ts +0 -4
  196. package/app/assets/script/model/singletons/countries.ts +0 -5
  197. package/app/assets/script/model/singletons/global.ts +0 -5
  198. package/app/assets/script/model/singletons/states.ts +0 -5
  199. package/app/assets/script/model/user.ts +0 -4
  200. package/app/assets/script/toolkit.ts +0 -11
  201. package/app/assets/script/types.d.ts +0 -16
  202. package/app/assets/script/utility/ajax.ts +0 -15
  203. package/app/assets/script/utility/form.ts +0 -119
  204. package/app/assets/script/utility/regex.ts +0 -26
  205. package/app/assets/script/utility/share.ts +0 -77
  206. package/app/assets/script/utility/string.ts +0 -148
  207. package/app/assets/script/view/default.tsx +0 -14
  208. package/app/assets/script/view/toolkit/youtube-player-1.ts +0 -31
  209. package/app/assets/style/base/_toolkit.scss +0 -39
  210. package/app/assets/style/base/colors/_colors.scss +0 -18
  211. package/app/assets/style/base/components/_modal-1.scss +0 -80
  212. package/app/assets/style/base/components/_sticky-1.scss +0 -19
  213. package/app/assets/style/base/components/_tabpanel-1.scss +0 -115
  214. package/app/assets/style/base/components/_tabpanel-2.scss +0 -64
  215. package/app/assets/style/base/components/_tabpanel-base-1.scss +0 -13
  216. package/app/assets/style/base/components/_youtube-player-1.scss +0 -16
  217. package/app/assets/style/base/elements/_anchors.scss +0 -27
  218. package/app/assets/style/base/elements/_buttons.scss +0 -25
  219. package/app/assets/style/base/elements/_fieldsets.scss +0 -9
  220. package/app/assets/style/base/elements/_images.scss +0 -8
  221. package/app/assets/style/base/elements/_inputs.scss +0 -78
  222. package/app/assets/style/base/elements/_labels.scss +0 -16
  223. package/app/assets/style/base/elements/_landmarks.scss +0 -3
  224. package/app/assets/style/base/elements/_legends.scss +0 -11
  225. package/app/assets/style/base/elements/_lists.scss +0 -15
  226. package/app/assets/style/base/elements/_pictures.scss +0 -4
  227. package/app/assets/style/base/elements/_tables.scss +0 -39
  228. package/app/assets/style/base/layouts/_staggered-1.scss +0 -29
  229. package/app/assets/style/base/type/_fonts.scss +0 -125
  230. package/app/assets/style/base/type/_headings.scss +0 -36
  231. package/app/assets/style/base/type/_text.scss +0 -9
  232. package/app/assets/style/base/utilities/_accessibility.scss +0 -55
  233. package/app/assets/style/base/utilities/_functions.scss +0 -98
  234. package/app/assets/style/base/utilities/_scroll.scss +0 -11
  235. package/app/assets/style/base/utilities/_type.scss +0 -13
  236. package/app/assets/style/global.scss +0 -123
  237. package/app/assets/style/print.scss +0 -6
  238. package/app/assets/style/toolkit.scss +0 -387
  239. package/app/index.hbs +0 -21
  240. package/app/robots.txt +0 -1
  241. package/app/toolkit/colors.hbs +0 -102
  242. package/app/toolkit/components.hbs +0 -261
  243. package/app/toolkit/elements.hbs +0 -497
  244. package/app/toolkit/grids.hbs +0 -413
  245. package/app/toolkit/icons.hbs +0 -87
  246. package/app/toolkit/index.hbs +0 -144
  247. package/app/toolkit/type.hbs +0 -120
  248. package/dist/app/assets/data/coutries.json +0 -992
  249. package/dist/app/assets/data/states.json +0 -204
  250. package/dist/app/assets/script/api/facebook.d.ts +0 -8
  251. package/dist/app/assets/script/api/facebook.js +0 -42
  252. package/dist/app/assets/script/api/facebook.js.map +0 -1
  253. package/dist/app/assets/script/api/youtube.d.ts +0 -8
  254. package/dist/app/assets/script/api/youtube.js +0 -22
  255. package/dist/app/assets/script/api/youtube.js.map +0 -1
  256. package/dist/app/assets/script/mediator/global.d.ts +0 -4
  257. package/dist/app/assets/script/mediator/global.js +0 -4
  258. package/dist/app/assets/script/mediator/global.js.map +0 -1
  259. package/dist/app/assets/script/model/singletons/countries.d.ts +0 -20
  260. package/dist/app/assets/script/model/singletons/countries.js +0 -5
  261. package/dist/app/assets/script/model/singletons/countries.js.map +0 -1
  262. package/dist/app/assets/script/model/singletons/global.d.ts +0 -20
  263. package/dist/app/assets/script/model/singletons/global.js +0 -5
  264. package/dist/app/assets/script/model/singletons/global.js.map +0 -1
  265. package/dist/app/assets/script/model/singletons/states.d.ts +0 -20
  266. package/dist/app/assets/script/model/singletons/states.js +0 -5
  267. package/dist/app/assets/script/model/singletons/states.js.map +0 -1
  268. package/dist/app/assets/script/model/user.d.ts +0 -4
  269. package/dist/app/assets/script/model/user.js +0 -6
  270. package/dist/app/assets/script/model/user.js.map +0 -1
  271. package/dist/app/assets/script/toolkit.d.ts +0 -1
  272. package/dist/app/assets/script/toolkit.js +0 -7
  273. package/dist/app/assets/script/toolkit.js.map +0 -1
  274. package/dist/app/assets/script/utility/ajax.d.ts +0 -8
  275. package/dist/app/assets/script/utility/ajax.js +0 -17
  276. package/dist/app/assets/script/utility/ajax.js.map +0 -1
  277. package/dist/app/assets/script/utility/form.d.ts +0 -64
  278. package/dist/app/assets/script/utility/form.js +0 -111
  279. package/dist/app/assets/script/utility/form.js.map +0 -1
  280. package/dist/app/assets/script/utility/regex.d.ts +0 -14
  281. package/dist/app/assets/script/utility/regex.js +0 -27
  282. package/dist/app/assets/script/utility/regex.js.map +0 -1
  283. package/dist/app/assets/script/utility/share.d.ts +0 -68
  284. package/dist/app/assets/script/utility/share.js +0 -81
  285. package/dist/app/assets/script/utility/share.js.map +0 -1
  286. package/dist/app/assets/script/utility/string.d.ts +0 -64
  287. package/dist/app/assets/script/utility/string.js +0 -135
  288. package/dist/app/assets/script/utility/string.js.map +0 -1
  289. package/dist/app/assets/script/view/default.d.ts +0 -11
  290. package/dist/app/assets/script/view/default.js +0 -15
  291. package/dist/app/assets/script/view/default.js.map +0 -1
  292. package/dist/app/assets/script/view/toolkit/youtube-player-1.d.ts +0 -23
  293. package/dist/app/assets/script/view/toolkit/youtube-player-1.js +0 -32
  294. package/dist/app/assets/script/view/toolkit/youtube-player-1.js.map +0 -1
  295. package/dist/generators/app/index.d.ts +0 -18
  296. package/dist/generators/app/index.js +0 -59
  297. package/dist/generators/app/index.js.map +0 -1
  298. package/generators/app/index.js +0 -2
  299. package/generators/app/index.ts +0 -61
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Static navigation is ordinary HTML first.
3
+ *
4
+ * White Label's router enhances only links that opt in with data-pushstate,
5
+ * so global navigation stays conventional, crawlable, and usable without
6
+ * client-side JavaScript.
7
+ */
8
+ export default function SiteHeader() {
9
+ return (
10
+ <header className="site-header">
11
+ <div className="container site-header__inner">
12
+ <a className="wordmark" href="#top">White Label</a>
13
+ <nav aria-label="Primary navigation">
14
+ <a href="#example">Example</a>
15
+ <a href="#packages">Packages</a>
16
+ <a href="#source">Source</a>
17
+ <a href="#start">Get started</a>
18
+ <a href="https://github.com/bshack/white-label">GitHub</a>
19
+ </nav>
20
+ </div>
21
+ </header>
22
+ );
23
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Build-time data available to every page template.
3
+ *
4
+ * Keeping this contract separate from the page component makes the top-level
5
+ * JSX read like application composition instead of deployment plumbing.
6
+ */
7
+ export interface PageData {
8
+ cdn: string;
9
+ meta: {description: string};
10
+ siteUrl: string;
11
+ structuredData: unknown;
12
+ title: string;
13
+ version: string;
14
+ }
@@ -0,0 +1,27 @@
1
+ import CodeBlock, {syntax} from '../CodeBlock.js';
2
+
3
+ /** Keep the shortest path from generator install to a verified local build visible. */
4
+ export default function GettingStartedSection() {
5
+ return (
6
+ <section className="section" id="start" aria-labelledby="start-title">
7
+ <div className="container start-grid">
8
+ <div className="section-intro">
9
+ <p className="eyebrow">Get started</p>
10
+ <h2 id="start-title">Create a project with the generator.</h2>
11
+ <p>The generated project is static-first, TypeScript-first, accessible by default, and ready for application-specific code.</p>
12
+ </div>
13
+ <div>
14
+ <p className="code-label">Create a project</p>
15
+ <CodeBlock lines={[<><span className={syntax.type}>npx</span> generator-white-label</>]} />
16
+ <p className="code-label">Then develop</p>
17
+ <CodeBlock lines={[
18
+ <><span className={syntax.type}>npm</span> ci</>,
19
+ <><span className={syntax.type}>npm</span> test</>,
20
+ <><span className={syntax.type}>npm</span> run build -- --version=<span className={syntax.value}>local</span></>
21
+ ]} />
22
+ <p><a href="https://github.com/bshack/white-label#readme">Read the complete generator documentation</a></p>
23
+ </div>
24
+ </div>
25
+ </section>
26
+ );
27
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Marketing content can stay a plain JSX function.
3
+ *
4
+ * White Label does not require every piece of markup to become a stateful
5
+ * View instance. Use View where lifecycle or observable state is useful;
6
+ * keep static presentation simple.
7
+ */
8
+ export default function HeroSection() {
9
+ return (
10
+ <section className="hero" id="top" aria-labelledby="hero-title">
11
+ <div className="container hero__inner">
12
+ <h1 id="hero-title">Small pieces.<br />Complete applications.</h1>
13
+ <p className="hero__lede">Framework-independent TypeScript packages for state, rendering, coordination, routing, and project generation.</p>
14
+ <div className="hero__links">
15
+ <a className="primary-link" href="#example">See the example</a>
16
+ <a href="#start">Install the generator</a>
17
+ </div>
18
+ </div>
19
+ </section>
20
+ );
21
+ }
@@ -0,0 +1,29 @@
1
+ import TaskExample from '../examples/tasks/TaskExample.js';
2
+ import {createInitialTaskState, describeTaskStatus} from '../examples/tasks/task-state.js';
3
+
4
+ /**
5
+ * The initial task UI is rendered at build time with the same JSX used later
6
+ * by white-label-view in the browser. JavaScript adds behavior; it does not
7
+ * need to invent the initial document.
8
+ */
9
+ export default function LiveExampleSection() {
10
+ const state = createInitialTaskState();
11
+
12
+ return (
13
+ <section className="section" id="example" aria-labelledby="example-title">
14
+ <div className="container">
15
+ <div className="section-intro">
16
+ <p className="eyebrow">Live example</p>
17
+ <h2 id="example-title">Four packages. One small application.</h2>
18
+ <p>Add a task, toggle its state, or change the filter. Router translates the URL into intent, Mediator coordinates actions, Model owns state, and View keeps the interface in sync.</p>
19
+ </div>
20
+ <div className="demo" data-task-example>
21
+ <TaskExample state={state} />
22
+ <p className="visually-hidden" role="status" aria-live="polite" aria-atomic="true" data-task-status>
23
+ {describeTaskStatus(state)}
24
+ </p>
25
+ </div>
26
+ </div>
27
+ </section>
28
+ );
29
+ }
@@ -0,0 +1,69 @@
1
+ import CodeBlock, {syntax} from '../CodeBlock.js';
2
+
3
+ const repositories = {
4
+ mediator: 'https://github.com/bshack/white-label-mediator',
5
+ model: 'https://github.com/bshack/white-label-model',
6
+ router: 'https://github.com/bshack/white-label-router',
7
+ view: 'https://github.com/bshack/white-label-view'
8
+ };
9
+
10
+ /** Package documentation stays close to the example it describes. */
11
+ export default function PackageDocsSection() {
12
+ return (
13
+ <section className="section" id="packages" aria-labelledby="packages-title">
14
+ <div className="container">
15
+ <div className="section-intro">
16
+ <p className="eyebrow">Documentation</p>
17
+ <h2 id="packages-title">The core flow stays explicit.</h2>
18
+ <p>Each package has one job, can be used independently, and stays easy to replace or test because application concerns are not hidden behind a framework.</p>
19
+ </div>
20
+ <div className="architecture"><ol>
21
+ <li><strong>Router</strong><span>turns a URL into application intent</span></li>
22
+ <li><strong>Mediator</strong><span>coordinates that intent between modules</span></li>
23
+ <li><strong>Model</strong><span>stores and publishes application state</span></li>
24
+ <li><strong>View</strong><span>renders the resulting interface</span></li>
25
+ </ol></div>
26
+ <div className="docs-grid">
27
+ <article>
28
+ <p className="eyebrow">Model</p><h3>State is observable, not magical.</h3>
29
+ <CodeBlock lines={[
30
+ <><span className={syntax.keyword}>const</span> model = <span className={syntax.keyword}>new</span> <span className={syntax.type}>Model</span>({'{'}count: 0{'}'});</>,
31
+ <>model.on(<span className={syntax.value}>'change'</span>, state =&gt; render(state));</>,
32
+ <>model.update({'{'}count: 1{'}'});</>
33
+ ]} />
34
+ <p><a href={repositories.model}>Model documentation</a></p>
35
+ </article>
36
+ <article>
37
+ <p className="eyebrow">View</p><h3>JSX renders through White Label.</h3>
38
+ <CodeBlock lines={[
39
+ <><span className={syntax.keyword}>const</span> view = <span className={syntax.keyword}>new</span> <span className={syntax.type}>View</span>({'{'}</>,
40
+ <> model,</>,
41
+ <> template: state =&gt; &lt;p&gt;{'{'}state.count{'}'}&lt;/p&gt;</>,
42
+ <>{'}'}).initialize();</>
43
+ ]} />
44
+ <p><a href={repositories.view}>View documentation</a></p>
45
+ </article>
46
+ <article>
47
+ <p className="eyebrow">Mediator</p><h3>Modules communicate through events.</h3>
48
+ <CodeBlock lines={[
49
+ <>mediator.on(<span className={syntax.value}>'counter:increment'</span>, increment);</>,
50
+ <>mediator.emit(<span className={syntax.value}>'counter:increment'</span>);</>
51
+ ]} />
52
+ <p><a href={repositories.mediator}>Mediator documentation</a></p>
53
+ </article>
54
+ <article>
55
+ <p className="eyebrow">Router</p><h3>Routes describe intent.</h3>
56
+ <CodeBlock lines={[
57
+ <>router.routes = {'{'}</>,
58
+ <> <span className={syntax.value}>'/'</span>: (_scope, location) =&gt; {'{'}</>,
59
+ <> mediator.emit(<span className={syntax.value}>'filter:set'</span>, location.data.query.filter);</>,
60
+ <> {'}'}</>,
61
+ <>{'}'};</>
62
+ ]} />
63
+ <p><a href={repositories.router}>Router documentation</a></p>
64
+ </article>
65
+ </div>
66
+ </div>
67
+ </section>
68
+ );
69
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Point learners at the implementation itself.
3
+ *
4
+ * The landing page is intentionally organized so each path answers a different
5
+ * question: composition, rendering, state, coordination, routing, or testing.
6
+ */
7
+ export default function SourceGuideSection() {
8
+ return (
9
+ <section className="section" id="source" aria-labelledby="source-title">
10
+ <div className="container start-grid">
11
+ <div className="section-intro">
12
+ <p className="eyebrow">Read the source</p>
13
+ <h2 id="source-title">This site is part of the documentation.</h2>
14
+ <p>The landing page is built with the same White Label packages it teaches. Start at the page composition, then follow the imports into the interactive example.</p>
15
+ </div>
16
+ <div className="source-paths">
17
+ <dl>
18
+ <div><dt>Page composition</dt><dd><code>app/index.tsx</code></dd></div>
19
+ <div><dt>Static JSX views</dt><dd><code>app/assets/view/</code></dd></div>
20
+ <div><dt>Task application</dt><dd><code>app/assets/script/tasks/</code></dd></div>
21
+ <div><dt>Integration tests</dt><dd><code>test/app.test.js</code></dd></div>
22
+ </dl>
23
+ <p><a href="https://github.com/bshack/white-label/tree/master/app">Browse the application source</a></p>
24
+ </div>
25
+ </div>
26
+ </section>
27
+ );
28
+ }
package/app/index.tsx ADDED
@@ -0,0 +1,54 @@
1
+ import {raw} from 'white-label-view/jsx-runtime';
2
+ import SiteFooter from './assets/view/layout/SiteFooter.js';
3
+ import SiteHeader from './assets/view/layout/SiteHeader.js';
4
+ import type {PageData} from './assets/view/page-data.js';
5
+ import GettingStartedSection from './assets/view/sections/GettingStartedSection.js';
6
+ import HeroSection from './assets/view/sections/HeroSection.js';
7
+ import LiveExampleSection from './assets/view/sections/LiveExampleSection.js';
8
+ import PackageDocsSection from './assets/view/sections/PackageDocsSection.js';
9
+ import SourceGuideSection from './assets/view/sections/SourceGuideSection.js';
10
+
11
+ /**
12
+ * The page template is intentionally thin.
13
+ *
14
+ * Developers learning White Label can start here to see the complete page at
15
+ * a glance, then follow each import into a focused example. Build-time data is
16
+ * kept at this boundary while individual views stay reusable and easy to read.
17
+ */
18
+ export default function IndexPage(data: Record<string, unknown>) {
19
+ const page = data as unknown as PageData;
20
+ const structuredData = JSON.stringify(page.structuredData).replaceAll('<', '\\u003c');
21
+
22
+ return (
23
+ <html id="index" lang="en-US" dir="ltr">
24
+ <head>
25
+ <meta charset="utf-8" />
26
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
27
+ <title>{page.title}</title>
28
+ <meta name="description" content={page.meta.description} />
29
+ <meta name="robots" content="index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1" />
30
+ <link rel="canonical" href={`${page.siteUrl}/`} />
31
+ <meta property="og:title" content={page.title} />
32
+ <meta property="og:description" content={page.meta.description} />
33
+ <meta property="og:type" content="website" />
34
+ <meta property="og:url" content={`${page.siteUrl}/`} />
35
+ <link rel="stylesheet" href={`${page.cdn}release/${page.version}/assets/style/global.css`} media="all" />
36
+ <link rel="stylesheet" href={`${page.cdn}release/${page.version}/assets/style/print.css`} media="print" />
37
+ <script type="application/ld+json">{raw(structuredData)}</script>
38
+ </head>
39
+ <body itemscope itemtype="https://schema.org/WebPage">
40
+ <a className="skip-link" href="#main">Skip to content</a>
41
+ <SiteHeader />
42
+ <main id="main" tabindex="-1">
43
+ <HeroSection />
44
+ <LiveExampleSection />
45
+ <PackageDocsSection />
46
+ <SourceGuideSection />
47
+ <GettingStartedSection />
48
+ </main>
49
+ <SiteFooter />
50
+ <script src={`${page.cdn}release/${page.version}/assets/script/index.compiled.js`} defer />
51
+ </body>
52
+ </html>
53
+ );
54
+ }
package/cli/index.ts ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {readdir} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+ import {createInterface} from 'node:readline/promises';
7
+ import {fileURLToPath} from 'node:url';
8
+ import {createProject} from '../scaffold/index.js';
9
+
10
+ export interface CliDependencies {
11
+ cwd?: () => string;
12
+ createProject?: typeof createProject;
13
+ stdout?: Pick<NodeJS.WriteStream, 'write'>;
14
+ stderr?: Pick<NodeJS.WriteStream, 'write'>;
15
+ input?: NodeJS.ReadableStream;
16
+ output?: NodeJS.WritableStream;
17
+ isInteractive?: boolean;
18
+ }
19
+
20
+ function usage() {
21
+ return [
22
+ 'Usage:',
23
+ ' white-label create <directory> [--jsx | --no-jsx]',
24
+ '',
25
+ 'Commands:',
26
+ ' create <directory> Create a White Label project.',
27
+ '',
28
+ 'Options:',
29
+ ' --jsx Generate TypeScript with JSX/TSX templates.',
30
+ ' --no-jsx Generate plain TypeScript with HTML string templates.',
31
+ ' -h, --help Show this help message.'
32
+ ].join('\n');
33
+ }
34
+
35
+ export function formatCliError(error: unknown) {
36
+ return error instanceof Error ? error.message : String(error);
37
+ }
38
+
39
+ async function askForJsx(input: NodeJS.ReadableStream, output: NodeJS.WritableStream): Promise<boolean> {
40
+ const readline = createInterface({input, output});
41
+ try {
42
+ const answer = await readline.question(
43
+ 'Use JSX/TSX for page and view templates? Choose Yes for JSX syntax like <section>...</section>, or No for plain TypeScript that returns HTML strings. (Y/n) '
44
+ );
45
+ return !/^n(?:o)?$/i.test(answer.trim());
46
+ } finally {
47
+ readline.close();
48
+ }
49
+ }
50
+
51
+ /** Reject an existing non-empty target so a mistyped CLI destination cannot overwrite project files. */
52
+ async function assertDestinationAvailable(destination: string) {
53
+ try {
54
+ const entries = await readdir(destination);
55
+ if (entries.length) {
56
+ throw new Error(`Destination directory must be empty: ${destination}`);
57
+ }
58
+ } catch (error) {
59
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {return;}
60
+ throw error;
61
+ }
62
+ }
63
+
64
+ export async function runCli(args: readonly string[], dependencies: CliDependencies = {}) {
65
+ const cwd = dependencies.cwd ?? process.cwd;
66
+ const scaffold = dependencies.createProject ?? createProject;
67
+ const stdout = dependencies.stdout ?? process.stdout;
68
+ const stderr = dependencies.stderr ?? process.stderr;
69
+ const input = dependencies.input ?? process.stdin;
70
+ const output = dependencies.output ?? process.stdout;
71
+ const [command, destination, ...options] = args;
72
+
73
+ if (command === '--help' || command === '-h' || args.length === 0) {
74
+ stdout.write(`${usage()}\n`);
75
+ return 0;
76
+ }
77
+
78
+ const allowedOptions = new Set(['--jsx', '--no-jsx']);
79
+ if (
80
+ command !== 'create' ||
81
+ !destination ||
82
+ options.some(option => !allowedOptions.has(option)) ||
83
+ (options.includes('--jsx') && options.includes('--no-jsx'))
84
+ ) {
85
+ stderr.write(`${usage()}\n`);
86
+ return 1;
87
+ }
88
+
89
+ let jsx: boolean;
90
+ if (options.includes('--jsx')) {
91
+ jsx = true;
92
+ } else if (options.includes('--no-jsx')) {
93
+ jsx = false;
94
+ } else if (dependencies.isInteractive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY)) {
95
+ jsx = await askForJsx(input, output);
96
+ } else {
97
+ jsx = true;
98
+ }
99
+
100
+ const resolvedDestination = path.resolve(cwd(), destination);
101
+ await assertDestinationAvailable(resolvedDestination);
102
+ await scaffold({destination: resolvedDestination, jsx});
103
+ stdout.write(`Created White Label project at ${resolvedDestination}\n`);
104
+ return 0;
105
+ }
106
+
107
+ const invokedPath = path.resolve(process.argv[1] as string);
108
+ const modulePath = fileURLToPath(import.meta.url);
109
+ if (invokedPath === modulePath) {
110
+ runCli(process.argv.slice(2)).then(
111
+ (exitCode) => {process.exitCode = exitCode;},
112
+ (error: unknown) => {
113
+ process.stderr.write(`Unable to create White Label project: ${formatCliError(error)}\n`);
114
+ process.exitCode = 1;
115
+ }
116
+ );
117
+ }
@@ -0,0 +1 @@
1
+ export default function NotFoundPage(data: Record<string, unknown>): import("white-label-view/jsx-runtime").JSXMarkup;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "white-label-view/jsx-runtime";
2
+ export default function NotFoundPage(data) {
3
+ const page = data;
4
+ return (_jsxs("html", { lang: "en-US", dir: "ltr", children: [_jsxs("head", { children: [_jsx("meta", { charset: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("meta", { name: "robots", content: "noindex,follow" }), _jsx("title", { children: "Page not found | White Label" }), _jsx("link", { rel: "stylesheet", href: `${page.cdn}release/${page.version}/assets/style/global.css` })] }), _jsxs("body", { children: [_jsx("a", { className: "skip-link", href: "#main", children: "Skip to the message" }), _jsxs("main", { id: "main", tabindex: "-1", className: "mx-auto max-w-5xl px-4 py-24 sm:px-6 lg:px-8", children: [_jsx("h1", { className: "text-5xl font-extrabold tracking-tight", children: "Page not found" }), _jsx("p", { className: "mt-6 text-lg", children: "The requested page does not exist." }), _jsx("p", { className: "mt-6", children: _jsx("a", { className: "font-bold underline", href: page.www, children: "Return to the White Label starter" }) })] })] })] }));
5
+ }
6
+ //# sourceMappingURL=404.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"404.js","sourceRoot":"","sources":["../../app/404.tsx"],"names":[],"mappings":";AAMA,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,IAA6B;IAC9D,MAAM,IAAI,GAAG,IAA2B,CAAC;IACzC,OAAO,CACH,gBAAM,IAAI,EAAC,OAAO,EAAC,GAAG,EAAC,KAAK,aACxB,2BACI,eAAM,OAAO,EAAC,OAAO,GAAG,EACxB,eAAM,IAAI,EAAC,UAAU,EAAC,OAAO,EAAC,qCAAqC,GAAG,EACtE,eAAM,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAC,gBAAgB,GAAG,EAC/C,2DAA2C,EAC3C,eAAM,GAAG,EAAC,YAAY,EAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,WAAW,IAAI,CAAC,OAAO,0BAA0B,GAAI,IAC1F,EACP,2BACI,YAAG,SAAS,EAAC,WAAW,EAAC,IAAI,EAAC,OAAO,oCAAwB,EAC7D,gBAAM,EAAE,EAAC,MAAM,EAAC,QAAQ,EAAC,IAAI,EAAC,SAAS,EAAC,8CAA8C,aAClF,aAAI,SAAS,EAAC,wCAAwC,+BAAoB,EAC1E,YAAG,SAAS,EAAC,cAAc,mDAAuC,EAClE,YAAG,SAAS,EAAC,MAAM,YAAC,YAAG,SAAS,EAAC,qBAAqB,EAAC,IAAI,EAAE,IAAI,CAAC,GAAG,kDAAuC,GAAI,IAC7G,IACJ,IACJ,CACV,CAAC;AACN,CAAC","sourcesContent":["interface PageData {\n cdn: string;\n version: string;\n www: string;\n}\n\nexport default function NotFoundPage(data: Record<string, unknown>) {\n const page = data as unknown as PageData;\n return (\n <html lang=\"en-US\" dir=\"ltr\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta name=\"robots\" content=\"noindex,follow\" />\n <title>Page not found | White Label</title>\n <link rel=\"stylesheet\" href={`${page.cdn}release/${page.version}/assets/style/global.css`} />\n </head>\n <body>\n <a className=\"skip-link\" href=\"#main\">Skip to the message</a>\n <main id=\"main\" tabindex=\"-1\" className=\"mx-auto max-w-5xl px-4 py-24 sm:px-6 lg:px-8\">\n <h1 className=\"text-5xl font-extrabold tracking-tight\">Page not found</h1>\n <p className=\"mt-6 text-lg\">The requested page does not exist.</p>\n <p className=\"mt-6\"><a className=\"font-bold underline\" href={page.www}>Return to the White Label starter</a></p>\n </main>\n </body>\n </html>\n );\n}\n"]}
@@ -1,2 +1,8 @@
1
- /** @module app/assets/script/index */
2
- export {};
1
+ /**
2
+ * Browser entry points should stay small.
3
+ *
4
+ * The task application owns its own composition and lifecycle, so the entry
5
+ * module has one obvious responsibility: start the feature for this document.
6
+ */
7
+ export { initializeTaskApplication } from './tasks/TaskApplication.js';
8
+ export { normalizeTaskFilter } from './tasks/TaskRouter.js';
@@ -1,2 +1,12 @@
1
- export {};
1
+ /** @module app/assets/script/index */
2
+ import { initializeTaskApplication } from './tasks/TaskApplication.js';
3
+ /**
4
+ * Browser entry points should stay small.
5
+ *
6
+ * The task application owns its own composition and lifecycle, so the entry
7
+ * module has one obvious responsibility: start the feature for this document.
8
+ */
9
+ export { initializeTaskApplication } from './tasks/TaskApplication.js';
10
+ export { normalizeTaskFilter } from './tasks/TaskRouter.js';
11
+ initializeTaskApplication(document);
2
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../app/assets/script/index.ts"],"names":[],"mappings":"","sourcesContent":["/** @module app/assets/script/index */\nexport {};\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../app/assets/script/index.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AAErE;;;;;GAKG;AACH,OAAO,EAAC,yBAAyB,EAAC,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAC,mBAAmB,EAAC,MAAM,uBAAuB,CAAC;AAE1D,yBAAyB,CAAC,QAAQ,CAAC,CAAC","sourcesContent":["/** @module app/assets/script/index */\nimport {initializeTaskApplication} from './tasks/TaskApplication.js';\n\n/**\n * Browser entry points should stay small.\n *\n * The task application owns its own composition and lifecycle, so the entry\n * module has one obvious responsibility: start the feature for this document.\n */\nexport {initializeTaskApplication} from './tasks/TaskApplication.js';\nexport {normalizeTaskFilter} from './tasks/TaskRouter.js';\n\ninitializeTaskApplication(document);\n"]}
@@ -0,0 +1,20 @@
1
+ import type Router from 'white-label-router';
2
+ import type View from 'white-label-view';
3
+ import { type TaskMediator } from './TaskMediator.js';
4
+ import TaskModel from './TaskModel.js';
5
+ /** Public handles make the example straightforward to test and tear down. */
6
+ export interface TaskApplication {
7
+ mediator: TaskMediator;
8
+ model: TaskModel;
9
+ router: Router;
10
+ view: View;
11
+ destroy(): void;
12
+ }
13
+ /**
14
+ * Compose the application at one explicit boundary.
15
+ *
16
+ * This is the only module that knows every moving part. Model, View, Router,
17
+ * and Mediator stay independently understandable, while this file documents
18
+ * the small amount of wiring needed to make them cooperate in a real feature.
19
+ */
20
+ export declare function initializeTaskApplication(documentRoot: Document): TaskApplication;
@@ -0,0 +1,63 @@
1
+ import { createInitialTaskState, describeTaskStatus } from '../../view/examples/tasks/task-state.js';
2
+ import { createTaskMediator } from './TaskMediator.js';
3
+ import TaskModel from './TaskModel.js';
4
+ import { createTaskRouter } from './TaskRouter.js';
5
+ import { createTaskView } from './TaskView.js';
6
+ /**
7
+ * Compose the application at one explicit boundary.
8
+ *
9
+ * This is the only module that knows every moving part. Model, View, Router,
10
+ * and Mediator stay independently understandable, while this file documents
11
+ * the small amount of wiring needed to make them cooperate in a real feature.
12
+ */
13
+ export function initializeTaskApplication(documentRoot) {
14
+ const parentElement = documentRoot.querySelector('[data-task-example]');
15
+ if (!parentElement) {
16
+ throw new TypeError('White Label task example requires a data-task-example container');
17
+ }
18
+ const mediator = createTaskMediator();
19
+ const model = new TaskModel(createInitialTaskState());
20
+ const view = createTaskView(parentElement, model);
21
+ const status = parentElement.querySelector('[data-task-status]');
22
+ const updateStatus = () => { status.textContent = describeTaskStatus(model.get()); };
23
+ const addTask = (title) => { model.add(title); updateStatus(); };
24
+ const toggleTask = (id) => { model.toggle(id); updateStatus(); };
25
+ const setFilter = (filter) => { model.setFilter(filter); updateStatus(); };
26
+ mediator.on('task:add', addTask);
27
+ mediator.on('task:toggle', toggleTask);
28
+ mediator.on('task:filter', setFilter);
29
+ const delegated = view.delegate(parentElement);
30
+ delegated.on('submit', '[data-task-form]', (event) => {
31
+ event.preventDefault();
32
+ const form = event.target;
33
+ const input = form.querySelector('[name="task"]');
34
+ mediator.emit('task:add', input.value);
35
+ parentElement.querySelector('[name="task"]').focus();
36
+ });
37
+ delegated.on('change', '[data-task-toggle]', (event) => {
38
+ const input = event.target;
39
+ const id = Number(input.dataset.taskId);
40
+ mediator.emit('task:toggle', id);
41
+ const focusTarget = parentElement.querySelector(`[data-task-id="${id}"]`)
42
+ ?? parentElement.querySelector('[data-task-filter][aria-current="page"]');
43
+ focusTarget.focus();
44
+ });
45
+ const router = createTaskRouter(documentRoot, mediator);
46
+ return {
47
+ mediator,
48
+ model,
49
+ router,
50
+ view,
51
+ destroy() {
52
+ delegated.clear();
53
+ router.destroy();
54
+ mediator.removeListener('task:add', addTask);
55
+ mediator.removeListener('task:toggle', toggleTask);
56
+ mediator.removeListener('task:filter', setFilter);
57
+ view.destroy();
58
+ model.destroy();
59
+ mediator.destroy();
60
+ }
61
+ };
62
+ }
63
+ //# sourceMappingURL=TaskApplication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskApplication.js","sourceRoot":"","sources":["../../../../../app/assets/script/tasks/TaskApplication.ts"],"names":[],"mappings":"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","sourcesContent":["import type Router from 'white-label-router';\nimport type View from 'white-label-view';\nimport {createInitialTaskState, describeTaskStatus} from '../../view/examples/tasks/task-state.js';\nimport {createTaskMediator, type TaskMediator} from './TaskMediator.js';\nimport TaskModel from './TaskModel.js';\nimport {createTaskRouter} from './TaskRouter.js';\nimport {createTaskView} from './TaskView.js';\n\n/** Public handles make the example straightforward to test and tear down. */\nexport interface TaskApplication {\n mediator: TaskMediator;\n model: TaskModel;\n router: Router;\n view: View;\n destroy(): void;\n}\n\n/**\n * Compose the application at one explicit boundary.\n *\n * This is the only module that knows every moving part. Model, View, Router,\n * and Mediator stay independently understandable, while this file documents\n * the small amount of wiring needed to make them cooperate in a real feature.\n */\nexport function initializeTaskApplication(documentRoot: Document): TaskApplication {\n const parentElement = documentRoot.querySelector<HTMLElement>('[data-task-example]');\n if (!parentElement) {throw new TypeError('White Label task example requires a data-task-example container');}\n\n const mediator = createTaskMediator();\n const model = new TaskModel(createInitialTaskState());\n const view = createTaskView(parentElement, model);\n const status = parentElement.querySelector<HTMLElement>('[data-task-status]')!;\n const updateStatus = (): void => {status.textContent = describeTaskStatus(model.get());};\n\n const addTask = (title: string): void => {model.add(title); updateStatus();};\n const toggleTask = (id: number): void => {model.toggle(id); updateStatus();};\n const setFilter = (filter: 'all' | 'active' | 'completed'): void => {model.setFilter(filter); updateStatus();};\n mediator.on('task:add', addTask);\n mediator.on('task:toggle', toggleTask);\n mediator.on('task:filter', setFilter);\n\n const delegated = view.delegate(parentElement);\n delegated.on('submit', '[data-task-form]', (event: Event) => {\n event.preventDefault();\n const form = event.target as HTMLFormElement;\n const input = form.querySelector<HTMLInputElement>('[name=\"task\"]')!;\n mediator.emit('task:add', input.value);\n parentElement.querySelector<HTMLInputElement>('[name=\"task\"]')!.focus();\n });\n delegated.on('change', '[data-task-toggle]', (event: Event) => {\n const input = event.target as HTMLInputElement;\n const id = Number(input.dataset.taskId);\n mediator.emit('task:toggle', id);\n const focusTarget = parentElement.querySelector<HTMLInputElement>(`[data-task-id=\"${id}\"]`)\n ?? parentElement.querySelector<HTMLAnchorElement>('[data-task-filter][aria-current=\"page\"]')!;\n focusTarget.focus();\n });\n\n const router = createTaskRouter(documentRoot, mediator);\n\n return {\n mediator,\n model,\n router,\n view,\n destroy() {\n delegated.clear();\n router.destroy();\n mediator.removeListener('task:add', addTask);\n mediator.removeListener('task:toggle', toggleTask);\n mediator.removeListener('task:filter', setFilter);\n view.destroy();\n model.destroy();\n mediator.destroy();\n }\n };\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import Mediator from 'white-label-mediator';
2
+ import type { TaskFilter } from '../../view/examples/tasks/task-state.js';
3
+ /**
4
+ * Document the event protocol in one place even though the currently installed
5
+ * Mediator exposes the EventEmitter-compatible runtime without a generic map.
6
+ * Keeping names and payloads explicit still gives learners one clear contract
7
+ * to follow when tracing the example.
8
+ */
9
+ export type TaskEvents = {
10
+ 'task:add': [title: string];
11
+ 'task:filter': [filter: TaskFilter];
12
+ 'task:toggle': [id: number];
13
+ };
14
+ export type TaskMediator = Mediator;
15
+ export declare function createTaskMediator(): TaskMediator;
@@ -0,0 +1,5 @@
1
+ import Mediator from 'white-label-mediator';
2
+ export function createTaskMediator() {
3
+ return new Mediator().initialize();
4
+ }
5
+ //# sourceMappingURL=TaskMediator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskMediator.js","sourceRoot":"","sources":["../../../../../app/assets/script/tasks/TaskMediator.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,sBAAsB,CAAC;AAiB5C,MAAM,UAAU,kBAAkB;IAC9B,OAAO,IAAI,QAAQ,EAAE,CAAC,UAAU,EAAE,CAAC;AACvC,CAAC","sourcesContent":["import Mediator from 'white-label-mediator';\nimport type {TaskFilter} from '../../view/examples/tasks/task-state.js';\n\n/**\n * Document the event protocol in one place even though the currently installed\n * Mediator exposes the EventEmitter-compatible runtime without a generic map.\n * Keeping names and payloads explicit still gives learners one clear contract\n * to follow when tracing the example.\n */\nexport type TaskEvents = {\n 'task:add': [title: string];\n 'task:filter': [filter: TaskFilter];\n 'task:toggle': [id: number];\n};\n\nexport type TaskMediator = Mediator;\n\nexport function createTaskMediator(): TaskMediator {\n return new Mediator().initialize();\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import { Model } from 'white-label-model';
2
+ import type { TaskFilter, TaskState } from '../../view/examples/tasks/task-state.js';
3
+ /**
4
+ * Domain state belongs in Model, not in the DOM or router.
5
+ *
6
+ * Extending Model with small domain methods gives application code meaningful
7
+ * operations while preserving White Label's observable state contract.
8
+ */
9
+ export default class TaskModel extends Model<TaskState> {
10
+ add(title: string): boolean;
11
+ toggle(id: number): boolean;
12
+ setFilter(filter: TaskFilter): boolean;
13
+ }
@@ -0,0 +1,33 @@
1
+ import { Model } from 'white-label-model';
2
+ /**
3
+ * Domain state belongs in Model, not in the DOM or router.
4
+ *
5
+ * Extending Model with small domain methods gives application code meaningful
6
+ * operations while preserving White Label's observable state contract.
7
+ */
8
+ export default class TaskModel extends Model {
9
+ add(title) {
10
+ const trimmed = title.trim();
11
+ if (!trimmed) {
12
+ return false;
13
+ }
14
+ const state = this.get();
15
+ const nextId = Math.max(0, ...state.tasks.map(task => task.id)) + 1;
16
+ return this.update({ tasks: [...state.tasks, { complete: false, id: nextId, title: trimmed }] });
17
+ }
18
+ toggle(id) {
19
+ const state = this.get();
20
+ const index = state.tasks.findIndex(task => task.id === id);
21
+ if (index < 0) {
22
+ return false;
23
+ }
24
+ const tasks = [...state.tasks];
25
+ const task = tasks[index];
26
+ tasks[index] = { ...task, complete: !task.complete };
27
+ return this.update({ tasks });
28
+ }
29
+ setFilter(filter) {
30
+ return this.update({ filter });
31
+ }
32
+ }
33
+ //# sourceMappingURL=TaskModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskModel.js","sourceRoot":"","sources":["../../../../../app/assets/script/tasks/TaskModel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAC,MAAM,mBAAmB,CAAC;AAGxC;;;;;GAKG;AACH,MAAM,CAAC,OAAO,OAAO,SAAU,SAAQ,KAAgB;IACnD,GAAG,CAAC,KAAa;QACb,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;YAAA,OAAO,KAAK,CAAC;QAAA,CAAC;QAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC,MAAM,CAAC,EAAC,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,EAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAC,CAAC,EAAC,CAAC,CAAC;IACjG,CAAC;IAED,MAAM,CAAC,EAAU;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5D,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YAAA,OAAO,KAAK,CAAC;QAAA,CAAC;QAC9B,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAE,CAAC;QAC3B,KAAK,CAAC,KAAK,CAAC,GAAG,EAAC,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAC,CAAC;QACnD,OAAO,IAAI,CAAC,MAAM,CAAC,EAAC,KAAK,EAAC,CAAC,CAAC;IAChC,CAAC;IAED,SAAS,CAAC,MAAkB;QACxB,OAAO,IAAI,CAAC,MAAM,CAAC,EAAC,MAAM,EAAC,CAAC,CAAC;IACjC,CAAC;CACJ","sourcesContent":["import {Model} from 'white-label-model';\nimport type {TaskFilter, TaskState} from '../../view/examples/tasks/task-state.js';\n\n/**\n * Domain state belongs in Model, not in the DOM or router.\n *\n * Extending Model with small domain methods gives application code meaningful\n * operations while preserving White Label's observable state contract.\n */\nexport default class TaskModel extends Model<TaskState> {\n add(title: string): boolean {\n const trimmed = title.trim();\n if (!trimmed) {return false;}\n const state = this.get();\n const nextId = Math.max(0, ...state.tasks.map(task => task.id)) + 1;\n return this.update({tasks: [...state.tasks, {complete: false, id: nextId, title: trimmed}]});\n }\n\n toggle(id: number): boolean {\n const state = this.get();\n const index = state.tasks.findIndex(task => task.id === id);\n if (index < 0) {return false;}\n const tasks = [...state.tasks];\n const task = tasks[index]!;\n tasks[index] = {...task, complete: !task.complete};\n return this.update({tasks});\n }\n\n setFilter(filter: TaskFilter): boolean {\n return this.update({filter});\n }\n}\n"]}