uhuu-components 0.3.12 → 0.4.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.
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,160 @@ 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
+ value: string;
856
+ hex?: string;
857
+ }
858
+
859
+ type PageOptionTarget = string | ((item: PageItem) => boolean);
860
+ type BasePageOption = {
861
+ id: string;
862
+ label: LocalizedText;
863
+ description?: LocalizedText;
864
+ appliesTo?: PageOptionTarget | PageOptionTarget[];
865
+ onChange?: (id: string, value: string, context: { item?: PageItem; updateItem: (patch: Partial<PageItem>) => void }) => void;
866
+ applyPatch?: (value: string, item?: PageItem) => Partial<PageItem> | void;
867
+ };
868
+
869
+ export type SelectPageOption = BasePageOption & {
870
+ type: "select";
871
+ options: PageOptionSelectValue[];
872
+ getValue: (item: PageItem) => string | number | boolean;
873
+ };
874
+ export type TogglePageOption = BasePageOption & { type: "toggle"; getValue: (item: PageItem) => boolean };
875
+ export type SliderPageOption = BasePageOption & {
876
+ type: "slider"; min: number; max: number; step: number; getValue: (item: PageItem) => number;
877
+ };
878
+ export type CounterPageOption = BasePageOption & {
879
+ type: "counter"; min: number; max: number; step: number; getValue: (item: PageItem) => number;
880
+ };
881
+ export type ColorSeriesPageOption = BasePageOption & {
882
+ type: "color-series"; options: PageOptionSelectValue[]; getValue: (item: PageItem) => string;
883
+ };
884
+ export type PageOption = SelectPageOption | TogglePageOption | SliderPageOption | CounterPageOption | ColorSeriesPageOption;
885
+
886
+ type PayloadOptionFor<Option> = Option extends PageOption
887
+ ? Omit<Option, "getValue" | "onChange" | "applyPatch"> & { defaultValue?: any; field?: string }
888
+ : never;
889
+ /** A page option stored automatically in `payload.pages`, without custom callbacks. */
890
+ export type PayloadOptionDefinition = PayloadOptionFor<PageOption>;
891
+
735
892
  export interface PageEditorProps {
736
893
  /** Pages, groups and initial items; `pageManagement` sets the page-management controls. */
737
- templateConfig: Record<string, unknown> & { pageManagement?: PageManagementOptions };
894
+ templateConfig: PageEditorTemplateConfig;
738
895
  payload?: Record<string, unknown>;
739
896
  onPayloadChange?: (nextPayload: Record<string, unknown>) => void;
740
897
  pageFormat?: {
741
898
  width: number;
742
899
  height: number;
743
- preview?: string;
900
+ preview?: "single_page" | "two_pages";
744
901
  bleed?: number;
745
902
  /** Perfect-binding spine/glue; only applied while `pageFilter.mode === 'cover'`. */
746
903
  binding?: BindingConfig | null;
747
904
  [key: string]: unknown;
748
905
  };
749
- pageOptions?: unknown[];
906
+ pageOptions?: Array<PageOption | PayloadOptionDefinition>;
750
907
  pageFilter?: PageFilterConfig;
751
908
  printConfigs?: PrintConfigMap;
752
909
  defaultZoomMode?: PageEditorDefaultZoomMode;
753
- onItemsChange?: (items: PageEditorItem[], state: PageEditorState) => void;
910
+ onItemsChange?: (items: PageItem[], state: PageEditorState) => void;
754
911
  onStateChange?: (state: PageEditorState) => void;
755
912
  /**
756
913
  * What prints over every page. Default: the shell's page number ("Page N / total", untranslated).
757
914
  * `false` or `null`: nothing, for templates that number their own pages.
758
915
  */
916
+ notifyError?: (message: string) => void;
917
+ referenceRenderer?: (page: StandalonePage & { pageNum: number }) => ReactNode;
918
+ renderPage?: (args: { page: StandalonePage & { pageNum: number }; parent?: PageItem }) => ReactNode;
919
+ menuItems?: ReactNode;
920
+ gridColsClass?: string;
921
+ resolveNewItem?: (draft: PageItem) => Promise<PageItem | null | void> | PageItem | null | void;
759
922
  renderOverlay?: false | null | ((context: {
760
- pageNo?: number;
761
- total?: number;
762
- pageId?: string;
763
- parent?: unknown;
923
+ pageNo: number;
924
+ total: number;
925
+ pageId: string;
926
+ parent?: PageItem;
764
927
  }) => ReactNode);
765
928
  reorderTitle?: LocalizedText;
766
929
  reorderDescription?: LocalizedText;
@@ -769,12 +932,18 @@ export interface PageEditorProps {
769
932
  activeBrandKitId?: string;
770
933
  onSelectBrandKit?: (id: string) => void;
771
934
  onAddBrandKit?: (input: string) => void;
772
- [key: string]: unknown;
773
935
  }
774
936
 
937
+ /** Template-owned JSON data and the maps the editor shell updates. */
938
+ export type TemplatePayload = Record<string, unknown> & {
939
+ pages?: Record<string, Record<string, unknown> | undefined>;
940
+ groups?: Record<string, Record<string, unknown> | undefined>;
941
+ integrations?: Record<string, unknown>;
942
+ };
943
+
775
944
  export interface TemplateDataProviderProps {
776
- payload?: Record<string, unknown>;
777
- onPayloadChange?: (nextPayload: Record<string, unknown>) => void;
945
+ payload?: TemplatePayload;
946
+ onPayloadChange?: (nextPayload: TemplatePayload) => void;
778
947
  children: ReactNode;
779
948
  stateKey?: string;
780
949
  }
@@ -794,12 +963,60 @@ export interface IntegrationAdapter {
794
963
  [key: string]: unknown;
795
964
  }
796
965
 
966
+ /**
967
+ * One document-level option: a top-level payload key the template reads (`payload.language`).
968
+ * The shape `uhuu templates theme inspect <templateId> --snippet` prints from a legacy template
969
+ * theme. See docs/document-options.md.
970
+ */
971
+ export interface DocumentOptionDefinition {
972
+ /** The top-level payload key, the same one the platform's theme option wrote. */
973
+ key: string;
974
+ /** Default `select`. */
975
+ type?: "select" | "toggle";
976
+ label: LocalizedText;
977
+ description?: LocalizedText;
978
+ /** A select's choices. A stored value matches a choice as a string: `2` matches `"2"`. */
979
+ options?: Array<{ value: string | number | boolean; label: LocalizedText }>;
980
+ /** Shown while the payload has no value for the key (`payload[key] ?? defaultValue`). */
981
+ defaultValue?: unknown;
982
+ /** Local onChange previews: default `string` for a select, `boolean` for a toggle. Editor selects save strings. */
983
+ valueType?: "string" | "number" | "boolean";
984
+ }
985
+
986
+ export interface DocumentOptionsProps {
987
+ /**
988
+ * The payload the template renders. Required outside a TemplateDataProvider; inside one, the
989
+ * provider's payload is used when it is left out.
990
+ */
991
+ payload?: Record<string, unknown> | null;
992
+ options: DocumentOptionDefinition[];
993
+ /** Heading of the control. Default: "Document options" in the shell's interface language. */
994
+ title?: LocalizedText;
995
+ /**
996
+ * Called for local choices when no supported $uhuu.editDialog is available. The editor field
997
+ * saves through the next payload update and does not call onChange.
998
+ */
999
+ onChange?: (key: string, value: unknown) => void;
1000
+ /**
1001
+ * Inside PageEditor's floating toolbar: the entry's wrapper. Otherwise the control's own
1002
+ * floating bar, bottom right by default (move it with `style`, e.g. `{ top: 16, bottom: "auto" }`).
1003
+ */
1004
+ className?: string;
1005
+ style?: CSSProperties;
1006
+ }
1007
+
797
1008
  export const EditorShell: {
798
1009
  TemplateDataProvider: ComponentType<TemplateDataProviderProps>;
799
1010
  PageEditor: ComponentType<PageEditorProps>;
800
1011
  InteractiveModeProvider: ComponentType<InteractiveModeProviderProps>;
801
1012
  useInteractive: () => InteractiveModeContextValue;
802
1013
  useIntegrationAdapter: (config: IntegrationAdapterConfig) => IntegrationAdapter;
1014
+ /**
1015
+ * Document-level switches for top-level payload keys, edited through the released editor's
1016
+ * `$uhuu.editDialog` field. Joins the nearest InteractiveModeProvider's PageEditor toolbar, else floats bottom right.
1017
+ * Hidden outside edit mode, in the PDF renderer and in print. See docs/document-options.md.
1018
+ */
1019
+ DocumentOptions: ComponentType<DocumentOptionsProps>;
803
1020
  };
804
1021
 
805
1022
  export interface StaticSheetProps {
@@ -835,7 +1052,7 @@ export interface SpineComponentProps {
835
1052
  /** Trim page height (mm); the strip is `height + 2 × bleed` tall and `spine` wide. */
836
1053
  height: number;
837
1054
  totalPages?: number;
838
- pages: { left?: PageEditorItem; right?: PageEditorItem };
1055
+ pages: { left?: StandalonePage; right?: StandalonePage };
839
1056
  }
840
1057
 
841
1058
  /** `templateConfig.spine` — optional artwork for the spine of a perfect-bound cover. */
@@ -954,7 +1171,7 @@ export interface StaticFlowProps<TItem = unknown> {
954
1171
  as?: keyof HTMLElementTagNameMap;
955
1172
  }
956
1173
 
957
- export interface StaticFlowTextProps {
1174
+ export interface StaticFlowTextProps extends HTMLAttributes<HTMLElement> {
958
1175
  /**
959
1176
  * A string (may contain `\n`) or inline content: text, `span`, `strong`, `em`, `b`, `i`, `a`,
960
1177
  * `small`, `sub`, `sup`, `br` (and other inline text elements) and fragments. Anything else
@@ -1122,6 +1339,7 @@ export const Static: {
1122
1339
  FlowText: ComponentType<StaticFlowTextProps>;
1123
1340
  planFlowChunks: (input?: StaticFlowPlanInput) => StaticFlowChunk[];
1124
1341
  planFlowColumnChunks: (input?: StaticFlowColumnPlanInput) => StaticFlowChunk[];
1342
+ /** @internal Instrumentation for library tests; does not affect pagination plans. */
1125
1343
  createFlowPlanMetrics: () => StaticFlowPlanMetrics;
1126
1344
  flowMeasure: StaticFlowMeasureApi;
1127
1345
  FlowDocument: ComponentType<StaticFlowDocumentProps>;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.12",
2
+ "version": "0.4.0",
3
3
  "name": "uhuu-components",
4
4
  "main": "uhuu-components.umd.cjs",
5
5
  "module": "uhuu-components.es.js",