@fuzdev/fuz_ui 0.191.4 → 0.193.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 (77) hide show
  1. package/dist/ApiIndex.svelte +3 -3
  2. package/dist/ApiModule.svelte +2 -2
  3. package/dist/ApiModulesList.svelte +3 -3
  4. package/dist/ColorSchemeInput.svelte +6 -6
  5. package/dist/ContextmenuLinkEntry.svelte +1 -1
  6. package/dist/ContextmenuRoot.svelte +10 -10
  7. package/dist/ContextmenuRootForSafariCompatibility.svelte +19 -19
  8. package/dist/ContextmenuSeparator.svelte +3 -3
  9. package/dist/ContextmenuSubmenu.svelte +3 -3
  10. package/dist/CopyToClipboard.svelte +3 -3
  11. package/dist/DeclarationLink.svelte +1 -1
  12. package/dist/Dialog.svelte +16 -16
  13. package/dist/Docs.svelte +12 -12
  14. package/dist/DocsFooter.svelte +2 -2
  15. package/dist/DocsList.svelte +7 -7
  16. package/dist/DocsMenu.svelte +4 -4
  17. package/dist/DocsMenu.svelte.d.ts.map +1 -1
  18. package/dist/DocsPageLinks.svelte +4 -4
  19. package/dist/DocsPrimaryNav.svelte +2 -2
  20. package/dist/DocsSecondaryNav.svelte +4 -4
  21. package/dist/DocsTertiaryNav.svelte +13 -13
  22. package/dist/DocsTertiaryNav.svelte.d.ts +1 -1
  23. package/dist/GithubLink.svelte +1 -1
  24. package/dist/Hashlink.svelte +2 -2
  25. package/dist/HueInput.svelte +5 -5
  26. package/dist/LibraryDetail.svelte +8 -8
  27. package/dist/LibrarySummary.svelte +4 -4
  28. package/dist/MdnLink.svelte +1 -1
  29. package/dist/ModuleLink.svelte +1 -1
  30. package/dist/PasteFromClipboard.svelte +1 -1
  31. package/dist/PendingAnimation.svelte +2 -2
  32. package/dist/PendingButton.svelte +2 -2
  33. package/dist/ProjectLinks.svelte +4 -4
  34. package/dist/Teleport.svelte +2 -2
  35. package/dist/ThemeInput.svelte +5 -5
  36. package/dist/TomeContent.svelte +3 -3
  37. package/dist/TomeHeader.svelte +15 -14
  38. package/dist/TomeHeader.svelte.d.ts.map +1 -1
  39. package/dist/TomeLink.svelte +13 -6
  40. package/dist/TomeLink.svelte.d.ts +1 -1
  41. package/dist/TomeLink.svelte.d.ts.map +1 -1
  42. package/dist/TomeSection.svelte +1 -1
  43. package/dist/TomeSectionHeader.svelte +3 -3
  44. package/dist/api_search.svelte.js +2 -2
  45. package/dist/contextmenu_state.svelte.d.ts.map +1 -1
  46. package/dist/contextmenu_state.svelte.js +11 -10
  47. package/dist/csp.d.ts +81 -90
  48. package/dist/csp.d.ts.map +1 -1
  49. package/dist/csp.js +145 -163
  50. package/dist/csp_of_fuzdev.d.ts +13 -3
  51. package/dist/csp_of_fuzdev.d.ts.map +1 -1
  52. package/dist/csp_of_fuzdev.js +23 -7
  53. package/dist/dimensions.svelte.d.ts.map +1 -1
  54. package/dist/dimensions.svelte.js +2 -2
  55. package/dist/library.svelte.d.ts +1 -1
  56. package/dist/library_generate.js +1 -1
  57. package/dist/style_variable_helpers.svelte.d.ts.map +1 -1
  58. package/dist/style_variable_helpers.svelte.js +1 -1
  59. package/dist/theme_state.svelte.d.ts.map +1 -1
  60. package/dist/theme_state.svelte.js +2 -2
  61. package/dist/tome.d.ts +8 -3
  62. package/dist/tome.d.ts.map +1 -1
  63. package/dist/tome.js +24 -8
  64. package/dist/tsdoc_helpers.d.ts +9 -0
  65. package/dist/tsdoc_helpers.d.ts.map +1 -1
  66. package/dist/tsdoc_helpers.js +15 -5
  67. package/package.json +5 -5
  68. package/src/lib/api_search.svelte.ts +2 -2
  69. package/src/lib/contextmenu_state.svelte.ts +11 -10
  70. package/src/lib/csp.ts +224 -258
  71. package/src/lib/csp_of_fuzdev.ts +24 -8
  72. package/src/lib/dimensions.svelte.ts +2 -2
  73. package/src/lib/library_generate.ts +1 -1
  74. package/src/lib/style_variable_helpers.svelte.ts +1 -1
  75. package/src/lib/theme_state.svelte.ts +2 -2
  76. package/src/lib/tome.ts +25 -8
  77. package/src/lib/tsdoc_helpers.ts +18 -7
