@fuzdev/fuz_ui 0.192.0 โ†’ 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.
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import {library_context, type Library} from './library.svelte.js';
3
- import {get_tome_by_name, type Tome} from './tome.js';
3
+ import {tome_get_by_slug, type Tome} from './tome.js';
4
4
  import TomeContent from './TomeContent.svelte';
5
5
  import TomeLink from './TomeLink.svelte';
6
6
  import DocsSearch from './DocsSearch.svelte';
@@ -10,7 +10,7 @@
10
10
 
11
11
  const {
12
12
  library = library_context.get(),
13
- tome = get_tome_by_name('api'),
13
+ tome = tome_get_by_slug('api'),
14
14
  minimal = false,
15
15
  }: {
16
16
  /**
@@ -40,7 +40,7 @@
40
40
  <TomeContent {tome}>
41
41
  {#if minimal}
42
42
  <section>
43
- <p>Browse the full <TomeLink name="api" /> docs.</p>
43
+ <p>Browse the full <TomeLink slug="api" /> docs.</p>
44
44
  </section>
45
45
  {:else}
46
46
  <section>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import {library_context, type Library} from './library.svelte.js';
3
- import {get_tome_by_name, type Tome} from './tome.js';
3
+ import {tome_get_by_slug, type Tome} from './tome.js';
4
4
  import TomeContent from './TomeContent.svelte';
5
5
  import TomeSection from './TomeSection.svelte';
6
6
  import TomeSectionHeader from './TomeSectionHeader.svelte';
@@ -13,7 +13,7 @@
13
13
  const {
14
14
  module_path: module_path_param,
15
15
  library = library_context.get(),
16
- tome = get_tome_by_name('api'),
16
+ tome = tome_get_by_slug('api'),
17
17
  }: {
18
18
  /**
19
19
  * The module path parameter from the route (e.g., "lib/Button.svelte").
package/dist/Docs.svelte CHANGED
@@ -27,8 +27,8 @@
27
27
  } = $props();
28
28
 
29
29
  // TODO this API is messy, inconsistent usage of props/context
30
- const tomes_by_name = $derived(new Map(tomes.map((t) => [t.name, t])));
31
- tomes_context.set(() => tomes_by_name);
30
+ const tomes_by_slug = $derived(new Map(tomes.map((t) => [t.slug, t])));
31
+ tomes_context.set(() => tomes_by_slug);
32
32
 
33
33
  // TODO @many dialog navs - this is messy to satisfy SSR with the current design that puts the secondary nav in a dialog
34
34
  const TERTIARY_NAV_BREAKPOINT = 1000;
@@ -69,7 +69,7 @@
69
69
  {/key}
70
70
  <!-- TODO @many dialog navs -->
71
71
  {#if !innerWidth.current || innerWidth.current > TERTIARY_NAV_BREAKPOINT}
72
- <DocsTertiaryNav {tomes} {tomes_by_name} />
72
+ <DocsTertiaryNav {tomes} {tomes_by_slug} />
73
73
  {/if}
74
74
  <section class="box">
75
75
  <DocsFooter {library} root_url="https://www.fuz.dev/">
@@ -102,7 +102,7 @@
102
102
  </div>
103
103
  <div class="px_lg pb_xl">
104
104
  <DocsSecondaryNav {tomes} sidebar={false} />
105
- <DocsTertiaryNav {tomes} {tomes_by_name} sidebar={false} />
105
+ <DocsTertiaryNav {tomes} {tomes_by_slug} sidebar={false} />
106
106
  </div>
107
107
  </div>
108
108
  </Dialog>
@@ -5,7 +5,7 @@
5
5
  import type {SvelteHTMLElements} from 'svelte/elements';
6
6
 
7
7
  import DocsList from './DocsList.svelte';
8
- import {to_tome_pathname, type Tome} from './tome.js';
8
+ import {tome_to_pathname, tome_to_title, type Tome} from './tome.js';
9
9
  import DocsMenuHeader from './DocsMenuHeader.svelte';
10
10
 
11
11
  const {
@@ -37,10 +37,10 @@
37
37
  >{/if}
38
38
  <ul class="unstyled">
39
39
  {#each tomes as item (item)}
40
- {@const pathname = to_tome_pathname(item)}
40
+ {@const pathname = tome_to_pathname(item)}
41
41
  <li role="none" transition:slide>
42
42
  <a class="menuitem" href={pathname} class:selected={pathname === page.url.pathname}
43
- ><div class="ellipsis">{item.name}</div></a
43
+ ><div class="ellipsis">{tome_to_title(item)}</div></a
44
44
  >
45
45
  </li>
46
46
  {/each}
@@ -1 +1 @@
1
- {"version":3,"file":"DocsMenu.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/DocsMenu.svelte"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAGxD,OAAO,EAAmB,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAGrD,KAAK,gBAAgB,GAAI,kBAAkB,CAAC,KAAK,CAAC,GAAG;IACpD,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IACvC,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAkDH,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"DocsMenu.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/DocsMenu.svelte"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAGxD,OAAO,EAAkC,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAGpE,KAAK,gBAAgB,GAAI,kBAAkB,CAAC,KAAK,CAAC,GAAG;IACpD,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IACvC,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAkDH,QAAA,MAAM,QAAQ,sDAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -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
 
@@ -57,8 +57,8 @@
57
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>
@@ -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, {}, "">;
@@ -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,7 +24,7 @@
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>
@@ -3,41 +3,42 @@
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
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>
@@ -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"}
@@ -213,7 +213,7 @@ export class ContextmenuState {
213
213
  do {
214
214
  i.selected = true;
215
215
  new_selections.unshift(i);
216
- } while ((i = i.menu) && i.menu); // eslint-disable-line @typescript-eslint/no-unnecessary-condition
216
+ } while ((i = i.menu) && i.menu);
217
217
  this.selections = new_selections;
218
218
  }
219
219
  collapse_selected() {
@@ -341,7 +341,6 @@ export const contextmenu_open = (target, x, y, contextmenu, options) => {
341
341
  // `navigator.vibrate()` works with `ContextmenuRoot` but gets blocked by some browsers
342
342
  // when used with `ContextmenuRootForSafariCompatibility` because its longpress
343
343
  // workaround triggers from a timeout rather than a direct user interaction.
344
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
345
344
  if (BROWSER && vibrate && navigator.vibrate) {
346
345
  navigator.vibrate(CONTEXTMENU_OPEN_VIBRATE_DURATION);
347
346
  }
package/dist/csp.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"csp.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/csp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"csp.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/csp.ts"],"names":[],"mappings":"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"}
package/dist/csp.js CHANGED
@@ -22,7 +22,6 @@
22
22
  */
23
23
  export const create_csp_directives = (options = {}) => {
24
24
  const { replace_defaults = csp_directive_value_defaults, extend, overrides } = options;
25
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
26
25
  if (replace_defaults === null) {
27
26
  throw new Error(`Invalid value 'null' for options.replace_defaults. ` +
28
27
  `Omit the option to use library defaults, or pass {} to start with no directives.`);
@@ -40,12 +39,10 @@ export const create_csp_directives = (options = {}) => {
40
39
  // Stage 1: starting state from `replace_defaults`.
41
40
  // `{}` starts blank โ€” every directive must come from `extend`/`overrides`.
42
41
  for_each_directive(replace_defaults, 'replace_defaults', (directive, value) => {
43
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
44
42
  if (value === null) {
45
43
  throw new Error(`Invalid value 'null' for directive '${directive}' in options.replace_defaults. ` +
46
44
  `Omit the key instead, or use \`overrides: { '${directive}': null }\` to remove.`);
47
45
  }
48
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
49
46
  if (value === undefined)
50
47
  return;
51
48
  assign(directive, value);
@@ -95,7 +92,6 @@ export const create_csp_directives = (options = {}) => {
95
92
  for_each_directive(overrides, 'overrides', (directive, value) => {
96
93
  if (value === null) {
97
94
  delete directives[directive]; // eslint-disable-line @typescript-eslint/no-dynamic-delete
98
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
99
95
  }
100
96
  else if (value !== undefined) {
101
97
  assign(directive, value);
@@ -144,9 +140,8 @@ const is_none = (value) => Array.isArray(value) && value.length === 1 && value[0
144
140
  * `Object.entries`.
145
141
  */
146
142
  const for_each_directive = (source, source_label, fn) => {
147
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
148
143
  if (source === null || typeof source !== 'object') {
149
- const got = source === null ? 'null' : typeof source; // eslint-disable-line @typescript-eslint/no-unnecessary-condition
144
+ const got = source === null ? 'null' : typeof source;
150
145
  throw new Error(`Invalid entry in options.${source_label}: expected an object, got ${got}.`);
151
146
  }
152
147
  for (const [key, value] of Object.entries(source)) {
package/dist/tome.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import type { Component } from 'svelte';
2
2
  import { z } from 'zod';
3
3
  export declare const Tome: z.ZodObject<{
4
- name: z.ZodString;
4
+ slug: z.ZodString;
5
+ title: z.ZodOptional<z.ZodString>;
5
6
  category: z.ZodString;
6
7
  Component: z.ZodCustom<Component<any, any, string>, Component<any, any, string>>;
7
8
  related_tomes: z.ZodArray<z.ZodString>;
@@ -12,13 +13,17 @@ export type Tome = z.infer<typeof Tome>;
12
13
  /**
13
14
  * @param hash - URL fragment to append, with or without the `#`
14
15
  */
15
- export declare const to_tome_pathname: (item: Tome | string, docs_path?: string, hash?: string) => string;
16
+ export declare const tome_to_pathname: (item: Tome | string, docs_path?: string, hash?: string) => string;
17
+ /**
18
+ * The display label for a tome โ€” its `title` if set, else its `slug`.
19
+ */
20
+ export declare const tome_to_title: (tome: Tome) => string;
16
21
  export declare const tomes_context: {
17
22
  get: (error_message?: string) => () => Map<string, Tome>;
18
23
  get_maybe: () => (() => Map<string, Tome>) | undefined;
19
24
  set: (value: () => Map<string, Tome>) => () => Map<string, Tome>;
20
25
  };
21
- export declare const get_tome_by_name: (name: string) => Tome;
26
+ export declare const tome_get_by_slug: (slug: string) => Tome;
22
27
  export declare const tome_context: {
23
28
  get: (error_message?: string) => () => Tome;
24
29
  get_maybe: () => (() => Tome) | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"tome.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/tome.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAMtB,eAAO,MAAM,IAAI;;;;;;;iBAQf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAC5B,MAAM,IAAI,GAAG,MAAM,EACnB,kBAA6B,EAC7B,OAAO,MAAM,KACX,MAIF,CAAC;AAEF,eAAO,MAAM,aAAa;2CAAwB,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC;4BAAjB,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC;uBAAjB,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,WAAjB,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC;CAAG,CAAC;AAEvE,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,IAK/C,CAAC;AAEF,eAAO,MAAM,YAAY;2CAAwB,IAAI;4BAAJ,IAAI;uBAAJ,IAAI,WAAJ,IAAI;CAAG,CAAC"}
1
+ {"version":3,"file":"tome.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/tome.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAMtB,eAAO,MAAM,IAAI;;;;;;;;iBAoBf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAC5B,MAAM,IAAI,GAAG,MAAM,EACnB,kBAA6B,EAC7B,OAAO,MAAM,KACX,MAIF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,IAAI,KAAG,MAAiC,CAAC;AAE7E,eAAO,MAAM,aAAa;2CAAwB,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC;4BAAjB,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC;uBAAjB,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,WAAjB,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC;CAAG,CAAC;AAEvE,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,IAK/C,CAAC;AAEF,eAAO,MAAM,YAAY;2CAAwB,IAAI;4BAAJ,IAAI;uBAAJ,IAAI,WAAJ,IAAI;CAAG,CAAC"}
package/dist/tome.js CHANGED
@@ -2,9 +2,21 @@ import { resolve } from '$app/paths';
2
2
  import { z } from 'zod';
3
3
  import { ensure_start } from '@fuzdev/fuz_util/string.js';
4
4
  import { create_context } from './context_helpers.js';
5
- import { DOCS_PATH_DEFAULT, docs_slugify } from './docs_helpers.svelte.js';
5
+ import { DOCS_PATH_DEFAULT } from './docs_helpers.svelte.js';
6
6
  export const Tome = z.object({
7
- name: z.string(),
7
+ /**
8
+ * Stable identifier and URL path segment โ€” must be a URL-safe slug
9
+ * (used directly as the route segment, the lookup key, and in
10
+ * `related_tomes` cross-references). Set `title` for the display label.
11
+ */
12
+ slug: z.string(),
13
+ /**
14
+ * Human-readable display label shown in menus, headings, and links.
15
+ * Falls back to `slug` when omitted, so set `title` to give multi-word or
16
+ * specially cased tomes a readable label (e.g. `slug: 'vite-plugin'` โ†’
17
+ * `title: 'vite plugin'`).
18
+ */
19
+ title: z.string().optional(),
8
20
  // TODO maybe now this makes sense with mdz? summary: z.string(),
9
21
  category: z.string(),
10
22
  Component: z.custom(),
@@ -15,17 +27,21 @@ export const Tome = z.object({
15
27
  /**
16
28
  * @param hash - URL fragment to append, with or without the `#`
17
29
  */
18
- export const to_tome_pathname = (item, docs_path = DOCS_PATH_DEFAULT, hash) => {
19
- const name = typeof item === 'string' ? item : item.name;
20
- const path = docs_path + '/' + docs_slugify(name);
30
+ export const tome_to_pathname = (item, docs_path = DOCS_PATH_DEFAULT, hash) => {
31
+ const slug = typeof item === 'string' ? item : item.slug;
32
+ const path = docs_path + '/' + slug;
21
33
  return resolve((hash ? path + ensure_start(hash, '#') : path));
22
34
  };
35
+ /**
36
+ * The display label for a tome โ€” its `title` if set, else its `slug`.
37
+ */
38
+ export const tome_to_title = (tome) => tome.title ?? tome.slug;
23
39
  export const tomes_context = create_context();
24
- export const get_tome_by_name = (name) => {
40
+ export const tome_get_by_slug = (slug) => {
25
41
  const get_tomes = tomes_context.get();
26
- const tome = get_tomes().get(name);
42
+ const tome = get_tomes().get(slug);
27
43
  if (!tome)
28
- throw Error(`unable to find tome "${name}"`);
44
+ throw Error(`unable to find tome "${slug}"`);
29
45
  return tome;
30
46
  };
31
47
  export const tome_context = create_context();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_ui",
3
- "version": "0.192.0",
3
+ "version": "0.193.0",
4
4
  "description": "Svelte UI library",
5
5
  "tagline": "friendly user zystem",
6
6
  "glyph": "๐Ÿงถ",
@@ -82,10 +82,10 @@
82
82
  "@changesets/changelog-git": "^0.2.1",
83
83
  "@fuzdev/fuz_code": "^0.45.1",
84
84
  "@fuzdev/fuz_css": "^0.59.0",
85
- "@fuzdev/fuz_util": "^0.57.0",
86
- "@fuzdev/gro": "^0.198.0",
85
+ "@fuzdev/fuz_util": "^0.61.1",
86
+ "@fuzdev/gro": "^0.199.1",
87
87
  "@jridgewell/trace-mapping": "^0.3.31",
88
- "@ryanatkn/eslint-config": "^0.11.0",
88
+ "@ryanatkn/eslint-config": "^0.12.1",
89
89
  "@sveltejs/acorn-typescript": "^1.0.9",
90
90
  "@sveltejs/adapter-static": "^3.0.10",
91
91
  "@sveltejs/kit": "^2.55.0",
@@ -253,7 +253,7 @@ export class ContextmenuState {
253
253
  do {
254
254
  i.selected = true;
255
255
  new_selections.unshift(i);
256
- } while ((i = i.menu) && i.menu); // eslint-disable-line @typescript-eslint/no-unnecessary-condition
256
+ } while ((i = i.menu) && i.menu);
257
257
  this.selections = new_selections;
258
258
  }
259
259
 
@@ -418,7 +418,7 @@ export const contextmenu_open = (
418
418
  // `navigator.vibrate()` works with `ContextmenuRoot` but gets blocked by some browsers
419
419
  // when used with `ContextmenuRootForSafariCompatibility` because its longpress
420
420
  // workaround triggers from a timeout rather than a direct user interaction.
421
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
421
+
422
422
  if (BROWSER && vibrate && navigator.vibrate) {
423
423
  navigator.vibrate(CONTEXTMENU_OPEN_VIBRATE_DURATION);
424
424
  }
package/src/lib/csp.ts CHANGED
@@ -89,7 +89,6 @@ export interface CreateCspDirectivesOptions {
89
89
  export const create_csp_directives = (options: CreateCspDirectivesOptions = {}): CspDirectives => {
90
90
  const {replace_defaults = csp_directive_value_defaults, extend, overrides} = options;
91
91
 
92
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
93
92
  if (replace_defaults === null) {
94
93
  throw new Error(
95
94
  `Invalid value 'null' for options.replace_defaults. ` +
@@ -112,14 +111,13 @@ export const create_csp_directives = (options: CreateCspDirectivesOptions = {}):
112
111
  // Stage 1: starting state from `replace_defaults`.
113
112
  // `{}` starts blank โ€” every directive must come from `extend`/`overrides`.
114
113
  for_each_directive(replace_defaults, 'replace_defaults', (directive, value) => {
115
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
116
114
  if (value === null) {
117
115
  throw new Error(
118
116
  `Invalid value 'null' for directive '${directive}' in options.replace_defaults. ` +
119
117
  `Omit the key instead, or use \`overrides: { '${directive}': null }\` to remove.`,
120
118
  );
121
119
  }
122
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
120
+
123
121
  if (value === undefined) return;
124
122
  assign(directive, value);
125
123
  });
@@ -172,7 +170,6 @@ export const create_csp_directives = (options: CreateCspDirectivesOptions = {}):
172
170
  for_each_directive(overrides, 'overrides', (directive, value) => {
173
171
  if (value === null) {
174
172
  delete directives[directive]; // eslint-disable-line @typescript-eslint/no-dynamic-delete
175
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
176
173
  } else if (value !== undefined) {
177
174
  assign(directive, value);
178
175
  }
@@ -242,9 +239,8 @@ const for_each_directive = <V>(
242
239
  source_label: 'replace_defaults' | 'extend' | 'overrides',
243
240
  fn: (directive: CspDirective, value: V) => void,
244
241
  ): void => {
245
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
246
242
  if (source === null || typeof source !== 'object') {
247
- const got = source === null ? 'null' : typeof source; // eslint-disable-line @typescript-eslint/no-unnecessary-condition
243
+ const got = source === null ? 'null' : typeof source;
248
244
  throw new Error(`Invalid entry in options.${source_label}: expected an object, got ${got}.`);
249
245
  }
250
246
  for (const [key, value] of Object.entries(source)) {
package/src/lib/tome.ts CHANGED
@@ -4,10 +4,22 @@ import {z} from 'zod';
4
4
  import {ensure_start} from '@fuzdev/fuz_util/string.js';
5
5
 
6
6
  import {create_context} from './context_helpers.js';
7
- import {DOCS_PATH_DEFAULT, docs_slugify} from './docs_helpers.svelte.js';
7
+ import {DOCS_PATH_DEFAULT} from './docs_helpers.svelte.js';
8
8
 
9
9
  export const Tome = z.object({
10
- name: z.string(),
10
+ /**
11
+ * Stable identifier and URL path segment โ€” must be a URL-safe slug
12
+ * (used directly as the route segment, the lookup key, and in
13
+ * `related_tomes` cross-references). Set `title` for the display label.
14
+ */
15
+ slug: z.string(),
16
+ /**
17
+ * Human-readable display label shown in menus, headings, and links.
18
+ * Falls back to `slug` when omitted, so set `title` to give multi-word or
19
+ * specially cased tomes a readable label (e.g. `slug: 'vite-plugin'` โ†’
20
+ * `title: 'vite plugin'`).
21
+ */
22
+ title: z.string().optional(),
11
23
  // TODO maybe now this makes sense with mdz? summary: z.string(),
12
24
  category: z.string(),
13
25
  Component: z.custom<Component<any, any>>(),
@@ -20,22 +32,27 @@ export type Tome = z.infer<typeof Tome>;
20
32
  /**
21
33
  * @param hash - URL fragment to append, with or without the `#`
22
34
  */
23
- export const to_tome_pathname = (
35
+ export const tome_to_pathname = (
24
36
  item: Tome | string,
25
37
  docs_path = DOCS_PATH_DEFAULT,
26
38
  hash?: string,
27
39
  ): string => {
28
- const name = typeof item === 'string' ? item : item.name;
29
- const path = docs_path + '/' + docs_slugify(name);
40
+ const slug = typeof item === 'string' ? item : item.slug;
41
+ const path = docs_path + '/' + slug;
30
42
  return resolve((hash ? path + ensure_start(hash, '#') : path) as any);
31
43
  };
32
44
 
45
+ /**
46
+ * The display label for a tome โ€” its `title` if set, else its `slug`.
47
+ */
48
+ export const tome_to_title = (tome: Tome): string => tome.title ?? tome.slug;
49
+
33
50
  export const tomes_context = create_context<() => Map<string, Tome>>();
34
51
 
35
- export const get_tome_by_name = (name: string): Tome => {
52
+ export const tome_get_by_slug = (slug: string): Tome => {
36
53
  const get_tomes = tomes_context.get();
37
- const tome = get_tomes().get(name);
38
- if (!tome) throw Error(`unable to find tome "${name}"`);
54
+ const tome = get_tomes().get(slug);
55
+ if (!tome) throw Error(`unable to find tome "${slug}"`);
39
56
  return tome;
40
57
  };
41
58