@masterteam/components 0.0.57 → 0.0.59

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.
@@ -2,6 +2,9 @@ import { providePrimeNG } from 'primeng/config';
2
2
  import Aura from '@primeuix/themes/aura';
3
3
  import { updatePreset, definePreset } from '@primeuix/themes';
4
4
  import { MessageService, ConfirmationService } from 'primeng/api';
5
+ import { computed } from '@angular/core';
6
+ import { of } from 'rxjs';
7
+ import { tap, catchError, finalize } from 'rxjs/operators';
5
8
 
6
9
  /**
7
10
  * Converts a hex color string to an HSL tuple.
@@ -447,12 +450,6 @@ function provideMTConfirmation() {
447
450
  return ConfirmationService;
448
451
  }
449
452
 
450
- function isInvalid(control) {
451
- if (!control)
452
- return false;
453
- return control && control?.invalid && control?.touched;
454
- }
455
-
456
453
  class ValidatorConfig {
457
454
  type;
458
455
  value;
@@ -902,13 +899,263 @@ function wrapValidatorWithMessage(validator, errorKey, message) {
902
899
  // return new TextFieldConfig()
903
900
  // }
904
901
 
902
+ /**
903
+ * Base facade with smart query selector factory
904
+ */
905
+ class BaseFacade {
906
+ /**
907
+ * Creates a query object with separate signals for data, loading, and error states
908
+ * Each property is its own signal for granular reactivity
909
+ *
910
+ * @example
911
+ * readonly modulesQuery = this.query(
912
+ * 'getModulesLoading',
913
+ * (state) => state.allModules
914
+ * );
915
+ *
916
+ * // In template - each is a separate signal:
917
+ * @if (modulesQuery.isPending()) { Loading... }
918
+ * @if (modulesQuery.error(); as error) { Error: {{ error }} }
919
+ * @if (modulesQuery.data(); as modules) {
920
+ * <div *ngFor="let m of modules">{{ m.title }}</div>
921
+ * }
922
+ */
923
+ query(loadingKey, dataSelector) {
924
+ const stateSignal = this.state();
925
+ const isPending = computed(() => {
926
+ const currentState = stateSignal();
927
+ return currentState.loadingActive?.includes(loadingKey) ?? false;
928
+ }, ...(ngDevMode ? [{ debugName: "isPending" }] : []));
929
+ const error = computed(() => {
930
+ const currentState = stateSignal();
931
+ return currentState.errors?.[loadingKey] ?? null;
932
+ }, ...(ngDevMode ? [{ debugName: "error" }] : []));
933
+ const data = computed(() => {
934
+ const currentState = stateSignal();
935
+ const hasError = currentState.errors?.[loadingKey];
936
+ return hasError ? null : dataSelector(currentState);
937
+ }, ...(ngDevMode ? [{ debugName: "data" }] : []));
938
+ const isSuccess = computed(() => !isPending() && !error() && data() !== null, ...(ngDevMode ? [{ debugName: "isSuccess" }] : []));
939
+ const isError = computed(() => !!error(), ...(ngDevMode ? [{ debugName: "isError" }] : []));
940
+ return {
941
+ data,
942
+ isPending,
943
+ isSuccess,
944
+ isError,
945
+ error,
946
+ };
947
+ }
948
+ /**
949
+ * Creates a loading signal for a specific operation
950
+ */
951
+ loading(loadingKey) {
952
+ const stateSignal = this.state();
953
+ return computed(() => {
954
+ const currentState = stateSignal();
955
+ return currentState.loadingActive?.includes(loadingKey) ?? false;
956
+ });
957
+ }
958
+ /**
959
+ * Creates an error signal for a specific operation
960
+ */
961
+ errorSignal(loadingKey) {
962
+ const stateSignal = this.state();
963
+ return computed(() => {
964
+ const currentState = stateSignal();
965
+ return currentState.errors?.[loadingKey] ?? null;
966
+ });
967
+ }
968
+ }
969
+
970
+ function createEntityAdapter() {
971
+ return {
972
+ addOne: (state, entity) => [...state, entity],
973
+ upsertOne: (state, entity, uniqueKey = 'id') => {
974
+ const key = entity[uniqueKey];
975
+ if (key == null)
976
+ return state;
977
+ const index = state.findIndex((e) => e[uniqueKey] === key);
978
+ if (index === -1) {
979
+ return [...state, entity];
980
+ }
981
+ const clone = [...state];
982
+ clone[index] = entity;
983
+ return clone;
984
+ },
985
+ updateOne: (state, id, changes, uniqueKey = 'id') => state.map((e) => (e[uniqueKey] === id ? { ...e, ...changes } : e)),
986
+ removeOne: (state, id, uniqueKey = 'id') => state.filter((e) => e[uniqueKey] !== id),
987
+ setAll: (_state, entities) => [...entities],
988
+ };
989
+ }
990
+
991
+ function startLoading(ctx, loadingName) {
992
+ const { loadingActive, errors } = ctx.getState();
993
+ if (!loadingActive.includes(loadingName)) {
994
+ ctx.patchState({
995
+ loadingActive: [...loadingActive, loadingName],
996
+ });
997
+ }
998
+ if (errors && errors[loadingName]) {
999
+ const { [loadingName]: _removed, ...rest } = errors;
1000
+ ctx.patchState({ errors: rest });
1001
+ }
1002
+ }
1003
+ function endLoading(ctx, loadingName) {
1004
+ const { loadingActive } = ctx.getState();
1005
+ ctx.patchState({
1006
+ loadingActive: loadingActive.filter((name) => name !== loadingName),
1007
+ });
1008
+ }
1009
+ function setLoadingError(ctx, loadingName, message) {
1010
+ const { errors } = ctx.getState();
1011
+ ctx.patchState({
1012
+ errors: { ...errors, [loadingName]: message },
1013
+ });
1014
+ }
1015
+
1016
+ function handleApiRequest(config) {
1017
+ const { ctx, key, request$, onSuccess, onError, errorMessage } = config;
1018
+ startLoading(ctx, key);
1019
+ return request$.pipe(tap((response) => {
1020
+ const state = ctx.getState();
1021
+ const patch = onSuccess(response, state);
1022
+ if (patch) {
1023
+ ctx.patchState(patch);
1024
+ }
1025
+ }), catchError((error) => {
1026
+ const state = ctx.getState();
1027
+ const message = error?.error?.message ?? error?.message ?? errorMessage;
1028
+ setLoadingError(ctx, key, message);
1029
+ // Call onError callback if provided
1030
+ if (onError) {
1031
+ const patch = onError(error, state);
1032
+ if (patch) {
1033
+ ctx.patchState(patch);
1034
+ }
1035
+ }
1036
+ return of(null);
1037
+ }), finalize(() => endLoading(ctx, key)));
1038
+ }
1039
+
1040
+ /**
1041
+ * T = entity type (Module, User, ...)
1042
+ * TState = full state model (must include LoadingStateShape)
1043
+ * TLoad = union type of loading keys for this feature
1044
+ */
1045
+ class CrudStateBase {
1046
+ adapter = createEntityAdapter();
1047
+ /**
1048
+ * Helper to create default updateState function
1049
+ */
1050
+ getDefaultUpdateState(stateProperty) {
1051
+ // Try to extract the property name from the stateProperty function
1052
+ const funcStr = stateProperty.toString();
1053
+ const match = funcStr.match(/\.([a-zA-Z_$][a-zA-Z0-9_$]*)/); // Match .propertyName
1054
+ const key = match ? match[1] : 'items';
1055
+ return (state, items) => ({ [key]: items });
1056
+ }
1057
+ /**
1058
+ * Load data (single item or array) with config object
1059
+ */
1060
+ load(ctx, config) {
1061
+ return handleApiRequest({
1062
+ ctx,
1063
+ key: config.key,
1064
+ request$: config.request$,
1065
+ onSuccess: (response, state) => {
1066
+ const data = config.transform
1067
+ ? config.transform(response.data)
1068
+ : response.data;
1069
+ // If stateProperty provided, use adapter for array operations
1070
+ if (config.stateProperty) {
1071
+ const items = this.adapter.setAll(config.stateProperty(state), data);
1072
+ const updateState = config.updateState ??
1073
+ this.getDefaultUpdateState(config.stateProperty);
1074
+ return updateState(state, items);
1075
+ }
1076
+ // Otherwise, use custom updateState directly
1077
+ if (config.updateState) {
1078
+ return config.updateState(state, data);
1079
+ }
1080
+ throw new Error('Either stateProperty or updateState must be provided');
1081
+ },
1082
+ onError: config.onError,
1083
+ errorMessage: config.errorMessage ?? 'Failed to load data',
1084
+ });
1085
+ }
1086
+ /**
1087
+ * Create one entity with config object
1088
+ */
1089
+ create(ctx, config) {
1090
+ const updateState = config.updateState ?? this.getDefaultUpdateState(config.stateProperty);
1091
+ return handleApiRequest({
1092
+ ctx,
1093
+ key: config.key,
1094
+ request$: config.request$,
1095
+ onSuccess: (response, state) => {
1096
+ const entity = config.transform
1097
+ ? config.transform(response.data)
1098
+ : response.data;
1099
+ const items = this.adapter.addOne(config.stateProperty(state), entity);
1100
+ return updateState(state, items);
1101
+ },
1102
+ onError: config.onError,
1103
+ errorMessage: config.errorMessage ?? 'Failed to create item',
1104
+ });
1105
+ }
1106
+ /**
1107
+ * Update one entity with config object
1108
+ */
1109
+ update(ctx, config) {
1110
+ const updateState = config.updateState ?? this.getDefaultUpdateState(config.stateProperty);
1111
+ return handleApiRequest({
1112
+ ctx,
1113
+ key: config.key,
1114
+ request$: config.request$,
1115
+ onSuccess: (response, state) => {
1116
+ const entity = config.transform
1117
+ ? config.transform(response.data)
1118
+ : response.data;
1119
+ const items = this.adapter.upsertOne(config.stateProperty(state), entity, config.uniqueKey);
1120
+ return updateState(state, items);
1121
+ },
1122
+ onError: config.onError,
1123
+ errorMessage: config.errorMessage ?? 'Failed to update item',
1124
+ });
1125
+ }
1126
+ /**
1127
+ * Delete one entity with config object
1128
+ */
1129
+ delete(ctx, config) {
1130
+ const updateState = config.updateState ?? this.getDefaultUpdateState(config.stateProperty);
1131
+ return handleApiRequest({
1132
+ ctx,
1133
+ key: config.key,
1134
+ request$: config.request$,
1135
+ onSuccess: (_response, state) => {
1136
+ const items = this.adapter.removeOne(config.stateProperty(state), config.id, config.uniqueKey);
1137
+ return updateState(state, items);
1138
+ },
1139
+ onError: config.onError,
1140
+ errorMessage: config.errorMessage ?? 'Failed to delete item',
1141
+ });
1142
+ }
1143
+ }
1144
+
1145
+ function isInvalid(control) {
1146
+ if (!control)
1147
+ return false;
1148
+ return control && control?.invalid && control?.touched;
1149
+ }
1150
+
905
1151
  /*
906
1152
  * Public API Surface of components
907
1153
  */
