pimelon-ui 0.1.198 → 0.1.201

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pimelon-ui",
3
- "version": "0.1.198",
3
+ "version": "0.1.201",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "main": "./src/index.ts",
6
6
  "type": "module",
@@ -53,6 +53,12 @@ watch(
53
53
  searchTerm.value = getDisplayValue(newValue)
54
54
  },
55
55
  )
56
+ watch(
57
+ () => getDisplayValue(props.modelValue),
58
+ (newDisplay) => {
59
+ if (!userHasTyped.value) searchTerm.value = newDisplay
60
+ },
61
+ )
56
62
 
57
63
  const onUpdateModelValue = (value: string | null) => {
58
64
  const selectedOpt = value
@@ -311,7 +317,7 @@ defineExpose({
311
317
  position="popper"
312
318
  @openAutoFocus.prevent
313
319
  @closeAutoFocus.prevent
314
- :align="'start'"
320
+ :align="props.placement || 'start'"
315
321
  >
316
322
  <ComboboxViewport
317
323
  class="max-h-60 overflow-auto pb-1.5"
@@ -32,4 +32,5 @@ export interface ComboboxProps {
32
32
  disabled?: boolean
33
33
  openOnFocus?: boolean
34
34
  openOnClick?: boolean
35
+ placement?: 'start' | 'center' | 'end'
35
36
  }
@@ -202,6 +202,7 @@ import { ref, computed, watch, toRefs } from 'vue'
202
202
  import { Popover } from '../Popover'
203
203
  import { Button } from '../Button'
204
204
  import { TextInput } from '../TextInput'
205
+ // @ts-ignore - Vue SFC without explicit types
205
206
  import FeatherIcon from '../FeatherIcon.vue'
206
207
  import { dayjs, dayjsLocal } from '../../utils/dayjs'
207
208
  import { months, monthStart, generateWeeks, getDateValue } from './utils'
@@ -235,7 +236,7 @@ const currentMonth = ref<number>(dayjs().month()) // 0-index
235
236
  const DATE_FORMAT = 'YYYY-MM-DD'
236
237
 
237
238
  const selected = ref<string>('')
238
- const initialValue = props.modelValue || props.value || ''
239
+ const initialValue = ref(props.modelValue || props.value || '')
239
240
 
240
241
  function coerceToDayjs(val?: string | null): Dayjs | null {
241
242
  if (!val) return null
@@ -277,7 +278,7 @@ function syncFromValue(val?: string): void {
277
278
  selected.value = d.format(DATE_FORMAT)
278
279
  }
279
280
 
280
- syncFromValue(initialValue)
281
+ syncFromValue(initialValue.value)
281
282
 
282
283
  function initFromValue(): void {
283
284
  syncFromValue(props.modelValue || props.value)
@@ -308,10 +309,6 @@ watch(displayLabel, (val) => {
308
309
  if (!isTyping.value) inputValue.value = val
309
310
  })
310
311
 
311
- function parseInput(val: string): Dayjs | null {
312
- return coerceToDayjs(val)
313
- }
314
-
315
312
  function maybeClose(togglePopover?: () => void, condition = true) {
316
313
  if (condition && autoClose.value && togglePopover) togglePopover()
317
314
  }
@@ -321,6 +318,7 @@ function clearSelection() {
321
318
  selected.value = ''
322
319
  emit('update:modelValue', '')
323
320
  emit('change', '')
321
+ initialValue.value = ''
324
322
  inputValue.value = ''
325
323
  }
326
324
 
@@ -336,7 +334,7 @@ function commitInput(close = false, togglePopover?: () => void): void {
336
334
  }
337
335
  return
338
336
  }
339
- const d = parseInput(raw)
337
+ const d = coerceToDayjs(raw)
340
338
  if (d) {
341
339
  selectDate(d)
342
340
  maybeClose(togglePopover, close)
@@ -370,8 +368,13 @@ function selectDate(date: string | Date | Dayjs): void {
370
368
  selected.value = d.format(DATE_FORMAT)
371
369
  currentYear.value = d.year()
372
370
  currentMonth.value = d.month()
373
- emit('update:modelValue', selected.value)
374
- if (selected.value !== prev) emit('change', selected.value)
371
+
372
+ if (selected.value !== initialValue.value) {
373
+ emit('update:modelValue', selected.value)
374
+ if (selected.value !== prev) emit('change', selected.value)
375
+ initialValue.value = selected.value
376
+ }
377
+
375
378
  // Reflect new value in input immediately if not typing
376
379
  if (!isTyping.value) {
377
380
  inputValue.value = props.format
@@ -273,7 +273,7 @@ const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
273
273
  const selectedDate = ref<string>('') // YYYY-MM-DD
274
274
  const timeValue = ref<string>('') // HH:mm:ss
275
275
 
276
- const initialValue = props.modelValue || props.value || ''
276
+ const initialValue = ref(props.modelValue || props.value || '')
277
277
 
278
278
  function coerceDateTime(val?: string | null): Dayjs | null {
279
279
  if (!val) return null
@@ -285,12 +285,12 @@ function coerceDateTime(val?: string | null): Dayjs | null {
285
285
  if (dStrict.isValid()) return dStrict
286
286
  }
287
287
 
288
- const dLoose = dayjs(raw)
288
+ const dLoose = dayjsLocal(raw)
289
289
  if (dLoose.isValid()) return dLoose
290
290
 
291
291
  const normalized = getDateValue(raw)
292
292
  if (normalized) {
293
- const dNorm = dayjs(normalized)
293
+ const dNorm = dayjsLocal(normalized)
294
294
  if (dNorm.isValid()) return dNorm
295
295
  }
296
296
  return null
@@ -322,7 +322,7 @@ function syncFromValue(val?: string): void {
322
322
  timeValue.value = d.format('HH:mm:ss')
323
323
  }
324
324
 
325
- syncFromValue(initialValue)
325
+ syncFromValue(initialValue.value)
326
326
 
327
327
  function initFromValue(): void {
328
328
  syncFromValue(props.modelValue || props.value)
@@ -339,10 +339,9 @@ watch(
339
339
  const combinedValue = computed<string>(() => {
340
340
  if (!selectedDate.value) return ''
341
341
  const base = `${selectedDate.value} ${timeValue.value || '00:00:00'}`
342
- const local = dayjsLocal(base)
342
+ const local = dayjs(base)
343
343
  if (!local.isValid()) return ''
344
- const sys = dayjsSystem(local.format(DATE_TIME_FORMAT))
345
- return sys.format(DATE_TIME_FORMAT)
344
+ return local.format(DATE_TIME_FORMAT)
346
345
  })
347
346
 
348
347
  const displayLabel = computed<string>(() => {
@@ -367,6 +366,7 @@ function clearSelection() {
367
366
  timeValue.value = ''
368
367
  emit('update:modelValue', '')
369
368
  emit('change', '')
369
+ initialValue.value = ''
370
370
  inputValue.value = ''
371
371
  }
372
372
 
@@ -512,10 +512,16 @@ function emitChange(close = false, togglePopover?: () => void) {
512
512
  clearSelection()
513
513
  return
514
514
  }
515
- const out = combinedValue.value
516
515
 
517
- emit('update:modelValue', out)
518
- emit('change', out)
516
+ const localDateTime = combinedValue.value
517
+ const systemDateTime = dayjsSystem(localDateTime).format(DATE_TIME_FORMAT)
518
+
519
+ if (systemDateTime !== initialValue.value) {
520
+ emit('update:modelValue', systemDateTime)
521
+ emit('change', systemDateTime)
522
+ initialValue.value = systemDateTime
523
+ }
524
+
519
525
  if (!isTyping.value) inputValue.value = displayLabel.value
520
526
  maybeClose(togglePopover, close)
521
527
  }
@@ -11,7 +11,7 @@
11
11
  :class="dialogPositionClasses"
12
12
  >
13
13
  <DialogContent
14
- class="my-8 inline-block w-full transform overflow-hidden rounded-xl bg-surface-modal text-left align-middle shadow-xl dialog-content"
14
+ class="my-8 inline-block w-full transform overflow-hidden rounded-xl bg-surface-modal text-left align-middle shadow-xl dialog-content focus-visible:outline-none"
15
15
  :class="{
16
16
  'max-w-7xl': options.size === '7xl',
17
17
  'max-w-6xl': options.size === '6xl',
@@ -3,7 +3,7 @@
3
3
  class="flex h-full w-full flex-col items-center justify-center text-base"
4
4
  >
5
5
  <slot>
6
- <div class="text-xl font-medium">{{ list.options.emptyState.title }}</div>
6
+ <div class="text-xl font-medium text-ink-gray-8 mt-6">{{ list.options.emptyState.title }}</div>
7
7
  <div class="mt-1 text-base text-ink-gray-5">
8
8
  {{ list.options.emptyState.description }}
9
9
  </div>
@@ -2,7 +2,7 @@
2
2
  <Button
3
3
  :label="props.label"
4
4
  @click="handleClick"
5
- class="!w-full"
5
+ class="!w-full focus-visible:ring-0 focus:outline-none"
6
6
  :class="
7
7
  props.isActive
8
8
  ? '!bg-surface-selected shadow-sm'
@@ -230,6 +230,9 @@ onMounted(() => {
230
230
  onUpdate: ({ editor }) => {
231
231
  emit('change', editor.getHTML())
232
232
  },
233
+ onTransaction: ({ editor }) => {
234
+ emit('transaction', editor)
235
+ },
233
236
  onFocus: ({ editor, event }) => {
234
237
  emit('focus', event)
235
238
  },
@@ -204,7 +204,9 @@ function setCursorBeforeIframe() {
204
204
  v-if="node.attrs.src"
205
205
  ref="iframeRef"
206
206
  class="rounded-lg border-0 block max-w-full h-auto"
207
- :class="{ 'pointer-events-none': isEditable }"
207
+ :class="{
208
+ 'pointer-events-none': isEditable && !props.node.attrs.interactive,
209
+ }"
208
210
  :src="node.attrs.src"
209
211
  :style="iframeStyles"
210
212
  :title="node.attrs.title || ''"
@@ -218,7 +220,7 @@ function setCursorBeforeIframe() {
218
220
 
219
221
  <!-- Transparent overlay for selection in edit mode -->
220
222
  <div
221
- v-if="isEditable"
223
+ v-if="isEditable && !props.node.attrs.interactive"
222
224
  class="absolute inset-0 cursor-pointer z-10"
223
225
  @click.stop="selectIframe"
224
226
  ></div>
@@ -20,6 +20,7 @@ export interface SetIframeOptions {
20
20
  width?: number
21
21
  height?: number
22
22
  title?: string
23
+ interactive?: boolean
23
24
  align?: 'left' | 'center' | 'right'
24
25
  }
25
26
 
@@ -128,6 +129,13 @@ export const IframeExtension = Node.create<IframeOptions>({
128
129
  sandbox: attributes.sandbox,
129
130
  }),
130
131
  },
132
+ interactive: {
133
+ default: false,
134
+ parseHTML: (el) => el.getAttribute('data-interactive') === 'true',
135
+ renderHTML: (attrs) => ({
136
+ 'data-interactive': attrs.interactive ? 'true' : 'false',
137
+ }),
138
+ },
131
139
  }
132
140
  },
133
141
 
@@ -196,7 +204,8 @@ export const IframeExtension = Node.create<IframeOptions>({
196
204
  height: options.height || optimalDimensions.height,
197
205
  title: options.title,
198
206
  align: options.align || 'center',
199
- aspectRatio: optimalDimensions.height / optimalDimensions.width
207
+ aspectRatio: optimalDimensions.height / optimalDimensions.width,
208
+ interactive: options.interactive,
200
209
  }
201
210
 
202
211
  return commands.insertContent({
@@ -133,6 +133,7 @@ export function getOptimalDimensions(url: string, containerWidth?: number): { wi
133
133
 
134
134
  export function validateURL(url: string, options: IframeOptions): boolean {
135
135
  try {
136
+ if (url.startsWith('/')) return true
136
137
  const urlObj = new URL(url)
137
138
  const domain = urlObj.hostname.toLowerCase()
138
139
 
@@ -31,4 +31,5 @@ export interface TextEditorEmits {
31
31
  change: [content: string]
32
32
  focus: [event: FocusEvent]
33
33
  blur: [event: FocusEvent]
34
+ transaction: [editor: object]
34
35
  }
@@ -40,6 +40,9 @@ export function createDocumentResource(options, vm) {
40
40
  fieldname: values,
41
41
  }
42
42
  },
43
+ validate(data) {
44
+ options.setValue?.validate?.call(vm, data)
45
+ },
43
46
  beforeSubmit(params) {
44
47
  out.previousDoc = JSON.stringify(out.doc)
45
48
  Object.assign(out.doc, params.fieldname || {})
@@ -5,7 +5,7 @@ export function melonProxy({
5
5
  source = '^/(app|login|api|assets|files|private)',
6
6
  } = {}) {
7
7
  const commonSiteConfig = getCommonSiteConfig()
8
- const env_web_server_port = process.env.FRAPPE_WEB_SERVER_PORT
8
+ const env_web_server_port = process.env.MELON_WEB_SERVER_PORT
9
9
  const webserver_port =
10
10
  env_web_server_port ||
11
11
  (commonSiteConfig ? commonSiteConfig.webserver_port : 8000)