@fuzdev/fuz_ui 0.206.7 → 0.206.9

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 (221) hide show
  1. package/README.md +3 -3
  2. package/dist/Alert.svelte +21 -25
  3. package/dist/Alert.svelte.d.ts.map +1 -1
  4. package/dist/ApiDeclarationList.svelte +2 -2
  5. package/dist/ApiDeclarationList.svelte.d.ts.map +1 -1
  6. package/dist/ApiIndex.svelte +4 -4
  7. package/dist/ApiIndex.svelte.d.ts.map +1 -1
  8. package/dist/ApiModule.svelte +13 -10
  9. package/dist/ApiModule.svelte.d.ts.map +1 -1
  10. package/dist/ApiModulesList.svelte +2 -2
  11. package/dist/ApiModulesList.svelte.d.ts.map +1 -1
  12. package/dist/Breadcrumb.svelte +31 -19
  13. package/dist/Breadcrumb.svelte.d.ts.map +1 -1
  14. package/dist/Card.svelte +17 -17
  15. package/dist/Card.svelte.d.ts.map +1 -1
  16. package/dist/ColorSchemeInput.svelte +6 -6
  17. package/dist/ColorSchemeInput.svelte.d.ts.map +1 -1
  18. package/dist/Contextmenu.svelte +6 -4
  19. package/dist/Contextmenu.svelte.d.ts.map +1 -1
  20. package/dist/ContextmenuEntry.svelte +8 -8
  21. package/dist/ContextmenuEntry.svelte.d.ts.map +1 -1
  22. package/dist/ContextmenuIcon.svelte +3 -3
  23. package/dist/ContextmenuIcon.svelte.d.ts.map +1 -1
  24. package/dist/ContextmenuLinkEntry.svelte +14 -14
  25. package/dist/ContextmenuLinkEntry.svelte.d.ts.map +1 -1
  26. package/dist/ContextmenuMenu.svelte +10 -10
  27. package/dist/ContextmenuMenu.svelte.d.ts.map +1 -1
  28. package/dist/ContextmenuRoot.svelte +7 -7
  29. package/dist/ContextmenuRootForSafariCompatibility.svelte +15 -15
  30. package/dist/ContextmenuSeparator.svelte +2 -2
  31. package/dist/ContextmenuSubmenu.svelte +15 -12
  32. package/dist/ContextmenuSubmenu.svelte.d.ts.map +1 -1
  33. package/dist/ContextmenuTextEntry.svelte +4 -4
  34. package/dist/ContextmenuTextEntry.svelte.d.ts.map +1 -1
  35. package/dist/CopyToClipboard.svelte +7 -7
  36. package/dist/CopyToClipboard.svelte.d.ts.map +1 -1
  37. package/dist/DeclarationDetail.svelte +35 -28
  38. package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
  39. package/dist/DeclarationLink.svelte +7 -7
  40. package/dist/DeclarationLink.svelte.d.ts.map +1 -1
  41. package/dist/Details.svelte +3 -3
  42. package/dist/Details.svelte.d.ts.map +1 -1
  43. package/dist/Dialog.svelte +7 -7
  44. package/dist/Dialog.svelte.d.ts.map +1 -1
  45. package/dist/DialogContent.svelte +6 -6
  46. package/dist/DialogContent.svelte.d.ts.map +1 -1
  47. package/dist/Docs.svelte +14 -12
  48. package/dist/Docs.svelte.d.ts.map +1 -1
  49. package/dist/DocsContent.svelte +4 -4
  50. package/dist/DocsContent.svelte.d.ts.map +1 -1
  51. package/dist/DocsFooter.svelte +7 -8
  52. package/dist/DocsFooter.svelte.d.ts.map +1 -1
  53. package/dist/DocsLink.svelte +24 -21
  54. package/dist/DocsLink.svelte.d.ts.map +1 -1
  55. package/dist/DocsList.svelte +3 -3
  56. package/dist/DocsList.svelte.d.ts.map +1 -1
  57. package/dist/DocsMenu.svelte +13 -10
  58. package/dist/DocsMenu.svelte.d.ts.map +1 -1
  59. package/dist/DocsMenuHeader.svelte +2 -2
  60. package/dist/DocsMenuHeader.svelte.d.ts.map +1 -1
  61. package/dist/DocsPageLinks.svelte +9 -7
  62. package/dist/DocsPageLinks.svelte.d.ts.map +1 -1
  63. package/dist/DocsPrimaryNav.svelte +5 -5
  64. package/dist/DocsPrimaryNav.svelte.d.ts.map +1 -1
  65. package/dist/DocsSearch.svelte +3 -3
  66. package/dist/DocsSearch.svelte.d.ts.map +1 -1
  67. package/dist/DocsSecondaryNav.svelte +3 -3
  68. package/dist/DocsSecondaryNav.svelte.d.ts.map +1 -1
  69. package/dist/DocsTertiaryNav.svelte +8 -8
  70. package/dist/DocsTertiaryNav.svelte.d.ts.map +1 -1
  71. package/dist/EcosystemLinks.svelte +3 -3
  72. package/dist/EcosystemLinks.svelte.d.ts.map +1 -1
  73. package/dist/EcosystemLinksPanel.svelte +2 -2
  74. package/dist/EcosystemLinksPanel.svelte.d.ts.map +1 -1
  75. package/dist/GithubLink.svelte +10 -10
  76. package/dist/GithubLink.svelte.d.ts.map +1 -1
  77. package/dist/Glyph.svelte +4 -2
  78. package/dist/Glyph.svelte.d.ts.map +1 -1
  79. package/dist/Hashlink.svelte +5 -5
  80. package/dist/Hashlink.svelte.d.ts.map +1 -1
  81. package/dist/HueInput.svelte +5 -5
  82. package/dist/HueInput.svelte.d.ts.map +1 -1
  83. package/dist/ImgOrSvg.svelte +20 -20
  84. package/dist/ImgOrSvg.svelte.d.ts.map +1 -1
  85. package/dist/LibraryDetail.svelte +16 -16
  86. package/dist/LibraryDetail.svelte.d.ts.map +1 -1
  87. package/dist/LibrarySummary.svelte +10 -8
  88. package/dist/LibrarySummary.svelte.d.ts.map +1 -1
  89. package/dist/MdnLink.svelte +8 -8
  90. package/dist/MdnLink.svelte.d.ts.map +1 -1
  91. package/dist/ModuleLink.svelte +5 -5
  92. package/dist/ModuleLink.svelte.d.ts.map +1 -1
  93. package/dist/PasteFromClipboard.svelte +1 -1
  94. package/dist/PasteFromClipboard.svelte.d.ts.map +1 -1
  95. package/dist/PendingAnimation.svelte +12 -11
  96. package/dist/PendingAnimation.svelte.d.ts.map +1 -1
  97. package/dist/PendingButton.svelte +3 -3
  98. package/dist/PendingButton.svelte.d.ts.map +1 -1
  99. package/dist/ProjectActivityChart.svelte +137 -0
  100. package/dist/ProjectActivityChart.svelte.d.ts +30 -0
  101. package/dist/ProjectActivityChart.svelte.d.ts.map +1 -0
  102. package/dist/ProjectLinks.svelte +29 -48
  103. package/dist/ProjectLinks.svelte.d.ts.map +1 -1
  104. package/dist/Redirect.svelte +12 -8
  105. package/dist/Redirect.svelte.d.ts.map +1 -1
  106. package/dist/Sparkline.svelte +131 -0
  107. package/dist/Sparkline.svelte.d.ts +42 -0
  108. package/dist/Sparkline.svelte.d.ts.map +1 -0
  109. package/dist/Spiders.svelte +5 -5
  110. package/dist/Spiders.svelte.d.ts.map +1 -1
  111. package/dist/StyleVariableButton.svelte +6 -6
  112. package/dist/StyleVariableButton.svelte.d.ts.map +1 -1
  113. package/dist/Svg.svelte +5 -5
  114. package/dist/Svg.svelte.d.ts.map +1 -1
  115. package/dist/Teleport.svelte +2 -2
  116. package/dist/Teleport.svelte.d.ts.map +1 -1
  117. package/dist/ThemeInput.svelte +10 -8
  118. package/dist/ThemeInput.svelte.d.ts.map +1 -1
  119. package/dist/ThemeRoot.svelte +10 -11
  120. package/dist/ThemeRoot.svelte.d.ts.map +1 -1
  121. package/dist/TomeContent.svelte +11 -11
  122. package/dist/TomeContent.svelte.d.ts.map +1 -1
  123. package/dist/TomeHeader.svelte +6 -6
  124. package/dist/TomeLink.svelte +7 -7
  125. package/dist/TomeLink.svelte.d.ts.map +1 -1
  126. package/dist/TomeSection.svelte +8 -8
  127. package/dist/TomeSection.svelte.d.ts.map +1 -1
  128. package/dist/TomeSectionHeader.svelte +12 -12
  129. package/dist/TomeSectionHeader.svelte.d.ts.map +1 -1
  130. package/dist/TypeLink.svelte +11 -11
  131. package/dist/TypeLink.svelte.d.ts.map +1 -1
  132. package/dist/alert.js +1 -1
  133. package/dist/api_search.svelte.d.ts.map +1 -1
  134. package/dist/api_search.svelte.js +5 -7
  135. package/dist/autofocus.svelte.d.ts.map +1 -1
  136. package/dist/context_helpers.js +1 -1
  137. package/dist/contextmenu_helpers.d.ts.map +1 -1
  138. package/dist/contextmenu_helpers.js +3 -3
  139. package/dist/contextmenu_state.svelte.d.ts.map +1 -1
  140. package/dist/contextmenu_state.svelte.js +4 -4
  141. package/dist/csp.d.ts.map +1 -1
  142. package/dist/csp.js +27 -27
  143. package/dist/csp_of_fuzdev.d.ts.map +1 -1
  144. package/dist/csp_of_fuzdev.js +1 -1
  145. package/dist/declaration.svelte.d.ts +44 -256
  146. package/dist/declaration.svelte.d.ts.map +1 -1
  147. package/dist/declaration.svelte.js +132 -52
  148. package/dist/declaration_contextmenu.d.ts.map +1 -1
  149. package/dist/declaration_contextmenu.js +2 -2
  150. package/dist/docs_helpers.svelte.d.ts.map +1 -1
  151. package/dist/icons.js +200 -200
  152. package/dist/intersect.svelte.d.ts.map +1 -1
  153. package/dist/library.svelte.d.ts +31 -18
  154. package/dist/library.svelte.d.ts.map +1 -1
  155. package/dist/library.svelte.js +76 -21
  156. package/dist/logos.js +42 -42
  157. package/dist/module.svelte.d.ts +30 -37
  158. package/dist/module.svelte.d.ts.map +1 -1
  159. package/dist/module.svelte.js +88 -35
  160. package/dist/module_contextmenu.d.ts.map +1 -1
  161. package/dist/module_contextmenu.js +2 -2
  162. package/dist/project_stats.d.ts +121 -0
  163. package/dist/project_stats.d.ts.map +1 -0
  164. package/dist/project_stats.js +153 -0
  165. package/dist/project_stats_data.d.ts +3 -0
  166. package/dist/project_stats_data.d.ts.map +1 -0
  167. package/dist/project_stats_data.js +469 -0
  168. package/dist/project_stats_update.task.d.ts +4 -0
  169. package/dist/project_stats_update.task.d.ts.map +1 -0
  170. package/dist/project_stats_update.task.js +50 -0
  171. package/dist/projects.d.ts +17 -0
  172. package/dist/projects.d.ts.map +1 -0
  173. package/dist/projects.js +20 -0
  174. package/dist/publish.task.d.ts +3 -0
  175. package/dist/publish.task.d.ts.map +1 -0
  176. package/dist/publish.task.js +23 -0
  177. package/dist/site.svelte.d.ts.map +1 -1
  178. package/dist/sparkline.d.ts +102 -0
  179. package/dist/sparkline.d.ts.map +1 -0
  180. package/dist/sparkline.js +63 -0
  181. package/dist/style_variable_helpers.svelte.d.ts.map +1 -1
  182. package/dist/svg.d.ts.map +1 -1
  183. package/dist/theme_state.svelte.d.ts.map +1 -1
  184. package/dist/theme_state.svelte.js +1 -1
  185. package/dist/tome.d.ts.map +1 -1
  186. package/dist/tome.js +1 -1
  187. package/dist/vite_plugin_pkg_json.d.ts.map +1 -1
  188. package/dist/vite_plugin_pkg_json.js +1 -1
  189. package/package.json +3 -22
  190. package/src/lib/alert.ts +3 -3
  191. package/src/lib/api_search.svelte.ts +9 -11
  192. package/src/lib/autofocus.svelte.ts +2 -2
  193. package/src/lib/context_helpers.ts +2 -2
  194. package/src/lib/contextmenu_helpers.ts +21 -21
  195. package/src/lib/contextmenu_state.svelte.ts +32 -34
  196. package/src/lib/csp.ts +42 -54
  197. package/src/lib/csp_of_fuzdev.ts +2 -2
  198. package/src/lib/declaration.svelte.ts +157 -62
  199. package/src/lib/declaration_contextmenu.ts +5 -5
  200. package/src/lib/dialog.ts +1 -1
  201. package/src/lib/docs_helpers.svelte.ts +9 -9
  202. package/src/lib/icons.ts +205 -205
  203. package/src/lib/intersect.svelte.ts +5 -5
  204. package/src/lib/library.svelte.ts +103 -36
  205. package/src/lib/library_helpers.ts +2 -2
  206. package/src/lib/logos.ts +43 -43
  207. package/src/lib/module.svelte.ts +98 -49
  208. package/src/lib/module_contextmenu.ts +4 -4
  209. package/src/lib/project_stats.ts +220 -0
  210. package/src/lib/project_stats_data.ts +472 -0
  211. package/src/lib/project_stats_update.task.ts +53 -0
  212. package/src/lib/projects.ts +49 -0
  213. package/src/lib/publish.task.ts +22 -0
  214. package/src/lib/site.svelte.ts +5 -5
  215. package/src/lib/sparkline.ts +173 -0
  216. package/src/lib/storage.ts +2 -2
  217. package/src/lib/style_variable_helpers.svelte.ts +2 -2
  218. package/src/lib/svg.ts +2 -2
  219. package/src/lib/theme_state.svelte.ts +8 -8
  220. package/src/lib/tome.ts +8 -8
  221. package/src/lib/vite_plugin_pkg_json.ts +8 -8
