@pie-players/pie-assessment-toolkit 0.3.25 → 0.3.27

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 (55) hide show
  1. package/README.md +70 -17
  2. package/dist/components/ItemToolBar.custom-element.js +116 -9
  3. package/dist/components/PieAssessmentToolkit.custom-element.js +302 -7
  4. package/dist/index.d.ts +9 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +5 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/services/HighlightCoordinator.d.ts +3 -2
  9. package/dist/services/HighlightCoordinator.d.ts.map +1 -1
  10. package/dist/services/HighlightCoordinator.js +6 -3
  11. package/dist/services/HighlightCoordinator.js.map +1 -1
  12. package/dist/services/TTSService.d.ts +3 -0
  13. package/dist/services/TTSService.d.ts.map +1 -1
  14. package/dist/services/TTSService.js +66 -5
  15. package/dist/services/TTSService.js.map +1 -1
  16. package/dist/services/ToolRegistry.d.ts +10 -1
  17. package/dist/services/ToolRegistry.d.ts.map +1 -1
  18. package/dist/services/ToolRegistry.js.map +1 -1
  19. package/dist/services/ToolkitCoordinator.d.ts +37 -5
  20. package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
  21. package/dist/services/ToolkitCoordinator.js +114 -35
  22. package/dist/services/ToolkitCoordinator.js.map +1 -1
  23. package/dist/services/create-tools-config.d.ts +15 -0
  24. package/dist/services/create-tools-config.d.ts.map +1 -0
  25. package/dist/services/create-tools-config.js +13 -0
  26. package/dist/services/create-tools-config.js.map +1 -0
  27. package/dist/services/framework-error.d.ts +34 -0
  28. package/dist/services/framework-error.d.ts.map +1 -0
  29. package/dist/services/framework-error.js +43 -0
  30. package/dist/services/framework-error.js.map +1 -0
  31. package/dist/services/tool-config-validation.d.ts +31 -0
  32. package/dist/services/tool-config-validation.d.ts.map +1 -0
  33. package/dist/services/tool-config-validation.js +242 -0
  34. package/dist/services/tool-config-validation.js.map +1 -0
  35. package/dist/services/toolbar-items.d.ts +1 -0
  36. package/dist/services/toolbar-items.d.ts.map +1 -1
  37. package/dist/services/toolbar-items.js +28 -2
  38. package/dist/services/toolbar-items.js.map +1 -1
  39. package/dist/services/tts/visual-line-ranges.d.ts +19 -0
  40. package/dist/services/tts/visual-line-ranges.d.ts.map +1 -0
  41. package/dist/services/tts/visual-line-ranges.js +227 -0
  42. package/dist/services/tts/visual-line-ranges.js.map +1 -0
  43. package/dist/services/tts-runtime-config.d.ts +29 -0
  44. package/dist/services/tts-runtime-config.d.ts.map +1 -1
  45. package/dist/services/tts-runtime-config.js +101 -9
  46. package/dist/services/tts-runtime-config.js.map +1 -1
  47. package/dist/tools/registrations/accessibility-tools.js +1 -1
  48. package/dist/tools/registrations/accessibility-tools.js.map +1 -1
  49. package/dist/tools/registrations/measurement-tools.js +4 -4
  50. package/dist/tools/registrations/measurement-tools.js.map +1 -1
  51. package/dist/tools/registrations/tts.d.ts +1 -0
  52. package/dist/tools/registrations/tts.d.ts.map +1 -1
  53. package/dist/tools/registrations/tts.js +52 -21
  54. package/dist/tools/registrations/tts.js.map +1 -1
  55. package/package.json +7 -7
