noph-ui 0.40.3 → 0.41.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.
@@ -29,6 +29,24 @@
29
29
  element?.hidePopover()
30
30
  }
31
31
 
32
+ // CSS anchor positioning has no way to read which position-try fallback won, so the
33
+ // scale pivot can't be expressed declaratively (see open spec request for @position-try
34
+ // to set transform-origin: https://github.com/w3c/csswg-drafts/issues/11666). Until that
35
+ // lands, mirror Compose's calculateTransformOrigin (Menu.kt) in JS instead.
36
+ const calculateTransformOrigin = (anchorRect: DOMRect, menuRect: DOMRect) => {
37
+ const pivot = (anchorStart: number, anchorEnd: number, menuStart: number, menuEnd: number) => {
38
+ if (menuStart >= anchorEnd) return 0
39
+ if (menuEnd <= anchorStart) return 1
40
+ if (menuEnd === menuStart) return 0
41
+ const intersectionCenter =
42
+ (Math.max(anchorStart, menuStart) + Math.min(anchorEnd, menuEnd)) / 2
43
+ return (intersectionCenter - menuStart) / (menuEnd - menuStart)
44
+ }
45
+ const x = pivot(anchorRect.left, anchorRect.right, menuRect.left, menuRect.right)
46
+ const y = pivot(anchorRect.top, anchorRect.bottom, menuRect.top, menuRect.bottom)
47
+ return `${x * 100}% ${y * 100}%`
48
+ }
49
+
32
50
  const refreshValues = () => {
33
51
  if (element && anchor && open) {
34
52
  const anchorRect = anchor.getBoundingClientRect()
@@ -40,6 +58,10 @@
40
58
  const above = Math.max(anchorRect.top - margin, 0)
41
59
  const overAnchor = coverAnchor && wanted > below && wanted > above && wanted <= room
42
60
  element.style.maxHeight = `${Math.floor(overAnchor ? room : Math.max(below, above))}px`
61
+ element.style.transformOrigin = calculateTransformOrigin(
62
+ anchorRect,
63
+ element.getBoundingClientRect(),
64
+ )
43
65
  }
44
66
  }
45
67
  $effect(refreshValues)
@@ -90,6 +112,7 @@
90
112
  scrollbar-width: thin;
91
113
  justify-self: var(--np-menu-justify-self, anchor-center);
92
114
  position-area: var(--np-menu-position-area, bottom);
115
+ transform-origin: var(--np-menu-transform-origin, top center);
93
116
  position-try-fallbacks:
94
117
  flip-block,
95
118
  flip-inline,
@@ -112,14 +135,19 @@
112
135
 
113
136
  .np-menu-container:popover-open {
114
137
  opacity: 1;
115
- animation: fadeIn var(--np-motion-expressive-default-effects);
138
+ scale: 1;
139
+ animation:
140
+ fadeIn var(--np-motion-expressive-fast-effects),
141
+ scaleIn var(--np-motion-expressive-fast-spatial);
116
142
  }
117
143
  @keyframes fadeIn {
118
- 0% {
144
+ from {
119
145
  opacity: 0;
120
146
  }
121
- 100% {
122
- opacity: 1;
147
+ }
148
+ @keyframes scaleIn {
149
+ from {
150
+ scale: 0.8;
123
151
  }
124
152
  }
125
153
  </style>
@@ -131,16 +131,21 @@
131
131
  box-shadow: var(--np-elevation-2);
132
132
  border: none;
133
133
  opacity: 0;
134
+ scale: 0.8;
135
+ transform-origin: top center;
134
136
  transition:
135
- opacity var(--easing-fast),
137
+ opacity var(--np-motion-expressive-fast-effects),
138
+ scale var(--np-motion-expressive-fast-spatial),
136
139
  display 150ms allow-discrete,
137
140
  overlay 150ms allow-discrete;
138
141
  }
139
142
 
140
143
  select:open::picker(select) {
141
144
  opacity: 1;
145
+ scale: 1;
142
146
  @starting-style {
143
147
  opacity: 0;
148
+ scale: 0.8;
144
149
  }
145
150
  }
146
151
 
@@ -148,10 +148,11 @@
148
148
  padding: 0 1rem;
149
149
  color: var(--np-color-on-surface-variant);
150
150
  height: 3rem;
151
- transition: color 0.3s cubic-bezier(0.33, 1, 0.68, 1);
151
+ transition: color var(--np-motion-expressive-fast-effects);
152
152
  }