@@ -1,12 +1,13 @@
1
1
  import type {
2
2
  ModuleJsonInput,
3
3
  ReExportJsonInput,
4
- ExternalReExportJsonInput,
4
+ ExternalReExportJsonInput
5
5
  } from 'svelte-docinfo/types.js';
6
+ import { EMPTY_ARRAY } from '@fuzdev/fuz_util/array.ts';
6
7
 
7
- import {Declaration} from './declaration.svelte.ts';
8
- import type {Library} from './library.svelte.ts';
9
- import {url_github_file} from '@fuzdev/fuz_util/package_helpers.ts';
8
+ import { Declaration } from './declaration.svelte.ts';
9
+ import type { Library } from './library.svelte.ts';
10
+ import { url_github_file } from '@fuzdev/fuz_util/package_helpers.ts';
10
11
 
11
12
  /**
12
13
  * Groups entries by a string key, with groups sorted by key. Entries within
@@ -14,7 +15,7 @@ import {url_github_file} from '@fuzdev/fuz_util/package_helpers.ts';
14
15
  */
15
16
  const group_sorted = <T>(
16
17
  entries: Array<T>,
17
- get_key: (entry: T) => string,
18
+ get_key: (entry: T) => string
18
19
  ): Map<string, Array<T>> => {
19
20
  const by_key: Map<string, Array<T>> = new Map();
20
21
  for (const entry of entries) {
@@ -32,8 +33,11 @@ const group_sorted = <T>(
32
33
  /**
33
34
  * Rich runtime representation of a module with computed properties.
34
35
  *
35
- * Wraps svelte-docinfo's `ModuleJson` with reactive derivations,
36
- * URL generation, and `Declaration` instances.
36
+ * Wraps svelte-docinfo's `ModuleJson` with computed properties, URL
37
+ * generation, and `Declaration` instances.
38
+ *
39
+ * Computed properties are plain getters rather than `$derived` — see
40
+ * `Library` for why.
37
41
  *
38
42
  * @see {@link https://github.com/ryanatkn/svelte-docinfo svelte-docinfo} for the analysis library
39
43
  * @see `declaration.svelte.ts` for the `Declaration` wrapper class
@@ -57,28 +61,38 @@ export class Module {
57
61
  * A leading `./` or a `.js` runtime extension will not match. Top-level
58
62
  * files match by bare filename; nested files require the full sub-path.
59
63
  */
60
- path = $derived(this.module_json.path);
64
+ get path(): string {
65
+ return this.module_json.path;
66
+ }
61
67
 
62
68
  /**
63
69
  * Import-style path with ./ prefix.
64
70
  */
65
- path_import = $derived('./' + this.path);
71
+ get path_import(): string {
72
+ return './' + this.path;
73
+ }
66
74
 
67
- module_comment = $derived(this.module_json.moduleComment);
75
+ get module_comment(): string | undefined {
76
+ return this.module_json.moduleComment;
77
+ }
78
+
79
+ #declarations: Array<Declaration> | undefined;
68
80
 
69
81
  /**
70
82
  * Array of `Declaration` instances. Filters out default exports.
71
83
  */
72
- declarations = $derived(
73
- (this.module_json.declarations ?? [])
84
+ get declarations(): Array<Declaration> {
85
+ return (this.#declarations ??= (this.module_json.declarations ?? [])
74
86
  .filter((declaration_json) => declaration_json.name !== 'default')
75
- .map((declaration_json) => new Declaration(this, declaration_json)),
76
- );
87
+ .map((declaration_json) => new Declaration(this, declaration_json)));
88
+ }
77
89
 
78
90
  /**
79
91
  * API documentation URL for this module.
80
92
  */
81
- url_api = $derived(`/docs/api${this.library.url_prefix}/${this.path}`);
93
+ get url_api(): string {
94
+ return `/docs/api${this.library.url_prefix}/${this.path}`;
95
+ }
82
96
 
83
97
  /**
84
98
  * Public documentation link (if `homepage_url` is available).
@@ -86,42 +100,52 @@ export class Module {
86
100
  * Absolute URL into the library's own deployed docs, for rendering a
87
101
  * foreign library's modules on a different site (e.g. an aggregator).
88
102
  */
89
- url_api_full = $derived(
90
- this.library.homepage_url
91
- ? `${this.library.homepage_url.replace(/\/$/, '')}/docs/api/${this.path}`
92
- : undefined,
93
- );
103
+ get url_api_full(): string | undefined {
104
+ const { homepage_url } = this.library;
105
+ return homepage_url ? `${homepage_url.replace(/\/$/, '')}/docs/api/${this.path}` : undefined;
106
+ }
94
107
 
95
108
  /**
96
109
  * GitHub source URL.
97
110
  */
98
- url_github = $derived(
99
- this.library.repo_url
100
- ? url_github_file(this.library.repo_url, `src/lib/${this.path}`)
101
- : undefined,
102
- );
111
+ get url_github(): string | undefined {
112
+ const { repo_url } = this.library;
113
+ return repo_url ? url_github_file(repo_url, `src/lib/${this.path}`) : undefined;
114
+ }
103
115
 
104
116
  /**
105
117
  * Whether the module has renderable declarations (after the default-export
106
118
  * filter in `declarations`).
107
119
  */
108
- has_declarations: boolean = $derived(this.declarations.length > 0);
120
+ get has_declarations(): boolean {
121
+ return this.declarations.length > 0;
122
+ }
109
123
 
110
- has_module_comment: boolean = $derived(!!this.module_comment);
124
+ get has_module_comment(): boolean {
125
+ return !!this.module_comment;
126
+ }
111
127
 
112
128
  /**
113
129
  * Modules this imports (paths relative to src/lib).
114
130
  */
115
- dependencies = $derived(this.module_json.dependencies ?? []);
131
+ get dependencies(): NonNullable<ModuleJsonInput['dependencies']> {
132
+ return this.module_json.dependencies ?? EMPTY_ARRAY;
133
+ }
116
134
 
117
135
  /**
118
136
  * Modules that import this (paths relative to src/lib).
119
137
  */
120
- dependents = $derived(this.module_json.dependents ?? []);
138
+ get dependents(): NonNullable<ModuleJsonInput['dependents']> {
139
+ return this.module_json.dependents ?? EMPTY_ARRAY;
140
+ }
121
141
 
122
- has_dependencies: boolean = $derived(this.dependencies.length > 0);
142
+ get has_dependencies(): boolean {
143
+ return this.dependencies.length > 0;
144
+ }
123
145
 
124
- has_dependents: boolean = $derived(this.dependents.length > 0);
146
+ get has_dependents(): boolean {
147
+ return this.dependents.length > 0;
148
+ }
125
149
 
126
150
  /**
127
151
  * Same-name re-export edges in this module's source (`ModuleJson.reExports`)
@@ -129,9 +153,13 @@ export class Module {
129
153
  * then module. Renamed re-exports appear as declarations with `aliasOf`
130
154
  * instead, and star exports in `star_exports`.
131
155
  */
132
- re_exports = $derived(this.module_json.reExports ?? []);
156
+ get re_exports(): NonNullable<ModuleJsonInput['reExports']> {
157
+ return this.module_json.reExports ?? EMPTY_ARRAY;
158
+ }
133
159
 
134
- has_re_exports: boolean = $derived(this.re_exports.length > 0);
160
+ get has_re_exports(): boolean {
161
+ return this.re_exports.length > 0;
162
+ }
135
163
 
136
164
  /**
137
165
  * Modules fully re-exported via `export * from './module'`
@@ -139,55 +167,76 @@ export class Module {
139
167
  * not materialized as declarations or `re_exports` edges — this is their
140
168
  * sole encoding.
141
169
  */
142
- star_exports = $derived(this.module_json.starExports ?? []);
170
+ get star_exports(): NonNullable<ModuleJsonInput['starExports']> {
171
+ return this.module_json.starExports ?? EMPTY_ARRAY;
172
+ }
143
173
 
144
- has_star_exports: boolean = $derived(this.star_exports.length > 0);
174
+ get has_star_exports(): boolean {
175
+ return this.star_exports.length > 0;
176
+ }
177
+
178
+ #re_exports_by_module: Map<string, Array<ReExportJsonInput>> | undefined;
145
179
 
146
180
  /**
147
181
  * Re-export edges grouped by canonical module path, groups sorted by path.
148
182
  * Entries within each group keep their name-sorted serialized order.
149
183
  */
150
- re_exports_by_module: Map<string, Array<ReExportJsonInput>> = $derived(
151
- group_sorted(this.re_exports, (entry) => entry.module),
152
- );
184
+ get re_exports_by_module(): Map<string, Array<ReExportJsonInput>> {
185
+ return (this.#re_exports_by_module ??= group_sorted(this.re_exports, (entry) => entry.module));
186
+ }
153
187
 
154
188
  /**
155
189
  * Re-exports whose immediate target is an external package
156
190
  * (`ModuleJson.externalReExports`) — `export {x} from 'pkg'` and
157
191
  * `export * as ns from 'pkg'` forms, sorted by name then specifier.
158
192
  */
159
- external_re_exports = $derived(this.module_json.externalReExports ?? []);
193
+ get external_re_exports(): NonNullable<ModuleJsonInput['externalReExports']> {
194
+ return this.module_json.externalReExports ?? EMPTY_ARRAY;
195
+ }
196
+
197
+ get has_external_re_exports(): boolean {
198
+ return this.external_re_exports.length > 0;
199
+ }
160
200
 
161
- has_external_re_exports: boolean = $derived(this.external_re_exports.length > 0);
201
+ #external_re_exports_by_specifier: Map<string, Array<ExternalReExportJsonInput>> | undefined;
162
202
 
163
203
  /**
164
204
  * External re-exports grouped by package specifier, groups sorted by
165
205
  * specifier. Entries within each group keep their name-sorted serialized
166
206
  * order.
167
207
  */
168
- external_re_exports_by_specifier: Map<string, Array<ExternalReExportJsonInput>> = $derived(
169
- group_sorted(this.external_re_exports, (entry) => entry.specifier),
170
- );
208
+ get external_re_exports_by_specifier(): Map<string, Array<ExternalReExportJsonInput>> {
209
+ return (this.#external_re_exports_by_specifier ??= group_sorted(
210
+ this.external_re_exports,
211
+ (entry) => entry.specifier
212
+ ));
213
+ }
171
214
 
172
215
  /**
173
216
  * External packages fully re-exported via `export * from 'pkg'`
174
217
  * (`ModuleJson.externalStarExports`), in source order. The projected
175
218
  * names are unknown — the package isn't analyzed.
176
219
  */
177
- external_star_exports = $derived(this.module_json.externalStarExports ?? []);
220
+ get external_star_exports(): NonNullable<ModuleJsonInput['externalStarExports']> {
221
+ return this.module_json.externalStarExports ?? EMPTY_ARRAY;
222
+ }
178
223
 
179
- has_external_star_exports: boolean = $derived(this.external_star_exports.length > 0);
224
+ get has_external_star_exports(): boolean {
225
+ return this.external_star_exports.length > 0;
226
+ }
180
227
 
181
228
  /**
182
229
  * Whether the module re-exports anything in any form — same-name edges,
183
230
  * star exports, or the external variants of both.
184
231
  */
185
- has_any_re_exports: boolean = $derived(
186
- this.has_re_exports ||
232
+ get has_any_re_exports(): boolean {
233
+ return (
234
+ this.has_re_exports ||
187
235
  this.has_star_exports ||
188
236
  this.has_external_re_exports ||
189
- this.has_external_star_exports,
190
- );
237
+ this.has_external_star_exports
238
+ );
239
+ }
191
240
 
192
241
  constructor(library: Library, module_json: ModuleJsonInput) {
193
242
  this.library = library;
@@ -1,5 +1,5 @@
1
- import type {Module} from './module.svelte.ts';
2
- import type {ContextmenuParams} from './contextmenu_state.svelte.ts';
1
+ import type { Module } from './module.svelte.ts';
2
+ import type { ContextmenuParams } from './contextmenu_state.svelte.ts';
3
3
 
4
4
  // TODO @many expand contextmenus for docs
5
5
 
@@ -11,8 +11,8 @@ export const create_module_contextmenu = (module: Module): Array<ContextmenuPara
11
11
  entries.push({
12
12
  snippet: 'link',
13
13
  props: {
14
- href: module.url_github,
15
- },
14
+ href: module.url_github
15
+ }
16
16
  });
17
17
  }
18
18
 
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Types and pure helpers for project commit-activity stats.
3
+ *
4
+ * The committed dataset (`project_stats_data.ts`, written by the
5
+ * `project_stats_update` task) stores daily commit counts per project as the
6
+ * master granularity; coarser scopes like monthly are derived at runtime by
7
+ * rebucketing. All date handling is UTC — `YYYY-MM-DD` strings and "epoch
8
+ * days" (days since the Unix epoch) — so results are deterministic across
9
+ * machines.
10
+ *
11
+ * @module
12
+ */
13
+
14
+ const DAY_MS = 86_400_000;
15
+
16
+ /**
17
+ * Daily commit counts for one project.
18
+ */
19
+ export interface ProjectCommitStats {
20
+ /**
21
+ * Project name, matching `project_items`.
22
+ */
23
+ name: string;
24
+ /**
25
+ * UTC date (`YYYY-MM-DD`) of the first day bucket, the day of the first commit.
26
+ */
27
+ start_date: string;
28
+ /**
29
+ * Commits per UTC day from `start_date` through the snapshot's `end_date`, inclusive.
30
+ */
31
+ daily_counts: Array<number>;
32
+ }
33
+
34
+ /**
35
+ * A snapshot of commit stats across projects, sharing a single `end_date`.
36
+ */
37
+ export interface ProjectStatsSnapshot {
38
+ /**
39
+ * ISO datetime when the snapshot was collected.
40
+ */
41
+ generated_at: string;
42
+ /**
43
+ * UTC date (`YYYY-MM-DD`) of the final day bucket, shared by every project.
44
+ */
45
+ end_date: string;
46
+ projects: Array<ProjectCommitStats>;
47
+ }
48
+
49
+ /**
50
+ * A derived series of bucketed counts for one project.
51
+ */
52
+ export interface ProjectStatsSeries {
53
+ name: string;
54
+ counts: Array<number>;
55
+ }
56
+
57
+ /**
58
+ * Converts a UTC `YYYY-MM-DD` date to days since the Unix epoch.
59
+ */
60
+ export const epoch_day_from_date = (date: string): number => {
61
+ const [year, month, day] = date.split('-').map(Number);
62
+ return Date.UTC(year!, month! - 1, day) / DAY_MS;
63
+ };
64
+
65
+ /**
66
+ * Converts days since the Unix epoch to a UTC `YYYY-MM-DD` date.
67
+ */
68
+ export const date_from_epoch_day = (epoch_day: number): string =>
69
+ new Date(epoch_day * DAY_MS).toISOString().slice(0, 10);
70
+
71
+ /**
72
+ * Buckets commit timestamps into daily counts from the earliest commit's UTC
73
+ * day through `end_date`, inclusive.
74
+ *
75
+ * @param timestamps - commit times as Unix epoch seconds
76
+ * @param end_date - UTC date (`YYYY-MM-DD`) of the final bucket; later timestamps clamp into it
77
+ * @returns the start date and daily counts, or `null` if `timestamps` is empty
78
+ */
79
+ export const daily_counts_from_timestamps = (
80
+ timestamps: Array<number>,
81
+ end_date: string
82
+ ): { start_date: string; daily_counts: Array<number> } | null => {
83
+ if (timestamps.length === 0) return null;
84
+ const days = timestamps.map((t) => Math.floor((t * 1000) / DAY_MS));
85
+ const end_day = epoch_day_from_date(end_date);
86
+ let start_day = end_day;
87
+ for (const day of days) {
88
+ if (day < start_day) start_day = day;
89
+ }
90
+ const daily_counts: Array<number> = new Array(end_day - start_day + 1).fill(0);
91
+ for (const day of days) {
92
+ daily_counts[Math.min(day, end_day) - start_day]! += 1;
93
+ }
94
+ return { start_date: date_from_epoch_day(start_day), daily_counts };
95
+ };
96
+
97
+ /**
98
+ * Pads `daily_counts` with leading zeros so the series starts at
99
+ * `padded_start_date` instead of `start_date`.
100
+ *
101
+ * Returns the input array unchanged when `padded_start_date` is not earlier
102
+ * than `start_date`.
103
+ */
104
+ export const daily_counts_pad_start = (
105
+ daily_counts: Array<number>,
106
+ start_date: string,
107
+ padded_start_date: string
108
+ ): Array<number> => {
109
+ const pad = epoch_day_from_date(start_date) - epoch_day_from_date(padded_start_date);
110
+ if (pad <= 0) return daily_counts;
111
+ return (new Array(pad).fill(0) as Array<number>).concat(daily_counts);
112
+ };
113
+
114
+ /**
115
+ * Rebuckets daily counts into calendar-month (UTC) counts.
116
+ *
117
+ * The first and last buckets may cover partial months.
118
+ */
119
+ export const monthly_counts_from_daily = (
120
+ daily_counts: Array<number>,
121
+ start_date: string
122
+ ): Array<number> => {
123
+ const start_day = epoch_day_from_date(start_date);
124
+ const counts: Array<number> = [];
125
+ let current_month_key: number | null = null;
126
+ for (let i = 0; i < daily_counts.length; i++) {
127
+ const date = new Date((start_day + i) * DAY_MS);
128
+ const month_key = date.getUTCFullYear() * 12 + date.getUTCMonth();
129
+ if (month_key !== current_month_key) {
130
+ current_month_key = month_key;
131
+ counts.push(0);
132
+ }
133
+ counts[counts.length - 1]! += daily_counts[i]!;
134
+ }
135
+ return counts;
136
+ };
137
+
138
+ /**
139
+ * Rebuckets daily counts into 7-day counts, anchored at the end of the series
140
+ * so the newest bucket always covers a full 7 days.
141
+ *
142
+ * The first bucket may cover fewer days when the length isn't a multiple of 7.
143
+ */
144
+ export const weekly_counts_from_daily = (daily_counts: Array<number>): Array<number> => {
145
+ const counts: Array<number> = [];
146
+ for (let end = daily_counts.length; end > 0; end -= 7) {
147
+ let sum = 0;
148
+ for (let i = Math.max(0, end - 7); i < end; i++) {
149
+ sum += daily_counts[i]!;
150
+ }
151
+ counts.push(sum);
152
+ }
153
+ return counts.reverse();
154
+ };
155
+
156
+ const to_aligned_series = (
157
+ snapshot: ProjectStatsSnapshot,
158
+ rebucket: (daily_counts: Array<number>, start_date: string) => Array<number>
159
+ ): { series: Array<ProjectStatsSeries>; max: number } => {
160
+ if (snapshot.projects.length === 0) return { series: [], max: 0 };
161
+ let shared_start = snapshot.projects[0]!.start_date;
162
+ for (const project of snapshot.projects) {
163
+ if (project.start_date < shared_start) shared_start = project.start_date;
164
+ }
165
+ let max = 0;
166
+ const series = snapshot.projects.map((project) => {
167
+ const daily = daily_counts_pad_start(project.daily_counts, project.start_date, shared_start);
168
+ const counts = rebucket(daily, shared_start);
169
+ for (const count of counts) {
170
+ if (count > max) max = count;
171
+ }
172
+ return { name: project.name, counts };
173
+ });
174
+ return { series, max };
175
+ };
176
+
177
+ /**
178
+ * Derives aligned monthly series for every project in `snapshot`, sharing one
179
+ * time window and one scale.
180
+ *
181
+ * Each series spans from the earliest project's `start_date` through the
182
+ * snapshot's `end_date` (younger projects are zero-padded at the front), so
183
+ * all series have the same length and bucket boundaries. `max` is the largest
184
+ * monthly count across all projects, for rendering every series at the same
185
+ * vertical scale.
186
+ */
187
+ export const project_stats_to_monthly_series = (
188
+ snapshot: ProjectStatsSnapshot
189
+ ): { series: Array<ProjectStatsSeries>; max: number } =>
190
+ to_aligned_series(snapshot, monthly_counts_from_daily);
191
+
192
+ /**
193
+ * Derives aligned weekly series for every project in `snapshot`, sharing one
194
+ * time window and one scale — the weekly sibling of
195
+ * `project_stats_to_monthly_series`.
196
+ */
197
+ export const project_stats_to_weekly_series = (
198
+ snapshot: ProjectStatsSnapshot
199
+ ): { series: Array<ProjectStatsSeries>; max: number } =>
200
+ to_aligned_series(snapshot, weekly_counts_from_daily);
201
+
202
+ const start_date_formatter = new Intl.DateTimeFormat('en-US', {
203
+ month: 'long',
204
+ day: 'numeric',
205
+ year: 'numeric',
206
+ timeZone: 'UTC'
207
+ });
208
+
209
+ /**
210
+ * Formats a one-line human-readable summary of a project's commit stats,
211
+ * e.g. `"gro — 2,386 commits since August 7, 2019"`. Used for accessible
212
+ * labels and tooltips.
213
+ */
214
+ export const project_stats_label = (project: ProjectCommitStats): string => {
215
+ let total = 0;
216
+ for (const count of project.daily_counts) {
217
+ total += count;
218
+ }
219
+ return `${project.name} — ${total.toLocaleString('en-US')} commits since ${start_date_formatter.format(new Date(project.start_date))}`;
220
+ };