@@ -25,7 +25,7 @@
25
25
  </script>
26
26
 
27
27
  {#if enabled}
28
- <div {...rest} class="docs_primary_nav {rest.class}" class:scrolled>
28
+ <div {...rest} class="docs-primary-nav {rest.class}" class:scrolled>
29
29
  <div class="background" aria-hidden="true"></div>
30
30
  <div class="content">
31
31
  <nav aria-label="Primary nav">
@@ -43,7 +43,7 @@
43
43
  {/if}
44
44
 
45
45
  <style>
46
- .docs_primary_nav {
46
+ .docs-primary-nav {
47
47
  position: sticky;
48
48
  top: 0;
49
49
  z-index: 10;
@@ -16,14 +16,14 @@
16
16
  const expand_width = $derived(!sidebar);
17
17
  </script>
18
18
 
19
- <aside {...rest} class="docs_secondary_nav unstyled {rest.class}" class:sidebar>
19
+ <aside {...rest} class="docs-secondary-nav unstyled {rest.class}" class:sidebar>
20
20
  <nav aria-label="Secondary nav">
21
21
  <DocsMenu {tomes} {expand_width} />
22
22
  </nav>
23
23
  </aside>
24
24
 
25
25
  <style>
26
- .docs_secondary_nav {
26
+ .docs-secondary-nav {
27
27
  /* TODO @many lots of duplicate code between `DocsSecondaryNav` and `DocsTertiaryNav` */
28
28
  position: fixed;
29
29
  left: 0;
@@ -48,7 +48,7 @@
48
48
 
49
49
  /* sync this breakpoint with `Docs` */
50
50
  @media (max-width: 800px) {
51
- .docs_secondary_nav {
51
+ .docs-secondary-nav {
52
52
  position: static;
53
53
  background-color: initial;
54
54
  align-items: initial;
@@ -56,7 +56,7 @@
56
56
  }
57
57
  }
58
58
  /* TODO @many dialog navs */
59
- .docs_secondary_nav:not(.sidebar) {
59
+ .docs-secondary-nav:not(.sidebar) {
60
60
  position: static;
61
61
  background-color: initial;
62
62
  align-items: initial;
@@ -3,7 +3,7 @@
3
3
  import type {SvelteHTMLElements} from 'svelte/elements';
4
4
 
5
5
  import DocsPageLinks from './DocsPageLinks.svelte';
6
- import {to_tome_pathname, Tome} from './tome.js';
6
+ import {tome_to_pathname, Tome} from './tome.js';
7
7
  import {docs_links_context} from './docs_helpers.svelte.js';
8
8
  import {library_context} from './library.svelte.js';
9
9
  import TomeLink from './TomeLink.svelte';
@@ -12,20 +12,20 @@
12
12
 
13
13
  const {
14
14
  tomes,
15
- tomes_by_name,
15
+ tomes_by_slug,
16
16
  sidebar = true,
17
17
  ...rest
18
18
  }: SvelteHTMLElements['aside'] & {
19
19
  tomes: Array<Tome>;
20
- tomes_by_name: Map<string, Tome>;
20
+ tomes_by_slug: Map<string, Tome>;
21
21
  sidebar?: boolean; // TODO @many dialog navs (this shouldn't exist)
22
22
  } = $props();
23
23
 
24
- const selected_tome = $derived(tomes.find((t) => to_tome_pathname(t) === page.url.pathname));
24
+ const selected_tome = $derived(tomes.find((t) => tome_to_pathname(t) === page.url.pathname));
25
25
 
26
26
  const tomes_related_to_selected = $derived(
27
27
  selected_tome?.related_tomes
28
- .map((name) => tomes_by_name.get(name))
28
+ .map((slug) => tomes_by_slug.get(slug))
29
29
  .filter((t) => t !== undefined) ?? [],
30
30
  );
31
31
 
@@ -49,22 +49,22 @@
49
49
  </script>
50
50
 
51
51
  <!-- TODO probably add a `nav` wrapper? around which? -->
52
- <aside {...rest} class="docs_tertiary_nav unstyled {rest.class}">
52
+ <aside {...rest} class="docs-tertiary-nav unstyled {rest.class}">
53
53
  {#if should_show_page_links}
54
54
  <DocsPageLinks {sidebar} expand_width />
55
55
  {/if}
56
56
  {#if tomes_related_to_selected.length}
57
- <section class="related_section">
57
+ <section class="related-section">
58
58
  <h4 class="mb_sm">related tomes</h4>
59
59
  <ul class="unstyled">
60
- {#each tomes_related_to_selected as tome (tome.name)}
61
- <li><TomeLink name={tome.name} class="menuitem" /></li>
60
+ {#each tomes_related_to_selected as tome (tome.slug)}
61
+ <li><TomeLink slug={tome.slug} class="menuitem" /></li>
62
62
  {/each}
63
63
  </ul>
64
64
  </section>
65
65
  {/if}
66
66
  {#if modules_related_to_selected.length}
67
- <section class="related_section">
67
+ <section class="related-section">
68
68
  <h4 class="mb_sm">related modules</h4>
69
69
  <ul class="unstyled">
70
70
  {#each modules_related_to_selected as module (module.path)}
@@ -74,7 +74,7 @@
74
74
  </section>
75
75
  {/if}
76
76
  {#if declarations_related_to_selected.length}
77
- <section class="related_section">
77
+ <section class="related-section">
78
78
  <h4 class="mb_sm">related declarations</h4>
79
79
  <ul class="unstyled">
80
80
  {#each declarations_related_to_selected as declaration (declaration.name)}
@@ -86,7 +86,7 @@
86
86
  </aside>
87
87
 
88
88
  <style>
89
- .docs_tertiary_nav {
89
+ .docs-tertiary-nav {
90
90
  /* TODO @many lots of duplicate code between `DocsSecondaryNav` and `DocsTertiaryNav` */
91
91
  position: fixed;
92
92
  right: 0;
@@ -104,7 +104,7 @@
104
104
 
105
105
  /* sync this breakpoint with `/docs/+layout` */
106
106
  @media (max-width: 1000px) {
107
- .docs_tertiary_nav {
107
+ .docs-tertiary-nav {
108
108
  position: static;
109
109
  background-color: initial;
110
110
  overflow: initial;
@@ -2,7 +2,7 @@ import type { SvelteHTMLElements } from 'svelte/elements';
2
2
  import { Tome } from './tome.js';
3
3
  type $$ComponentProps = SvelteHTMLElements['aside'] & {
4
4
  tomes: Array<Tome>;
5
- tomes_by_name: Map<string, Tome>;
5
+ tomes_by_slug: Map<string, Tome>;
6
6
  sidebar?: boolean;
7
7
  };
8
8
  declare const DocsTertiaryNav: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -75,7 +75,7 @@
75
75
 
76
76
  <!-- TODO -next-line doesnt work? -->
77
77
  <!-- eslint-disable svelte/no-navigation-without-resolve -->
78
- <a {...rest} {href} class="github_link white-space:nowrap {rest.class}" rel="noopener"
78
+ <a {...rest} {href} class="github-link white-space:nowrap {rest.class}" rel="noopener"
79
79
  ><Svg data={logo_github} inline size="var(--icon_size_xs)" class="mx_xs3 vertical-align:middle" />
80
80
  {#if typeof final_children === 'string'}
81
81
  <span class="font_family_mono">{final_children}</span>
@@ -7,7 +7,7 @@
7
7
  <a {...rest} class="hashlink {rest.class}" href="#{fragment}" aria-label="hashlink"
8
8
  >{#if children}{@render children()}{:else}#{/if}</a
9
9
  >
10
- <span class="hashlink_scroll_target" id={fragment} aria-hidden="true"></span>
10
+ <span class="hashlink-scroll-target" id={fragment} aria-hidden="true"></span>
11
11
 
12
12
  <style>
13
13
  /* TODO @many how can this be done generically so it's composable? currently using `:global` at usage site - ideally we'd continue to use :hover instead of JS */
@@ -21,7 +21,7 @@
21
21
  transition: opacity var(--duration_2);
22
22
  }
23
23
 
24
- .hashlink_scroll_target {
24
+ .hashlink-scroll-target {
25
25
  position: absolute;
26
26
  top: calc(var(--font_size, var(--font_size_md)) * -4);
27
27
  left: 0;
@@ -32,7 +32,7 @@
32
32
  update_hue(parsed);
33
33
  };
34
34
 
35
- let el: HTMLInputElement | undefined = $state();
35
+ let el: HTMLInputElement | undefined = $state.raw();
36
36
 
37
37
  const set_hue_from_minimap = (e: MouseEvent & {currentTarget: EventTarget & HTMLElement}) => {
38
38
  const rect = e.currentTarget.getBoundingClientRect();
@@ -43,7 +43,7 @@
43
43
  </script>
44
44
 
45
45
  <!-- TODO consider making this a text input or otherwise editable directly -->
46
- <div {...rest} class="hue_input {rest.class}" style:--hue={value}>
46
+ <div {...rest} class="hue-input {rest.class}" style:--hue={value}>
47
47
  <label class="unstyled">
48
48
  {#if children}<div class="title">{@render children()}</div>{/if}
49
49
  <div class="preview">
@@ -51,7 +51,7 @@
51
51
  <input type="number" step="0" class="hue" {value} oninput={on_input_event} />
52
52
  </div>
53
53
  </label>
54
- <div class="minimap_wrapper">
54
+ <div class="minimap-wrapper">
55
55
  <div class="minimap" onclick={set_hue_from_minimap} aria-hidden="true"></div>
56
56
  </div>
57
57
  <input
@@ -66,7 +66,7 @@
66
66
  </div>
67
67
 
68
68
  <style>
69
- .hue_input {
69
+ .hue-input {
70
70
  --outline_color: hsl(var(--hue) 50% 60%);
71
71
  /* TODO @many figure these variables out so they're easily customized (similar pattern in a lot of places) */
72
72
  /* --thumb_background_color: hsl(var(--hue) 50% 50%); */
@@ -84,7 +84,7 @@
84
84
  border-top-left-radius: var(--border_radius, var(--border_radius_md));
85
85
  border-top-right-radius: var(--border_radius, var(--border_radius_md));
86
86
  }
87
- .minimap_wrapper {
87
+ .minimap-wrapper {
88
88
  padding: 0 var(--input_padding_x);
89
89
  background: hsl(0 50% 50%) 0%;
90
90
  }
@@ -45,7 +45,7 @@
45
45
 
46
46
  <!-- eslint-disable svelte/no-navigation-without-resolve -->
47
47
 
48
- <div class="library_detail">
48
+ <div class="library-detail">
49
49
  <!-- TODO maybe continue this snippet pattern, or maybe simplify? -->
50
50
  <div class="info">
51
51
  <div class="display:flex flex:1">
@@ -55,7 +55,7 @@
55
55
  {#if repo_name}
56
56
  {@render repo_name(library.repo_name)}
57
57
  {:else}
58
- <div class="repo_name">
58
+ <div class="repo-name">
59
59
  {library.repo_name}{#if package_json.glyph}&nbsp;{package_json.glyph}{/if}
60
60
  </div>
61
61
  {/if}
@@ -79,7 +79,7 @@
79
79
  {#if npm_url}
80
80
  {@render npm_url(library.npm_url)}
81
81
  {:else}
82
- <blockquote class="npm_url">npm i -D {package_json.name}</blockquote>
82
+ <blockquote class="npm-url">npm i -D {package_json.name}</blockquote>
83
83
  {/if}
84
84
  {/if}
85
85
  <!-- TODO accessible HTML -->
@@ -175,7 +175,7 @@
175
175
  class:css={module_is_css(module.path)}
176
176
  class:json={module_is_json(module.path)}
177
177
  >
178
- <div class="module_content">
178
+ <div class="module-content">
179
179
  <span class="font_size_xl">
180
180
  <ModuleLink module_path={module.path}>
181
181
  {module.path}
@@ -204,7 +204,7 @@
204
204
  <!-- TODO better rendering, also show author, etc -->
205
205
 
206
206
  <style>
207
- .library_detail {
207
+ .library-detail {
208
208
  display: flex;
209
209
  flex-direction: column;
210
210
  align-items: flex-start;
@@ -232,12 +232,12 @@
232
232
  width: 100%;
233
233
  margin-bottom: var(--space_lg);
234
234
  }
235
- .repo_name {
235
+ .repo-name {
236
236
  font-family: var(--font_family_serif);
237
237
  font-size: var(--font_size_xl2);
238
238
  font-weight: 400;
239
239
  }
240
- .npm_url {
240
+ .npm-url {
241
241
  font-family: var(--font_family_mono);
242
242
  text-align: center;
243
243
  margin-bottom: var(--space_lg);
@@ -269,7 +269,7 @@
269
269
  margin-bottom: var(--space_xs);
270
270
  --link_color: var(--text_70);
271
271
  }
272
- .module_content {
272
+ .module-content {
273
273
  background-color: var(--shade_10);
274
274
  border-radius: var(--border_radius_sm);
275
275
  padding: var(--space_xs);
@@ -29,13 +29,13 @@
29
29
  const {package_json} = $derived(library);
30
30
  </script>
31
31
 
32
- <div class="library_summary">
32
+ <div class="library-summary">
33
33
  <!-- TODO maybe continue this snippet pattern, or maybe simplify? -->
34
34
  <header class="box mb_lg">
35
35
  {#if repo_name}
36
36
  {@render repo_name(library.repo_name)}
37
37
  {:else}
38
- <div class="repo_name">{library.repo_name}</div>
38
+ <div class="repo-name">{library.repo_name}</div>
39
39
  {/if}
40
40
  <!-- TODO maybe add `icon_alt` to package.json -->
41
41
  <!-- TODO what about svg logos? maybe a package.json logo url that defaults to favicon? -->
@@ -118,14 +118,14 @@
118
118
  <!-- TODO better rendering, also show author, etc -->
119
119
 
120
120
  <style>
121
- .library_summary {
121
+ .library-summary {
122
122
  padding: var(--space_lg);
123
123
  display: flex;
124
124
  flex-direction: column;
125
125
  align-items: center;
126
126
  max-width: var(--max_width, var(--distance_sm));
127
127
  }
128
- .repo_name {
128
+ .repo-name {
129
129
  font-family: var(--font_family_serif);
130
130
  font-size: var(--font_size_xl2);
131
131
  font-weight: 400;
@@ -28,7 +28,7 @@
28
28
 
29
29
  <!-- TODO -next-line doesnt work? -->
30
30
  <!-- eslint-disable svelte/no-navigation-without-resolve -->
31
- <a {...rest} {href} class="mdn_link white-space:nowrap {rest.class}" rel="noopener"
31
+ <a {...rest} {href} class="mdn-link white-space:nowrap {rest.class}" rel="noopener"
32
32
  ><Svg data={logo_mdn} inline size="var(--icon_size_xs)" class="mx_xs3 vertical-align:middle" />
33
33
  {#if typeof final_children === 'string'}
34
34
  <span class="font_family_mono">{final_children}</span>
@@ -33,7 +33,7 @@
33
33
  <!-- eslint-disable svelte/no-navigation-without-resolve -->
34
34
  <a
35
35
  {...rest}
36
- class="module_link {class_prop} {color_class}"
36
+ class="module-link {class_prop} {color_class}"
37
37
  href={module.url_api + (hash ? ensure_start(hash, '#') : '')}
38
38
  {@attach contextmenu_attachment(contextmenu_entries)}
39
39
  >
@@ -20,7 +20,7 @@
20
20
  type="button"
21
21
  title="paste from clipboard"
22
22
  {...rest}
23
- class="paste_from_clipboard {rest.class}"
23
+ class="paste-from-clipboard {rest.class}"
24
24
  onclick={async () => {
25
25
  try {
26
26
  const text = await navigator.clipboard.readText();
@@ -21,7 +21,7 @@
21
21
 
22
22
  <span
23
23
  {...rest}
24
- class="pending_animation {rest.class} {inline
24
+ class="pending-animation {rest.class} {inline
25
25
  ? 'display:inline-flex align-items:center'
26
26
  : 'display:flex'}"
27
27
  class:running
@@ -35,7 +35,7 @@
35
35
  >
36
36
 
37
37
  <style>
38
- .pending_animation span {
38
+ .pending-animation span {
39
39
  font-size: var(--font_size, inherit);
40
40
  display: inline-block;
41
41
  transform: scale3d(var(--scale_x, 0.4), var(--scale_y, 0.4), var(--scale_z, 0.4));
@@ -20,7 +20,7 @@
20
20
  children: Snippet;
21
21
  } = $props();
22
22
 
23
- let el: HTMLButtonElement | undefined = $state();
23
+ let el: HTMLButtonElement | undefined = $state.raw();
24
24
 
25
25
  export const focus = (options?: FocusOptions): void => el?.focus(options);
26
26
 
@@ -31,7 +31,7 @@
31
31
  bind:this={el}
32
32
  type="button"
33
33
  {...rest}
34
- class="pending_button {rest.class}"
34
+ class="pending-button {rest.class}"
35
35
  disabled={disabled_prop ?? pending}
36
36
  title={title_prop}
37
37
  class:pending
@@ -55,8 +55,8 @@
55
55
  <a class="thumbnail row shade_00 px_md py_xs border_radius_sm mb_lg" href={project_item.url}
56
56
  ><Svg shrink={false} data={project_item.logo} size="var(--icon_size_lg)" />
57
57
  <div class="pl_lg width_atmost_sm">
58
- <div class="thumbnail_name {project_item.color_class}">{project_item.name}</div>
59
- <div class="thumbnail_description font_size_md text_70 font-weight:500">
58
+ <div class="thumbnail-name {project_item.color_class}">{project_item.name}</div>
59
+ <div class="thumbnail-description font_size_md text_70 font-weight:500">
60
60
  {project_item.description}
61
61
  {project_item.glyph}
62
62
  </div>
@@ -83,10 +83,10 @@
83
83
 
84
84
  /* show the underline only on the name, not the description */
85
85
  text-decoration: none;
86
- .thumbnail_name {
86
+ .thumbnail-name {
87
87
  text-decoration: underline;
88
88
  }
89
- .thumbnail_description {
89
+ .thumbnail-description {
90
90
  text-decoration: none;
91
91
  }
92
92
  }
@@ -16,7 +16,7 @@
16
16
  children: Snippet;
17
17
  } = $props();
18
18
 
19
- let el: HTMLElement | undefined | null = $state();
19
+ let el: HTMLElement | undefined | null = $state.raw();
20
20
 
21
21
  $effect(() => {
22
22
  if (el && to) {
@@ -24,7 +24,7 @@
24
24
  }
25
25
  });
26
26
 
27
- let moved = $state(false);
27
+ let moved = $state.raw(false);
28
28
 
29
29
  const move = (el: HTMLElement, to: HTMLElement): void => {
30
30
  moved = true;
@@ -28,14 +28,14 @@
28
28
  } = $props();
29
29
  </script>
30
30
 
31
- <menu {...rest} class="theme_input unstyled {rest.class}">
31
+ <menu {...rest} class="theme-input unstyled {rest.class}">
32
32
  {#each themes as theme (theme.name)}
33
33
  <!-- TODO @many proper equality check, won't work when we allow editing, need an id or unique names and a deep equality check -->
34
34
  {@const selected = theme.name === selected_theme.theme.name}
35
35
  <li class="row" role="none">
36
36
  <button
37
37
  type="button"
38
- class="theme_button color_a"
38
+ class="theme-button color_a"
39
39
  role="menuitemradio"
40
40
  aria-label="{theme.name} theme"
41
41
  aria-checked={selected}
@@ -64,15 +64,15 @@
64
64
  </menu>
65
65
 
66
66
  <style>
67
- .theme_button {
67
+ .theme-button {
68
68
  flex: 1;
69
69
  border-radius: 0;
70
70
  }
71
- li:first-child .theme_button {
71
+ li:first-child .theme-button {
72
72
  border-top-left-radius: var(--border_radius_sm);
73
73
  border-top-right-radius: var(--border_radius_sm);
74
74
  }
75
- li:last-child .theme_button {
75
+ li:last-child .theme-button {
76
76
  border-bottom-left-radius: var(--border_radius_sm);
77
77
  border-bottom-right-radius: var(--border_radius_sm);
78
78
  }
@@ -5,7 +5,7 @@
5
5
 
6
6
  import TomeHeader from './TomeHeader.svelte';
7
7
  import {tome_context, type Tome} from './tome.js';
8
- import {DOCS_PATH_DEFAULT, docs_links_context, docs_slugify} from './docs_helpers.svelte.js';
8
+ import {DOCS_PATH_DEFAULT, docs_links_context} from './docs_helpers.svelte.js';
9
9
  import {intersect} from './intersect.svelte.js';
10
10
 
11
11
  const {
@@ -24,13 +24,13 @@
24
24
 
25
25
  tome_context.set(() => tome);
26
26
 
27
- const fragment = $derived(docs_slugify(tome.name));
27
+ const fragment = $derived(tome.slug);
28
28
 
29
29
  const at_root = $derived(page.url.pathname === resolve(docs_path as any));
30
30
  </script>
31
31
 
32
32
  <section
33
- class="tome_content width_atmost_md mb_xl9"
33
+ class="tome-content width_atmost_md mb_xl9"
34
34
  {@attach intersect(() =>
35
35
  at_root
36
36
  ? ({intersecting}) => {
@@ -3,45 +3,46 @@
3
3
  import {DEV} from 'esm-env';
4
4
  import type {SvelteHTMLElements} from 'svelte/elements';
5
5
 
6
- import {tome_context} from './tome.js';
6
+ import {tome_context, tome_to_title} from './tome.js';
7
7
  import Hashlink from './Hashlink.svelte';
8
- import {docs_links_context, docs_slugify, to_docs_path_info} from './docs_helpers.svelte.js';
8
+ import {docs_links_context, to_docs_path_info} from './docs_helpers.svelte.js';
9
9
 
10
10
  // eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents
11
11
  const props: SvelteHTMLElements['h1'] | SvelteHTMLElements['h2'] = $props();
12
12
 
13
13
  const get_tome = tome_context.get();
14
- if (DEV && !get_tome) throw Error('TomeHeader expects a tome in context'); // eslint-disable-line @typescript-eslint/no-unnecessary-condition
14
+ if (DEV && !get_tome) throw Error('TomeHeader expects a tome in context');
15
15
  const tome = $derived(get_tome());
16
16
 
17
17
  const docs_links = docs_links_context.get();
18
18
 
19
- const fragment = $derived(docs_slugify(tome.name));
20
- const path_slug = $derived(docs_slugify(tome.name));
19
+ // The slug is the route segment + anchor fragment; only the visible label uses `title`.
20
+ const fragment = $derived(tome.slug);
21
+ const title = $derived(tome_to_title(tome));
21
22
  $effect(() => {
22
- const id = docs_links.add(fragment, tome.name, page.url.pathname);
23
+ const id = docs_links.add(fragment, title, page.url.pathname);
23
24
  return () => docs_links.remove(id);
24
25
  });
25
26
 
26
- const {path, path_is_selected} = $derived(to_docs_path_info(path_slug, page.url.pathname));
27
+ const {path, path_is_selected} = $derived(to_docs_path_info(fragment, page.url.pathname));
27
28
  </script>
28
29
 
29
- <svelte:element this={path_is_selected ? 'h1' : 'h2'} {...props} class="tome_header">
30
+ <svelte:element this={path_is_selected ? 'h1' : 'h2'} {...props} class="tome-header">
30
31
  {#if path_is_selected}
31
- {@render content(tome.name)}
32
+ {@render content(title)}
32
33
  {:else}
33
34
  <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
34
- <a href={path}>{@render content(tome.name)}</a>
35
+ <a href={path}>{@render content(title)}</a>
35
36
  {/if}
36
37
  <Hashlink {fragment} />
37
38
  </svelte:element>
38
39
 
39
- {#snippet content(name: string)}
40
- {name}
40
+ {#snippet content(text: string)}
41
+ {text}
41
42
  {/snippet}
42
43
 
43
44
  <style>
44
- .tome_header {
45
+ .tome-header {
45
46
  position: relative;
46
47
  display: flex;
47
48
  align-items: center;
@@ -49,7 +50,7 @@
49
50
  margin-top: var(--space_xl4);
50
51
  }
51
52
  /* TODO @many how can this be done composably? currently using `:global` at usage site - ideally we'd continue to use :hover instead of JS */
52
- .tome_header:hover :global(.hashlink) {
53
+ .tome-header:hover :global(.hashlink) {
53
54
  opacity: 1;
54
55
  }
55
56
  </style>
@@ -1 +1 @@
1
- {"version":3,"file":"TomeHeader.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/TomeHeader.svelte"],"names":[],"mappings":"AA0DA,QAAA,MAAM,UAAU,kGAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"TomeHeader.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/TomeHeader.svelte"],"names":[],"mappings":"AA2DA,QAAA,MAAM,UAAU,kGAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -2,18 +2,18 @@
2
2
  import {DEV} from 'esm-env';
3
3
  import type {SvelteHTMLElements} from 'svelte/elements';
4
4
 
5
- import {get_tome_by_name, to_tome_pathname} from './tome.js';
5
+ import {tome_get_by_slug, tome_to_pathname, tome_to_title, tomes_context} from './tome.js';
6
6
  import {DOCS_PATH_DEFAULT} from './docs_helpers.svelte.js';
7
7
 
8
8
  const {
9
- name,
9
+ slug,
10
10
  docs_path = DOCS_PATH_DEFAULT,
11
11
  hash,
12
12
  children,
13
13
  class: class_prop = 'chip',
14
14
  ...rest
15
15
  }: SvelteHTMLElements['a'] & {
16
- name: string; // TODO type, generate from `tomes`?
16
+ slug: string; // TODO type, generate from `tomes`?
17
17
  docs_path?: string;
18
18
  /** URL fragment to append, with or without the `#`. */
19
19
  hash?: string;
@@ -21,14 +21,21 @@
21
21
 
22
22
  if (DEV) {
23
23
  $effect(() => {
24
- get_tome_by_name(name); // throws if not found
24
+ tome_get_by_slug(slug); // throws if not found
25
25
  });
26
26
  }
27
27
 
28
+ // Default link text is the tome's display title, falling back to its `slug`.
29
+ const get_tomes = tomes_context.get_maybe();
30
+ const label = $derived.by(() => {
31
+ const tome = get_tomes?.().get(slug);
32
+ return tome ? tome_to_title(tome) : slug;
33
+ });
34
+
28
35
  // TODO add contextmenu behavior
29
36
  </script>
30
37
 
31
38
  <!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
32
- <a {...rest} class="tome_link {class_prop}" href={to_tome_pathname(name, docs_path, hash)}
33
- >{#if children}{@render children()}{:else}{name}{/if}</a
39
+ <a {...rest} class="tome-link {class_prop}" href={tome_to_pathname(slug, docs_path, hash)}
40
+ >{#if children}{@render children()}{:else}{label}{/if}</a
34
41
  >
@@ -1,6 +1,6 @@
1
1
  import type { SvelteHTMLElements } from 'svelte/elements';
2
2
  type $$ComponentProps = SvelteHTMLElements['a'] & {
3
- name: string;
3
+ slug: string;
4
4
  docs_path?: string;
5
5
  /** URL fragment to append, with or without the `#`. */
6
6
  hash?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"TomeLink.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/TomeLink.svelte"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAKvD,KAAK,gBAAgB,GAAI,kBAAkB,CAAC,GAAG,CAAC,GAAG;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AA+BH,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"TomeLink.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/TomeLink.svelte"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAKvD,KAAK,gBAAgB,GAAI,kBAAkB,CAAC,GAAG,CAAC,GAAG;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAsCH,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -39,7 +39,7 @@
39
39
  // Provide own section ID to direct children (header) via context
40
40
  section_id_context.set(section_id);
41
41
 
42
- let get_fragment: (() => string) | undefined = $state();
42
+ let get_fragment: (() => string) | undefined = $state.raw();
43
43
 
44
44
  register_section_header_context.set((gf) => {
45
45
  if (DEV && get_fragment !== undefined) {