153
153
  .np-tab-content-active {
154
154
  --_focus-bottom: 4px;
155
+ transition: color var(--np-motion-expressive-default-effects);
155
156
  }
156
157
  .np-tab-content-active.primary {
157
158
  color: var(--np-color-primary);
@@ -80,7 +80,7 @@
80
80
  border-start-start-radius: var(--np-indicator-radius, var(--np-shape-corner-full));
81
81
  border-start-end-radius: var(--np-indicator-radius, var(--np-shape-corner-full));
82
82
  position-anchor: --np-tab-indicator;
83
- transition: var(--np-motion-expressive-fast-spatial);
83
+ transition: var(--np-motion-expressive-default-spatial);
84
84
  }
85
85
  }
86
86
  </style>
@@ -18,6 +18,8 @@
18
18
  populated = false,
19
19
  inputElement = $bindable(),
20
20
  placeholder = ' ',
21
+ minLines = 1,
22
+ maxLines = 4,
21
23
  children,
22
24
  focused = $bindable(false),
23
25
  clientWidth = $bindable(),
@@ -31,11 +33,13 @@
31
33
 
32
34
  <label
33
35
  style={(variant === 'outlined'
34
- ? '--_label-text-color:var(--np-outlined-text-field-label-text-color);--top-space:1rem;--bottom-space:1rem;--floating-label-top:-0.5rem;--floating-label-inline-start:-2.25rem;--_focus-outline-width:3px;'
36
+ ? '--_label-text-color:var(--np-outlined-text-field-label-text-color);--top-space:1rem;--bottom-space:1rem;--floating-label-top:-0.5rem;--floating-label-inline-start:-2.25rem;'
35
37
  : !label?.length
36
38
  ? '--_label-text-color:var(--np-filled-text-field-label-text-color);--np-input-chip-outline-color:var(--np-color-outline);--top-space:1rem;--bottom-space:1rem; '
37
39
  : '--_label-text-color:var(--np-filled-text-field-label-text-color);--np-input-chip-outline-color:var(--np-color-outline); ' +
38
- (children ? '--top-space:2rem;--bottom-space:1rem;' : '')) + style}
40
+ (children ? '--top-space:2rem;--bottom-space:1rem;' : '')) +
41
+ `--_min-height:calc(${minLines} * 1lh);--_max-height:${`calc(${maxLines} * 1lh)`};` +
42
+ style}
39
43
  class={['text-field', attributes.class]}
40
44
  bind:this={element}
41
45
  bind:clientWidth
@@ -43,7 +47,6 @@
43
47
  >
44
48
  <div
45
49
  class="field"
46
- class:resizable={attributes.type === 'textarea'}
47
50
  class:no-label={!label?.length}
48
51
  class:with-start={start}
49
52
  class:with-end={end}
@@ -109,8 +112,7 @@
109
112
  bind:focused
110
113
  bind:value
111
114
  bind:this={inputElement}
112
- class="input"
113
- rows={attributes.rows || 2}></textarea>
115
+ class="input"></textarea>
114
116
  {:else}
115
117
  <div class="input-wrapper">
116
118
  {#if suffixText}
@@ -272,7 +274,6 @@
272
274
  }
273
275
  .text-field {
274
276
  display: inline-flex;
275
- resize: both;
276
277
  text-align: start;
277
278
  }