1154
+ // Config
908
1155
 
909
1156
  /**
910
1157
  * Generated bundle index. Do not edit.
911
1158
  */
912
1159
 
913
- export { BaseFieldConfig, CheckboxFieldConfig, ColorPickerFieldConfig, DateFieldConfig, EditorFieldConfig, IconFieldConfig, MultiSelectFieldConfig, NumberFieldConfig, PickListFieldConfig, RadioButtonFieldConfig, RadioCardsFieldConfig, SelectFieldConfig, SliderFieldConfig, SpacerFieldConfig, TextFieldConfig, TextareaFieldConfig, ToggleFieldConfig, UploadFileFieldConfig, UserSearchFieldConfig, ValidatorConfig, changeBackgroundColor, changePrimaryColor, changeTextColor, createCustomValidator, generateTailwindPalette, isInvalid, provideMTComponents, provideMTConfirmation, provideMTMessages, wrapValidatorWithMessage };
1160
+ export { BaseFacade, BaseFieldConfig, CheckboxFieldConfig, ColorPickerFieldConfig, CrudStateBase, DateFieldConfig, EditorFieldConfig, IconFieldConfig, MultiSelectFieldConfig, NumberFieldConfig, PickListFieldConfig, RadioButtonFieldConfig, RadioCardsFieldConfig, SelectFieldConfig, SliderFieldConfig, SpacerFieldConfig, TextFieldConfig, TextareaFieldConfig, ToggleFieldConfig, UploadFileFieldConfig, UserSearchFieldConfig, ValidatorConfig, changeBackgroundColor, changePrimaryColor, changeTextColor, createCustomValidator, createEntityAdapter, endLoading, generateTailwindPalette, handleApiRequest, isInvalid, provideMTComponents, provideMTConfirmation, provideMTMessages, setLoadingError, startLoading, wrapValidatorWithMessage };
914
1161
  //# sourceMappingURL=masterteam-components.mjs.map