uhuu-components 0.3.12 → 0.4.1

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.
package/index.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import type {
3
3
  ComponentType,
4
4
  CSSProperties,
5
+ HTMLAttributes,
5
6
  ImgHTMLAttributes,
6
7
  Key,
7
8
  MouseEventHandler,
@@ -187,8 +188,11 @@ export interface StaticFlowMeasureApi {
187
188
  getEffectiveScale: (element: HTMLElement) => number;
188
189
  /** Border-box height plus vertical margins, in layout space. */
189
190
  getOuterHeight: (element: HTMLElement, scale?: number) => number;
191
+ /** @internal Library measurement detail; retained for compatibility. */
190
192
  readItemMeta: (element: HTMLElement) => StaticFlowItemMeta;
193
+ /** @internal Library measurement detail; retained for compatibility. */
191
194
  readHeaderGroupKey: (element: HTMLElement) => string | undefined;
195
+ /** @internal Library measurement detail; retained for compatibility. */
192
196
  readHeaderGroupRepeats: (itemElements: HTMLElement[]) => Record<string, boolean>;
193
197
  /** `unwrapped`: heights from `readUnwrappedHeights`, used for the headers it covers. */
194
198
  readHeaderGroupHeights: (
@@ -200,6 +204,7 @@ export interface StaticFlowMeasureApi {
200
204
  * Heights of the items and group headers an `asChild` Flow renders as direct children of its
201
205
  * root, from positions: margins collapse without a wrapper, so each item is the distance from
202
206
  * the bottom of the element before it (the area's top for the first) to its own bottom.
207
+ * @internal Library measurement detail; retained for compatibility.
203
208
  */
204
209
  readUnwrappedHeights: (area: HTMLElement, flow: HTMLElement, scale?: number) => Map<HTMLElement, number>;
205
210
  /** Every measured leaf under `root`, in document order. */
@@ -207,24 +212,36 @@ export interface StaticFlowMeasureApi {
207
212
  /**
208
213
  * The first of `items` that does not lie below the item before it in `area` (beside it, in
209
214
  * another column), or `null`: such a flow cannot be planned (`unstacked-flow-items`).
215
+ * @internal Library measurement detail; retained for compatibility.
210
216
  */
211
217
  readUnstackedItem: (area: HTMLElement, flow: HTMLElement, items: HTMLElement[]) => HTMLElement | null;
212
218
  /**
213
219
  * A CSS length with an absolute unit (`"20mm"`, `"12px"`) or a number of px, in CSS px;
214
220
  * `undefined` for anything else. What `breakReserve` is read with.
221
+ * @internal Library measurement detail; retained for compatibility.
215
222
  */
216
223
  parseAbsoluteLength: (value: number | string | undefined) => number | undefined;
224
+ /** @internal Library measurement detail; retained for compatibility. */
217
225
  parseKeepWithNext: (value: string | undefined) => boolean | number;
226
+ /** @internal Library measurement detail; retained for compatibility. */
218
227
  serializeKeepWithNext: (value: StaticFlowItemMeta['keepWithNext']) => string | undefined;
219
- /** `orphans` / `widows` attribute values: a positive whole number, else `undefined`. */
228
+ /**
229
+ * `orphans` / `widows` attribute values: a positive whole number, else `undefined`.
230
+ * @internal Library measurement detail; retained for compatibility.
231
+ */
220
232
  parseLineCount: (value: string | undefined) => number | undefined;
233
+ /** @internal Library measurement detail; retained for compatibility. */
221
234
  serializeLineCount: (value: number | undefined) => string | undefined;
222
- /** The `Static.FlowText` elements of `item` itself (not those of a Flow nested in it). */
235
+ /**
236
+ * The `Static.FlowText` elements of `item` itself (not those of a Flow nested in it).
237
+ * @internal Library measurement detail; retained for compatibility.
238
+ */
223
239
  readItemFlowTexts: (item: HTMLElement) => HTMLElement[];
224
240
  /**
225
241
  * The lines of the one `Static.FlowText` in a measured item, for `planFlowChunks({ lines })`, or
226
242
  * `undefined` when it has none or cannot split (`report` says why). `height` is the item's
227
243
  * measured height (`getOuterHeight`, or `readUnwrappedHeights` for `asChild`).
244
+ * @internal Library measurement detail; retained for compatibility.
228
245
  */
229
246
  readFlowTextLines: (
230
247
  item: HTMLElement,
@@ -232,7 +249,10 @@ export interface StaticFlowMeasureApi {
232
249
  scale?: number,
233
250
  report?: (issue: StaticFlowTextIssue, element: HTMLElement) => void
234
251
  ) => StaticFlowItemLines | undefined;
235
- /** FNV-1a. For opaque change-detection tokens compared only for equality. */
252
+ /**
253
+ * FNV-1a. For opaque change-detection tokens compared only for equality.
254
+ * @internal Library measurement detail; retained for compatibility.
255
+ */
236
256
  hashString: (value: string) => string;
237
257
  }
238
258
 
@@ -639,8 +659,10 @@ export interface PageEditorItem {
639
659
  }
640
660
 
641
661
  export interface PageEditorState {
642
- items?: PageEditorItem[];
643
- [key: string]: unknown;
662
+ key: string;
663
+ items: PageItem[];
664
+ totalPages: number;
665
+ updatedAt: string;
644
666
  }
645
667
 
646
668
  /**
@@ -648,7 +670,7 @@ export interface PageEditorState {
648
670
  * page-option values) and the sheet being rendered. A `hasFlow` page renders one sheet per flow
649
671
  * chunk; its continuation sheets share the page's `id`.
650
672
  */
651
- export interface PageEditorPage {
673
+ export interface PageEditorPage extends StandalonePage {
652
674
  id: string;
653
675
  componentKey?: string;
654
676
  templateId?: string;
@@ -666,6 +688,22 @@ export interface PageEditorPage {
666
688
  [key: string]: unknown;
667
689
  }
668
690
 
691
+ /** Dialog paths and the resolved integration for one logical page. */
692
+ export interface PageDataBinding {
693
+ payload: any;
694
+ pageId: string;
695
+ pagePayload?: any;
696
+ parentGroupId?: string;
697
+ integration: { instanceId: string | null; data?: any; path: (subPath?: string) => string | null };
698
+ paths: {
699
+ integration: (subPath?: string) => string | null;
700
+ page: (subPath?: string) => string | null;
701
+ group: (subPath?: string) => string | null;
702
+ document: (subPath?: string) => string | null;
703
+ };
704
+ defaults: { imageGalleryPath?: string | null };
705
+ }
706
+
669
707
  /** The props PageEditor passes to a page component. */
670
708
  export interface PageComponentProps {
671
709
  payload?: Record<string, unknown>;
@@ -681,15 +719,15 @@ export interface PageComponentProps {
681
719
  /** Sheets in the flow-expanded document. */
682
720
  totalPages?: number;
683
721
  page?: PageEditorPage;
684
- parentGroup?: PageEditorItem;
722
+ parentGroup?: PageGroup;
685
723
  /** Dialog path helpers and defaults (docs/page-data-binding.md). */
686
- dataBinding?: Record<string, unknown>;
724
+ dataBinding?: PageDataBinding;
687
725
  /** Set while the page is a panel of a perfect-binding cover spread. */
688
726
  spread?: SpreadInfo;
689
727
  }
690
728
 
691
729
  export interface PageFilterConfig {
692
- mode?: "all" | "cover" | "text" | "custom" | string;
730
+ mode?: "all" | "cover" | "text" | "custom";
693
731
  coverPageCount?: number;
694
732
  ranges?: Array<{ start: number; end: number }>;
695
733
  [key: string]: unknown;
@@ -732,35 +770,182 @@ export interface PageManagementOptions {
732
770
  rename?: boolean;
733
771
  }
734
772
 
773
+ /** A page or group the shell supplies to page-option callbacks. */
774
+ export type PageItem = StandalonePage | PageGroup;
775
+
776
+ export interface StandalonePage {
777
+ kind: "page";
778
+ id: string;
779
+ componentKey?: string;
780
+ dataKey?: string;
781
+ templateId?: string;
782
+ label?: LocalizedText;
783
+ className?: string;
784
+ hasFlow?: boolean;
785
+ repeatable?: boolean;
786
+ maxInstances?: number | null;
787
+ integration?: "required" | "optional" | "none" | { mode?: "required" | "optional" | "none" };
788
+ pageNum?: number;
789
+ component?: ComponentType<any>;
790
+ [key: string]: any;
791
+ }
792
+
793
+ export interface PageGroup {
794
+ kind: "group";
795
+ id: string;
796
+ templateId?: string;
797
+ repeatable?: boolean;
798
+ maxInstances?: number | null;
799
+ integration?: StandalonePage["integration"];
800
+ label?: LocalizedText;
801
+ pages: Array<Omit<StandalonePage, "pageNum" | "kind"> & { id: string }>;
802
+ pageNum?: never;
803
+ [key: string]: any;
804
+ }
805
+
806
+ /** Page blueprint in `templateConfig.pages`; metadata applies whenever that layout is used. */
807
+ export interface PageTemplateDefinition {
808
+ label?: LocalizedText;
809
+ className?: string;
810
+ componentKey?: string;
811
+ dataKey?: string;
812
+ component?: ComponentType<any>;
813
+ hasFlow?: boolean;
814
+ repeatable?: boolean;
815
+ maxInstances?: number | null;
816
+ thumbnail?: string;
817
+ integration?: StandalonePage["integration"];
818
+ allowAsSinglePage?: boolean;
819
+ strictPosition?: "start" | "end";
820
+ }
821
+
822
+ export interface PageComponentKeysContext {
823
+ payload: any;
824
+ item?: PageGroup;
825
+ parent?: PageItem;
826
+ }
827
+
828
+ /** Group blueprint; record keys provide `id`, array and inline definitions supply it themselves. */
829
+ export interface GroupDefinition {
830
+ id: string;
831
+ label?: LocalizedText;
832
+ thumbnail?: string;
833
+ repeatable?: boolean;
834
+ maxInstances?: number | null;
835
+ pageComponentKeys?: string[] | ((context: PageComponentKeysContext) => string[]);
836
+ integration?: StandalonePage["integration"];
837
+ strictPosition?: "start" | "end";
838
+ }
839
+
840
+ export interface PageEditorTemplateConfig {
841
+ initial: Array<string | (Partial<StandalonePage> & { id: string }) | GroupDefinition>;
842
+ pageManagement?: PageManagementOptions;
843
+ groups?: GroupDefinition[] | Record<string, Omit<GroupDefinition, "id"> & { id?: string }>;
844
+ pageComponentKeys?: string[];
845
+ pages?: Record<string, PageTemplateDefinition>;
846
+ pageComponents?: Record<string, ComponentType<any>>;
847
+ spine?: PageEditorSpineConfig;
848
+ payload?: any;
849
+ /** Template-owned metadata may accompany the shell configuration. */
850
+ [key: string]: unknown;
851
+ }
852
+
853
+ export interface PageOptionSelectValue {
854
+ label: LocalizedText;
855
+ /** Matched to the stored value as a string (`2` is `"2"`); a payload option writes it typed by `valueType`. */
856
+ value: string | number | boolean;
857
+ hex?: string;
858
+ }
859
+
860
+ type PageOptionTarget = string | ((item: PageItem) => boolean);
861
+ type BasePageOption = {
862
+ id: string;
863
+ label: LocalizedText;
864
+ description?: LocalizedText;
865
+ appliesTo?: PageOptionTarget | PageOptionTarget[];
866
+ onChange?: (id: string, value: string, context: { item?: PageItem; updateItem: (patch: Partial<PageItem>) => void }) => void;
867
+ applyPatch?: (value: string, item?: PageItem) => Partial<PageItem> | void;
868
+ };
869
+
870
+ export type SelectPageOption = BasePageOption & {
871
+ type: "select";
872
+ options: PageOptionSelectValue[];
873
+ getValue: (item: PageItem) => string | number | boolean;
874
+ };
875
+ export type TogglePageOption = BasePageOption & { type: "toggle"; getValue: (item: PageItem) => boolean };
876
+ export type SliderPageOption = BasePageOption & {
877
+ type: "slider"; min: number; max: number; step: number; getValue: (item: PageItem) => number;
878
+ };
879
+ export type CounterPageOption = BasePageOption & {
880
+ type: "counter"; min: number; max: number; step: number; getValue: (item: PageItem) => number;
881
+ };
882
+ export type ColorSeriesPageOption = BasePageOption & {
883
+ type: "color-series"; options: PageOptionSelectValue[]; getValue: (item: PageItem) => string;
884
+ };
885
+ export type PageOption = SelectPageOption | TogglePageOption | SliderPageOption | CounterPageOption | ColorSeriesPageOption;
886
+
887
+ /** How a payload option writes a choice: default `boolean` for a toggle, `number` for a slider or counter, else `string`. */
888
+ export type OptionValueType = "string" | "number" | "boolean";
889
+
890
+ /** What a payload option (a page option without callbacks, or a document option) adds to its control. */
891
+ interface PayloadOptionStorage {
892
+ /** Shown while the payload holds no value (`stored ?? defaultValue`). */
893
+ defaultValue?: any;
894
+ /** The stored key. Default: `id`. */
895
+ field?: string;
896
+ valueType?: OptionValueType;
897
+ }
898
+
899
+ type PayloadOptionFor<Option> = Option extends PageOption
900
+ ? Omit<Option, "getValue" | "onChange" | "applyPatch"> & PayloadOptionStorage
901
+ : never;
902
+ /** A page option stored automatically in `payload.pages[pageId]`, without custom callbacks. */
903
+ export type PayloadOptionDefinition = PayloadOptionFor<PageOption>;
904
+
905
+ /** The corner of PageEditor's floating toolbar. */
906
+ export type ToolbarPosition = "bottom-right" | "bottom-left" | "top-right" | "top-left";
907
+
735
908
  export interface PageEditorProps {
736
909
  /** Pages, groups and initial items; `pageManagement` sets the page-management controls. */
737
- templateConfig: Record<string, unknown> & { pageManagement?: PageManagementOptions };
910
+ templateConfig: PageEditorTemplateConfig;
738
911
  payload?: Record<string, unknown>;
739
912
  onPayloadChange?: (nextPayload: Record<string, unknown>) => void;
740
913
  pageFormat?: {
741
914
  width: number;
742
915
  height: number;
743
- preview?: string;
916
+ preview?: "single_page" | "two_pages";
744
917
  bleed?: number;
745
918
  /** Perfect-binding spine/glue; only applied while `pageFilter.mode === 'cover'`. */
746
919
  binding?: BindingConfig | null;
747
920
  [key: string]: unknown;
748
921
  };
749
- pageOptions?: unknown[];
922
+ pageOptions?: Array<PageOption | PayloadOptionDefinition>;
750
923
  pageFilter?: PageFilterConfig;
751
924
  printConfigs?: PrintConfigMap;
752
925
  defaultZoomMode?: PageEditorDefaultZoomMode;
753
- onItemsChange?: (items: PageEditorItem[], state: PageEditorState) => void;
926
+ /**
927
+ * The corner the floating toolbar (page count, Add, Reorder, document options, zoom) starts in.
928
+ * Default `bottom-right`. A user moves it from the zoom menu; their corner is remembered in the
929
+ * browser and wins over this one.
930
+ */
931
+ toolbarPosition?: ToolbarPosition;
932
+ onItemsChange?: (items: PageItem[], state: PageEditorState) => void;
754
933
  onStateChange?: (state: PageEditorState) => void;
755
934
  /**
756
935
  * What prints over every page. Default: the shell's page number ("Page N / total", untranslated).
757
936
  * `false` or `null`: nothing, for templates that number their own pages.
758
937
  */
938
+ notifyError?: (message: string) => void;
939
+ referenceRenderer?: (page: StandalonePage & { pageNum: number }) => ReactNode;
940
+ renderPage?: (args: { page: StandalonePage & { pageNum: number }; parent?: PageItem }) => ReactNode;
941
+ menuItems?: ReactNode;
942
+ gridColsClass?: string;
943
+ resolveNewItem?: (draft: PageItem) => Promise<PageItem | null | void> | PageItem | null | void;
759
944
  renderOverlay?: false | null | ((context: {
760
- pageNo?: number;
761
- total?: number;
762
- pageId?: string;
763
- parent?: unknown;
945
+ pageNo: number;
946
+ total: number;
947
+ pageId: string;
948
+ parent?: PageItem;
764
949
  }) => ReactNode);
765
950
  reorderTitle?: LocalizedText;
766
951
  reorderDescription?: LocalizedText;
@@ -769,12 +954,18 @@ export interface PageEditorProps {
769
954
  activeBrandKitId?: string;
770
955
  onSelectBrandKit?: (id: string) => void;
771
956
  onAddBrandKit?: (input: string) => void;
772
- [key: string]: unknown;
773
957
  }
774
958
 
959
+ /** Template-owned JSON data and the maps the editor shell updates. */
960
+ export type TemplatePayload = Record<string, unknown> & {
961
+ pages?: Record<string, Record<string, unknown> | undefined>;
962
+ groups?: Record<string, Record<string, unknown> | undefined>;
963
+ integrations?: Record<string, unknown>;
964
+ };
965
+
775
966
  export interface TemplateDataProviderProps {
776
- payload?: Record<string, unknown>;
777
- onPayloadChange?: (nextPayload: Record<string, unknown>) => void;
967
+ payload?: TemplatePayload;
968
+ onPayloadChange?: (nextPayload: TemplatePayload) => void;
778
969
  children: ReactNode;
779
970
  stateKey?: string;
780
971
  }
@@ -794,12 +985,66 @@ export interface IntegrationAdapter {
794
985
  [key: string]: unknown;
795
986
  }
796
987
 
988
+ type DocumentOptionFor<Option> = Option extends PageOption
989
+ ? Omit<Option, "getValue" | "onChange" | "applyPatch" | "appliesTo"> & PayloadOptionStorage
990
+ : never;
991
+
992
+ /**
993
+ * The 0.4 shape, as `uhuu templates theme inspect <templateId> --snippet` prints a legacy theme:
994
+ * `key` names the payload key, and a select may leave out `type`.
995
+ * @deprecated Define document options as payload page options are, with `id`.
996
+ */
997
+ export interface LegacyDocumentOptionDefinition {
998
+ key: string;
999
+ type?: "select" | "toggle";
1000
+ label: LocalizedText;
1001
+ description?: LocalizedText;
1002
+ options?: Array<{ value: string | number | boolean; label: LocalizedText }>;
1003
+ defaultValue?: unknown;
1004
+ valueType?: OptionValueType;
1005
+ }
1006
+
1007
+ /**
1008
+ * One document-level option: a top-level payload key the template reads (`payload.language`),
1009
+ * defined, valued and drawn as a payload page option is (`id` names the key, `field` overrides
1010
+ * it). See docs/document-options.md.
1011
+ */
1012
+ export type DocumentOptionDefinition = DocumentOptionFor<PageOption> | LegacyDocumentOptionDefinition;
1013
+
1014
+ export interface DocumentOptionsProps {
1015
+ /**
1016
+ * The payload the template renders. Required outside a TemplateDataProvider; inside one, the
1017
+ * provider's payload is used when it is left out.
1018
+ */
1019
+ payload?: Record<string, unknown> | null;
1020
+ options: DocumentOptionDefinition[];
1021
+ /** Heading of the control. Default: "Document options" in the shell's interface language. */
1022
+ title?: LocalizedText;
1023
+ /**
1024
+ * Called for local choices, when there is no uhuu document editor on the page. In the editor a
1025
+ * choice arrives through the next payload update.
1026
+ */
1027
+ onChange?: (key: string, value: unknown) => void;
1028
+ /** The corner of the control's own floating bar, drawn when no PageEditor toolbar is on screen. Default `bottom-right`. */
1029
+ toolbarPosition?: ToolbarPosition;
1030
+ /** Inside PageEditor's floating toolbar: the entry's wrapper. Otherwise the control's own floating bar. */
1031
+ className?: string;
1032
+ style?: CSSProperties;
1033
+ }
1034
+
797
1035
  export const EditorShell: {
798
1036
  TemplateDataProvider: ComponentType<TemplateDataProviderProps>;
799
1037
  PageEditor: ComponentType<PageEditorProps>;
800
1038
  InteractiveModeProvider: ComponentType<InteractiveModeProviderProps>;
801
1039
  useInteractive: () => InteractiveModeContextValue;
802
1040
  useIntegrationAdapter: (config: IntegrationAdapterConfig) => IntegrationAdapter;
1041
+ /**
1042
+ * Document-level options for top-level payload keys, defined and drawn as page options are. A
1043
+ * choice is an edit in the uhuu document editor. Joins the nearest InteractiveModeProvider's
1044
+ * PageEditor toolbar, else floats in a corner. Hidden outside edit mode, in the PDF renderer and
1045
+ * in print. See docs/document-options.md.
1046
+ */
1047
+ DocumentOptions: ComponentType<DocumentOptionsProps>;
803
1048
  };
804
1049
 
805
1050
  export interface StaticSheetProps {
@@ -835,7 +1080,7 @@ export interface SpineComponentProps {
835
1080
  /** Trim page height (mm); the strip is `height + 2 × bleed` tall and `spine` wide. */
836
1081
  height: number;
837
1082
  totalPages?: number;
838
- pages: { left?: PageEditorItem; right?: PageEditorItem };
1083
+ pages: { left?: StandalonePage; right?: StandalonePage };
839
1084
  }
840
1085
 
841
1086
  /** `templateConfig.spine` — optional artwork for the spine of a perfect-bound cover. */
@@ -954,7 +1199,7 @@ export interface StaticFlowProps<TItem = unknown> {
954
1199
  as?: keyof HTMLElementTagNameMap;
955
1200
  }
956
1201
 
957
- export interface StaticFlowTextProps {
1202
+ export interface StaticFlowTextProps extends HTMLAttributes<HTMLElement> {
958
1203
  /**
959
1204
  * A string (may contain `\n`) or inline content: text, `span`, `strong`, `em`, `b`, `i`, `a`,
960
1205
  * `small`, `sub`, `sup`, `br` (and other inline text elements) and fragments. Anything else
@@ -1122,6 +1367,7 @@ export const Static: {
1122
1367
  FlowText: ComponentType<StaticFlowTextProps>;
1123
1368
  planFlowChunks: (input?: StaticFlowPlanInput) => StaticFlowChunk[];
1124
1369
  planFlowColumnChunks: (input?: StaticFlowColumnPlanInput) => StaticFlowChunk[];
1370
+ /** @internal Instrumentation for library tests; does not affect pagination plans. */
1125
1371
  createFlowPlanMetrics: () => StaticFlowPlanMetrics;
1126
1372
  flowMeasure: StaticFlowMeasureApi;
1127
1373
  FlowDocument: ComponentType<StaticFlowDocumentProps>;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.12",
2
+ "version": "0.4.1",
3
3
  "name": "uhuu-components",
4
4
  "main": "uhuu-components.umd.cjs",
5
5
  "module": "uhuu-components.es.js",