package/README.md CHANGED
@@ -33,7 +33,7 @@ const toolkitCoordinator = new ToolkitCoordinator({
33
33
  assessmentId: 'my-assessment',
34
34
  tools: {
35
35
  providers: {
36
- tts: { enabled: true, backend: 'browser' },
36
+ textToSpeech: { enabled: true, backend: 'browser' },
37
37
  calculator: { enabled: true }
38
38
  },
39
39
  placement: {
@@ -126,7 +126,7 @@ const coordinator = new ToolkitCoordinator({
126
126
  assessmentId: 'demo-assessment',
127
127
  tools: {
128
128
  providers: {
129
- tts: { enabled: true, backend: 'browser', defaultVoice: 'en-US' },
129
+ textToSpeech: { enabled: true, backend: 'browser', defaultVoice: 'en-US' },
130
130
  calculator: { enabled: true }
131
131
  },
132
132
  placement: {
@@ -219,7 +219,7 @@ The toolkit uses one canonical `tools` model with three concerns:
219
219
 
220
220
  - `policy`: allow/block constraints (global gates)
221
221
  - `placement`: where tools appear (`assessment`, `section`, `item`, `passage`, `rubric`, plus custom registered levels)
222
- - `providers`: provider/runtime options (calculator, tts, etc.)
222
+ - `providers`: provider/runtime options (calculator, textToSpeech, etc.)
223
223
 
224
224
  Example:
225
225
 
@@ -238,7 +238,7 @@ tools: {
238
238
  },
239
239
  providers: {
240
240
  calculator: { authFetcher: async () => ({ apiKey: '...' }) },
241
- tts: { enabled: true, backend: 'browser', defaultVoice: 'en-US' }
241
+ textToSpeech: { enabled: true, backend: 'browser', defaultVoice: 'en-US' }
242
242
  }
243
243
  }
244
244
  ```
@@ -361,7 +361,7 @@ const coordinator = new ToolkitCoordinator({
361
361
  return response.json();
362
362
  }
363
363
  },
364
- tts: { enabled: true, backend: 'browser' }
364
+ textToSpeech: { enabled: true, backend: 'browser' }
365
365
  }
366
366
  },
367
367
  accessibility: {
@@ -386,7 +386,7 @@ const coordinator = new ToolkitCoordinator({
386
386
  },
387
387
  providers: {
388
388
  calculator: { enabled: true },
389
- tts: { enabled: true, backend: 'browser' }
389
+ textToSpeech: { enabled: true, backend: 'browser' }
390
390
  }
391
391
  }
392
392
  });
@@ -402,7 +402,7 @@ Common options are defaulted so you can start with:
402
402
  ```typescript
403
403
  tools: {
404
404
  providers: {
405
- tts: {
405
+ textToSpeech: {
406
406
  enabled: true,
407
407
  backend: 'polly'
408
408
  }
@@ -425,7 +425,7 @@ Inline TTS speed buttons are configurable via `speedOptions` in provider setting
425
425
  ```typescript
426
426
  tools: {
427
427
  providers: {
428
- tts: {
428
+ textToSpeech: {
429
429
  enabled: true,
430
430
  backend: "browser",
431
431
  settings: {
@@ -438,7 +438,7 @@ tools: {
438
438
 
439
439
  `speedOptions` semantics:
440
440
 
441
- - Omitted or non-array: default speed buttons are shown (`1.5x`, `2x`).
441
+ - Omitted or non-array: default speed buttons are shown (`0.8x`, `1.25x`).
442
442
  - Explicit empty array (`[]`): hide all speed buttons.
443
443
  - Invalid-only arrays (for example `["fast", -1, 1]`): fall back to defaults.
444
444
  - Valid numeric values are deduplicated and keep first-seen order.
@@ -468,7 +468,7 @@ requires runtime auth material for TTS requests:
468
468
  ```typescript
469
469
  tools: {
470
470
  providers: {
471
- tts: {
471
+ textToSpeech: {
472
472
  enabled: true,
473
473
  backend: 'polly',
474
474
  apiEndpoint: '/api/tts',
@@ -493,7 +493,7 @@ prefer a host-owned proxy endpoint so secrets never ship to the browser.
493
493
  ```typescript
494
494
  tools: {
495
495
  providers: {
496
- tts: {
496
+ textToSpeech: {
497
497
  enabled: true,
498
498
  backend: "server",
499
499
  serverProvider: "custom",
@@ -615,7 +615,7 @@ export interface ToolkitCoordinatorConfig {
615
615
  rubric?: string[];
616
616
  };
617
617
  providers?: {
618
- tts?: {
618
+ textToSpeech?: {
619
619
  enabled?: boolean;
620
620
  backend?: 'browser' | 'polly' | 'google' | 'server';
621
621
  defaultVoice?: string;
@@ -627,6 +627,7 @@ export interface ToolkitCoordinatorConfig {
627
627
  };
628
628
  };
629
629
  };
630
+ toolRegistry?: ToolRegistry | null;
630
631
  accessibility?: {
631
632
  catalogs?: any[];
632
633
  language?: string;
@@ -642,9 +643,9 @@ const services = coordinator.getServiceBundle();
642
643
  // Returns: { ttsService, toolCoordinator, highlightCoordinator, elementToolStateStore, catalogResolver }
643
644
 
644
645
  // Tool configuration
645
- coordinator.isToolEnabled('tts'); // Check if tool is enabled
646
- coordinator.getToolConfig('tts'); // Get tool-specific config
647
- coordinator.updateToolConfig('tts', { rate: 1.5 }); // Update tool config
646
+ coordinator.isToolEnabled('textToSpeech'); // Check if tool is enabled
647
+ coordinator.getToolConfig('textToSpeech'); // Get tool-specific config
648
+ coordinator.updateToolConfig('textToSpeech', { rate: 1.5 }); // Update tool config
648
649
  ```
649
650
 
650
651
  ### Direct Service Access
@@ -865,7 +866,7 @@ The section player provides automatic ToolkitCoordinator integration:
865
866
  const coordinator = new ToolkitCoordinator({
866
867
  assessmentId: 'my-assessment',
867
868
  tools: {
868
- providers: { tts: { enabled: true, backend: 'browser' } },
869
+ providers: { textToSpeech: { enabled: true, backend: 'browser' } },
869
870
  placement: {
870
871
  section: ['calculator', 'graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
871
872
  item: ['calculator', 'textToSpeech', 'answerEliminator'],
@@ -929,7 +930,7 @@ player.section = mySection;
929
930
  // new ToolkitCoordinator({
930
931
  // assessmentId: 'anon_...', // auto-generated
931
932
  // tools: {
932
- // providers: { tts: { enabled: true, backend: 'browser' }, calculator: { enabled: true } },
933
+ // providers: { textToSpeech: { enabled: true, backend: 'browser' }, calculator: { enabled: true } },
933
934
  // placement: {
934
935
  // section: ['calculator', 'graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
935
936
  // item: ['calculator', 'textToSpeech', 'answerEliminator'],
@@ -939,6 +940,56 @@ player.section = mySection;
939
940
  // })
940
941
  ```
941
942
 
943
+ ### Safe Custom Tool Configuration
944
+
945
+ Default behavior is now framework-owned: invalid tools/runtime initialization is handled in `pie-assessment-toolkit` without host try/catch.
946
+
947
+ - Framework logs a deterministic console error prefix: `[pie-framework:<kind>:<source>]`
948
+ - Framework emits a canonical `framework-error` event (and still emits `runtime-error` for compatibility)
949
+ - Framework renders a fallback error panel instead of a blank player
950
+ - Startup tool-config validation can surface as `kind: "coordinator-init"` when the owned coordinator construction path throws.
951
+
952
+ Use `createToolsConfig()` when you want to pre-validate and inspect diagnostics before mounting:
953
+
954
+ ```typescript
955
+ import {
956
+ createPackagedToolRegistry,
957
+ createToolsConfig,
958
+ ToolkitCoordinator
959
+ } from "@pie-players/pie-assessment-toolkit";
960
+
961
+ const toolRegistry = createPackagedToolRegistry();
962
+ const { config, diagnostics } = createToolsConfig({
963
+ source: "host.bootstrap",
964
+ strictness: "error",
965
+ toolRegistry,
966
+ tools: {
967
+ providers: {
968
+ textToSpeech: { enabled: true, backend: "browser" },
969
+ calculator: { enabled: true }
970
+ },
971
+ placement: {
972
+ item: ["calculator", "textToSpeech"]
973
+ }
974
+ }
975
+ });
976
+
977
+ // Fail-fast default: invalid config throws at the boundary.
978
+ const coordinator = new ToolkitCoordinator({
979
+ assessmentId: "demo-assessment",
980
+ toolRegistry,
981
+ tools: config,
982
+ toolConfigStrictness: "error"
983
+ });
984
+ ```
985
+
986
+ Notes:
987
+ - `providers.textToSpeech` is the canonical TTS provider key.
988
+ - `providers.tts` is rejected by the validation contract.
989
+ - Custom tools can provide provider-level `sanitizeConfig` and `validateConfig` hooks.
990
+ - Hosts can react to framework errors via `onframework-error` listeners or `onFrameworkError` callback prop.
991
+ - See `docs/tools-and-accomodations/framework-owned-error-handling.md` for event payload and error-kind mapping details.
992
+
942
993
  ## State Separation: Tool State vs Session Data
943
994
 
944
995
  The toolkit enforces a clear separation between ephemeral tool state and persistent session data:
@@ -1013,6 +1064,8 @@ import type {
1013
1064
  - **[PNP Configuration Guide](docs/PNP_CONFIGURATION.md)** - ⭐ NEW - How to configure student profiles, district policies, and governance rules
1014
1065
  - [ToolkitCoordinator Architecture](../../docs/architecture/TOOLKIT_COORDINATOR.md) - Design decisions and patterns
1015
1066
  - [Section Player README](../section-player/README.md) - Section player integration
1067
+ - [Framework-Owned Error Handling](../../docs/tools-and-accomodations/framework-owned-error-handling.md) - Canonical framework error model/events and fallback behavior
1068
+ - [Safe Custom Tool Configuration](../../docs/tools-and-accomodations/safe-custom-tool-config.md) - Host-side config patterns and validation guidance
1016
1069
  - [Architecture Overview](../../docs/architecture/architecture.md) - Complete system architecture
1017
1070
 
1018
1071
  ## License
@@ -22,7 +22,13 @@ import {
22
22
  connectAssessmentToolkitShellContext
23
23
  } from '../context/runtime-context-consumer.js';
24
24
 
25
- import { isExternalIconUrl, isInlineSvgIcon, isToolbarLinkItem } from '../services/toolbar-items.js';
25
+ import {
26
+ isExternalIconUrl,
27
+ isInlineSvgIcon,
28
+ isToolbarLinkItem,
29
+ isValidToolbarItemShape
30
+ } from '../services/toolbar-items.js';
31
+
26
32
  import { createPackagedToolRegistry } from '../services/createDefaultToolRegistry.js';
27
33
  import { DEFAULT_TOOL_MODULE_LOADERS } from '../tools/default-tool-module-loaders.js';
28
34
  import { normalizeToolsConfig, parseToolList, resolveToolsForLevel } from '../services/tools-config-normalizer.js';
@@ -38,11 +44,14 @@ var root_19 = $.from_html(`<i aria-hidden="true"></i>`);
38
44
  var root_13 = $.from_html(`<button type="button"><!></button>`);
39
45
  var root_21 = $.from_html(`<span class="item-toolbar__element-host svelte-rnx3ie"></span>`);
40
46
  var root_22 = $.from_html(`<span class="item-toolbar__element-host svelte-rnx3ie"></span>`);
41
- var root_1 = $.from_html(`<div><div class="item-toolbar__tools-row svelte-rnx3ie"><!> <!> <!></div> <div class="item-toolbar__controls-row svelte-rnx3ie" aria-hidden="true"></div></div>`);
47
+ var root_25 = $.from_html(`<span class="item-toolbar__controls-host svelte-rnx3ie"></span>`);
48
+ var root_26 = $.from_html(`<span class="item-toolbar__controls-host svelte-rnx3ie"></span>`);
49
+ var root_23 = $.from_html(`<div></div>`);
50
+ var root_1 = $.from_html(`<div><div class="item-toolbar__tools-row svelte-rnx3ie"><!> <!> <!></div> <!></div>`);
42
51
 
43
52
  const $$css = {
44
53
  hash: 'svelte-rnx3ie',
45
- code: '.item-toolbar.svelte-rnx3ie {display:flex;flex-direction:column;align-items:flex-end;gap:0;--pie-toolbar-tools-row-height: 2rem;--pie-tts-controls-row-height: 2.875rem;}.item-toolbar__tools-row.svelte-rnx3ie {display:flex;align-items:center;justify-content:flex-end;flex-wrap:nowrap;gap:0.5rem;min-height:var(--pie-toolbar-tools-row-height);}.item-toolbar__controls-row.svelte-rnx3ie {display:flex;align-items:center;justify-content:flex-end;width:100%;min-height:var(--pie-tts-controls-row-height);height:var(--pie-tts-controls-row-height);}.item-toolbar__controls-host.svelte-rnx3ie {display:inline-flex;align-items:center;justify-content:flex-end;width:100%;}.item-toolbar--top.svelte-rnx3ie,\n .item-toolbar--bottom.svelte-rnx3ie {align-items:flex-end;}.item-toolbar--left.svelte-rnx3ie,\n .item-toolbar--right.svelte-rnx3ie {align-items:stretch;}.item-toolbar--left.svelte-rnx3ie .item-toolbar__tools-row:where(.svelte-rnx3ie),\n .item-toolbar--right.svelte-rnx3ie .item-toolbar__tools-row:where(.svelte-rnx3ie) {flex-direction:column;align-items:center;justify-content:flex-start;flex-wrap:nowrap;}.item-toolbar--left.svelte-rnx3ie .item-toolbar__controls-row:where(.svelte-rnx3ie),\n .item-toolbar--right.svelte-rnx3ie .item-toolbar__controls-row:where(.svelte-rnx3ie) {justify-content:flex-start;width:auto;min-height:0;height:auto;}.item-toolbar__button.svelte-rnx3ie {display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;padding:0.25rem;border:1px solid var(--pie-button-border, var(--pie-border, #ccc));border-radius:0.25rem;background-color:var(--pie-button-bg, var(--pie-background, white));color:var(--pie-button-color, var(--pie-text, #333));cursor:pointer;transition:all 0.15s ease;text-decoration:none;}.item-toolbar__element-host.svelte-rnx3ie {display:contents;}.item-toolbar--sm.svelte-rnx3ie .item-toolbar__button:where(.svelte-rnx3ie) {width:2.75rem;height:2.75rem;}.item-toolbar--sm.svelte-rnx3ie {--pie-toolbar-tools-row-height: 2.75rem;--pie-tts-controls-row-height: 3.625rem;}.item-toolbar--lg.svelte-rnx3ie .item-toolbar__button:where(.svelte-rnx3ie) {width:2.5rem;height:2.5rem;}.item-toolbar--lg.svelte-rnx3ie {--pie-toolbar-tools-row-height: 2.5rem;--pie-tts-controls-row-height: 3.375rem;}.item-toolbar__button.svelte-rnx3ie:hover:not(:disabled) {background-color:var(--pie-button-hover-bg, var(--pie-secondary-background, #f5f5f5));border-color:var(--pie-button-hover-border, var(--pie-button-border, var(--pie-border, #ccc)));color:var(--pie-button-hover-color, var(--pie-button-color, var(--pie-text, #333)));transform:translateY(-1px);box-shadow:0 2px 4px color-mix(in srgb, var(--pie-black, #000) 10%, transparent);}.item-toolbar__button.svelte-rnx3ie:active:not(:disabled) {transform:translateY(0);box-shadow:none;}.item-toolbar__button--active.svelte-rnx3ie {background-color:var(--pie-primary, #1976d2);color:var(--pie-white, #fff);border-color:var(--pie-primary, #1976d2);}.item-toolbar__button--active.svelte-rnx3ie:hover:not(:disabled) {background-color:var(--pie-primary-dark, #1565c0);}.item-toolbar__button.svelte-rnx3ie:focus-visible {outline:2px solid var(--pie-button-focus-outline, var(--pie-primary, #1976d2));outline-offset:2px;}.item-toolbar__button.svelte-rnx3ie:disabled {opacity:0.5;cursor:not-allowed;}.item-toolbar__button.svelte-rnx3ie svg {width:100%;height:100%;}.item-toolbar__icon-image.svelte-rnx3ie {width:100%;height:100%;object-fit:contain;}'
54
+ code: '.item-toolbar.svelte-rnx3ie {display:flex;flex-direction:column;align-items:flex-end;gap:0;--pie-toolbar-tools-row-height: 2rem;--pie-tts-controls-row-height: 2.875rem;}.item-toolbar__tools-row.svelte-rnx3ie {display:flex;align-items:center;justify-content:flex-end;flex-wrap:nowrap;gap:0.5rem;min-height:var(--pie-toolbar-tools-row-height);}.item-toolbar__controls-row.svelte-rnx3ie {display:flex;align-items:center;justify-content:flex-end;width:100%;min-height:0;height:auto;}.item-toolbar__controls-row--reserve.svelte-rnx3ie {min-height:var(--pie-tts-controls-row-height);height:var(--pie-tts-controls-row-height);}.item-toolbar__controls-row--active.svelte-rnx3ie {min-height:var(--pie-tts-controls-row-height);height:var(--pie-tts-controls-row-height);}.item-toolbar__controls-row--align-start.svelte-rnx3ie {justify-content:flex-start;}.item-toolbar__controls-host.svelte-rnx3ie {display:inline-flex;align-items:center;justify-content:flex-end;width:100%;}.item-toolbar--top.svelte-rnx3ie,\n .item-toolbar--bottom.svelte-rnx3ie {align-items:flex-end;}.item-toolbar--left.svelte-rnx3ie,\n .item-toolbar--right.svelte-rnx3ie {align-items:stretch;}.item-toolbar--left.svelte-rnx3ie .item-toolbar__tools-row:where(.svelte-rnx3ie),\n .item-toolbar--right.svelte-rnx3ie .item-toolbar__tools-row:where(.svelte-rnx3ie) {flex-direction:column;align-items:center;justify-content:flex-start;flex-wrap:nowrap;}.item-toolbar--left.svelte-rnx3ie .item-toolbar__controls-row:where(.svelte-rnx3ie),\n .item-toolbar--right.svelte-rnx3ie .item-toolbar__controls-row:where(.svelte-rnx3ie) {justify-content:flex-start;width:auto;min-height:0;height:auto;}.item-toolbar__button.svelte-rnx3ie {display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;padding:0.25rem;border:1px solid var(--pie-button-border, var(--pie-border, #ccc));border-radius:0.25rem;background-color:var(--pie-button-bg, var(--pie-background, white));color:var(--pie-button-color, var(--pie-text, #333));cursor:pointer;transition:all 0.15s ease;text-decoration:none;}.item-toolbar__element-host.svelte-rnx3ie {display:contents;}.item-toolbar--sm.svelte-rnx3ie .item-toolbar__button:where(.svelte-rnx3ie) {width:2.75rem;height:2.75rem;}.item-toolbar--sm.svelte-rnx3ie {--pie-toolbar-tools-row-height: 2.75rem;--pie-tts-controls-row-height: 3.625rem;}.item-toolbar--lg.svelte-rnx3ie .item-toolbar__button:where(.svelte-rnx3ie) {width:2.5rem;height:2.5rem;}.item-toolbar--lg.svelte-rnx3ie {--pie-toolbar-tools-row-height: 2.5rem;--pie-tts-controls-row-height: 3.375rem;}.item-toolbar__button.svelte-rnx3ie:hover:not(:disabled) {background-color:var(--pie-button-hover-bg, var(--pie-secondary-background, #f5f5f5));border-color:var(--pie-button-hover-border, var(--pie-button-border, var(--pie-border, #ccc)));color:var(--pie-button-hover-color, var(--pie-button-color, var(--pie-text, #333)));transform:translateY(-1px);box-shadow:0 2px 4px color-mix(in srgb, var(--pie-black, #000) 10%, transparent);}.item-toolbar__button.svelte-rnx3ie:active:not(:disabled) {transform:translateY(0);box-shadow:none;}.item-toolbar__button--active.svelte-rnx3ie {background-color:var(--pie-primary, #1976d2);color:var(--pie-white, #fff);border-color:var(--pie-primary, #1976d2);}.item-toolbar__button--active.svelte-rnx3ie:hover:not(:disabled) {background-color:var(--pie-primary-dark, #1565c0);}.item-toolbar__button.svelte-rnx3ie:focus-visible {outline:2px solid var(--pie-button-focus-outline, var(--pie-primary, #1976d2));outline-offset:2px;}.item-toolbar__button.svelte-rnx3ie:disabled {opacity:0.5;cursor:not-allowed;}.item-toolbar__button.svelte-rnx3ie svg {width:100%;height:100%;}.item-toolbar__icon-image.svelte-rnx3ie {width:100%;height:100%;object-fit:contain;}'
46
55
  };
47
56
 
48
57
  export default function ItemToolBar($$anchor, $$props) {
@@ -401,9 +410,23 @@ export default function ItemToolBar($$anchor, $$props) {
401
410
  return rendered;
402
411
  });
403
412
 
404
- const normalizedHostButtons = $.derived(() => Array.isArray(hostButtons())
405
- ? hostButtons().filter((item) => !!item && typeof item.id === 'string')
406
- : []);
413
+ const normalizedHostButtons = $.derived(() => {
414
+ if (!Array.isArray(hostButtons())) return [];
415
+
416
+ const valid = [];
417
+
418
+ hostButtons().forEach((item, index) => {
419
+ if (isValidToolbarItemShape(item)) {
420
+ valid.push(item);
421
+
422
+ return;
423
+ }
424
+
425
+ console.warn(`[ItemToolBar] Ignoring invalid host button at index ${index}. Expected { id, label, href | onClick } with valid types.`);
426
+ });
427
+
428
+ return valid;
429
+ });
407
430
 
408
431
  const nativeToolbarItems = $.derived(() => $.get(renderedTools).filter((renderedTool) => !!renderedTool.button).map((renderedTool) => {
409
432
  const button = renderedTool.button;
@@ -450,6 +473,22 @@ export default function ItemToolBar($$anchor, $$props) {
450
473
  entry
451
474
  }))));
452
475
 
476
+ const mountedElementsControlsRow = $.derived(() => $.get(renderedTools).flatMap((renderedTool) => (renderedTool.elements || []).filter((entry) => entry.mount === 'controls-row').filter((entry) => Boolean(entry.element)).map((entry, index) => ({
477
+ key: `controls-row-${renderedTool.toolId}-${index}`,
478
+ toolId: renderedTool.toolId,
479
+ entry
480
+ }))));
481
+
482
+ const controlsRowHints = $.derived(() => $.get(renderedTools).flatMap((renderedTool) => (renderedTool.elements || []).map((entry) => ({
483
+ toolId: renderedTool.toolId,
484
+ hint: entry.layoutHints?.controlsRow
485
+ })).filter((entry) => Boolean(entry.hint))));
486
+
487
+ const controlsRowShouldReserveSpace = $.derived(() => $.get(controlsRowHints).some((entry) => entry.hint?.reserveSpace === true));
488
+ const controlsRowShouldExpandForActiveTool = $.derived(() => $.get(controlsRowHints).some((entry) => entry.hint?.showWhenToolActive === true && ($.get(renderedToolActiveById)[entry.toolId] ?? false)));
489
+ const controlsRowAlignStart = $.derived(() => position() === 'left' || position() === 'right');
490
+ const shouldRenderControlsRow = $.derived(() => $.get(mountedElementsControlsRow).length > 0 || $.get(controlsRowShouldReserveSpace) || $.get(controlsRowShouldExpandForActiveTool));
491
+
453
492
  function isToolbarItemActive(item) {
454
493
  if (item.id in $.get(activeToolState)) {
455
494
  return $.get(activeToolState)[item.id] === true;
@@ -516,6 +555,20 @@ export default function ItemToolBar($$anchor, $$props) {
516
555
  });
517
556
  });
518
557
 
558
+ $.user_effect(() => {
559
+ const toolkitCoordinator = $.get(toolbarContext).toolkitCoordinator;
560
+
561
+ if (typeof toolkitCoordinator?.subscribeTelemetry !== 'function') return;
562
+
563
+ return toolkitCoordinator.subscribeTelemetry(({ eventName, payload }) => {
564
+ if (eventName !== 'tool-config-updated') return;
565
+ if (payload?.toolId && !$.get(toolbarVisibleToolIds).includes(payload.toolId)) return;
566
+
567
+ $.set(moduleLoadVersion, $.get(moduleLoadVersion) + 1);
568
+ syncRenderedToolsState();
569
+ });
570
+ });
571
+
519
572
  function mountElement(node, element) {
520
573
  let mountedElement = null;
521
574
 
@@ -1245,7 +1298,7 @@ export default function ItemToolBar($$anchor, $$props) {
1245
1298
  var node_1 = $.first_child(fragment);
1246
1299
 
1247
1300
  {
1248
- var consequent_11 = ($$anchor) => {
1301
+ var consequent_13 = ($$anchor) => {
1249
1302
  var div = root_1();
1250
1303
  let classes;
1251
1304
  var div_1 = $.child(div);
@@ -1530,7 +1583,61 @@ export default function ItemToolBar($$anchor, $$props) {
1530
1583
  });
1531
1584
 
1532
1585
  $.reset(div_1);
1533
- $.next(2);
1586
+
1587
+ var node_18 = $.sibling(div_1, 2);
1588
+
1589
+ {
1590
+ var consequent_12 = ($$anchor) => {
1591
+ var div_2 = root_23();
1592
+ let classes_3;
1593
+
1594
+ $.each(div_2, 21, () => $.get(mountedElementsControlsRow), (mounted) => mounted.key, ($$anchor, mounted) => {
1595
+ var fragment_12 = $.comment();
1596
+ var node_19 = $.first_child(fragment_12);
1597
+
1598
+ {
1599
+ var consequent_11 = ($$anchor) => {
1600
+ var span_4 = root_25();
1601
+
1602
+ $.action(span_4, ($$node, $$action_arg) => mountElementWithShell?.($$node, $$action_arg), () => ({
1603
+ mounted: $.get(mounted),
1604
+ active: $.get(renderedToolActiveById)[$.get(mounted).toolId] ?? false
1605
+ }));
1606
+
1607
+ $.append($$anchor, span_4);
1608
+ };
1609
+
1610
+ var alternate_7 = ($$anchor) => {
1611
+ var span_5 = root_26();
1612
+
1613
+ $.action(span_5, ($$node, $$action_arg) => mountElement?.($$node, $$action_arg), () => $.get(mounted).entry.element);
1614
+ $.append($$anchor, span_5);
1615
+ };
1616
+
1617
+ $.if(node_19, ($$render) => {
1618
+ if ($.get(mounted).entry.shell) $$render(consequent_11); else $$render(alternate_7, -1);
1619
+ });
1620
+ }
1621
+
1622
+ $.append($$anchor, fragment_12);
1623
+ });
1624
+
1625
+ $.reset(div_2);
1626
+
1627
+ $.template_effect(() => classes_3 = $.set_class(div_2, 1, 'item-toolbar__controls-row svelte-rnx3ie', null, classes_3, {
1628
+ 'item-toolbar__controls-row--reserve': $.get(controlsRowShouldReserveSpace),
1629
+ 'item-toolbar__controls-row--active': $.get(controlsRowShouldExpandForActiveTool),
1630
+ 'item-toolbar__controls-row--align-start': $.get(controlsRowAlignStart)
1631
+ }));
1632
+
1633
+ $.append($$anchor, div_2);
1634
+ };
1635
+
1636
+ $.if(node_18, ($$render) => {
1637
+ if ($.get(shouldRenderControlsRow)) $$render(consequent_12);
1638
+ });
1639
+ }
1640
+
1534
1641
  $.reset(div);
1535
1642
  $.bind_this(div, ($$value) => $.set(toolbarRootElement, $$value), () => $.get(toolbarRootElement));
1536
1643
 
@@ -1550,7 +1657,7 @@ export default function ItemToolBar($$anchor, $$props) {
1550
1657
  };
1551
1658
 
1552
1659
  $.if(node_1, ($$render) => {
1553
- if (isBrowser) $$render(consequent_11);
1660
+ if (isBrowser) $$render(consequent_13);
1554
1661
  });
1555
1662
  }
1556
1663