@hostlink/nuxt-light 1.66.2 → 1.67.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/dist/module.d.mts CHANGED
@@ -2,6 +2,11 @@ import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
3
  interface ModuleOptions {
4
4
  checkPagePermissions?: boolean;
5
+ tableActionIcons?: {
6
+ view?: string;
7
+ edit?: string;
8
+ delete?: string;
9
+ };
5
10
  }
6
11
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
7
12
 
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "light",
3
3
  "configKey": "light",
4
- "version": "1.66.2",
4
+ "version": "1.67.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -334,6 +334,16 @@ const module$1 = defineNuxtModule({
334
334
  }, */
335
335
  async setup(options, nuxt) {
336
336
  const resolver = createResolver(import.meta.url);
337
+ nuxt.options.runtimeConfig = nuxt.options.runtimeConfig || {};
338
+ nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
339
+ nuxt.options.runtimeConfig.public.light = {
340
+ ...nuxt.options.runtimeConfig.public.light || {},
341
+ tableActionIcons: {
342
+ view: options.tableActionIcons?.view,
343
+ edit: options.tableActionIcons?.edit,
344
+ delete: options.tableActionIcons?.delete
345
+ }
346
+ };
337
347
  extendPages((pages) => {
338
348
  for (const route of routes) {
339
349
  if (route.children) {
@@ -19,13 +19,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
19
19
  square: boolean;
20
20
  dense: boolean;
21
21
  disable: boolean;
22
+ range: boolean;
22
23
  outlined: boolean;
23
24
  filled: boolean;
24
25
  stackLabel: boolean;
25
26
  standout: string | boolean;
26
27
  hideBottomSpace: boolean;
27
28
  todayBtn: boolean;
28
- range: boolean;
29
29
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
30
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
31
31
  declare const _default: typeof __VLS_export;
@@ -19,13 +19,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
19
19
  square: boolean;
20
20
  dense: boolean;
21
21
  disable: boolean;
22
+ range: boolean;
22
23
  outlined: boolean;
23
24
  filled: boolean;
24
25
  stackLabel: boolean;
25
26
  standout: string | boolean;
26
27
  hideBottomSpace: boolean;
27
28
  todayBtn: boolean;
28
- range: boolean;
29
29
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
30
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
31
31
  declare const _default: typeof __VLS_export;
@@ -1,11 +1,20 @@
1
1
  declare const _default: typeof __VLS_export;
2
2
  export default _default;
3
- declare const __VLS_export: import("vue").DefineComponent<{
3
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
4
+ type __VLS_WithSlots<T, S> = T & (new () => {
5
+ $slots: S;
6
+ });
7
+ declare const __VLS_base: import("vue").DefineComponent<{
8
+ icon?: any;
4
9
  to?: any;
5
10
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
11
  submit: (...args: any[]) => void;
7
12
  }, string, import("vue").PublicProps, Readonly<{
13
+ icon?: any;
8
14
  to?: any;
9
15
  }> & Readonly<{
10
16
  onSubmit?: ((...args: any[]) => any) | undefined;
11
17
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
18
+ type __VLS_Slots = {
19
+ default?: ((props: {}) => any) | undefined;
20
+ };
@@ -2,7 +2,7 @@
2
2
  import { useQuasar } from "quasar";
3
3
  import { useI18n } from "vue-i18n";
4
4
  const { t } = useI18n();
5
- const props = defineProps(["to"]);
5
+ const props = defineProps(["to", "icon"]);
6
6
  const emit = defineEmits(["submit"]);
7
7
  const qua = useQuasar();
8
8
  const onDelete = () => {
@@ -19,5 +19,7 @@ const onDelete = () => {
19
19
  </script>
20
20
 
21
21
  <template>
22
- <q-btn flat round icon="sym_o_delete" @click="onDelete" />
22
+ <q-btn flat round :icon="icon || 'sym_o_delete'" @click="onDelete">
23
+ <slot></slot>
24
+ </q-btn>
23
25
  </template>
@@ -1,11 +1,20 @@
1
1
  declare const _default: typeof __VLS_export;
2
2
  export default _default;
3
- declare const __VLS_export: import("vue").DefineComponent<{
3
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
4
+ type __VLS_WithSlots<T, S> = T & (new () => {
5
+ $slots: S;
6
+ });
7
+ declare const __VLS_base: import("vue").DefineComponent<{
8
+ icon?: any;
4
9
  to?: any;
5
10
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
11
  submit: (...args: any[]) => void;
7
12
  }, string, import("vue").PublicProps, Readonly<{
13
+ icon?: any;
8
14
  to?: any;
9
15
  }> & Readonly<{
10
16
  onSubmit?: ((...args: any[]) => any) | undefined;
11
17
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
18
+ type __VLS_Slots = {
19
+ default?: ((props: {}) => any) | undefined;
20
+ };
@@ -0,0 +1,16 @@
1
+ type __VLS_Props = {
2
+ modelValue?: number | string | {
3
+ min?: number | null;
4
+ max?: number | null;
5
+ } | null;
6
+ placeholder?: string;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ change: (value: any) => any;
10
+ "update:modelValue": (value: any) => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onChange?: ((value: any) => any) | undefined;
13
+ "onUpdate:modelValue"?: ((value: any) => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
@@ -0,0 +1,139 @@
1
+ <script setup>
2
+ import { ref, watch, computed } from "vue";
3
+ import { useI18n } from "vue-i18n";
4
+ const props = defineProps({
5
+ modelValue: { type: [Number, String, Object, null], required: false },
6
+ placeholder: { type: String, required: false }
7
+ });
8
+ const emit = defineEmits(["update:modelValue", "change"]);
9
+ const { t } = useI18n();
10
+ const mode = ref("equals");
11
+ const equalsValue = ref(null);
12
+ const minValue = ref(null);
13
+ const maxValue = ref(null);
14
+ watch(() => props.modelValue, (val) => {
15
+ if (val == null || val === "") {
16
+ equalsValue.value = null;
17
+ minValue.value = null;
18
+ maxValue.value = null;
19
+ } else if (typeof val === "object") {
20
+ mode.value = "range";
21
+ equalsValue.value = null;
22
+ minValue.value = val.min ?? null;
23
+ maxValue.value = val.max ?? null;
24
+ } else {
25
+ mode.value = "equals";
26
+ equalsValue.value = Number(val);
27
+ minValue.value = null;
28
+ maxValue.value = null;
29
+ }
30
+ }, { immediate: true });
31
+ const hasValue = computed(() => {
32
+ if (props.modelValue == null || props.modelValue === "") return false;
33
+ if (typeof props.modelValue === "object") {
34
+ const { min, max } = props.modelValue;
35
+ return min != null || max != null;
36
+ }
37
+ return true;
38
+ });
39
+ const displayLabel = computed(() => {
40
+ if (!hasValue.value) return t("Search");
41
+ if (typeof props.modelValue === "object") {
42
+ const { min, max } = props.modelValue;
43
+ if (min != null && max != null) return `${min} \u2013 ${max}`;
44
+ if (min != null) return `\u2265 ${min}`;
45
+ if (max != null) return `\u2264 ${max}`;
46
+ }
47
+ return `= ${props.modelValue}`;
48
+ });
49
+ const apply = () => {
50
+ let out = null;
51
+ if (mode.value === "equals") {
52
+ out = equalsValue.value;
53
+ } else {
54
+ const outRange = {};
55
+ if (minValue.value != null) outRange.min = minValue.value;
56
+ if (maxValue.value != null) outRange.max = maxValue.value;
57
+ if (Object.keys(outRange).length > 0) out = outRange;
58
+ }
59
+ emit("update:modelValue", out);
60
+ emit("change", out);
61
+ };
62
+ const clear = () => {
63
+ equalsValue.value = null;
64
+ minValue.value = null;
65
+ maxValue.value = null;
66
+ emit("update:modelValue", null);
67
+ emit("change", null);
68
+ };
69
+ </script>
70
+
71
+ <template>
72
+ <q-btn-dropdown
73
+ dense flat square no-caps
74
+ :color="hasValue ? 'primary' : 'grey-7'"
75
+ :label="displayLabel"
76
+ :icon="hasValue ? 'sym_o_filter_alt' : 'sym_o_search'"
77
+ size="sm"
78
+ content-class="l-search-number-dropdown"
79
+ >
80
+ <div class="q-pa-md" style="min-width: 280px">
81
+ <q-tabs
82
+ v-model="mode"
83
+ dense
84
+ no-caps
85
+ align="left"
86
+ active-color="primary"
87
+ indicator-color="primary"
88
+ class="text-grey"
89
+ >
90
+ <q-tab name="equals" :label="t('Equals')" />
91
+ <q-tab name="range" :label="t('Range')" />
92
+ </q-tabs>
93
+
94
+ <q-separator class="q-mb-sm" />
95
+
96
+ <q-tab-panels v-model="mode" animated>
97
+ <q-tab-panel name="equals" class="q-pa-none">
98
+ <q-input
99
+ v-model.number="equalsValue"
100
+ dense filled square
101
+ type="number"
102
+ mask="##########"
103
+ :placeholder="placeholder || t('Enter a number')"
104
+ autofocus
105
+ @keydown.enter.prevent="apply"
106
+ />
107
+ </q-tab-panel>
108
+ <q-tab-panel name="range" class="q-pa-none">
109
+ <div class="row q-gutter-sm items-center">
110
+ <q-input
111
+ v-model.number="minValue"
112
+ dense filled square
113
+ type="number"
114
+ mask="##########"
115
+ :placeholder="t('Min')"
116
+ class="col"
117
+ @keydown.enter.prevent="apply"
118
+ />
119
+ <span class="text-grey">—</span>
120
+ <q-input
121
+ v-model.number="maxValue"
122
+ dense filled square
123
+ type="number"
124
+ mask="##########"
125
+ :placeholder="t('Max')"
126
+ class="col"
127
+ @keydown.enter.prevent="apply"
128
+ />
129
+ </div>
130
+ </q-tab-panel>
131
+ </q-tab-panels>
132
+
133
+ <div class="row q-gutter-sm justify-end q-mt-md">
134
+ <q-btn flat dense :label="t('Clear')" color="negative" @click="clear" />
135
+ <q-btn flat dense :label="t('Apply')" color="primary" @click="apply" />
136
+ </div>
137
+ </div>
138
+ </q-btn-dropdown>
139
+ </template>
@@ -0,0 +1,16 @@
1
+ type __VLS_Props = {
2
+ modelValue?: number | string | {
3
+ min?: number | null;
4
+ max?: number | null;
5
+ } | null;
6
+ placeholder?: string;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
+ change: (value: any) => any;
10
+ "update:modelValue": (value: any) => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onChange?: ((value: any) => any) | undefined;
13
+ "onUpdate:modelValue"?: ((value: any) => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ declare const _default: typeof __VLS_export;
16
+ export default _default;
@@ -1,10 +1,18 @@
1
1
  import type { Component } from "vue";
2
2
  import { Dialog } from 'quasar';
3
3
  import type { QTableColumn, QTableProps } from 'quasar';
4
+ export interface SearchOptionsContext {
5
+ filters: Record<string, any>;
6
+ columnValue: any;
7
+ column: LTableColumn;
8
+ }
9
+ export type SearchOptionsResolver = (ctx: SearchOptionsContext) => Promise<Record<string, any>[]>;
4
10
  export type LTableColumn = QTableColumn & {
5
11
  searchType?: "date" | "text" | "number" | "select" | "boolean";
6
12
  searchable?: boolean;
7
- searchOptions?: Record<string, any>[] | Function;
13
+ searchPlaceholder?: string;
14
+ searchOptions?: Record<string, any>[] | SearchOptionsResolver;
15
+ searchDependsOn?: string[];
8
16
  searchMultiple?: boolean;
9
17
  searchIndex?: string;
10
18
  component?: Component;
@@ -15,7 +23,7 @@ export type LTableColumn = QTableColumn & {
15
23
  * @deprecated use gql instead
16
24
  */
17
25
  gqlField?: string | Array<string> | Object;
18
- searchMethod?: "equals" | "contains";
26
+ searchMethod?: "equals" | "contains" | "range";
19
27
  autoWidth?: boolean;
20
28
  };
21
29
  export type LTableProps = QTableProps & {
@@ -71,17 +79,17 @@ export interface LTableRequest {
71
79
  }) => void;
72
80
  }
73
81
  declare function requestServerInteraction(): void;
74
- declare var __VLS_114: any, __VLS_117: string, __VLS_118: any, __VLS_149: any, __VLS_152: any, __VLS_315: string, __VLS_316: any;
82
+ declare var __VLS_135: any, __VLS_138: string, __VLS_139: any, __VLS_170: any, __VLS_173: any, __VLS_343: string, __VLS_344: any;
75
83
  type __VLS_Slots = {} & {
76
- [K in NonNullable<typeof __VLS_117>]?: (props: typeof __VLS_118) => any;
84
+ [K in NonNullable<typeof __VLS_138>]?: (props: typeof __VLS_139) => any;
77
85
  } & {
78
- [K in NonNullable<typeof __VLS_315>]?: (props: typeof __VLS_316) => any;
86
+ [K in NonNullable<typeof __VLS_343>]?: (props: typeof __VLS_344) => any;
79
87
  } & {
80
- actions?: (props: typeof __VLS_114) => any;
88
+ actions?: (props: typeof __VLS_135) => any;
81
89
  } & {
82
- 'row-expand'?: (props: typeof __VLS_149) => any;
90
+ 'row-expand'?: (props: typeof __VLS_170) => any;
83
91
  } & {
84
- 'top-right'?: (props: typeof __VLS_152) => any;
92
+ 'top-right'?: (props: typeof __VLS_173) => any;
85
93
  };
86
94
  declare const __VLS_base: import("vue").DefineComponent<LTableProps, {
87
95
  requestServerInteraction: typeof requestServerInteraction;
@@ -149,7 +149,8 @@ const localColumns = computed(
149
149
  ...col,
150
150
  align: col.align ?? "left",
151
151
  field: col.field ?? col.name,
152
- label: t(col.label)
152
+ label: t(col.label),
153
+ searchPlaceholder: col.searchPlaceholder ? t(col.searchPlaceholder) : void 0
153
154
  }))
154
155
  );
155
156
  const emits = defineEmits(["request-data", "update:selected", "delete"]);
@@ -171,15 +172,45 @@ const table = ref();
171
172
  const rows = ref(props.rows ?? []);
172
173
  const resolvedSearchOptions = ref({});
173
174
  const localSearchOptions = ref({});
174
- onMounted(async () => {
175
- for (const col of props.columns ?? []) {
176
- if (!col.searchOptions) continue;
177
- const resolved = typeof col.searchOptions === "function" ? await col.searchOptions() : col.searchOptions;
178
- resolvedSearchOptions.value[col.name] = resolved;
179
- localSearchOptions.value[col.name] = resolved;
175
+ let resolveSeq = 0;
176
+ const resolveColumnOptions = async (col) => {
177
+ if (!col.searchOptions || !col.name) return;
178
+ if (Array.isArray(col.searchOptions)) {
179
+ resolvedSearchOptions.value[col.name] = col.searchOptions;
180
+ localSearchOptions.value[col.name] = col.searchOptions;
181
+ return;
180
182
  }
183
+ const ctx = {
184
+ filters: { ...filters.value },
185
+ columnValue: filters.value[col.name],
186
+ column: col
187
+ };
188
+ const seq = ++resolveSeq;
189
+ try {
190
+ const result = await col.searchOptions(ctx);
191
+ if (seq !== resolveSeq) return;
192
+ resolvedSearchOptions.value[col.name] = result;
193
+ localSearchOptions.value[col.name] = result;
194
+ } catch {
195
+ }
196
+ };
197
+ onMounted(async () => {
198
+ await Promise.all(
199
+ (props.columns ?? []).map((col) => resolveColumnOptions(col))
200
+ );
181
201
  table.value?.requestServerInteraction();
182
202
  });
203
+ watch(filters, (newFilters, oldFilters) => {
204
+ if (!props.columns) return;
205
+ for (const col of props.columns) {
206
+ if (!col.searchDependsOn?.length) continue;
207
+ if (typeof col.searchOptions !== "function") continue;
208
+ if (!col.name) continue;
209
+ const deps = col.searchDependsOn;
210
+ const changed = deps.some((dep) => newFilters?.[dep] !== oldFilters?.[dep]);
211
+ if (changed) resolveColumnOptions(col);
212
+ }
213
+ }, { deep: true });
183
214
  const primaryKey = ref(props.rowKey);
184
215
  const modelName = ref(props.modelName);
185
216
  const validateData = () => {
@@ -335,6 +366,18 @@ const getFilterValue = () => {
335
366
  const k = col.searchIndex ?? col.name;
336
367
  if (col.searchType == "boolean") {
337
368
  f[k] = filters.value[col.name];
369
+ } else if (col.searchMethod == "range" && typeof filters.value[col.name] === "object") {
370
+ const rangeValue = filters.value[col.name];
371
+ const rangeFilter = {};
372
+ if (rangeValue.min !== null && rangeValue.min !== "" && rangeValue.min !== void 0) {
373
+ rangeFilter._gte = Number(rangeValue.min);
374
+ }
375
+ if (rangeValue.max !== null && rangeValue.max !== "" && rangeValue.max !== void 0) {
376
+ rangeFilter._lte = Number(rangeValue.max);
377
+ }
378
+ if (Object.keys(rangeFilter).length > 0) {
379
+ f[k] = rangeFilter;
380
+ }
338
381
  } else if (col.searchType == "number") {
339
382
  f[k] = filters.value[col.name];
340
383
  } else if (col.searchType == "date") {
@@ -460,6 +503,9 @@ const localFilterMethod = (rows2, terms) => {
460
503
  if (!String(cell ?? "").toLowerCase().includes(String(value.contains).toLowerCase())) return false;
461
504
  } else if (typeof value === "object" && value !== null && "_between" in value) {
462
505
  if (cell < value._between[0] || cell > value._between[1]) return false;
506
+ } else if (typeof value === "object" && value !== null && ("_gte" in value || "_lte" in value)) {
507
+ if ("_gte" in value && value._gte !== null && value._gte !== void 0 && cell < value._gte) return false;
508
+ if ("_lte" in value && value._lte !== null && value._lte !== void 0 && cell > value._lte) return false;
463
509
  } else {
464
510
  if (cell !== value) return false;
465
511
  }
@@ -549,13 +595,22 @@ const hasFilters = computed(() => {
549
595
  <div :class="{ 'text-grey-8': !isDark }" v-if="primaryKey">
550
596
 
551
597
  <l-action-btn v-if="actionView && props.row.canView"
552
- :to="`/${modelName}/${props.row[primaryKey]}/view`" icon="sym_o_search" />
598
+ :to="`/${modelName}/${props.row[primaryKey]}/view`"
599
+ :icon="light.styles.table.actionIcons.view">
600
+ <q-tooltip>{{ $t("View") }}</q-tooltip>
601
+ </l-action-btn>
553
602
 
554
603
  <l-action-btn v-if="activeEdit && props.row.canUpdate"
555
- :to="`/${modelName}/${props.row[primaryKey]}/edit`" icon="sym_o_edit" />
604
+ :to="`/${modelName}/${props.row[primaryKey]}/edit`"
605
+ :icon="light.styles.table.actionIcons.edit">
606
+ <q-tooltip>{{ $t("Edit") }}</q-tooltip>
607
+ </l-action-btn>
556
608
 
557
609
  <l-delete-btn v-if="actionDelete && props.row.canDelete"
558
- @submit="onDelete(props.row[primaryKey])" size="sm"></l-delete-btn>
610
+ :icon="light.styles.table.actionIcons.delete"
611
+ @submit="onDelete(props.row[primaryKey])" size="sm">
612
+ <q-tooltip>{{ $t("Delete") }}</q-tooltip>
613
+ </l-delete-btn>
559
614
 
560
615
  <slot name="actions" v-bind="props"></slot>
561
616
  </div>
@@ -648,7 +703,14 @@ const hasFilters = computed(() => {
648
703
 
649
704
  <template v-if="col.searchable">
650
705
 
651
- <template v-if="col.searchType == 'number'">
706
+ <template v-if="col.searchMethod == 'range' && (!col.searchType || col.searchType == 'number')">
707
+ <l-search-number
708
+ v-model="filters[col.name]"
709
+ @change="onFilters"
710
+ />
711
+ </template>
712
+
713
+ <template v-else-if="col.searchType == 'number'">
652
714
  <q-input style="min-width: 80px;" dense clearable filled square
653
715
  v-model.number="tempFilters[col.name]"
654
716
  @keydown.enter.prevent="filters[col.name] = tempFilters[col.name]"
@@ -677,9 +739,9 @@ const hasFilters = computed(() => {
677
739
 
678
740
  </template>
679
741
 
680
- <template v-if="!col.searchType || col.searchType == 'text'">
742
+ <template v-if="(!col.searchType || col.searchType == 'text') && col.searchMethod != 'range'">
681
743
  <q-input style="min-width: 80px;" dense clearable filled square
682
- v-model="tempFilters[col.name]" @keydown.enter.prevent="filters[col.name] = tempFilters[col.name]" @clear="delete filters[col.name]" :enterkeyhint="$t('search')" :style="col.searchStyle"></q-input>
744
+ v-model="tempFilters[col.name]" @keydown.enter.prevent="filters[col.name] = tempFilters[col.name]" @clear="delete filters[col.name]" :enterkeyhint="$t('search')" :placeholder="col.searchPlaceholder" :style="col.searchStyle"></q-input>
683
745
 
684
746
  </template>
685
747
 
@@ -1,10 +1,18 @@
1
1
  import type { Component } from "vue";
2
2
  import { Dialog } from 'quasar';
3
3
  import type { QTableColumn, QTableProps } from 'quasar';
4
+ export interface SearchOptionsContext {
5
+ filters: Record<string, any>;
6
+ columnValue: any;
7
+ column: LTableColumn;
8
+ }
9
+ export type SearchOptionsResolver = (ctx: SearchOptionsContext) => Promise<Record<string, any>[]>;
4
10
  export type LTableColumn = QTableColumn & {
5
11
  searchType?: "date" | "text" | "number" | "select" | "boolean";
6
12
  searchable?: boolean;
7
- searchOptions?: Record<string, any>[] | Function;
13
+ searchPlaceholder?: string;
14
+ searchOptions?: Record<string, any>[] | SearchOptionsResolver;
15
+ searchDependsOn?: string[];
8
16
  searchMultiple?: boolean;
9
17
  searchIndex?: string;
10
18
  component?: Component;
@@ -15,7 +23,7 @@ export type LTableColumn = QTableColumn & {
15
23
  * @deprecated use gql instead
16
24
  */
17
25
  gqlField?: string | Array<string> | Object;
18
- searchMethod?: "equals" | "contains";
26
+ searchMethod?: "equals" | "contains" | "range";
19
27
  autoWidth?: boolean;
20
28
  };
21
29
  export type LTableProps = QTableProps & {
@@ -71,17 +79,17 @@ export interface LTableRequest {
71
79
  }) => void;
72
80
  }
73
81
  declare function requestServerInteraction(): void;
74
- declare var __VLS_114: any, __VLS_117: string, __VLS_118: any, __VLS_149: any, __VLS_152: any, __VLS_315: string, __VLS_316: any;
82
+ declare var __VLS_135: any, __VLS_138: string, __VLS_139: any, __VLS_170: any, __VLS_173: any, __VLS_343: string, __VLS_344: any;
75
83
  type __VLS_Slots = {} & {
76
- [K in NonNullable<typeof __VLS_117>]?: (props: typeof __VLS_118) => any;
84
+ [K in NonNullable<typeof __VLS_138>]?: (props: typeof __VLS_139) => any;
77
85
  } & {
78
- [K in NonNullable<typeof __VLS_315>]?: (props: typeof __VLS_316) => any;
86
+ [K in NonNullable<typeof __VLS_343>]?: (props: typeof __VLS_344) => any;
79
87
  } & {
80
- actions?: (props: typeof __VLS_114) => any;
88
+ actions?: (props: typeof __VLS_135) => any;
81
89
  } & {
82
- 'row-expand'?: (props: typeof __VLS_149) => any;
90
+ 'row-expand'?: (props: typeof __VLS_170) => any;
83
91
  } & {
84
- 'top-right'?: (props: typeof __VLS_152) => any;
92
+ 'top-right'?: (props: typeof __VLS_173) => any;
85
93
  };
86
94
  declare const __VLS_base: import("vue").DefineComponent<LTableProps, {
87
95
  requestServerInteraction: typeof requestServerInteraction;
@@ -40,7 +40,12 @@ const light = reactive({
40
40
  dense: true,
41
41
  flat: true,
42
42
  bordered: true,
43
- separator: "cell"
43
+ separator: "cell",
44
+ actionIcons: {
45
+ view: "sym_o_search",
46
+ edit: "sym_o_edit",
47
+ delete: "sym_o_delete"
48
+ }
44
49
  },
45
50
  card: {
46
51
  flat: true,
@@ -220,6 +225,10 @@ const light = reactive({
220
225
  light.styles.button = { ...light.styles.button, ...styles.button || {} };
221
226
  light.styles.table = { ...light.styles.table, ...styles.table || {} };
222
227
  light.styles.table.color = light.color;
228
+ light.styles.table.actionIcons = {
229
+ ...light.styles.table.actionIcons,
230
+ ...styles.table?.actionIcons || {}
231
+ };
223
232
  watch(() => light.theme, async () => {
224
233
  await light.setStyle("theme", light.theme);
225
234
  });
@@ -17,7 +17,11 @@ export default defineLightModel({
17
17
  id: {
18
18
  label: "ID",
19
19
  sortable: true,
20
- searchable: true
20
+ searchable: true,
21
+ searchMethod: "range",
22
+ // 啟用 min/max 搜尋
23
+ searchType: "number"
24
+ // 配合 number input
21
25
  },
22
26
  action: {
23
27
  label: "Action",
@@ -60,27 +60,37 @@ const rows = computed(() => {
60
60
  const onUpdate = async (value, role, permission) => {
61
61
  const updateKey = `${role}-${permission}`;
62
62
  updating.value.add(updateKey);
63
+ const roleObj = app.value?.roles.find((r) => r.name === role);
64
+ if (!roleObj) {
65
+ updating.value.delete(updateKey);
66
+ return;
67
+ }
68
+ const originalPermissions = [...roleObj.permissions];
69
+ if (value) {
70
+ if (!roleObj.permissions.includes(permission)) {
71
+ roleObj.permissions = [...roleObj.permissions, permission];
72
+ }
73
+ } else {
74
+ if (roleObj.permissions.includes(permission)) {
75
+ roleObj.permissions = roleObj.permissions.filter((p) => p !== permission);
76
+ }
77
+ }
63
78
  try {
64
79
  if (value) {
65
80
  await m("addPermission", { value: permission, role });
66
- $q.notify({
67
- type: "positive",
68
- message: t("Permission granted successfully")
69
- });
70
81
  } else {
71
82
  await m("removePermission", { value: permission, role });
72
- $q.notify({
73
- type: "positive",
74
- message: t("Permission removed successfully")
75
- });
76
83
  }
77
- app.value = await fetchApp();
84
+ $q.notify({
85
+ type: "positive",
86
+ message: t(value ? "Permission granted successfully" : "Permission removed successfully")
87
+ });
78
88
  } catch (error) {
89
+ roleObj.permissions = originalPermissions;
79
90
  $q.notify({
80
91
  type: "negative",
81
92
  message: t("Failed to update permission")
82
93
  });
83
- app.value = await fetchApp();
84
94
  } finally {
85
95
  updating.value.delete(updateKey);
86
96
  }
@@ -94,8 +104,9 @@ const toggleAllForRole = async (role, grant) => {
94
104
  }
95
105
  }
96
106
  if (updates.length === 0) return;
107
+ const originalPermissions = [...role.permissions];
108
+ role.permissions = grant ? [...app.value.permissions] : [];
97
109
  try {
98
- loading.value = true;
99
110
  for (const update of updates) {
100
111
  if (update.grant) {
101
112
  await m("addPermission", { value: update.permission, role: update.role });
@@ -103,18 +114,16 @@ const toggleAllForRole = async (role, grant) => {
103
114
  await m("removePermission", { value: update.permission, role: update.role });
104
115
  }
105
116
  }
106
- app.value = await fetchApp();
107
117
  $q.notify({
108
118
  type: "positive",
109
119
  message: t(grant ? "All permissions granted" : "All permissions removed")
110
120
  });
111
121
  } catch (error) {
122
+ role.permissions = originalPermissions;
112
123
  $q.notify({
113
124
  type: "negative",
114
125
  message: t("Failed to update permissions")
115
126
  });
116
- } finally {
117
- loading.value = false;
118
127
  }
119
128
  };
120
129
  const filter = ref("");
@@ -3,7 +3,7 @@ import "@quasar/quasar-ui-qmarkdown/dist/index.css";
3
3
  import { createClient, setApiClient, defineModel } from "@hostlink/light";
4
4
  import { createI18n } from "vue-i18n";
5
5
  import createLight from "./composables/createLight.js";
6
- import { defineNuxtPlugin, useRoute } from "#app";
6
+ import { defineNuxtPlugin, useRoute, useRuntimeConfig } from "#app";
7
7
  import "./assets/main.css";
8
8
  import message_en from "./locales/en.json";
9
9
  import message_zh from "./locales/zh-hk.json";
@@ -22,14 +22,14 @@ export default defineNuxtPlugin((nuxtApp) => {
22
22
  defineModel("Config", {}).setDataPath("app.listConfig");
23
23
  nuxtApp.vueApp.config.errorHandler = (err, instance, info) => {
24
24
  const $route = useRoute();
25
- const light = useLight();
26
- if (light.devMode) {
25
+ const light2 = useLight();
26
+ if (light2.devMode) {
27
27
  console.log(err);
28
28
  }
29
29
  if (err instanceof Error) {
30
- light.addError(err.message + " at " + $route.fullPath);
30
+ light2.addError(err.message + " at " + $route.fullPath);
31
31
  } else {
32
- light.addError(JSON.stringify(err) + " at " + $route.fullPath);
32
+ light2.addError(JSON.stringify(err) + " at " + $route.fullPath);
33
33
  }
34
34
  };
35
35
  nuxtApp.vueApp.use(QMarkdownVuePlugin);
@@ -44,6 +44,14 @@ export default defineNuxtPlugin((nuxtApp) => {
44
44
  fallbackWarn: false,
45
45
  missingWarn: false
46
46
  });
47
+ const light = useLight();
48
+ const runtimeLight = useRuntimeConfig().public.light;
49
+ if (runtimeLight?.tableActionIcons) {
50
+ const t = runtimeLight.tableActionIcons;
51
+ if (t.view) light.styles.table.actionIcons.view = t.view;
52
+ if (t.edit) light.styles.table.actionIcons.edit = t.edit;
53
+ if (t.delete) light.styles.table.actionIcons.delete = t.delete;
54
+ }
47
55
  nuxtApp.vueApp.use(createLight({
48
56
  i18n
49
57
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hostlink/nuxt-light",
3
- "version": "1.66.2",
3
+ "version": "1.67.0",
4
4
  "description": "HostLink Nuxt Light Framework",
5
5
  "repository": {
6
6
  "type": "git",