278
279
  :global(label) {
@@ -324,11 +325,6 @@
324
325
  background: var(--np-color-on-surface);
325
326
  opacity: 0.08;
326
327
  }
327
- .resizable .np-container > * {
328
- top: var(--_focus-outline-width, 3px);
329
- inset-inline-start: var(--_focus-outline-width, 0);
330
- }
331
-
332
328
  .content * {
333
329
  all: unset;
334
330
  color: currentColor;
@@ -418,6 +414,11 @@
418
414
  .content textarea {
419
415
  margin-top: var(--top-space, 1.5rem);
420
416
  margin-bottom: var(--bottom-space, 0.5rem);
417
+ field-sizing: content;
418
+ min-inline-size: 20ch;
419
+ max-inline-size: 100%;
420
+ min-height: var(--_min-height, 1lh);
421
+ max-height: var(--_max-height, 4lh);
421
422
  }
422
423
 
423
424
  .content .input-wrapper .input,
@@ -627,10 +628,6 @@
627
628
  .disabled .label:not(.np-hidden) {
628
629
  opacity: 0.38;
629
630
  }
630
- .resizable:not(.disabled) .np-container {
631
- resize: inherit;
632
- overflow: hidden;
633
- }
634
631
  .disabled.no-label .content,
635
632
  .disabled:has(input:-webkit-autofill) .content,
636
633
  .disabled:has(input:not(:placeholder-shown)) .content,
@@ -638,15 +635,6 @@
638
635
  .disabled.populated .content {
639
636
  opacity: 0.38;
640
637
  }
641
- .field,
642
- .container-overflow {
643
- resize: inherit;
644
- }
645
- .resizable .np-container {
646
- bottom: 3px;
647
- inset-inline-end: var(--_focus-outline-width, 0);
648
- clip-path: inset(3px 0 0 var(--_focus-outline-width));
649
- }
650
638
  .outline-start,
651
639
  .outline-end {
652
640
  border: inherit;
@@ -26,10 +26,14 @@ export interface InputFieldProps extends HTMLInputAttributes, FieldProps {
26
26
  }
27
27
  export interface TextAreaFieldProps extends HTMLTextareaAttributes, FieldProps {
28
28
  type: 'textarea';
29
+ minLines?: number;
30
+ maxLines?: number;
29
31
  }
30
32
  export interface TextFieldProps extends HTMLAttributes<TextFieldElement>, Omit<HTMLInputAttributes, keyof HTMLAttributes<HTMLInputElement> | 'type' | 'value' | 'defaultValue' | 'defaultvalue'>, Omit<HTMLTextareaAttributes, keyof HTMLAttributes<HTMLTextAreaElement> | 'value' | 'defaultValue' | 'defaultvalue'>, FieldProps {
31
33
  type?: TextFieldType | 'textarea';
32
34
  value?: string | number | null;
33
35
  defaultValue?: string | number | null;
36
+ minLines?: number;
37
+ maxLines?: number;
34
38
  }
35
39
  export {};
@@ -141,15 +141,20 @@
141
141
  }
142
142
  .np-tooltip:popover-open {
143
143
  opacity: 1;
144
- animation: scaleIn var(--np-motion-expressive-fast-spatial);
144
+ scale: 1;
145
+ animation:
146
+ fadeIn var(--np-motion-expressive-fast-effects),
147
+ scaleIn var(--np-motion-expressive-fast-spatial);
145
148
  }
146
149
 
147
- @keyframes scaleIn {
150
+ @keyframes fadeIn {
148
151
  from {
149
- scale: 0;
152
+ opacity: 0;
150
153
  }
151
- to {
152
- scale: 1;
154
+ }
155
+ @keyframes scaleIn {
156
+ from {
157
+ scale: 0.8;
153
158
  }
154
159
  }
155
160
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noph-ui",
3
- "version": "0.40.3",
3
+ "version": "0.41.0",
4
4
  "license": "MIT",
5
5
  "homepage": "https://noph.dev",
6
6
  "repository": {